refactor(templates): 統一模板目錄並移除 fallback loader

ADR-017 Phase 3f-4:根目錄模板搬入 templates/,補 trends/login_history,移除 ChoiceLoader 根目錄 fallback,搬移 components,刪除 web/templates 下的空檔/死檔與 compose 舊模板 mount。
This commit is contained in:
OoO
2026-04-29 21:44:19 +08:00
parent 9528d6c23e
commit 53edcc0077
30 changed files with 227 additions and 1662 deletions

View File

@@ -0,0 +1,337 @@
{#
WOOO 品牌載入動畫組件
使用方式:
1. 在頁面中 include 此組件:
{% include 'components/_loading.html' %}
2. 在 JavaScript 中控制顯示/隱藏:
showLoading('正在載入數據...') // 顯示
hideLoading() // 隱藏
3. 也可直接操作 DOM
document.getElementById('loadingOverlay').style.display = 'flex'; // 顯示
document.getElementById('loadingOverlay').style.display = 'none'; // 隱藏
#}
<!-- WOOO Loading Overlay -->
<div id="loadingOverlay" class="wooo-loading-overlay">
<div class="loading-logo-container">
<!-- 脈衝光暈 -->
<div class="logo-pulse"></div>
<!-- 外層旋轉光環 -->
<div class="logo-ring"></div>
<!-- 內層反向旋轉光環 -->
<div class="logo-ring-inner"></div>
<!-- 環繞粒子 -->
<div class="orbit-particles">
<div class="orbit-particle"></div>
<div class="orbit-particle"></div>
<div class="orbit-particle"></div>
<div class="orbit-particle"></div>
</div>
<!-- 閃爍星星 -->
<div class="sparkles">
<div class="sparkle"></div>
<div class="sparkle"></div>
<div class="sparkle"></div>
<div class="sparkle"></div>
<div class="sparkle"></div>
<div class="sparkle"></div>
</div>
<!-- LOGO -->
<div class="loading-logo">WOOO</div>
</div>
<div class="loading-text" id="loadingText">
<i class="fas fa-spinner fa-spin me-2"></i>正在載入數據...
</div>
<div class="loading-progress">
<div class="loading-progress-bar"></div>
</div>
<div class="loading-hint" id="loadingHint">
大量資料可能需要較長時間,請稍候
</div>
</div>
<style>
/* ============================================
WOOO 品牌載入動畫樣式
============================================ */
.wooo-loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(240, 245, 255, 0.95) 100%);
z-index: 9999;
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 25px;
backdrop-filter: blur(8px);
}
/* LOGO 動畫容器 */
.wooo-loading-overlay .loading-logo-container {
position: relative;
width: 160px;
height: 160px;
display: flex;
justify-content: center;
align-items: center;
}
.wooo-loading-overlay .loading-logo {
z-index: 3;
animation: wooo-cloud-float 3s ease-in-out infinite;
font-size: 2.5rem;
font-weight: 800;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: linear-gradient(135deg, #4F46E5 0%, #7C3AED 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
letter-spacing: 4px;
filter: drop-shadow(0 4px 15px rgba(79, 70, 229, 0.4));
}
/* 雲端飄動動畫 */
@keyframes wooo-cloud-float {
0%, 100% {
transform: translateY(0) translateX(0) scale(1);
}
25% {
transform: translateY(-15px) translateX(5px) scale(1.02);
}
50% {
transform: translateY(-8px) translateX(-3px) scale(1);
}
75% {
transform: translateY(-20px) translateX(3px) scale(1.01);
}
}
/* 外層旋轉光環 */
.wooo-loading-overlay .logo-ring {
position: absolute;
width: 150px;
height: 150px;
border: 4px solid transparent;
border-top-color: #4F46E5;
border-right-color: #7C3AED;
border-radius: 50%;
animation: wooo-ring-spin 2s linear infinite;
}
/* 內層反向旋轉光環 */
.wooo-loading-overlay .logo-ring-inner {
position: absolute;
width: 120px;
height: 120px;
border: 3px solid transparent;
border-bottom-color: #EC4899;
border-left-color: #F59E0B;
border-radius: 50%;
animation: wooo-ring-spin-reverse 1.5s linear infinite;
}
/* 脈衝光暈 */
.wooo-loading-overlay .logo-pulse {
position: absolute;
width: 100px;
height: 100px;
background: radial-gradient(circle, rgba(79, 70, 229, 0.3) 0%, transparent 70%);
border-radius: 50%;
animation: wooo-pulse-expand 2s ease-out infinite;
}
@keyframes wooo-ring-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes wooo-ring-spin-reverse {
0% { transform: rotate(0deg); }
100% { transform: rotate(-360deg); }
}
@keyframes wooo-pulse-expand {
0% {
transform: scale(0.8);
opacity: 1;
}
100% {
transform: scale(1.8);
opacity: 0;
}
}
/* 環繞粒子 */
.wooo-loading-overlay .orbit-particles {
position: absolute;
width: 160px;
height: 160px;
animation: wooo-orbit-rotate 4s linear infinite;
}
.wooo-loading-overlay .orbit-particle {
position: absolute;
width: 8px;
height: 8px;
background: linear-gradient(135deg, #4F46E5, #7C3AED);
border-radius: 50%;
box-shadow: 0 0 10px rgba(79, 70, 229, 0.8);
}
.wooo-loading-overlay .orbit-particle:nth-child(1) {
top: 0;
left: 50%;
transform: translateX(-50%);
}
.wooo-loading-overlay .orbit-particle:nth-child(2) {
top: 50%;
right: 0;
transform: translateY(-50%);
}
.wooo-loading-overlay .orbit-particle:nth-child(3) {
bottom: 0;
left: 50%;
transform: translateX(-50%);
}
.wooo-loading-overlay .orbit-particle:nth-child(4) {
top: 50%;
left: 0;
transform: translateY(-50%);
}
@keyframes wooo-orbit-rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 閃爍星星 */
.wooo-loading-overlay .sparkles {
position: absolute;
width: 180px;
height: 180px;
}
.wooo-loading-overlay .sparkle {
position: absolute;
width: 4px;
height: 4px;
background: #FCD34D;
border-radius: 50%;
animation: wooo-sparkle-twinkle 1.5s ease-in-out infinite;
}
.wooo-loading-overlay .sparkle:nth-child(1) { top: 10%; left: 20%; animation-delay: 0s; }
.wooo-loading-overlay .sparkle:nth-child(2) { top: 5%; right: 25%; animation-delay: 0.3s; }
.wooo-loading-overlay .sparkle:nth-child(3) { bottom: 15%; right: 15%; animation-delay: 0.6s; }
.wooo-loading-overlay .sparkle:nth-child(4) { bottom: 10%; left: 25%; animation-delay: 0.9s; }
.wooo-loading-overlay .sparkle:nth-child(5) { top: 30%; left: 5%; animation-delay: 1.2s; }
.wooo-loading-overlay .sparkle:nth-child(6) { top: 25%; right: 5%; animation-delay: 0.4s; }
@keyframes wooo-sparkle-twinkle {
0%, 100% {
transform: scale(0);
opacity: 0;
}
50% {
transform: scale(1);
opacity: 1;
}
}
/* 載入文字 */
.wooo-loading-overlay .loading-text {
font-size: 1.2rem;
color: #4F46E5;
font-weight: 600;
text-align: center;
letter-spacing: 0.5px;
}
.wooo-loading-overlay .loading-hint {
font-size: 0.85rem;
color: #6b7280;
text-align: center;
max-width: 300px;
}
/* 進度條 */
.wooo-loading-overlay .loading-progress {
width: 200px;
height: 4px;
background: rgba(79, 70, 229, 0.2);
border-radius: 2px;
overflow: hidden;
}
.wooo-loading-overlay .loading-progress-bar {
height: 100%;
background: linear-gradient(90deg, #4F46E5, #7C3AED, #4F46E5);
background-size: 200% 100%;
animation: wooo-progress-flow 1.5s linear infinite;
width: 100%;
}
@keyframes wooo-progress-flow {
0% { background-position: 100% 0; }
100% { background-position: -100% 0; }
}
</style>
<script>
/**
* WOOO 載入動畫控制函數
*/
// 顯示載入動畫
function showLoading(text, hint) {
const overlay = document.getElementById('loadingOverlay');
const loadingText = document.getElementById('loadingText');
const loadingHint = document.getElementById('loadingHint');
if (text && loadingText) {
loadingText.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>' + text;
}
if (hint && loadingHint) {
loadingHint.textContent = hint;
}
if (overlay) {
overlay.style.display = 'flex';
}
}
// 隱藏載入動畫
function hideLoading() {
const overlay = document.getElementById('loadingOverlay');
if (overlay) {
overlay.style.display = 'none';
}
}
// 更新載入文字
function updateLoadingText(text) {
const loadingText = document.getElementById('loadingText');
if (loadingText && text) {
loadingText.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>' + text;
}
}
// 更新載入提示
function updateLoadingHint(hint) {
const loadingHint = document.getElementById('loadingHint');
if (loadingHint && hint) {
loadingHint.textContent = hint;
}
}
</script>