Files
zclaw_openfang/docs/archive/openclaw-legacy/workbuddy界面/技能.html
iven 3518fc8ece feat(automation): complete unified automation system redesign
Phase 4 completion:
- Add ApprovalQueue component for managing pending approvals
- Add ExecutionResult component for displaying hand/workflow results
- Update Sidebar navigation to use unified AutomationPanel
- Replace separate 'hands' and 'workflow' tabs with single 'automation' tab
- Fix TypeScript type safety issues with unknown types in JSX expressions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 17:12:05 +08:00

537 lines
29 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WorkBuddy - 技能</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
overflow: hidden;
}
/* Custom scrollbar */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #9ca3af; }
/* Skill card animations */
.skill-card {
transition: all 0.2s ease;
border: 1px solid #e5e7eb;
}
.skill-card:hover {
border-color: #d1d5db;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
transform: translateY(-1px);
}
/* Add button animation */
.add-btn {
transition: all 0.2s ease;
border: 1px solid #e5e7eb;
}
.add-btn:hover {
border-color: #10b981;
color: #10b981;
background-color: #ecfdf5;
transform: scale(1.05);
}
.add-btn:active {
transform: scale(0.95);
}
.add-btn.added {
background-color: #10b981;
border-color: #10b981;
color: white;
}
/* Icon container */
.skill-icon {
background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
transition: all 0.2s ease;
}
.skill-card:hover .skill-icon {
background: linear-gradient(135deg, #e5e7eb 0%, #d1d5db 100%);
}
/* Sidebar active state */
.sidebar-item.active {
background-color: #f3f4f6;
font-weight: 500;
}
/* Search input focus */
.search-input:focus {
border-color: #10b981;
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
}
/* Import button hover */
.import-btn {
transition: all 0.2s ease;
}
.import-btn:hover {
background-color: #f3f4f6;
border-color: #d1d5db;
}
</style>
</head>
<body class="bg-white h-screen flex text-gray-800">
<!-- Left Sidebar -->
<aside class="w-64 bg-white border-r border-gray-200 flex flex-col h-full flex-shrink-0">
<!-- App Header -->
<div class="h-14 flex items-center px-4 border-b border-gray-100 flex-shrink-0">
<div class="flex items-center gap-2">
<div class="w-8 h-8 bg-gradient-to-br from-emerald-400 to-teal-500 rounded-lg flex items-center justify-center text-white font-bold text-lg shadow-sm">
<i class="fas fa-code text-sm"></i>
</div>
<span class="font-bold text-lg text-gray-800 tracking-tight">WorkBuddy</span>
</div>
<button class="ml-auto p-1.5 hover:bg-gray-100 rounded-lg text-gray-400 transition-colors">
<i class="far fa-clone text-sm"></i>
</button>
</div>
<!-- Menu Items -->
<nav class="flex-1 py-3 px-3 space-y-0.5 overflow-y-auto">
<!-- Search -->
<div class="px-1 mb-3">
<div class="relative">
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 text-sm"></i>
<input type="text" placeholder="搜索任务"
class="w-full pl-9 pr-8 py-2 bg-gray-50 border border-gray-200 rounded-lg text-sm focus:outline-none focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500 transition-all">
<button class="absolute right-2 top-1/2 -translate-y-1/2 p-1 hover:bg-gray-200 rounded text-gray-400 transition-colors">
<i class="fas fa-sliders-h text-xs"></i>
</button>
</div>
</div>
<!-- New Task Button -->
<button class="w-full flex items-center gap-3 px-3 py-2 hover:bg-gray-100 rounded-lg text-gray-700 transition-colors mb-1">
<i class="far fa-check-square text-emerald-600 w-5"></i>
<span class="font-medium">新建任务</span>
</button>
<!-- Nav Items -->
<a href="#" class="sidebar-item flex items-center gap-3 px-3 py-2 text-gray-600 rounded-lg transition-colors">
<i class="fas fa-robot text-gray-400 w-5"></i>
<span>Claw</span>
</a>
<a href="#" class="sidebar-item flex items-center gap-3 px-3 py-2 text-gray-600 rounded-lg transition-colors">
<i class="fas fa-user-tie text-gray-400 w-5"></i>
<span>专家</span>
</a>
<a href="#" class="sidebar-item active flex items-center gap-3 px-3 py-2 text-gray-900 rounded-lg transition-colors">
<i class="fas fa-wand-magic-sparkles text-gray-700 w-5"></i>
<span>技能</span>
</a>
<a href="#" class="sidebar-item flex items-center gap-3 px-3 py-2 text-gray-600 rounded-lg transition-colors">
<i class="fas fa-puzzle-piece text-gray-400 w-5"></i>
<span>插件</span>
</a>
<a href="#" class="sidebar-item flex items-center gap-3 px-3 py-2 text-gray-600 rounded-lg transition-colors">
<i class="far fa-clock text-gray-400 w-5"></i>
<span>自动化</span>
</a>
<!-- Empty State -->
<div class="mt-8 px-4 text-center">
<p class="text-gray-900 font-medium mb-1">暂无任务</p>
<p class="text-gray-400 text-sm">点击上方按钮开始新任务</p>
</div>
</nav>
<!-- User Profile -->
<div class="p-3 border-t border-gray-200 flex-shrink-0">
<button class="flex items-center gap-3 w-full hover:bg-gray-50 p-2 rounded-lg transition-colors">
<div class="w-8 h-8 bg-gradient-to-br from-emerald-400 to-cyan-500 rounded-full flex items-center justify-center text-white font-bold shadow-sm">
<i class="fas fa-robot text-xs"></i>
</div>
<span class="flex-1 text-left text-sm font-medium text-gray-700">iven</span>
<i class="fas fa-chevron-right text-gray-400 text-xs"></i>
</button>
</div>
</aside>
<!-- Main Content -->
<main class="flex-1 flex flex-col h-full bg-white overflow-hidden">
<!-- Top Bar -->
<header class="h-14 bg-white border-b border-gray-200 flex items-center justify-between px-6 flex-shrink-0">
<div class="flex items-center gap-2">
<div class="w-8 h-8 bg-gradient-to-br from-emerald-400 to-teal-500 rounded-lg flex items-center justify-center text-white font-bold">
<i class="fas fa-code text-sm"></i>
</div>
<span class="font-bold text-lg">WorkBuddy</span>
</div>
<div class="text-sm text-gray-600 font-medium">Agents</div>
<div class="flex items-center gap-2">
<button class="p-2 hover:bg-gray-100 rounded-lg text-gray-500">
<i class="fas fa-minus text-xs"></i>
</button>
<button class="p-2 hover:bg-gray-100 rounded-lg text-gray-500">
<i class="far fa-square text-xs"></i>
</button>
<button class="p-2 hover:bg-red-50 rounded-lg text-gray-500 hover:text-red-500">
<i class="fas fa-times text-xs"></i>
</button>
</div>
</header>
<!-- Content Area -->
<div class="flex-1 overflow-y-auto bg-white">
<div class="max-w-6xl mx-auto px-8 py-8">
<!-- Header Section -->
<div class="flex items-start justify-between mb-8">
<div>
<h1 class="text-2xl font-bold text-gray-900 mb-1">技能</h1>
<p class="text-gray-500 text-base">赋予 WorkBuddy 更强大的能力</p>
</div>
<div class="flex items-center gap-3">
<!-- Search -->
<div class="relative">
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 text-sm"></i>
<input type="text" id="skillSearch" placeholder="搜索技能"
class="search-input w-64 pl-9 pr-4 py-2 bg-white border border-gray-300 rounded-lg text-sm outline-none transition-all">
</div>
<!-- Import Button -->
<button class="import-btn flex items-center gap-2 px-4 py-2 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 bg-white hover:bg-gray-50" onclick="importSkill()">
<i class="fas fa-plus text-xs"></i>
导入技能
</button>
</div>
</div>
<!-- Installed Count -->
<div class="mb-6">
<span class="text-gray-900 font-medium">已安装</span>
<span class="ml-2 px-2 py-0.5 bg-gray-100 text-gray-600 text-xs rounded-full" id="installedCount">0</span>
</div>
<!-- Recommended Section -->
<div>
<h2 class="text-base font-semibold text-gray-900 mb-4">推荐</h2>
<!-- Skills Grid -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4" id="skillsGrid">
<!-- Skill Card 1 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="find-skills">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">find-skills</h3>
<p class="text-gray-500 text-sm truncate">Helps users discover and install agent skills whe...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'find-skills')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 2 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="workbuddy-channel-setup">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">workbuddy-channel-setup</h3>
<p class="text-gray-500 text-sm truncate">Automate WorkBuddy channel integration setu...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'workbuddy-channel-setup')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 3 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="FBS-BookWriter">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">FBS-BookWriter</h3>
<p class="text-gray-500 text-sm truncate">福帮手出品 | 人机协同写书。联网校验-千书千面...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'FBS-BookWriter')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 4 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="xiaohongshu">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">xiaohongshu</h3>
<p class="text-gray-500 text-sm truncate">小红书RedNote内容工具。使用场景搜索...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'xiaohongshu')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 5 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="agentmail">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">agentmail</h3>
<p class="text-gray-500 text-sm truncate">Email inbox for AI agents. Check messages, sen...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'agentmail')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 6 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="apple-notes">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">apple-notes</h3>
<p class="text-gray-500 text-sm truncate">Manage Apple Notes via the `memo` CLI on ma...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'apple-notes')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 7 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="apple-reminders">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">apple-reminders</h3>
<p class="text-gray-500 text-sm truncate">Manage Apple Reminders via the `remindctl` CL...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'apple-reminders')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 8 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="blogwatcher">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">blogwatcher</h3>
<p class="text-gray-500 text-sm truncate">Monitor blogs and RSS/Atom feeds for updates...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'blogwatcher')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 9 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="cos-vectors">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">cos-vectors</h3>
<p class="text-gray-500 text-sm truncate">Manage Tencent Cloud COS vector buckets via ...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'cos-vectors')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 10 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="gifgrep">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">gifgrep</h3>
<p class="text-gray-500 text-sm truncate">Search GIF providers with CLI/TUI, download re...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'gifgrep')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 11 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="github">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">github</h3>
<p class="text-gray-500 text-sm truncate">Interact with GitHub using the `gh` CLI. Use `gh ...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'github')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 12 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="gog">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">gog</h3>
<p class="text-gray-500 text-sm truncate">Google Workspace CLI for Gmail, Calendar, Driv...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'gog')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 13 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="healthcheck">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">healthcheck</h3>
<p class="text-gray-500 text-sm truncate">Track water and sleep with JSON file storage.</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'healthcheck')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 14 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="himalaya">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">himalaya</h3>
<p class="text-gray-500 text-sm truncate">CLI to manage emails via IMAP/SMTP. Use `him...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'himalaya')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
</div>
</div>
<!-- Empty State (Hidden by default) -->
<div id="emptyState" class="hidden mt-12 text-center py-12">
<div class="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
<i class="fas fa-search text-gray-400 text-2xl"></i>
</div>
<p class="text-gray-500">未找到相关技能</p>
</div>
</div>
</div>
</main>
<!-- Toast Notification -->
<div id="toast" class="fixed bottom-8 right-8 bg-gray-900 text-white px-6 py-3 rounded-xl shadow-2xl transform translate-y-20 opacity-0 transition-all duration-300 flex items-center gap-3 z-50">
<i class="fas fa-check-circle text-emerald-400"></i>
<span id="toastMessage">操作成功</span>
</div>
<script>
let installedCount = 0;
const installedSkills = new Set();
// Toggle skill installation
function toggleSkill(btn, skillName) {
const icon = btn.querySelector('i');
if (installedSkills.has(skillName)) {
// Uninstall
installedSkills.delete(skillName);
btn.classList.remove('added');
icon.classList.remove('fa-check');
icon.classList.add('fa-plus');
installedCount--;
showToast(`已卸载 ${skillName}`);
} else {
// Install
installedSkills.add(skillName);
btn.classList.add('added');
icon.classList.remove('fa-plus');
icon.classList.add('fa-check');
installedCount++;
showToast(`已安装 ${skillName}`);
}
document.getElementById('installedCount').textContent = installedCount;
}
// Import skill action
function importSkill() {
showToast('请选择要导入的技能文件');
}
// Show toast notification
function showToast(message) {
const toast = document.getElementById('toast');
const toastMessage = document.getElementById('toastMessage');
toastMessage.textContent = message;
toast.classList.remove('translate-y-20', 'opacity-0');
setTimeout(() => {
toast.classList.add('translate-y-20', 'opacity-0');
}, 2500);
}
// Search functionality
document.getElementById('skillSearch').addEventListener('input', function(e) {
const searchTerm = e.target.value.toLowerCase();
const cards = document.querySelectorAll('.skill-card');
let visibleCount = 0;
cards.forEach(card => {
const name = card.getAttribute('data-name').toLowerCase();
const desc = card.querySelector('p').textContent.toLowerCase();
if (name.includes(searchTerm) || desc.includes(searchTerm)) {
card.style.display = 'flex';
visibleCount++;
} else {
card.style.display = 'none';
}
});
// Show/hide empty state
const emptyState = document.getElementById('emptyState');
if (visibleCount === 0) {
emptyState.classList.remove('hidden');
} else {
emptyState.classList.add('hidden');
}
});
// Add stagger animation on load
window.addEventListener('load', () => {
const cards = document.querySelectorAll('.skill-card');
cards.forEach((card, index) => {
card.style.opacity = '0';
card.style.transform = 'translateY(10px)';
setTimeout(() => {
card.style.transition = 'all 0.3s ease';
card.style.opacity = '1';
card.style.transform = 'translateY(0)';
}, index * 30);
});
});
</script>
</body>
</html>