Update index.html
Browse files- index.html +62 -96
index.html
CHANGED
|
@@ -19,9 +19,6 @@
|
|
| 19 |
<link href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.css" rel="stylesheet">
|
| 20 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.js"></script>
|
| 21 |
|
| 22 |
-
<!-- FontAwesome 6 (For Consistent Icons) -->
|
| 23 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 24 |
-
|
| 25 |
<!-- Google Fonts -->
|
| 26 |
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;700&display=swap" rel="stylesheet">
|
| 27 |
|
|
@@ -100,12 +97,6 @@
|
|
| 100 |
width: 100%;
|
| 101 |
height: 100%;
|
| 102 |
}
|
| 103 |
-
|
| 104 |
-
/* FontAwesome Adjustments */
|
| 105 |
-
.fa-icon {
|
| 106 |
-
display: inline-block;
|
| 107 |
-
line-height: 1;
|
| 108 |
-
}
|
| 109 |
</style>
|
| 110 |
</head>
|
| 111 |
<body class="h-screen overflow-hidden text-slate-800">
|
|
@@ -121,7 +112,10 @@
|
|
| 121 |
<div class="p-6 border-b border-slate-100 bg-slate-50">
|
| 122 |
<div class="flex justify-between items-center mb-2">
|
| 123 |
<h1 class="text-2xl font-bold text-indigo-600 flex items-center gap-2">
|
| 124 |
-
|
|
|
|
|
|
|
|
|
|
| 125 |
魔法摺紙
|
| 126 |
</h1>
|
| 127 |
</div>
|
|
@@ -169,7 +163,10 @@
|
|
| 169 |
@click="clearAllContent"
|
| 170 |
class="w-full py-2 text-sm text-red-600 border border-red-200 bg-red-50 hover:bg-red-100 rounded-lg transition-all flex items-center justify-center gap-1"
|
| 171 |
>
|
| 172 |
-
|
|
|
|
|
|
|
|
|
|
| 173 |
一鍵清空所有內容
|
| 174 |
</button>
|
| 175 |
</div>
|
|
@@ -181,7 +178,10 @@
|
|
| 181 |
@click="applyTemplate('lucky')"
|
| 182 |
class="w-full py-3 bg-gradient-to-r from-emerald-500 to-teal-600 hover:from-emerald-600 hover:to-teal-700 text-white font-bold rounded-lg shadow-md hover:shadow-lg active:scale-95 transition-all flex items-center justify-center gap-2"
|
| 183 |
>
|
| 184 |
-
|
|
|
|
|
|
|
|
|
|
| 185 |
<span class="text-sm">套用:幸運遇見你<br><span class="text-xs opacity-90">(如皓老師分享)</span></span>
|
| 186 |
</button>
|
| 187 |
<p class="text-xs text-slate-400 mt-2">提示:套用後會自動切換為 6x8 網格並填入內容。</p>
|
|
@@ -195,14 +195,16 @@
|
|
| 195 |
@click="exportProject"
|
| 196 |
class="py-2 bg-slate-600 hover:bg-slate-700 text-white font-bold rounded-lg shadow-sm active:scale-95 transition-all flex items-center justify-center gap-1 text-sm"
|
| 197 |
>
|
| 198 |
-
|
|
|
|
| 199 |
匯出存檔
|
| 200 |
</button>
|
| 201 |
<button
|
| 202 |
@click="triggerImport"
|
| 203 |
class="py-2 bg-slate-600 hover:bg-slate-700 text-white font-bold rounded-lg shadow-sm active:scale-95 transition-all flex items-center justify-center gap-1 text-sm"
|
| 204 |
>
|
| 205 |
-
|
|
|
|
| 206 |
匯入舊檔
|
| 207 |
</button>
|
| 208 |
<input
|
|
@@ -267,7 +269,7 @@
|
|
| 267 |
<label class="block text-sm font-bold text-slate-700 mb-3">3. 選擇圖示</label>
|
| 268 |
<div class="grid grid-cols-4 gap-2 max-h-60 overflow-y-auto p-1 custom-scrollbar">
|
| 269 |
<button
|
| 270 |
-
v-for="(
|
| 271 |
:key="name"
|
| 272 |
@click="applyIconToCell(name)"
|
| 273 |
:disabled="selectedCellIndex === null"
|
|
@@ -275,7 +277,9 @@
|
|
| 275 |
:class="selectedCellIndex === null ? 'border-slate-200 text-slate-300 cursor-not-allowed' : 'border-slate-300 text-slate-600 hover:border-indigo-400 hover:text-indigo-600 cursor-pointer'"
|
| 276 |
:title="name"
|
| 277 |
>
|
| 278 |
-
<
|
|
|
|
|
|
|
| 279 |
</button>
|
| 280 |
</div>
|
| 281 |
</div>
|
|
@@ -288,7 +292,7 @@
|
|
| 288 |
@click="triggerImageUpload"
|
| 289 |
class="text-xs bg-indigo-100 text-indigo-700 px-2 py-1 rounded hover:bg-indigo-200 font-bold flex items-center gap-1"
|
| 290 |
>
|
| 291 |
-
<
|
| 292 |
上傳圖片
|
| 293 |
</button>
|
| 294 |
<input type="file" ref="imageUploadInput" class="hidden" accept="image/*" @change="handleImageUpload">
|
|
@@ -349,11 +353,16 @@
|
|
| 349 |
class="w-full py-4 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl shadow-lg hover:shadow-xl active:scale-95 transition-all flex items-center justify-center gap-2 disabled:opacity-70 disabled:cursor-wait"
|
| 350 |
>
|
| 351 |
<span v-if="!isGenerating">
|
| 352 |
-
|
|
|
|
| 353 |
下載雙頁 PDF
|
| 354 |
</span>
|
| 355 |
<span v-else>
|
| 356 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 357 |
處理中...
|
| 358 |
</span>
|
| 359 |
</button>
|
|
@@ -404,8 +413,10 @@
|
|
| 404 |
<div class="rotation-wrapper" :style="{ transform: `rotate(${cell.rotation}deg)` }">
|
| 405 |
<span v-if="cell.type === 'text'" class="text-2xl font-bold cell-text" :style="{ color: cell.color }">{{ cell.content }}</span>
|
| 406 |
|
| 407 |
-
<!-- Render Icon:
|
| 408 |
-
<
|
|
|
|
|
|
|
| 409 |
|
| 410 |
<!-- Image Renderer -->
|
| 411 |
<img v-if="cell.type === 'image'" :src="cell.content" class="w-4/5 h-4/5 object-contain">
|
|
@@ -419,7 +430,9 @@
|
|
| 419 |
<!-- EDIT MODE -->
|
| 420 |
<div v-else class="flex flex-col gap-2 animate-fade-in">
|
| 421 |
<button @click="viewMode = 'overview'" class="self-start text-slate-500 hover:text-indigo-600 font-bold text-sm flex items-center gap-1">
|
| 422 |
-
|
|
|
|
|
|
|
| 423 |
</button>
|
| 424 |
|
| 425 |
<div
|
|
@@ -451,8 +464,10 @@
|
|
| 451 |
{{ cell.content }}
|
| 452 |
</span>
|
| 453 |
|
| 454 |
-
<!-- Render Icon:
|
| 455 |
-
<
|
|
|
|
|
|
|
| 456 |
|
| 457 |
<!-- Image Renderer -->
|
| 458 |
<img v-if="cell.type === 'image'" :src="cell.content" class="w-4/5 h-4/5 object-contain">
|
|
@@ -498,25 +513,26 @@
|
|
| 498 |
// Slate-800 is default
|
| 499 |
const colors = ['#1e293b', '#ef4444', '#f97316', '#f59e0b', '#22c55e', '#14b8a6', '#3b82f6', '#6366f1', '#a855f7', '#ec4899'];
|
| 500 |
|
| 501 |
-
// --- Icons (
|
|
|
|
| 502 |
const icons = {
|
| 503 |
-
'幸運草': '
|
| 504 |
-
'愛心': '
|
| 505 |
-
'星星': '
|
| 506 |
-
'勝利': '
|
| 507 |
-
'獎盃': '
|
| 508 |
-
'笑臉': '
|
| 509 |
-
'皇冠': '
|
| 510 |
-
'鑽石': '
|
| 511 |
-
'燈泡': '
|
| 512 |
-
'太陽': '
|
| 513 |
-
'月亮': '
|
| 514 |
-
'雲朵': '
|
| 515 |
-
'音符': '
|
| 516 |
-
'飛機': '
|
| 517 |
-
'花朵': '
|
| 518 |
-
'樹木': '
|
| 519 |
-
'禮物': '
|
| 520 |
};
|
| 521 |
|
| 522 |
// --- State Initialization ---
|
|
@@ -893,32 +909,6 @@
|
|
| 893 |
}, { deep: true });
|
| 894 |
});
|
| 895 |
|
| 896 |
-
// Helper to extract unicode from FontAwesome Class
|
| 897 |
-
const getIconUnicode = (className) => {
|
| 898 |
-
const el = document.createElement('i');
|
| 899 |
-
el.className = className;
|
| 900 |
-
document.body.appendChild(el);
|
| 901 |
-
const content = window.getComputedStyle(el, ':before').getPropertyValue('content');
|
| 902 |
-
document.body.removeChild(el);
|
| 903 |
-
return content.replace(/['"]/g, '');
|
| 904 |
-
};
|
| 905 |
-
|
| 906 |
-
// Helper to convert text (or icon) to image data URL
|
| 907 |
-
const textToImage = (text, color, font) => {
|
| 908 |
-
const canvas = document.createElement('canvas');
|
| 909 |
-
// Increase resolution for print quality
|
| 910 |
-
canvas.width = 150;
|
| 911 |
-
canvas.height = 150;
|
| 912 |
-
const ctx = canvas.getContext('2d');
|
| 913 |
-
ctx.font = font;
|
| 914 |
-
ctx.fillStyle = color;
|
| 915 |
-
ctx.textAlign = 'center';
|
| 916 |
-
ctx.textBaseline = 'middle';
|
| 917 |
-
// Draw
|
| 918 |
-
ctx.fillText(text, 75, 75);
|
| 919 |
-
return canvas.toDataURL('image/png');
|
| 920 |
-
};
|
| 921 |
-
|
| 922 |
// Helper to replace text element with SVG for perfect centering
|
| 923 |
const replaceWithSvgText = (el, content, color, fontFamily, fontWeight) => {
|
| 924 |
const ns = "http://www.w3.org/2000/svg";
|
|
@@ -972,12 +962,9 @@
|
|
| 972 |
// Text uses the SVG text replacement trick for perfect centering
|
| 973 |
contentHtml = `<span class="export-text" data-color="${cell.color}" style="font-size: 40px; font-weight: bold; color: ${cell.color}; font-family: 'Noto Sans TC', sans-serif;">${cell.content}</span>`;
|
| 974 |
} else if (cell.type === 'icon') {
|
| 975 |
-
//
|
| 976 |
-
|
| 977 |
-
|
| 978 |
-
const unicode = getIconUnicode(iconClass);
|
| 979 |
-
// Using 'Font Awesome 6 Free' with font-weight 900 for solid icons
|
| 980 |
-
contentHtml = `<span class="export-icon" data-color="${cell.color}" style="font-size: 50px; line-height: 1; color: ${cell.color};">${unicode}</span>`;
|
| 981 |
} else if (cell.type === 'image') {
|
| 982 |
contentHtml = `<img src="${cell.content}" style="width: 80%; height: 80%; object-fit: contain;">`;
|
| 983 |
}
|
|
@@ -1004,7 +991,7 @@
|
|
| 1004 |
useCORS: true,
|
| 1005 |
backgroundColor: '#ffffff',
|
| 1006 |
onclone: (clonedDoc) => {
|
| 1007 |
-
// Handle Text Centering
|
| 1008 |
const textElements = clonedDoc.querySelectorAll('.export-text');
|
| 1009 |
textElements.forEach(el => {
|
| 1010 |
const textContent = el.innerText;
|
|
@@ -1012,27 +999,6 @@
|
|
| 1012 |
if (!textContent) return;
|
| 1013 |
replaceWithSvgText(el, textContent, color, "'Noto Sans TC', sans-serif", "bold");
|
| 1014 |
});
|
| 1015 |
-
|
| 1016 |
-
// Handle Icon Centering via Image conversion (Fixes PDF box issue)
|
| 1017 |
-
const iconElements = clonedDoc.querySelectorAll('.export-icon');
|
| 1018 |
-
iconElements.forEach(el => {
|
| 1019 |
-
const textContent = el.innerText;
|
| 1020 |
-
const color = el.getAttribute('data-color');
|
| 1021 |
-
if (!textContent) return;
|
| 1022 |
-
|
| 1023 |
-
// Convert to image using canvas in main thread context
|
| 1024 |
-
// 'Font Awesome 6 Free' must be loaded in the page
|
| 1025 |
-
const imgData = textToImage(textContent, color, "900 100px 'Font Awesome 6 Free'");
|
| 1026 |
-
|
| 1027 |
-
const img = document.createElement('img');
|
| 1028 |
-
img.src = imgData;
|
| 1029 |
-
img.style.width = '100%';
|
| 1030 |
-
img.style.height = '100%';
|
| 1031 |
-
img.style.objectFit = 'contain';
|
| 1032 |
-
|
| 1033 |
-
el.parentNode.innerHTML = ''; // Clear parent
|
| 1034 |
-
el.parentNode.appendChild(img);
|
| 1035 |
-
});
|
| 1036 |
}
|
| 1037 |
});
|
| 1038 |
|
|
|
|
| 19 |
<link href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.css" rel="stylesheet">
|
| 20 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.js"></script>
|
| 21 |
|
|
|
|
|
|
|
|
|
|
| 22 |
<!-- Google Fonts -->
|
| 23 |
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;700&display=swap" rel="stylesheet">
|
| 24 |
|
|
|
|
| 97 |
width: 100%;
|
| 98 |
height: 100%;
|
| 99 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
</style>
|
| 101 |
</head>
|
| 102 |
<body class="h-screen overflow-hidden text-slate-800">
|
|
|
|
| 112 |
<div class="p-6 border-b border-slate-100 bg-slate-50">
|
| 113 |
<div class="flex justify-between items-center mb-2">
|
| 114 |
<h1 class="text-2xl font-bold text-indigo-600 flex items-center gap-2">
|
| 115 |
+
<!-- Magic Wand Icon -->
|
| 116 |
+
<svg viewBox="0 0 512 512" class="w-8 h-8 fill-current">
|
| 117 |
+
<path d="M447.4 9.4c-9.4-9.4-24.6-9.4-33.9 0l-80 80c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l80-80c9.4-9.4 9.4-24.6 0-33.9zM368 224c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm64-96c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm-64 96c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zM96 192c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zM0 224c0-17.7 14.3-32 32-32s32 14.3 32 32-14.3 32-32 32-32-14.3-32-32zM64 64c0-17.7 14.3-32 32-32s32 14.3 32 32-14.3 32-32 32-32-14.3-32-32zm0 384c0-17.7 14.3-32 32-32s32 14.3 32 32-14.3 32-32 32-32-14.3-32-32zm256 32c0-17.7 14.3-32 32-32s32 14.3 32 32-14.3 32-32 32-32-14.3-32-32zm-64-96c0-17.7 14.3-32 32-32s32 14.3 32 32-14.3 32-32 32-32-14.3-32-32zm-96-32c0-17.7 14.3-32 32-32s32 14.3 32 32-14.3 32-32 32-32-14.3-32-32zm336-32c0-17.7 14.3-32 32-32s32 14.3 32 32-14.3 32-32 32-32-14.3-32-32zm-136.6 20.7c-9.4-9.4-24.6-9.4-33.9 0L19.4 430.6c-9.4 9.4-9.4 24.6 0 33.9l62.1 62.1c9.4 9.4 24.6 9.4 33.9 0l229.3-229.3c9.4-9.4 9.4-24.6 0-33.9l-62.1-62.1z"/>
|
| 118 |
+
</svg>
|
| 119 |
魔法摺紙
|
| 120 |
</h1>
|
| 121 |
</div>
|
|
|
|
| 163 |
@click="clearAllContent"
|
| 164 |
class="w-full py-2 text-sm text-red-600 border border-red-200 bg-red-50 hover:bg-red-100 rounded-lg transition-all flex items-center justify-center gap-1"
|
| 165 |
>
|
| 166 |
+
<!-- Trash Icon -->
|
| 167 |
+
<svg viewBox="0 0 448 512" class="w-4 h-4 fill-current mr-1">
|
| 168 |
+
<path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/>
|
| 169 |
+
</svg>
|
| 170 |
一鍵清空所有內容
|
| 171 |
</button>
|
| 172 |
</div>
|
|
|
|
| 178 |
@click="applyTemplate('lucky')"
|
| 179 |
class="w-full py-3 bg-gradient-to-r from-emerald-500 to-teal-600 hover:from-emerald-600 hover:to-teal-700 text-white font-bold rounded-lg shadow-md hover:shadow-lg active:scale-95 transition-all flex items-center justify-center gap-2"
|
| 180 |
>
|
| 181 |
+
<!-- Clover Icon (FA6) -->
|
| 182 |
+
<svg viewBox="0 0 512 512" class="w-5 h-5 fill-current">
|
| 183 |
+
<path d="M224 96a64 64 0 1 1 128 0l0 32-32 0c-17.7 0-32 14.3-32 32l0 128c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-160L96 128c-35.3 0-64 28.7-64 64s28.7 64 64 64l32 0 0 32c0 17.7 14.3 32 32 32l128 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-160 0c0-35.3-28.7-64-64-64s-64 28.7-64 64s28.7 64 64 64l32 0 0 32c0 17.7 14.3 32 32 32l112 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-48 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l48 0c44.2 0 80-35.8 80-80l0-16c35.3 0 64-28.7 64-64s-28.7-64-64-64l-32 0 0-32c0-17.7-14.3-32-32-32l-128 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l160 0c0 35.3 28.7 64 64 64s64-28.7 64-64s-28.7-64-64-64l-32 0 0-32c0-17.7-14.3-32-32-32s-32 14.3-32 32z"/>
|
| 184 |
+
</svg>
|
| 185 |
<span class="text-sm">套用:幸運遇見你<br><span class="text-xs opacity-90">(如皓老師分享)</span></span>
|
| 186 |
</button>
|
| 187 |
<p class="text-xs text-slate-400 mt-2">提示:套用後會自動切換為 6x8 網格並填入內容。</p>
|
|
|
|
| 195 |
@click="exportProject"
|
| 196 |
class="py-2 bg-slate-600 hover:bg-slate-700 text-white font-bold rounded-lg shadow-sm active:scale-95 transition-all flex items-center justify-center gap-1 text-sm"
|
| 197 |
>
|
| 198 |
+
<!-- Download Icon -->
|
| 199 |
+
<svg viewBox="0 0 512 512" class="w-4 h-4 fill-current mr-1"><path d="M288 32c0-17.7-14.3-32-32-32s-32 14.3-32 32V274.7l-73.4-73.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l128 128c12.5 12.5 32.8 12.5 45.3 0l128-128c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L288 274.7V32zM64 352c-35.3 0-64 28.7-64 64v32c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V416c0-35.3-28.7-64-64-64H346.5l-45.3 45.3c-25 25-65.5 25-90.5 0L165.5 352H64zM432 456c-13.3 0-24-10.7-24-24s10.7-24 24-24s24 10.7 24 24s-10.7 24-24 24z"/></svg>
|
| 200 |
匯出存檔
|
| 201 |
</button>
|
| 202 |
<button
|
| 203 |
@click="triggerImport"
|
| 204 |
class="py-2 bg-slate-600 hover:bg-slate-700 text-white font-bold rounded-lg shadow-sm active:scale-95 transition-all flex items-center justify-center gap-1 text-sm"
|
| 205 |
>
|
| 206 |
+
<!-- Upload Icon -->
|
| 207 |
+
<svg viewBox="0 0 512 512" class="w-4 h-4 fill-current mr-1"><path d="M288 109.3V352c0 17.7-14.3 32-32 32s-32-14.3-32-32V109.3l-73.4 73.4c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l128-128c12.5-12.5 32.8-12.5 45.3 0l128 128c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L288 109.3zM64 352H192c0 35.3 28.7 64 64 64s64-28.7 64-64H448c35.3 0 64 28.7 64 64v32c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V416c0-35.3 28.7-64 64-64zM432 456c13.3 0 24-10.7 24-24s-10.7-24-24-24s-24 10.7-24 24s10.7 24 24 24z"/></svg>
|
| 208 |
匯入舊檔
|
| 209 |
</button>
|
| 210 |
<input
|
|
|
|
| 269 |
<label class="block text-sm font-bold text-slate-700 mb-3">3. 選擇圖示</label>
|
| 270 |
<div class="grid grid-cols-4 gap-2 max-h-60 overflow-y-auto p-1 custom-scrollbar">
|
| 271 |
<button
|
| 272 |
+
v-for="(path, name) in icons"
|
| 273 |
:key="name"
|
| 274 |
@click="applyIconToCell(name)"
|
| 275 |
:disabled="selectedCellIndex === null"
|
|
|
|
| 277 |
:class="selectedCellIndex === null ? 'border-slate-200 text-slate-300 cursor-not-allowed' : 'border-slate-300 text-slate-600 hover:border-indigo-400 hover:text-indigo-600 cursor-pointer'"
|
| 278 |
:title="name"
|
| 279 |
>
|
| 280 |
+
<svg viewBox="0 0 512 512" class="w-8 h-8" :style="{ fill: selectedCellIndex !== null ? selectedColor : '#1e293b' }">
|
| 281 |
+
<path :d="path"/>
|
| 282 |
+
</svg>
|
| 283 |
</button>
|
| 284 |
</div>
|
| 285 |
</div>
|
|
|
|
| 292 |
@click="triggerImageUpload"
|
| 293 |
class="text-xs bg-indigo-100 text-indigo-700 px-2 py-1 rounded hover:bg-indigo-200 font-bold flex items-center gap-1"
|
| 294 |
>
|
| 295 |
+
<svg viewBox="0 0 512 512" class="w-3 h-3 fill-current mr-1"><path d="M0 96C0 60.7 28.7 32 64 32H448c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6h96 32H424c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192c26.5 0 48-21.5 48-48s-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48z"/></svg>
|
| 296 |
上傳圖片
|
| 297 |
</button>
|
| 298 |
<input type="file" ref="imageUploadInput" class="hidden" accept="image/*" @change="handleImageUpload">
|
|
|
|
| 353 |
class="w-full py-4 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl shadow-lg hover:shadow-xl active:scale-95 transition-all flex items-center justify-center gap-2 disabled:opacity-70 disabled:cursor-wait"
|
| 354 |
>
|
| 355 |
<span v-if="!isGenerating">
|
| 356 |
+
<!-- PDF Icon -->
|
| 357 |
+
<svg viewBox="0 0 384 512" class="w-5 h-5 fill-current inline-block"><path d="M181.9 256.1c-5-16-4.9-46.9-2-46.9 8.4 0 7.6 36.9 2 46.9zm-1.7 47.2c-7.7 20.2-17.3 43.3-28.4 62.7 18.3-7 39-17.2 62.9-21.9-12.7-9.6-24.9-23.4-34.5-40.8zM86.1 428.1c0 .8 13.2-5.4 34.9-40.2-6.7 6.3-29.1 24.5-34.9 40.2zM248 160h136v328c0 13.3-10.7 24-24 24H24c-13.3 0-24-10.7-24-24V24C0 10.7 10.7 0 24 0h200v136c0 13.2 10.8 24 24 24zm-8 171.8c-20-12.2-33.3-29-42.7-53.8 4.5-18.5 11.6-46.6 6.2-64.2-4.7-29.4-42.4-26.5-47.8-6.8-5 18.3-.4 44.1 8.1 77-11.6 27.6-28.7 64.6-40.8 85.8-.1 0-.1.1-.2.1-27.1 13.9-73.6 44.5-54.5 68 5.6 6.9 16 10 21.5 10 17.9 0 35.7-18 61.1-61.8 25.8-8.5 54.1-19.1 79-23.2 21.7 11.8 47.1 19.5 64 19.5 29.2 0 31.2-32 19.7-43.4-13.9-13.6-54.3-9.7-73.6-7.2zM377 105L279 7c-4.5-4.5-10.6-7-17-7h-6v128h128v-6.1c0-6.3-2.5-12.4-7-16.9zm-74.1 255.3c4.1-2.7-2.5-11.9-4.2-17.9-2.9-4.1-32.9-14.9-49.6-19.7 1.4 4.9 3 9.8 4.2 14.6 3.1 17.9 6.9 51.1 12.8 62.1 9.5 1 34.1-33 36.8-39.1z"/></svg>
|
| 358 |
下載雙頁 PDF
|
| 359 |
</span>
|
| 360 |
<span v-else>
|
| 361 |
+
<!-- Spinner -->
|
| 362 |
+
<svg class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
| 363 |
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
| 364 |
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
| 365 |
+
</svg>
|
| 366 |
處理中...
|
| 367 |
</span>
|
| 368 |
</button>
|
|
|
|
| 413 |
<div class="rotation-wrapper" :style="{ transform: `rotate(${cell.rotation}deg)` }">
|
| 414 |
<span v-if="cell.type === 'text'" class="text-2xl font-bold cell-text" :style="{ color: cell.color }">{{ cell.content }}</span>
|
| 415 |
|
| 416 |
+
<!-- Render Icon: SVG Path -->
|
| 417 |
+
<svg v-if="cell.type === 'icon'" viewBox="0 0 512 512" class="w-3/5 h-3/5" :style="{ fill: cell.color }">
|
| 418 |
+
<path :d="icons[cell.content]"/>
|
| 419 |
+
</svg>
|
| 420 |
|
| 421 |
<!-- Image Renderer -->
|
| 422 |
<img v-if="cell.type === 'image'" :src="cell.content" class="w-4/5 h-4/5 object-contain">
|
|
|
|
| 430 |
<!-- EDIT MODE -->
|
| 431 |
<div v-else class="flex flex-col gap-2 animate-fade-in">
|
| 432 |
<button @click="viewMode = 'overview'" class="self-start text-slate-500 hover:text-indigo-600 font-bold text-sm flex items-center gap-1">
|
| 433 |
+
<!-- Arrow Left -->
|
| 434 |
+
<svg viewBox="0 0 448 512" class="w-4 h-4 fill-current mr-1 inline-block"><path d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.2 288 416 288c17.7 0 32-14.3 32-32s-14.3-32-32-32l-306.7 0L214.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg>
|
| 435 |
+
回到全覽
|
| 436 |
</button>
|
| 437 |
|
| 438 |
<div
|
|
|
|
| 464 |
{{ cell.content }}
|
| 465 |
</span>
|
| 466 |
|
| 467 |
+
<!-- Render Icon: SVG Path -->
|
| 468 |
+
<svg v-if="cell.type === 'icon'" viewBox="0 0 512 512" class="w-3/5 h-3/5" :style="{ fill: cell.color }">
|
| 469 |
+
<path :d="icons[cell.content]"/>
|
| 470 |
+
</svg>
|
| 471 |
|
| 472 |
<!-- Image Renderer -->
|
| 473 |
<img v-if="cell.type === 'image'" :src="cell.content" class="w-4/5 h-4/5 object-contain">
|
|
|
|
| 513 |
// Slate-800 is default
|
| 514 |
const colors = ['#1e293b', '#ef4444', '#f97316', '#f59e0b', '#22c55e', '#14b8a6', '#3b82f6', '#6366f1', '#a855f7', '#ec4899'];
|
| 515 |
|
| 516 |
+
// --- Icons (FontAwesome 6 SVG Paths) ---
|
| 517 |
+
// This ensures perfect PDF export without needing webfonts
|
| 518 |
const icons = {
|
| 519 |
+
'幸運草': 'M224 96a64 64 0 1 1 128 0l0 32-32 0c-17.7 0-32 14.3-32 32l0 128c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-160L96 128c-35.3 0-64 28.7-64 64s28.7 64 64 64l32 0 0 32c0 17.7 14.3 32 32 32l128 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-160 0c0-35.3-28.7-64-64-64s-64 28.7-64 64s28.7 64 64 64l32 0 0 32c0 17.7 14.3 32 32 32l112 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-48 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l48 0c44.2 0 80-35.8 80-80l0-16c35.3 0 64-28.7 64-64s-28.7-64-64-64l-32 0 0-32c0-17.7-14.3-32-32-32l-128 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l160 0c0 35.3 28.7 64 64 64s64-28.7 64-64s-28.7-64-64-64l-32 0 0-32c0-17.7-14.3-32-32-32s-32 14.3-32 32z',
|
| 520 |
+
'愛心': 'M47.6 300.4L228.3 469.1c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z',
|
| 521 |
+
'星星': 'M316.9 18C311.6 7 300.4 0 288.1 0s-23.4 7-28.8 18L195 150.3 51.4 171.5c-12 1.8-22 10.2-25.7 21.7s-.7 24.2 7.9 32.7L137.8 329 113.2 474.7c-2 12 3 24.2 12.9 31.3s23 8 33.8 2.3l128.3-68.5 128.3 68.5c10.8 5.7 23.9 4.9 33.8-2.3s14.9-19.3 12.9-31.3L438.5 329 542.7 225.9c8.6-8.5 11.7-21.2 7.9-32.7s-13.7-19.9-25.7-21.7L381.2 150.3 316.9 18z',
|
| 522 |
+
'勝利': 'M128 0c17.7 0 32 14.3 32 32l0 263.7L235 220.1c16.3-8.8 36.6-2.9 45.4 13.3s2.9 36.6-13.3 45.4l-117.6 63.6c-19.4 10.5-32.3 29.8-34.9 51.8l-4.1 34.7c-3.7 31.6 18.5 60.5 49.8 65.6l96.6 15.7c33.9 5.5 67.2-16.1 75.6-49.1L355.7 350.2c2.5-10 11.6-16.9 21.9-16.6l9.5 .2c23 0 42.8-16.4 47.1-39.1l2.5-13.2c4.3-22.7-10.6-44.5-33.3-48.8l-15.1-2.9c-24-4.5-39.7-28-35.1-52.1l19.1-100.9C382.9 26.6 427.1-3.6 477.3 6.9c25.3 5.3 45.7 23.1 54.3 47.7s4.8 51.7-10.1 72l-58.4 79.7c-30.7 41.9-40.4 96.1-26.6 146.7l12.7 46.2c17.3 63.3 10.6 131.2-18.6 189.4c-9.2 18.3-25.9 32.1-45.7 37.9L288 456c-38.3 11.2-80-6-102.6-39.2L128 332.8 128 32c0-17.7 14.3-32 32-32zM32 64C14.3 64 0 78.3 0 96L0 448c0 17.7 14.3 32 32 32s32-14.3 32-32l0-352c0-17.7-14.3-32-32-32z',
|
| 523 |
+
'獎盃': 'M400 96l48 0c26.5 0 48 21.5 48 48l0 48c0 62.1-44.5 113.8-103.2 126.3c-13.2 38.3-43.1 68.6-80.8 81.3L336 448l16 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-192 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l16 0 24-48.4c-37.7-12.7-67.6-43-80.8-81.3C60.5 305.8 16 254.1 16 192L16 144c0-26.5 21.5-48 48-48l48 0 0-64c0-17.7 14.3-32 32-32l224 0c17.7 0 32 14.3 32 32l0 64zM80 144l0 48c0 23.4 16.8 42.9 38.9 47.1c4.4-7.3 9.4-14.2 14.8-20.6L128 144l-48 0zm352 48l0-48-48 0 0 74.4c5.4 6.4 10.4 13.3 14.8 20.6C383.2 234.9 400 215.4 400 192z',
|
| 524 |
+
'笑臉': 'M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM164.1 325.5C182 346.2 212.6 368 256 368s74-21.8 91.9-42.5c5.8-6.7 15.9-7.4 22.6-1.6s7.4 15.9 1.6 22.6C349.8 372.1 311.1 400 256 400s-93.8-27.9-116.1-53.5c-5.8-6.7-5.1-16.8 1.6-22.6s16.8-5.1 22.6 1.6zM144.4 208a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zm192-32a32 32 0 1 1 0 64 32 32 0 1 1 0-64z',
|
| 525 |
+
'皇冠': 'M4.1 38.2C1.4 34.2 0 29.4 0 24.6C0 11 11 0 24.6 0C33.2 0 41.1 4.3 45.9 11.5L98.6 90.7l93.1-78.1C197 7.3 204.6 4.3 212.6 4.3C223 4.3 233 8.6 240 16.6l0 0 0 0c7-8 17-12.3 27.4-12.3c8 0 15.6 3 20.9 8.3l93.1 78.1 52.7-79.2C438.9 4.3 446.8 0 455.4 0C469 0 480 11 480 24.6c0 4.8-1.4 9.6-4.1 13.6L420.7 165.6c-5.5 8.2-13.8 14.1-23.4 16.5l-93.7 23.4L252.1 442.2c-2.8 11.3-13.1 19.3-24.8 19.2s-21.7-8.3-24.2-19.7l-49.4-235.6L58.7 182.2c-9.6-2.4-17.9-8.3-23.4-16.5L4.1 38.2z',
|
| 526 |
+
'鑽石': 'M242.4 292.5C247.8 287.1 257.1 287.1 262.5 292.5L339.5 369.5C353.5 383.5 372.6 391.3 392.4 391.3H416C416 417.7 394.7 439 368.4 439H143.6C117.3 439 96 417.7 96 391.3H119.6C139.4 391.3 158.5 383.5 172.5 369.5L242.4 292.5zM262.5 219.5C257.1 224.9 247.8 224.9 242.4 219.5L172.5 142.5C158.5 128.5 139.4 120.7 119.6 120.7H96C96 94.3 117.3 73 143.6 73H368.4C394.7 73 416 94.3 416 120.7H392.4C372.6 120.7 353.5 128.5 339.5 142.5L262.5 219.5z',
|
| 527 |
+
'燈泡': 'M272 64c-8.8 0-16 7.2-16 16s7.2 16 16 16c17.7 0 32 14.3 32 32s7.2 16 16 16 16-7.2 16-16c0-35.3-28.7-64-64-64zM256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-448c-11.8 0-23.2 .9-34.2 2.6c-4.3 .7-8.8-1.7-10.6-5.8s.3-8.9 4.1-11.6C235 35.5 256 24.3 278.4 16.9c4.2-1.4 8.7 .5 10.9 4.5s.9 8.9-3.1 11.7c-19.1 13.3-30.2 35.2-30.2 58.9z',
|
| 528 |
+
'太陽': 'M361.5 1.2c5 2.1 8.6 6.6 9.6 11.9L391 121l107.9 19.8c5.3 1 9.8 4.6 11.9 9.6s1.5 10.7-1.6 14.8L432.2 240l76.9 74.8c3.1 4.1 3.7 9.8 1.6 14.8s-6.6 8.6-11.9 9.6L391 359 371.1 466.9c-1 5.3-4.6 9.8-9.6 11.9s-10.7 1.5-14.8-1.6L272 400.2l-74.8 76.9c-4.1 3.1-9.8 3.7-14.8 1.6s-8.6-6.6-9.6-11.9L153 359 45.1 339.1c-5.3-1-9.8-4.6-11.9-9.6s-1.5-10.7 1.6-14.8L111.8 240 34.9 165.2c-3.1-4.1-3.7-9.8-1.6-14.8s6.6-8.6 11.9-9.6L153 121 172.9 13.1c1-5.3 4.6-9.8 9.6-11.9s10.7-1.5 14.8 1.6L272 79.8 346.8 2.9c4.1-3.1 9.8-3.7 14.8-1.6zM272 168c0-44.2-35.8-80-80-80s-80 35.8-80 80 35.8 80 80 80 80-35.8 80-80z',
|
| 529 |
+
'月亮': 'M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4 5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3 6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z',
|
| 530 |
+
'雲朵': 'M0 336c0 79.5 64.5 144 144 144H368c79.5 0 144-64.5 144-144c0-47.8-23.7-89.8-59.5-115.9c-2.3-67-56.7-120.1-124.5-120.1c-60.8 0-111.4 43.1-122.9 100.9C91.6 211.3 0 263.9 0 336z',
|
| 531 |
+
'音符': 'M496 64a16 16 0 0 0 -16 16v224c0 35.3-28.7 64-64 64s-64-28.7-64-64 28.7-64 64-64c12.5 0 24.1 3.6 34.1 9.8l13.9 8.7V64C464 28.7 435.3 0 400 0H112C76.7 0 48 28.7 48 64v288c0 35.3-28.7 64-64 64s-64-28.7-64-64 28.7-64 64-64c12.5 0 24.1 3.6 34.1 9.8l13.9 8.7V128h288v64H160v224c0 35.3-28.7 64-64 64s-64-28.7-64-64 28.7-64 64-64c12.5 0 24.1 3.6 34.1 9.8l13.9 8.7V112c0-8.8 7.2-16 16-16h320c8.8 0 16 7.2 16 16V80c0-8.8 7.2-16 16-16z',
|
| 532 |
+
'飛機': 'M384 160c-17.7 0-32-14.3-32-32s14.3-32 32-32H512c17.7 0 32 14.3 32 32V256c0 17.7-14.3 32-32 32s-32-14.3-32-32V160H384zM64 480c-17.7 0-32-14.3-32-32s14.3-32 32-32H192c17.7 0 32 14.3 32 32s-14.3 32-32 32H64zM224 96c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 64c0 17.7 14.3 32 32 32s32-14.3 32-32l0-64zM480 320c-17.7 0-32 14.3-32 32v64c0 17.7 14.3 32 32 32s32-14.3 32-32V352c0-17.7-14.3-32-32-32zM32 64C14.3 64 0 78.3 0 96v64c0 17.7 14.3 32 32 32s32-14.3 32-32V96H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H32zM276.9 221.7c-4.8 16.6-21.8 26.2-38.3 21.4s-26.2-21.8-21.4-38.3L237 135c4.8-16.6 21.8-26.2 38.3-21.4s26.2 21.8 21.4 38.3l-19.8 69.8z',
|
| 533 |
+
'花朵': 'M261.8 229.6l-20.2 20.2c-7.1 7.1-18.5 7.1-25.5 0l-20.2-20.2c-1.4-1.4-2.1-3.2-2.2-5.1c-14-159.2 122.9-281.3 266-223c6.3 2.5 10.3 8.7 10.3 15.4c0 82.6-67 149.6-149.6 149.6c-4.8 0-9.5-2.2-12.7-6.2c-3.1 4-7.9 6.2-12.7 6.2c-82.6 0-149.6 67-149.6 149.6c0 6.8 4 12.9 10.3 15.4c143.2 58.3 280-63.8 266-223c0-1.9-.8-3.7-2.2-5.1L491.4 88.9c7.1-7.1 7.1-18.5 0-25.5L471.2 43.1c-7.1-7.1-18.5-7.1-25.5 0l-20.2 20.2c-1.4 1.4-3.2 2.1-5.1 2.2c-159.2 14-281.3-122.9-223-266C200 193.3 206.1 189.3 212.9 189.3c82.6 0 149.6 67 149.6 149.6c0 4.8-2.2 9.5-6.2 12.7c4 3.1 6.2 7.9 6.2 12.7c0 82.6 67 149.6 149.6 149.6c6.8 0 12.9-4 15.4-10.3c58.3-143.2-63.8-280-223-266c-1.9 0-3.7 .8-5.1 2.2L261.8 229.6z',
|
| 534 |
+
'樹木': 'M448 32c-35.3 0-64 28.7-64 64V256c0 35.3 28.7 64 64 64H512c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H448zM96 32C60.7 32 32 60.7 32 96v256c0 35.3 28.7 64 64 64H224c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H96zM48 208v96c0 8.8-7.2 16-16 16s-16-7.2-16-16V208c0-8.8 7.2-16 16-16s16 7.2 16 16zm256-48c0 8.8-7.2 16-16 16s-16-7.2-16-16V144c0-8.8 7.2-16 16-16s16 7.2 16 16v16zm-96 96v96c0 8.8-7.2 16-16 16s-16-7.2-16-16V256c0-8.8 7.2-16 16-16s16 7.2 16 16z',
|
| 535 |
+
'禮物': 'M96 0C78.3 0 64 14.3 64 32v96h64V32c0-17.7-14.3-32-32-32H96zM288 128V32c0-17.7-14.3-32-32-32H224c-17.7 0-32 14.3-32 32v96h96zM64 160c-35.3 0-64 28.7-64 64V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V224c0-35.3-28.7-64-64-64H64z'
|
| 536 |
};
|
| 537 |
|
| 538 |
// --- State Initialization ---
|
|
|
|
| 909 |
}, { deep: true });
|
| 910 |
});
|
| 911 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 912 |
// Helper to replace text element with SVG for perfect centering
|
| 913 |
const replaceWithSvgText = (el, content, color, fontFamily, fontWeight) => {
|
| 914 |
const ns = "http://www.w3.org/2000/svg";
|
|
|
|
| 962 |
// Text uses the SVG text replacement trick for perfect centering
|
| 963 |
contentHtml = `<span class="export-text" data-color="${cell.color}" style="font-size: 40px; font-weight: bold; color: ${cell.color}; font-family: 'Noto Sans TC', sans-serif;">${cell.content}</span>`;
|
| 964 |
} else if (cell.type === 'icon') {
|
| 965 |
+
// Render SVG directly for PDF
|
| 966 |
+
const path = icons[cell.content];
|
| 967 |
+
contentHtml = `<svg viewBox="0 0 512 512" style="width: 60%; height: 60%; fill: ${cell.color};"><path d="${path}"/></svg>`;
|
|
|
|
|
|
|
|
|
|
| 968 |
} else if (cell.type === 'image') {
|
| 969 |
contentHtml = `<img src="${cell.content}" style="width: 80%; height: 80%; object-fit: contain;">`;
|
| 970 |
}
|
|
|
|
| 991 |
useCORS: true,
|
| 992 |
backgroundColor: '#ffffff',
|
| 993 |
onclone: (clonedDoc) => {
|
| 994 |
+
// Handle Text Centering (Only for Text now)
|
| 995 |
const textElements = clonedDoc.querySelectorAll('.export-text');
|
| 996 |
textElements.forEach(el => {
|
| 997 |
const textContent = el.innerText;
|
|
|
|
| 999 |
if (!textContent) return;
|
| 1000 |
replaceWithSvgText(el, textContent, color, "'Noto Sans TC', sans-serif", "bold");
|
| 1001 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1002 |
}
|
| 1003 |
});
|
| 1004 |
|