Some checks failed
CD Pipeline / deploy (push) Failing after 59s
- 建立 Gitea Actions CD pipeline (.gitea/workflows/cd.yaml) - 部署模式: rsync Python 檔案至 188 → docker restart (volume mount) - Dockerfile/requirements 變動時自動重建 Docker image - 部署通知: Telegram (開始/成功/失敗) - 健康檢查: https://mo.wooo.work/health (最多 5 次重試) - 同步最新 CLAUDE.md / ADR-008 / memory (2026-04-19) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
338 lines
8.7 KiB
HTML
338 lines
8.7 KiB
HTML
{#
|
||
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>
|