Update index.html
Browse files- index.html +33 -19
index.html
CHANGED
|
@@ -199,11 +199,9 @@
|
|
| 199 |
display: flex;
|
| 200 |
flex-direction: column;
|
| 201 |
overflow: hidden;
|
| 202 |
-
transition: border 0.2s, box-shadow 0.2s;
|
| 203 |
touch-action: none;
|
| 204 |
}
|
| 205 |
|
| 206 |
-
/* 4 大分類的主題代表色 */
|
| 207 |
.board-card[data-cat="exam"] {
|
| 208 |
--theme-color: #38bdf8;
|
| 209 |
--num-color: #38bdf8;
|
|
@@ -236,7 +234,6 @@
|
|
| 236 |
z-index: 2;
|
| 237 |
}
|
| 238 |
|
| 239 |
-
/* 重點高亮卡片:覆蓋為極致琥珀金強調色 */
|
| 240 |
.board-card.highlight {
|
| 241 |
border: 1.8px solid #fbbf24;
|
| 242 |
box-shadow: 0 16px 40px rgba(251, 191, 36, 0.28);
|
|
@@ -300,7 +297,6 @@
|
|
| 300 |
padding-left: 2px;
|
| 301 |
}
|
| 302 |
|
| 303 |
-
/* 數字文字色彩重新連動主題色 */
|
| 304 |
.card-center-metric {
|
| 305 |
flex: 1;
|
| 306 |
display: flex;
|
|
@@ -341,7 +337,7 @@
|
|
| 341 |
height: 100%;
|
| 342 |
background: var(--theme-color);
|
| 343 |
border-radius: 999px;
|
| 344 |
-
transition: width 0.
|
| 345 |
}
|
| 346 |
.metric-details {
|
| 347 |
display: flex;
|
|
@@ -375,17 +371,18 @@
|
|
| 375 |
cursor: grabbing;
|
| 376 |
}
|
| 377 |
|
|
|
|
| 378 |
.resize-handle {
|
| 379 |
display: none;
|
| 380 |
position: absolute;
|
| 381 |
right: 0px;
|
| 382 |
bottom: 0px;
|
| 383 |
-
width:
|
| 384 |
-
height:
|
| 385 |
cursor: se-resize;
|
| 386 |
-
background: radial-gradient(circle at 75% 75%,
|
| 387 |
-
border-bottom-right-radius:
|
| 388 |
-
z-index:
|
| 389 |
touch-action: none;
|
| 390 |
pointer-events: auto;
|
| 391 |
}
|
|
@@ -931,7 +928,7 @@
|
|
| 931 |
document.body.removeChild(a);
|
| 932 |
URL.revokeObjectURL(url);
|
| 933 |
|
| 934 |
-
alert("✓ 固化成功!請用剛下載的 `index.html` 直接覆蓋
|
| 935 |
}
|
| 936 |
|
| 937 |
function openPinModal() {
|
|
@@ -1101,8 +1098,7 @@
|
|
| 1101 |
<span class="metric-unit">天</span>
|
| 1102 |
</div>
|
| 1103 |
|
| 1104 |
-
${isTall ?
|
| 1105 |
-
<div class="card-dynamic-body">
|
| 1106 |
<div class="progress-track">
|
| 1107 |
<div class="progress-fill" style="width: ${progressPct}%;"></div>
|
| 1108 |
</div>
|
|
@@ -1111,7 +1107,6 @@
|
|
| 1111 |
<span>約合 <b>${weeksLeft}</b> 週</span>
|
| 1112 |
</div>
|
| 1113 |
</div>
|
| 1114 |
-
` : ''}
|
| 1115 |
|
| 1116 |
<div class="card-badge-wrap">
|
| 1117 |
<div class="status-capsule">${badgeText}</div>
|
|
@@ -1125,9 +1120,13 @@
|
|
| 1125 |
});
|
| 1126 |
}
|
| 1127 |
|
|
|
|
|
|
|
|
|
|
| 1128 |
function attachTouchAndPointerEvents(cardEl, cardData) {
|
| 1129 |
const resizeHandle = cardEl.querySelector('.resize-handle');
|
| 1130 |
|
|
|
|
| 1131 |
cardEl.addEventListener('pointerdown', (e) => {
|
| 1132 |
if (!isEditing) return;
|
| 1133 |
if (e.target === resizeHandle || e.target.classList.contains('resize-handle')) return;
|
|
@@ -1170,6 +1169,7 @@
|
|
| 1170 |
cardEl.addEventListener('pointercancel', onPointerUp);
|
| 1171 |
});
|
| 1172 |
|
|
|
|
| 1173 |
resizeHandle.addEventListener('pointerdown', (e) => {
|
| 1174 |
if (!isEditing) return;
|
| 1175 |
e.stopPropagation();
|
|
@@ -1181,17 +1181,31 @@
|
|
| 1181 |
let startW = cardEl.offsetWidth;
|
| 1182 |
let startH = cardEl.offsetHeight;
|
| 1183 |
|
|
|
|
|
|
|
|
|
|
| 1184 |
function onResizeMove(ev) {
|
| 1185 |
if (ev.pointerId !== e.pointerId) return;
|
| 1186 |
ev.preventDefault();
|
|
|
|
|
|
|
| 1187 |
const newW = Math.max(240, startW + (ev.clientX - startX));
|
| 1188 |
const newH = Math.max(180, startH + (ev.clientY - startY));
|
| 1189 |
|
|
|
|
| 1190 |
cardEl.style.width = `${newW}px`;
|
| 1191 |
cardEl.style.height = `${newH}px`;
|
| 1192 |
cardData.w = newW;
|
| 1193 |
cardData.h = newH;
|
| 1194 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1195 |
}
|
| 1196 |
|
| 1197 |
function onResizeUp(ev) {
|
|
@@ -1200,7 +1214,10 @@
|
|
| 1200 |
resizeHandle.removeEventListener('pointermove', onResizeMove);
|
| 1201 |
resizeHandle.removeEventListener('pointerup', onResizeUp);
|
| 1202 |
resizeHandle.removeEventListener('pointercancel', onResizeUp);
|
|
|
|
|
|
|
| 1203 |
saveAppData();
|
|
|
|
| 1204 |
}
|
| 1205 |
|
| 1206 |
resizeHandle.addEventListener('pointermove', onResizeMove);
|
|
@@ -1209,7 +1226,7 @@
|
|
| 1209 |
});
|
| 1210 |
}
|
| 1211 |
|
| 1212 |
-
// 極光引擎 (
|
| 1213 |
const canvas = document.getElementById("aurora-canvas");
|
| 1214 |
const ctx = canvas.getContext("2d");
|
| 1215 |
let animId = null;
|
|
@@ -1226,7 +1243,6 @@
|
|
| 1226 |
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
| 1227 |
step += 0.008;
|
| 1228 |
|
| 1229 |
-
// 抓取所有被設為高亮的卡片
|
| 1230 |
const highlightCards = appState.cards.filter(c => c.highlight);
|
| 1231 |
const primaryHighlight = highlightCards[0];
|
| 1232 |
const focalX = primaryHighlight ? (primaryHighlight.x + primaryHighlight.w / 2) : canvas.width * 0.7;
|
|
@@ -1271,7 +1287,6 @@
|
|
| 1271 |
ctx.filter = "none";
|
| 1272 |
});
|
| 1273 |
|
| 1274 |
-
// 為所有複選高亮的卡片同時繪製共振金色光暈
|
| 1275 |
highlightCards.forEach(c => {
|
| 1276 |
const cx = c.x + c.w / 2;
|
| 1277 |
const cy = c.y + c.h / 2;
|
|
@@ -1391,7 +1406,6 @@
|
|
| 1391 |
});
|
| 1392 |
}
|
| 1393 |
|
| 1394 |
-
// 支援複選高亮
|
| 1395 |
function toggleCardHighlight(idx, isChecked) {
|
| 1396 |
appState.cards[idx].highlight = isChecked;
|
| 1397 |
renderBoard();
|
|
|
|
| 199 |
display: flex;
|
| 200 |
flex-direction: column;
|
| 201 |
overflow: hidden;
|
|
|
|
| 202 |
touch-action: none;
|
| 203 |
}
|
| 204 |
|
|
|
|
| 205 |
.board-card[data-cat="exam"] {
|
| 206 |
--theme-color: #38bdf8;
|
| 207 |
--num-color: #38bdf8;
|
|
|
|
| 234 |
z-index: 2;
|
| 235 |
}
|
| 236 |
|
|
|
|
| 237 |
.board-card.highlight {
|
| 238 |
border: 1.8px solid #fbbf24;
|
| 239 |
box-shadow: 0 16px 40px rgba(251, 191, 36, 0.28);
|
|
|
|
| 297 |
padding-left: 2px;
|
| 298 |
}
|
| 299 |
|
|
|
|
| 300 |
.card-center-metric {
|
| 301 |
flex: 1;
|
| 302 |
display: flex;
|
|
|
|
| 337 |
height: 100%;
|
| 338 |
background: var(--theme-color);
|
| 339 |
border-radius: 999px;
|
| 340 |
+
transition: width 0.2s ease;
|
| 341 |
}
|
| 342 |
.metric-details {
|
| 343 |
display: flex;
|
|
|
|
| 371 |
cursor: grabbing;
|
| 372 |
}
|
| 373 |
|
| 374 |
+
/* 放大把手並加強視覺指引 */
|
| 375 |
.resize-handle {
|
| 376 |
display: none;
|
| 377 |
position: absolute;
|
| 378 |
right: 0px;
|
| 379 |
bottom: 0px;
|
| 380 |
+
width: 42px;
|
| 381 |
+
height: 42px;
|
| 382 |
cursor: se-resize;
|
| 383 |
+
background: radial-gradient(circle at 75% 75%, rgba(56, 189, 248, 0.9) 0%, transparent 68%);
|
| 384 |
+
border-bottom-right-radius: 24px;
|
| 385 |
+
z-index: 20;
|
| 386 |
touch-action: none;
|
| 387 |
pointer-events: auto;
|
| 388 |
}
|
|
|
|
| 928 |
document.body.removeChild(a);
|
| 929 |
URL.revokeObjectURL(url);
|
| 930 |
|
| 931 |
+
alert("✓ 固化成功!請用剛下載的 `index.html` 直接覆蓋原檔,開機將永久預設此版面。");
|
| 932 |
}
|
| 933 |
|
| 934 |
function openPinModal() {
|
|
|
|
| 1098 |
<span class="metric-unit">天</span>
|
| 1099 |
</div>
|
| 1100 |
|
| 1101 |
+
<div class="card-dynamic-body" style="display: ${isTall ? 'flex' : 'none'};">
|
|
|
|
| 1102 |
<div class="progress-track">
|
| 1103 |
<div class="progress-fill" style="width: ${progressPct}%;"></div>
|
| 1104 |
</div>
|
|
|
|
| 1107 |
<span>約合 <b>${weeksLeft}</b> 週</span>
|
| 1108 |
</div>
|
| 1109 |
</div>
|
|
|
|
| 1110 |
|
| 1111 |
<div class="card-badge-wrap">
|
| 1112 |
<div class="status-capsule">${badgeText}</div>
|
|
|
|
| 1120 |
});
|
| 1121 |
}
|
| 1122 |
|
| 1123 |
+
// ========================================================
|
| 1124 |
+
// 大屏觸控與滑鼠拖曳/縮放核心 (修復卡頓:拖動中不摧毀 DOM)
|
| 1125 |
+
// ========================================================
|
| 1126 |
function attachTouchAndPointerEvents(cardEl, cardData) {
|
| 1127 |
const resizeHandle = cardEl.querySelector('.resize-handle');
|
| 1128 |
|
| 1129 |
+
// 1. 卡片移動拖曳
|
| 1130 |
cardEl.addEventListener('pointerdown', (e) => {
|
| 1131 |
if (!isEditing) return;
|
| 1132 |
if (e.target === resizeHandle || e.target.classList.contains('resize-handle')) return;
|
|
|
|
| 1169 |
cardEl.addEventListener('pointercancel', onPointerUp);
|
| 1170 |
});
|
| 1171 |
|
| 1172 |
+
// 2. 右下角角標拉伸縮放 (流暢優化:即時修改樣式,放開才全面儲存)
|
| 1173 |
resizeHandle.addEventListener('pointerdown', (e) => {
|
| 1174 |
if (!isEditing) return;
|
| 1175 |
e.stopPropagation();
|
|
|
|
| 1181 |
let startW = cardEl.offsetWidth;
|
| 1182 |
let startH = cardEl.offsetHeight;
|
| 1183 |
|
| 1184 |
+
const metricNum = cardEl.querySelector('.metric-number');
|
| 1185 |
+
const dynamicBody = cardEl.querySelector('.card-dynamic-body');
|
| 1186 |
+
|
| 1187 |
function onResizeMove(ev) {
|
| 1188 |
if (ev.pointerId !== e.pointerId) return;
|
| 1189 |
ev.preventDefault();
|
| 1190 |
+
|
| 1191 |
+
// 直覺計算全新寬度與高度
|
| 1192 |
const newW = Math.max(240, startW + (ev.clientX - startX));
|
| 1193 |
const newH = Math.max(180, startH + (ev.clientY - startY));
|
| 1194 |
|
| 1195 |
+
// 直接操作當前卡片樣式,不中斷事件流
|
| 1196 |
cardEl.style.width = `${newW}px`;
|
| 1197 |
cardEl.style.height = `${newH}px`;
|
| 1198 |
cardData.w = newW;
|
| 1199 |
cardData.h = newH;
|
| 1200 |
+
|
| 1201 |
+
// 動態平滑調整大數字字級
|
| 1202 |
+
const isTall = newH >= 360;
|
| 1203 |
+
const fontScale = Math.min(newW / 4, newH / (isTall ? 3.8 : 2.5));
|
| 1204 |
+
const fontSizeRem = Math.max(2.8, Math.min(fontScale / 16, 7.5));
|
| 1205 |
+
if (metricNum) metricNum.style.fontSize = `${fontSizeRem}rem`;
|
| 1206 |
+
|
| 1207 |
+
// 動態顯示/隱藏進度條
|
| 1208 |
+
if (dynamicBody) dynamicBody.style.display = isTall ? 'flex' : 'none';
|
| 1209 |
}
|
| 1210 |
|
| 1211 |
function onResizeUp(ev) {
|
|
|
|
| 1214 |
resizeHandle.removeEventListener('pointermove', onResizeMove);
|
| 1215 |
resizeHandle.removeEventListener('pointerup', onResizeUp);
|
| 1216 |
resizeHandle.removeEventListener('pointercancel', onResizeUp);
|
| 1217 |
+
|
| 1218 |
+
// 手指放開時,保存數據並重新校準極光焦點
|
| 1219 |
saveAppData();
|
| 1220 |
+
renderBoard();
|
| 1221 |
}
|
| 1222 |
|
| 1223 |
resizeHandle.addEventListener('pointermove', onResizeMove);
|
|
|
|
| 1226 |
});
|
| 1227 |
}
|
| 1228 |
|
| 1229 |
+
// 極光引擎 (多重高亮焦點光暈)
|
| 1230 |
const canvas = document.getElementById("aurora-canvas");
|
| 1231 |
const ctx = canvas.getContext("2d");
|
| 1232 |
let animId = null;
|
|
|
|
| 1243 |
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
| 1244 |
step += 0.008;
|
| 1245 |
|
|
|
|
| 1246 |
const highlightCards = appState.cards.filter(c => c.highlight);
|
| 1247 |
const primaryHighlight = highlightCards[0];
|
| 1248 |
const focalX = primaryHighlight ? (primaryHighlight.x + primaryHighlight.w / 2) : canvas.width * 0.7;
|
|
|
|
| 1287 |
ctx.filter = "none";
|
| 1288 |
});
|
| 1289 |
|
|
|
|
| 1290 |
highlightCards.forEach(c => {
|
| 1291 |
const cx = c.x + c.w / 2;
|
| 1292 |
const cy = c.y + c.h / 2;
|
|
|
|
| 1406 |
});
|
| 1407 |
}
|
| 1408 |
|
|
|
|
| 1409 |
function toggleCardHighlight(idx, isChecked) {
|
| 1410 |
appState.cards[idx].highlight = isChecked;
|
| 1411 |
renderBoard();
|