Some checks failed
CD Pipeline / build-and-deploy (push) Failing after 1m39s
前端: - OpenClawLobster 全新 SVG (參考 dashboardicons.com/icons/openclaw) 圓潤身體 + 大眼睛 + 鉗子 + 觸角 + 微笑 + 小腳 - 三色版本: red(異常/預設) / green(健康) / yellow(警告) - LobsterLoading 改用新 SVG 測試修正: - test_nemotron_failure_still_returns_proposal: func_body 截取 5000→10000 原因: 函數超過 5000 字元,導致 rfind 找不到最後的 return Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
124 lines
5.0 KiB
TypeScript
124 lines
5.0 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* LobsterLoading — 小龍蝦載入動畫
|
|
* =================================
|
|
* Sprint 5: 所有「載入中」用小龍蝦動畫取代純文字
|
|
* 設計: Q版龍蝦上下浮動 + 文字提示
|
|
*
|
|
* 建立時間: 2026-04-08 (台北時區)
|
|
*/
|
|
|
|
import { useTranslations } from 'next-intl'
|
|
|
|
// =============================================================================
|
|
// OpenClaw 風格龍蝦 SVG (參考 dashboardicons.com/icons/openclaw)
|
|
// 圓潤可愛風格 + 三色版本: red(預設/異常) / green(健康) / yellow(警告)
|
|
// =============================================================================
|
|
|
|
type LobsterColor = 'red' | 'green' | 'yellow'
|
|
|
|
const COLOR_MAP: Record<LobsterColor, { body: string; dark: string; eye: string }> = {
|
|
red: { body: '#ef0011', dark: '#8a000a', eye: '#0b0303' },
|
|
green: { body: '#22C55E', dark: '#15803d', eye: '#0b0303' },
|
|
yellow: { body: '#F59E0B', dark: '#b45309', eye: '#0b0303' },
|
|
}
|
|
|
|
/** OpenClaw 風格可愛龍蝦 SVG */
|
|
export function OpenClawLobster({ size = 36, color = 'red' }: { size?: number; color?: LobsterColor }) {
|
|
const c = COLOR_MAP[color]
|
|
return (
|
|
<svg width={size} height={size} viewBox="0 0 100 100" fill="none">
|
|
{/* 身體 (圓潤橢圓) */}
|
|
<ellipse cx="50" cy="58" rx="28" ry="32" fill={c.body} />
|
|
{/* 頭 (大圓) */}
|
|
<circle cx="50" cy="35" r="22" fill={c.body} />
|
|
{/* 肚子高光 */}
|
|
<ellipse cx="50" cy="60" rx="18" ry="20" fill={c.body} opacity="0.6" />
|
|
<ellipse cx="48" cy="56" rx="12" ry="14" fill="white" opacity="0.15" />
|
|
{/* 眼睛 (大圓白底 + 黑瞳) */}
|
|
<circle cx="40" cy="30" r="7" fill="white" />
|
|
<circle cx="60" cy="30" r="7" fill="white" />
|
|
<circle cx="41" cy="31" r="4" fill={c.eye} />
|
|
<circle cx="61" cy="31" r="4" fill={c.eye} />
|
|
{/* 眼睛高光 */}
|
|
<circle cx="43" cy="29" r="1.5" fill="white" />
|
|
<circle cx="63" cy="29" r="1.5" fill="white" />
|
|
{/* 左鉗 */}
|
|
<ellipse cx="16" cy="52" rx="10" ry="8" fill={c.body} transform="rotate(-15 16 52)" />
|
|
<ellipse cx="13" cy="48" rx="6" ry="5" fill={c.body} transform="rotate(-20 13 48)" />
|
|
<path d="M22 56 Q24 50 22 44" stroke={c.dark} strokeWidth="1.5" fill="none" strokeLinecap="round" />
|
|
{/* 右鉗 */}
|
|
<ellipse cx="84" cy="52" rx="10" ry="8" fill={c.body} transform="rotate(15 84 52)" />
|
|
<ellipse cx="87" cy="48" rx="6" ry="5" fill={c.body} transform="rotate(20 87 48)" />
|
|
<path d="M78 56 Q76 50 78 44" stroke={c.dark} strokeWidth="1.5" fill="none" strokeLinecap="round" />
|
|
{/* 觸角 */}
|
|
<path d="M38 16 Q32 4 24 8" stroke={c.dark} strokeWidth="2" fill="none" strokeLinecap="round" />
|
|
<circle cx="24" cy="8" r="3" fill={c.body} />
|
|
<path d="M62 16 Q68 4 76 8" stroke={c.dark} strokeWidth="2" fill="none" strokeLinecap="round" />
|
|
<circle cx="76" cy="8" r="3" fill={c.body} />
|
|
{/* 嘴巴 (微笑) */}
|
|
<path d="M43 40 Q50 46 57 40" stroke={c.dark} strokeWidth="1.5" fill="none" strokeLinecap="round" />
|
|
{/* 腳 */}
|
|
<line x1="35" y1="82" x2="30" y2="94" stroke={c.dark} strokeWidth="2" strokeLinecap="round" />
|
|
<line x1="45" y1="84" x2="42" y2="96" stroke={c.dark} strokeWidth="2" strokeLinecap="round" />
|
|
<line x1="55" y1="84" x2="58" y2="96" stroke={c.dark} strokeWidth="2" strokeLinecap="round" />
|
|
<line x1="65" y1="82" x2="70" y2="94" stroke={c.dark} strokeWidth="2" strokeLinecap="round" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Loading 元件
|
|
// =============================================================================
|
|
|
|
interface LobsterLoadingProps {
|
|
text?: string
|
|
size?: 'sm' | 'md' | 'lg'
|
|
color?: LobsterColor
|
|
}
|
|
|
|
const SIZES = {
|
|
sm: { svg: 28, bob: 2, fontSize: 11 },
|
|
md: { svg: 40, bob: 3, fontSize: 12 },
|
|
lg: { svg: 56, bob: 4, fontSize: 13 },
|
|
}
|
|
|
|
export function LobsterLoading({ text, size = 'md', color = 'red' }: LobsterLoadingProps) {
|
|
const tc = useTranslations('common')
|
|
const s = SIZES[size]
|
|
const displayText = text ?? tc('loading')
|
|
|
|
return (
|
|
<div style={{
|
|
display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
|
|
padding: size === 'lg' ? 48 : size === 'md' ? 32 : 16,
|
|
gap: 8,
|
|
}}>
|
|
<style>{`
|
|
@keyframes lobster-loading-bob {
|
|
0%, 100% { transform: translateY(0); }
|
|
50% { transform: translateY(-${s.bob}px); }
|
|
}
|
|
@keyframes lobster-loading-wave {
|
|
0%, 100% { opacity: 0.4; }
|
|
50% { opacity: 1; }
|
|
}
|
|
`}</style>
|
|
<div style={{ animation: 'lobster-loading-bob 1.2s ease-in-out infinite' }}>
|
|
<OpenClawLobster size={s.svg} color={color} />
|
|
</div>
|
|
<div style={{
|
|
fontSize: s.fontSize,
|
|
color: '#87867f',
|
|
fontFamily: "'DM Mono', monospace",
|
|
animation: 'lobster-loading-wave 2s ease-in-out infinite',
|
|
}}>
|
|
{displayText}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default LobsterLoading
|