Countdown / style.css
Lashtw's picture
Update style.css
d1095ed verified
Raw History Blame Contribute Delete
25.6 kB
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Classroom Hub - 自適應倒數看板</title>
<style>
:root {
--bg-gradient: radial-gradient(circle at 15% 15%, #0f172a 0%, #020617 100%);
--card-bg: rgba(30, 41, 59, 0.55);
--card-border: rgba(255, 255, 255, 0.12);
--card-shadow: 0 20px 40px rgba(0, 0, 0, 0.45);
--primary-text: #ffffff;
--secondary-text: #94a3b8;
--badge-bg: rgba(255, 255, 255, 0.08);
}
/* 全域主題樣式切換 */
body[data-theme="dark-slate"] {
--bg-gradient: radial-gradient(circle at 15% 15%, #1e293b 0%, #020617 100%);
--card-bg: rgba(30, 41, 59, 0.55);
--card-border: rgba(255, 255, 255, 0.12);
--primary-text: #ffffff;
--secondary-text: #94a3b8;
--badge-bg: rgba(255, 255, 255, 0.08);
}
body[data-theme="midnight-purple"] {
--bg-gradient: radial-gradient(circle at 20% 20%, #2e1065 0%, #0b0217 100%);
--card-bg: rgba(46, 16, 101, 0.4);
--card-border: rgba(192, 132, 252, 0.2);
--primary-text: #ffffff;
--secondary-text: #d8b4fe;
--badge-bg: rgba(192, 132, 252, 0.12);
}
body[data-theme="forest-calm"] {
--bg-gradient: radial-gradient(circle at 20% 20%, #064e3b 0%, #021a14 100%);
--card-bg: rgba(6, 78, 59, 0.4);
--card-border: rgba(52, 211, 153, 0.2);
--primary-text: #ffffff;
--secondary-text: #a7f3d0;
--badge-bg: rgba(52, 211, 153, 0.12);
}
body[data-theme="minimal-light"] {
--bg-gradient: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
--card-bg: rgba(255, 255, 255, 0.85);
--card-border: rgba(203, 213, 225, 0.8);
--card-shadow: 0 15px 30px rgba(0, 0, 0, 0.08);
--primary-text: #0f172a;
--secondary-text: #64748b;
--badge-bg: rgba(0, 0, 0, 0.06);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans TC", sans-serif;
}
body {
width: 100vw;
height: 100vh;
overflow: hidden;
background: var(--bg-gradient);
color: var(--primary-text);
position: relative;
transition: background 0.4s ease;
}
/* 頂部輕量時鐘 */
.top-bar {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
text-align: center;
pointer-events: none;
z-index: 5;
}
.live-clock {
font-size: 2.2rem;
font-weight: 700;
color: var(--primary-text);
letter-spacing: -0.5px;
font-feature-settings: "tnum";
text-shadow: 0 4px 12px rgba(0,0,0,0.4);
}
.live-date {
font-size: 0.85rem;
color: var(--secondary-text);
letter-spacing: 2px;
}
#canvas-workspace {
width: 100vw;
height: 100vh;
position: absolute;
top: 0;
left: 0;
}
/* =========================================
自適應卡片容器 (Container Query 架構)
========================================= */
.board-card {
position: absolute;
background: var(--card-bg);
border: 1px solid var(--card-border);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border-radius: 24px;
box-shadow: var(--card-shadow);
display: flex;
flex-direction: column;
overflow: hidden;
transition: border 0.2s, box-shadow 0.2s;
container-type: size; /* 讓內部元件能根據卡片尺寸等比自適應 */
}
/* 分類主題色 */
.board-card[data-cat="exam"] { --cat-color: #38bdf8; }
.board-card[data-cat="sports"] { --cat-color: #f97316; }
.board-card[data-cat="event"] { --cat-color: #ec4899; }
.board-card[data-cat="life"] { --cat-color: #10b981; }
.board-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: clamp(4px, 1.8cqh, 8px);
background: var(--cat-color);
z-index: 2;
}
.board-card.highlight {
border: 1.8px solid #fbbf24;
box-shadow: 0 15px 35px rgba(251, 191, 36, 0.25);
}
.board-card.highlight::before {
background: #fbbf24;
height: clamp(5px, 2.2cqh, 10px);
}
/* 卡片內部排版:使用容器單位 (cqw, cqh) */
.card-content-box {
width: 100%;
height: 100%;
padding: clamp(14px, 6cqw, 36px) clamp(16px, 7cqw, 40px);
display: flex;
flex-direction: column;
justify-content: space-between;
}
.card-header-area {
display: flex;
flex-direction: column;
gap: clamp(4px, 1.5cqh, 10px);
}
.card-title-row {
display: flex;
align-items: center;
gap: clamp(8px, 2.5cqw, 16px);
}
.icon-wrapper {
width: clamp(28px, 11cqw, 54px);
height: clamp(28px, 11cqw, 54px);
border-radius: clamp(8px, 2.5cqw, 14px);
background: rgba(255, 255, 255, 0.08);
display: flex;
align-items: center;
justify-content: center;
color: var(--cat-color);
flex-shrink: 0;
}
.icon-wrapper svg {
width: 60%;
height: 60%;
}
.board-card.highlight .icon-wrapper {
color: #fbbf24;
background: rgba(251, 191, 36, 0.16);
}
.card-title {
font-size: clamp(1rem, 6.5cqw, 2.2rem);
font-weight: 600;
color: var(--primary-text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.2;
}
.card-date {
font-size: clamp(0.75rem, 4.2cqw, 1.15rem);
color: var(--secondary-text);
letter-spacing: 0.5px;
}
/* 倒數主核心:天數動態膨脹填滿中段 */
.countdown-body {
flex: 1;
display: flex;
align-items: baseline;
justify-content: center;
gap: clamp(4px, 1.5cqw, 12px);
min-height: 0;
}
.countdown-digits {
font-size: clamp(2.8rem, 24cqw, 14rem); /* 隨寬度與高度巨大化放大 */
font-weight: 800;
line-height: 0.95;
letter-spacing: -2px;
font-feature-settings: "tnum";
color: var(--primary-text);
}
.board-card.highlight .countdown-digits {
color: #fbbf24;
text-shadow: 0 0 clamp(15px, 5cqw, 35px) rgba(251, 191, 36, 0.45);
}
.countdown-unit {
font-size: clamp(0.9rem, 5.5cqw, 2.2rem);
color: var(--secondary-text);
font-weight: 500;
}
/* 底部狀態膠囊標籤 */
.card-footer-status {
align-self: center;
font-size: clamp(0.7rem, 3.8cqw, 1.05rem);
padding: clamp(4px, 1.5cqh, 8px) clamp(10px, 4cqw, 24px);
border-radius: 999px;
background: var(--badge-bg);
color: var(--secondary-text);
font-weight: 500;
white-space: nowrap;
}
.board-card.highlight .card-footer-status {
background: rgba(251, 191, 36, 0.15);
color: #fbbf24;
}
/* 編輯排版模式樣式 */
.is-editing .board-card {
border: 1.5px dashed #38bdf8 !important;
cursor: move;
}
.resize-handle {
display: none;
position: absolute;
right: 2px;
bottom: 2px;
width: 24px;
height: 24px;
cursor: se-resize;
background: linear-gradient(135deg, transparent 50%, rgba(56, 189, 248, 0.8) 50%);
border-bottom-right-radius: 18px;
z-index: 10;
}
.is-editing .resize-handle {
display: block;
}
/* 編輯浮動狀態條 */
.edit-banner {
display: none;
position: fixed;
top: 16px;
right: 20px;
background: rgba(15, 23, 42, 0.95);
border: 1px solid #38bdf8;
backdrop-filter: blur(8px);
padding: 8px 16px;
border-radius: 12px;
z-index: 1000;
align-items: center;
gap: 12px;
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}
.is-editing .edit-banner {
display: flex;
}
/* 齒輪圖示 */
.settings-trigger {
position: fixed;
bottom: 20px;
right: 20px;
width: 46px;
height: 46px;
border-radius: 50%;
background: rgba(15, 23, 42, 0.7);
border: 1px solid rgba(255, 255, 255, 0.15);
backdrop-filter: blur(8px);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: #94a3b8;
opacity: 0.25;
transition: all 0.2s;
z-index: 500;
}
.settings-trigger:hover {
opacity: 1;
transform: rotate(30deg);
color: #fff;
}
/* 彈窗與抽屜 */
.modal-backdrop {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.75);
backdrop-filter: blur(10px);
z-index: 2000;
align-items: center;
justify-content: center;
}
.modal-box {
background: #1e293b;
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 16px;
padding: 28px;
width: 320px;
text-align: center;
color: #fff;
}
.modal-box input {
width: 100%;
padding: 10px;
border-radius: 8px;
border: 1px solid #334155;
background: #0f172a;
color: #fff;
font-size: 1.2rem;
text-align: center;
letter-spacing: 6px;
margin: 15px 0 20px;
outline: none;
}
.btn {
padding: 8px 16px;
border-radius: 8px;
border: none;
font-weight: 600;
cursor: pointer;
font-size: 0.85rem;
}
.btn-primary { background: #38bdf8; color: #0f172a; }
.btn-secondary { background: #334155; color: #fff; }
.btn-success { background: #10b981; color: #fff; }
.btn-danger { background: #ef4444; color: #fff; }
.admin-drawer {
display: none;
position: fixed;
top: 0;
right: 0;
width: 520px;
max-width: 92vw;
height: 100vh;
background: #0f172a;
border-left: 1px solid #334155;
box-shadow: -15px 0 40px rgba(0,0,0,0.6);
z-index: 2100;
flex-direction: column;
color: #e2e8f0;
}
.drawer-header {
padding: 20px;
border-bottom: 1px solid #1e293b;
display: flex;
justify-content: space-between;
align-items: center;
}
.drawer-content {
padding: 20px;
overflow-y: auto;
flex: 1;
display: flex;
flex-direction: column;
gap: 18px;
}
.theme-control-box {
background: #1e293b;
border: 1px solid #334155;
border-radius: 10px;
padding: 14px;
display: flex;
justify-content: space-between;
align-items: center;
}
.item-row {
background: #1e293b;
border: 1px solid #334155;
border-radius: 10px;
padding: 14px;
display: flex;
flex-direction: column;
gap: 10px;
}
.item-inputs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.item-row input, .item-row select, .theme-control-box select {
background: #0f172a;
border: 1px solid #334155;
color: #fff;
padding: 7px 10px;
border-radius: 6px;
font-size: 0.85rem;
}
</style>
</head>
<body data-theme="dark-slate">
<!-- 頂部即時時鐘 -->
<div class="top-bar">
<div class="live-clock" id="liveClock">00:00:00</div>
<div class="live-date" id="liveDate">LOADING CALENDAR...</div>
</div>
<!-- 自由自適應畫布 -->
<div id="canvas-workspace"></div>
<!-- 編輯狀態控制頂列 -->
<div class="edit-banner" id="editBanner">
<span style="font-size:0.85rem; color:#38bdf8;">✦ 自由排版中:拖動卡片改變位置,拉右下角角標縮放</span>
<button class="btn btn-secondary" onclick="openAdminDrawer()">設定與項目</button>
<button class="btn btn-success" onclick="exitEditMode()">完成並鎖定</button>
</div>
<!-- 右下角隱密齒輪 -->
<div class="settings-trigger" onclick="openAuthModal()" title="排版設定">
<svg width="22" height="22" fill="currentColor" viewBox="0 0 24 24">
<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"/>
</svg>
</div>
<!-- 密碼 Modal -->
<div class="modal-backdrop" id="authModal">
<div class="modal-box">
<h3>進入看板管理</h3>
<p style="color:#94a3b8; font-size:0.8rem; margin-top:6px;">請輸入管理員密碼</p>
<input type="password" id="authPassword" placeholder="PIN" maxlength="6" onkeydown="if(event.key==='Enter') verifyPassword()">
<div style="display:flex; justify-content:center; gap:8px;">
<button class="btn btn-secondary" onclick="closeAuthModal()">取消</button>
<button class="btn btn-primary" onclick="verifyPassword()">解鎖排版</button>
</div>
</div>
</div>
<!-- 側拉管理抽屜 -->
<div class="admin-drawer" id="adminDrawer">
<div class="drawer-header">
<h3>看板設定與項目管理</h3>
<button class="btn btn-secondary" onclick="closeAdminDrawer()">關閉</button>
</div>
<div class="drawer-content">
<!-- 主題風格選擇區塊 -->
<div class="theme-control-box">
<span style="font-weight:600;">整體版面風格</span>
<select id="themeSelect" onchange="changeTheme(this.value)">
<option value="dark-slate">深邃板岩灰 (Dark Slate)</option>
<option value="midnight-purple">星夜紫韻 (Midnight Purple)</option>
<option value="forest-calm">靜謐青綠 (Forest Calm)</option>
<option value="minimal-light">晨光清透 (Minimal Light)</option>
</select>
</div>
<div style="display:flex; justify-content:space-between; align-items:center; margin-top:8px;">
<span style="font-size:0.85rem; color:#94a3b8;">倒數項目清單</span>
<button class="btn btn-primary" onclick="addNewEvent()">+ 新增卡片</button>
</div>
<div id="editorItemList" style="display:flex; flex-direction:column; gap:10px;"></div>
</div>
<div style="padding:16px; border-top:1px solid #1e293b; display:flex; justify-content:flex-end;">
<button class="btn btn-success" onclick="saveEditorList()">儲存並發布</button>
</div>
</div>
<script>
// 精緻 SVG 向量圖示庫
const ICONS = {
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>`,
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>`,
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>`,
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>`
};
// 初始預設資料
const DEFAULT_DATA = {
theme: "dark-slate",
cards: [
{ id: '1', title: "第一次定期評量", date: "2026-10-15", cat: "exam", highlight: false, x: 100, y: 150, w: 300, h: 250 },
{ id: '2', title: "校慶運動大會", date: "2026-11-20", cat: "sports", highlight: false, x: 100, y: 430, w: 300, h: 250 },
{ id: '3', title: "國中教育會考", date: "2027-05-15", cat: "exam", highlight: true, x: 440, y: 150, w: 420, h: 530 }
]
};
let appState = loadAppData();
let isEditing = false;
function loadAppData() {
const saved = localStorage.getItem("classroom_dashboard_data");
return saved ? JSON.parse(saved) : DEFAULT_DATA;
}
function saveAppData() {
localStorage.setItem("classroom_dashboard_data", JSON.stringify(appState));
}
// 切換主題
function changeTheme(themeName) {
appState.theme = themeName;
document.body.setAttribute("data-theme", themeName);
}
// 渲染整個自由畫布
function renderBoard() {
document.body.setAttribute("data-theme", appState.theme);
const ws = document.getElementById("canvas-workspace");
ws.innerHTML = "";
const today = new Date();
today.setHours(0,0,0,0);
appState.cards.forEach(card => {
const target = new Date(card.date + "T00:00:00");
const diffDays = Math.ceil((target - today) / (1000 * 60 * 60 * 24));
const displayDays = diffDays >= 0 ? diffDays : 0;
const badgeText = diffDays > 0 ? `倒數 ${diffDays} 天` : (diffDays === 0 ? "🔥 就是今天" : "已圓滿結束");
const el = document.createElement("div");
el.className = `board-card ${card.highlight ? 'highlight' : ''}`;
el.id = `card-${card.id}`;
el.setAttribute("data-cat", card.cat);
el.style.left = `${card.x}px`;
el.style.top = `${card.y}px`;
el.style.width = `${card.w}px`;
el.style.height = `${card.h}px`;
el.innerHTML = `
<div class="card-content-box">
<div class="card-header-area">
<div class="card-title-row">
<div class="icon-wrapper">${ICONS[card.cat] || ICONS.life}</div>
<div class="card-title">${card.title}</div>
</div>
<div class="card-date">${card.date.replace(/-/g, '.')}</div>
</div>
<div class="countdown-body">
<span class="countdown-digits">${displayDays}</span>
<span class="countdown-unit">天</span>
</div>
<div class="card-footer-status">${badgeText}</div>
</div>
<div class="resize-handle"></div>
`;
makeDraggable(el, card);
makeResizable(el.querySelector('.resize-handle'), card, el);
ws.appendChild(el);
});
}
// 拖動定位
function makeDraggable(el, cardData) {
let isDragging = false;
let startX, startY, initLeft, initTop;
el.addEventListener('mousedown', (e) => {
if (!isEditing || e.target.classList.contains('resize-handle')) return;
isDragging = true;
startX = e.clientX;
startY = e.clientY;
initLeft = el.offsetLeft;
initTop = el.offsetTop;
el.style.zIndex = 100;
function onMouseMove(e) {
if (!isDragging) return;
const newX = Math.max(0, Math.min(window.innerWidth - el.offsetWidth, initLeft + (e.clientX - startX)));
const newY = Math.max(0, Math.min(window.innerHeight - el.offsetHeight, initTop + (e.clientY - startY)));
el.style.left = `${newX}px`;
el.style.top = `${newY}px`;
cardData.x = newX;
cardData.y = newY;
}
function onMouseUp() {
isDragging = false;
el.style.zIndex = '';
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
saveAppData();
}
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
});
}
// 右下角拖拉自適應縮放
function makeResizable(handle, cardData, cardEl) {
let isResizing = false;
let startW, startH, startX, startY;
handle.addEventListener('mousedown', (e) => {
if (!isEditing) return;
e.stopPropagation();
isResizing = true;
startX = e.clientX;
startY = e.clientY;
startW = cardEl.offsetWidth;
startH = cardEl.offsetHeight;
function onMouseMove(e) {
if (!isResizing) return;
// 限制最小尺寸,避免過小破版
const newW = Math.max(200, startW + (e.clientX - startX));
const newH = Math.max(160, startH + (e.clientY - startY));
cardEl.style.width = `${newW}px`;
cardEl.style.height = `${newH}px`;
cardData.w = newW;
cardData.h = newH;
// container query 會自動完成內部文字與數字等比自適應放大,不需手動換算
}
function onMouseUp() {
isResizing = false;
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
saveAppData();
}
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
});
}
// 密碼與狀態
function openAuthModal() {
document.getElementById("authPassword").value = "";
document.getElementById("authModal").style.display = "flex";
document.getElementById("authPassword").focus();
}
function closeAuthModal() {
document.getElementById("authModal").style.display = "none";
}
function verifyPassword() {
if (document.getElementById("authPassword").value === "902") {
closeAuthModal();
enterEditMode();
} else {
alert("密碼錯誤");
}
}
function enterEditMode() {
isEditing = true;
document.body.classList.add("is-editing");
}
function exitEditMode() {
isEditing = false;
document.body.classList.remove("is-editing");
saveAppData();
}
// 抽屜面板控制
function openAdminDrawer() {
document.getElementById("themeSelect").value = appState.theme;
renderDrawerEditor();
document.getElementById("adminDrawer").style.display = "flex";
}
function closeAdminDrawer() {
document.getElementById("adminDrawer").style.display = "none";
}
function renderDrawerEditor() {
const container = document.getElementById("editorItemList");
container.innerHTML = "";
appState.cards.forEach((item, index) => {
const row = document.createElement("div");
row.className = "item-row";
row.innerHTML = `
<div class="item-inputs">
<input type="text" value="${item.title}" placeholder="名稱" onchange="appState.cards[${index}].title=this.value">
<input type="date" value="${item.date}" onchange="appState.cards[${index}].date=this.value">
</div>
<div style="display:flex; justify-content:space-between; align-items:center;">
<select onchange="appState.cards[${index}].cat=this.value">
<option value="exam" ${item.cat==='exam'?'selected':''}>學業考試 (書本)</option>
<option value="sports" ${item.cat==='sports'?'selected':''}>運動競賽 (獎盃)</option>
<option value="event" ${item.cat==='event'?'selected':''}>校慶活動 (旗幟)</option>
<option value="life" ${item.cat==='life'?'selected':''}>常態生活 (時鐘)</option>
</select>
<label style="font-size:0.8rem; color:#cbd5e1; display:flex; align-items:center; gap:4px; cursor:pointer;">
<input type="checkbox" ${item.highlight ? 'checked':''} onchange="appState.cards[${index}].highlight=this.checked"> 重要高亮
</label>
<button class="btn btn-danger" style="padding:4px 8px;" onclick="removeCard(${index})">刪除</button>
</div>
`;
container.appendChild(row);
});
}
function addNewEvent() {
const todayStr = new Date().toISOString().split('T')[0];
appState.cards.push({
id: String(Date.now()),
title: "新倒數目標",
date: todayStr,
cat: "exam",
highlight: false,
x: 100 + (appState.cards.length * 30),
y: 200,
w: 280,
h: 220
});
renderDrawerEditor();
}
function removeCard(idx) {
appState.cards.splice(idx, 1);
renderDrawerEditor();
}
function saveEditorList() {
saveAppData();
renderBoard();
closeAdminDrawer();
}
// 即時時鐘
function updateClock() {
const now = new Date();
const h = String(now.getHours()).padStart(2, '0');
const m = String(now.getMinutes()).padStart(2, '0');
const s = String(now.getSeconds()).padStart(2, '0');
document.getElementById("liveClock").textContent = `${h}:${m}:${s}`;
document.getElementById("liveDate").textContent = now.toLocaleDateString('zh-TW', {
year: 'numeric', month: 'long', day: 'numeric', weekday: 'long'
});
}
setInterval(updateClock, 1000);
updateClock();
// 啟動渲染
renderBoard();
</script>
</body>
</html>