Refactor code structure for improved readability and maintainability

This commit is contained in:
catlog22
2026-02-28 22:32:07 +08:00
parent 19fb4d86c7
commit 67b2129f3c
60 changed files with 3002 additions and 643 deletions

View File

@@ -1,46 +1,89 @@
<template>
<div class="agent-orchestration">
<div class="orchestration-title">🤖 Agent Orchestration</div>
<div class="orchestration-title">
<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="title-icon">
<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>
</svg>
Agent Orchestration
</div>
<div class="agent-flow">
<!-- CLI Layer -->
<div class="flow-layer cli-layer">
<div class="layer-label">CLI Tools</div>
<div class="agents-row">
<div class="agent-card cli" @mouseenter="showTooltip('cli-explore')" @mouseleave="hideTooltip">🔍 Explore</div>
<div class="agent-card cli" @mouseenter="showTooltip('cli-plan')" @mouseleave="hideTooltip">📋 Plan</div>
<div class="agent-card cli" @mouseenter="showTooltip('cli-exec')" @mouseleave="hideTooltip"> Execute</div>
<div class="agent-card cli" @mouseenter="showTooltip('cli-discuss')" @mouseleave="hideTooltip">💬 Discuss</div>
<div class="agent-card cli" @mouseenter="showTooltip('cli-explore')" @mouseleave="hideTooltip">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
Explore
</div>
<div class="agent-card cli" @mouseenter="showTooltip('cli-plan')" @mouseleave="hideTooltip">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="8" y1="8" x2="16" y2="8"/><line x1="8" y1="12" x2="16" y2="12"/><line x1="8" y1="16" x2="12" y2="16"/></svg>
Plan
</div>
<div class="agent-card cli" @mouseenter="showTooltip('cli-exec')" @mouseleave="hideTooltip">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
Execute
</div>
<div class="agent-card cli" @mouseenter="showTooltip('cli-discuss')" @mouseleave="hideTooltip">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>
Discuss
</div>
</div>
</div>
<!-- Flow Arrow -->
<div class="flow-arrow"></div>
<div class="flow-arrow">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/>
</svg>
</div>
<!-- Development Layer -->
<div class="flow-layer dev-layer">
<div class="layer-label">Development</div>
<div class="agents-row">
<div class="agent-card dev" @mouseenter="showTooltip('code-dev')" @mouseleave="hideTooltip">👨💻 Code</div>
<div class="agent-card dev" @mouseenter="showTooltip('tdd')" @mouseleave="hideTooltip">🧪 TDD</div>
<div class="agent-card dev" @mouseenter="showTooltip('test-fix')" @mouseleave="hideTooltip">🔧 Fix</div>
<div class="agent-card dev" @mouseenter="showTooltip('code-dev')" @mouseleave="hideTooltip">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
Code
</div>
<div class="agent-card dev" @mouseenter="showTooltip('tdd')" @mouseleave="hideTooltip">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 3h6M12 3v7l-4 8h8l-4-8"/><circle cx="8" cy="20" r="1"/><circle cx="16" cy="20" r="1"/></svg>
TDD
</div>
<div class="agent-card dev" @mouseenter="showTooltip('test-fix')" @mouseleave="hideTooltip">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 000 1.4l1.6 1.6a1 1 0 001.4 0l3.77-3.77a6 6 0 01-7.94 7.94l-6.91 6.91a2.12 2.12 0 01-3-3l6.91-6.91a6 6 0 017.94-7.94l-3.76 3.76z"/></svg>
Fix
</div>
</div>
</div>
<!-- Flow Arrow -->
<div class="flow-arrow"></div>
<div class="flow-arrow">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/>
</svg>
</div>
<!-- Output Layer -->
<div class="flow-layer output-layer">
<div class="layer-label">Output</div>
<div class="agents-row">
<div class="agent-card doc" @mouseenter="showTooltip('doc-gen')" @mouseleave="hideTooltip">📄 Docs</div>
<div class="agent-card ui" @mouseenter="showTooltip('ui-design')" @mouseleave="hideTooltip">🎨 UI</div>
<div class="agent-card universal" @mouseenter="showTooltip('universal')" @mouseleave="hideTooltip">🌐 Universal</div>
<div class="agent-card doc" @mouseenter="showTooltip('doc-gen')" @mouseleave="hideTooltip">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="14" y2="17"/></svg>
Docs
</div>
<div class="agent-card ui" @mouseenter="showTooltip('ui-design')" @mouseleave="hideTooltip">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
UI
</div>
<div class="agent-card universal" @mouseenter="showTooltip('universal')" @mouseleave="hideTooltip">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/></svg>
Universal
</div>
</div>
</div>
</div>
<div class="tooltip" v-if="tooltip" :class="{ visible: tooltip }">
{{ tooltipText }}
</div>
@@ -54,16 +97,16 @@ const tooltip = ref(false)
const tooltipText = ref('')
const tooltips = {
'cli-explore': 'cli-explore-agent: 代码库探索和语义搜索',
'cli-plan': 'cli-planning-agent: 任务规划和分解',
'cli-exec': 'cli-execution-agent: 命令执行和结果处理',
'cli-discuss': 'cli-discuss-agent: 多视角讨论和共识达成',
'code-dev': 'code-developer: 代码实现和开发',
'tdd': 'tdd-developer: 测试驱动开发',
'test-fix': 'test-fix-agent: 测试修复循环',
'doc-gen': 'doc-generator: 文档自动生成',
'ui-design': 'ui-design-agent: UI设计和设计令牌',
'universal': 'universal-executor: 通用任务执行器'
'cli-explore': 'cli-explore-agent: Codebase exploration & semantic search',
'cli-plan': 'cli-planning-agent: Task planning & decomposition',
'cli-exec': 'cli-execution-agent: Command execution & result handling',
'cli-discuss': 'cli-discuss-agent: Multi-perspective discussion & consensus',
'code-dev': 'code-developer: Code implementation & development',
'tdd': 'tdd-developer: Test-driven development',
'test-fix': 'test-fix-agent: Test-fix iteration loop',
'doc-gen': 'doc-generator: Auto documentation generation',
'ui-design': 'ui-design-agent: UI design & design tokens',
'universal': 'universal-executor: General-purpose task executor'
}
function showTooltip(key) {
@@ -88,6 +131,10 @@ function hideTooltip() {
}
.orchestration-title {
display: flex;
align-items: center;
justify-content: center;
gap: 0.6rem;
text-align: center;
font-size: 1.75rem;
font-weight: 700;
@@ -95,6 +142,10 @@ function hideTooltip() {
margin-bottom: 2.5rem;
}
.title-icon {
color: var(--vp-c-brand-1);
}
.agent-flow {
display: flex;
flex-direction: column;
@@ -125,6 +176,9 @@ function hideTooltip() {
}
.agent-card {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.8rem 1.5rem;
border-radius: 12px;
font-size: 0.9rem;
@@ -171,14 +225,13 @@ function hideTooltip() {
}
.flow-arrow {
color: var(--vp-c-divider);
font-size: 1.25rem;
animation: bounce 2s infinite;
color: var(--vp-c-text-4);
animation: arrowBounce 2s ease-in-out infinite;
}
@keyframes bounce {
0%, 100% { transform: translateY(0); opacity: 0.5; }
50% { transform: translateY(8px); opacity: 1; }
@keyframes arrowBounce {
0%, 100% { transform: translateY(0); opacity: 0.4; }
50% { transform: translateY(6px); opacity: 0.8; }
}
.tooltip {
@@ -203,11 +256,19 @@ function hideTooltip() {
opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
@media (max-width: 640px) {
.agent-orchestration {
padding: 2rem 1rem;
}
.agent-card {
padding: 0.6rem 1rem;
font-size: 0.8rem;

View File

@@ -1,54 +1,188 @@
<template>
<div class="hero-animation-container" :class="{ 'is-visible': isVisible }">
<div class="glow-bg"></div>
<svg viewBox="0 0 400 320" class="hero-svg" preserveAspectRatio="xMidYMid meet">
<div class="hero-anim" :class="{ 'is-visible': isVisible }">
<svg viewBox="0 0 360 340" class="hero-svg" preserveAspectRatio="xMidYMid meet">
<defs>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="3.5" result="coloredBlur"/>
<feMerge>
<feMergeNode in="coloredBlur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<linearGradient id="pathGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="var(--vp-c-brand-1)" stop-opacity="0" />
<stop offset="50%" stop-color="var(--vp-c-brand-1)" stop-opacity="0.5" />
<stop offset="100%" stop-color="var(--vp-c-brand-1)" stop-opacity="0" />
<linearGradient id="coreGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="var(--vp-c-brand-1)"/>
<stop offset="100%" stop-color="#8B5CF6"/>
</linearGradient>
<linearGradient id="ccwBorder" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="var(--vp-c-brand-1)" stop-opacity="0.6"/>
<stop offset="50%" stop-color="#8B5CF6" stop-opacity="0.4"/>
<stop offset="100%" stop-color="var(--vp-c-brand-1)" stop-opacity="0.6"/>
</linearGradient>
<filter id="ccwShadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="1" stdDeviation="3" flood-color="var(--vp-c-brand-1)" flood-opacity="0.12"/>
</filter>
<linearGradient id="beltFade" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="var(--vp-c-text-3)" stop-opacity="0"/>
<stop offset="8%" stop-color="var(--vp-c-text-3)" stop-opacity="0.12"/>
<stop offset="92%" stop-color="var(--vp-c-text-3)" stop-opacity="0.12"/>
<stop offset="100%" stop-color="var(--vp-c-text-3)" stop-opacity="0"/>
</linearGradient>
<clipPath id="beltClip">
<rect x="10" y="211" width="340" height="14"/>
</clipPath>
</defs>
<!-- Connection Lines -->
<g class="data-paths">
<path v-for="(path, i) in paths" :key="'path-'+i" :d="path" class="connection-path" />
<circle v-for="(path, i) in paths" :key="'dot-'+i" r="2" class="data-pulse">
<animateMotion :dur="2 + i * 0.4 + 's'" repeatCount="indefinite" :path="path" />
</circle>
<!-- Background mesh -->
<g opacity="0.025">
<line v-for="i in 6" :key="'v'+i" :x1="i*60" y1="0" :x2="i*60" y2="340" stroke="var(--vp-c-brand-1)" stroke-width="0.5"/>
<line v-for="i in 6" :key="'h'+i" x1="0" :y1="i*57" x2="360" :y2="i*57" stroke="var(--vp-c-brand-1)" stroke-width="0.5"/>
</g>
<!-- Orbit Rings -->
<g class="orbit-rings">
<circle cx="200" cy="160" r="130" class="orbit-ring ring-outer" />
<circle cx="200" cy="160" r="95" class="orbit-ring ring-inner" />
<!-- === CCW Controller === -->
<g transform="translate(180, 30)">
<!-- Outer pulse rings -->
<circle r="38" fill="none" stroke="url(#coreGrad)" stroke-width="0.5" opacity="0.08" class="ccw-ring1"/>
<circle r="32" fill="none" stroke="url(#coreGrad)" stroke-width="0.4" opacity="0.12" class="ccw-ring2"/>
<!-- Main badge -->
<rect x="-56" y="-20" width="112" height="40" rx="12" class="ccw-bg" filter="url(#ccwShadow)"/>
<rect x="-56" y="-20" width="112" height="40" rx="12" fill="none" stroke="url(#ccwBorder)" stroke-width="1.2"/>
<!-- Logo icon (simplified favicon: blue square + white lines + green dot) -->
<g transform="translate(-48, -13)">
<rect width="26" height="26" rx="6" fill="var(--vp-c-brand-1)" opacity="0.12"/>
<rect width="26" height="26" rx="6" fill="none" stroke="var(--vp-c-brand-1)" stroke-width="0.8" opacity="0.25"/>
<line x1="6" y1="9" x2="20" y2="9" stroke="var(--vp-c-brand-1)" stroke-width="2" stroke-linecap="round" opacity="0.65"/>
<line x1="6" y1="13" x2="17" y2="13" stroke="var(--vp-c-brand-1)" stroke-width="2" stroke-linecap="round" opacity="0.65"/>
<line x1="6" y1="17" x2="14" y2="17" stroke="var(--vp-c-brand-1)" stroke-width="2" stroke-linecap="round" opacity="0.65"/>
<circle cx="19" cy="17.5" r="3" fill="#22C55E" opacity="0.8"/>
</g>
<!-- Text shifted right to avoid logo overlap -->
<text x="16" y="0" text-anchor="middle" class="ccw-label">CCW</text>
<text x="16" y="12" text-anchor="middle" class="ccw-sub">Orchestrator</text>
<!-- Signal indicator -->
<circle cx="50" cy="-12" r="2.5" fill="#22C55E" opacity="0.5" class="ccw-signal"/>
<circle cx="50" cy="-12" r="1.2" fill="#22C55E" opacity="0.8"/>
</g>
<!-- Agent Nodes -->
<g v-for="(agent, i) in agents" :key="'agent-'+i" class="agent-node" :style="{ '--delay': i * 0.4 + 's' }">
<g class="agent-group" :style="{ transform: `translate(${agent.x}px, ${agent.y}px)` }">
<circle r="8" :fill="agent.color" class="agent-circle" filter="url(#glow)" />
<circle r="12" :stroke="agent.color" fill="none" class="agent-halo" />
<text y="22" text-anchor="middle" class="agent-label">{{ agent.name }}</text>
<!-- Control lines (CCW stations) -->
<path d="M134,50 L134,68 L60,68 L60,90" class="ctrl-line" style="--d:0.3s"/>
<path d="M180,50 L180,90" class="ctrl-line" style="--d:0.5s"/>
<path d="M226,50 L226,68 L300,68 L300,90" class="ctrl-line" style="--d:0.7s"/>
<!-- Signal pulses on control lines -->
<circle r="1.5" fill="var(--vp-c-brand-1)" class="line-pulse">
<animateMotion dur="2s" repeatCount="indefinite" path="M134,50 L134,68 L60,68 L60,90" begin="0.3s"/>
</circle>
<circle r="1.5" fill="var(--vp-c-brand-1)" class="line-pulse">
<animateMotion dur="2s" repeatCount="indefinite" path="M180,50 L180,90" begin="0.5s"/>
</circle>
<circle r="1.5" fill="var(--vp-c-brand-1)" class="line-pulse">
<animateMotion dur="2s" repeatCount="indefinite" path="M226,50 L226,68 L300,68 L300,90" begin="0.7s"/>
</circle>
<circle cx="60" cy="90" r="2.5" fill="#D97757" opacity="0.5"/>
<circle cx="180" cy="90" r="2.5" fill="#10A37F" opacity="0.5"/>
<circle cx="300" cy="90" r="2.5" fill="#4285F4" opacity="0.5"/>
<!-- === Station: Claude (Design) === -->
<!-- Outer <g> for SVG positioning, inner <g> for CSS animation -->
<g transform="translate(60, 94)">
<g class="station" style="--sd:0.3s">
<rect x="-40" y="0" width="80" height="66" rx="12" class="station-box" style="--sc:#D97757"/>
<circle cy="24" r="14" fill="#D97757" opacity="0.05"/>
<!-- Official Claude (Anthropic) icon -->
<svg x="-11" y="8" width="22" height="22" viewBox="0 0 16 16" fill="#D97757">
<path d="m3.127 10.604 3.135-1.76.053-.153-.053-.085H6.11l-.525-.032-1.791-.048-1.554-.065-1.505-.08-.38-.081L0 7.832l.036-.234.32-.214.455.04 1.009.069 1.513.105 1.097.064 1.626.17h.259l.036-.105-.089-.065-.068-.064-1.566-1.062-1.695-1.121-.887-.646-.48-.327-.243-.306-.104-.67.435-.48.585.04.15.04.593.456 1.267.981 1.654 1.218.242.202.097-.068.012-.049-.109-.181-.9-1.626-.96-1.655-.428-.686-.113-.411a2 2 0 0 1-.068-.484l.496-.674L4.446 0l.662.089.279.242.411.94.666 1.48 1.033 2.014.302.597.162.553.06.17h.105v-.097l.085-1.134.157-1.392.154-1.792.052-.504.25-.605.497-.327.387.186.319.456-.045.294-.19 1.23-.37 1.93-.243 1.29h.142l.161-.16.654-.868 1.097-1.372.484-.545.565-.601.363-.287h.686l.505.751-.226.775-.707.895-.585.759-.839 1.13-.524.904.048.072.125-.012 1.897-.403 1.024-.186 1.223-.21.553.258.06.263-.218.536-1.307.323-1.533.307-2.284.54-.028.02.032.04 1.029.098.44.024h1.077l2.005.15.525.346.315.424-.053.323-.807.411-3.631-.863-.872-.218h-.12v.073l.726.71 1.331 1.202 1.667 1.55.084.383-.214.302-.226-.032-1.464-1.101-.565-.497-1.28-1.077h-.084v.113l.295.432 1.557 2.34.08.718-.112.234-.404.141-.444-.08-.911-1.28-.94-1.44-.759-1.291-.093.053-.448 4.821-.21.246-.484.186-.403-.307-.214-.496.214-.98.258-1.28.21-1.016.19-1.263.112-.42-.008-.028-.092.012-.953 1.307-1.448 1.957-1.146 1.227-.274.109-.477-.247.045-.44.266-.39 1.586-2.018.956-1.25.617-.723-.004-.105h-.036l-4.212 2.736-.75.096-.324-.302.04-.496.154-.162 1.267-.871z"/>
</svg>
<text y="46" text-anchor="middle" class="st-name" fill="#D97757">Claude</text>
<text y="58" text-anchor="middle" class="st-role">Design</text>
<line x1="0" y1="66" x2="0" y2="104" stroke="#D97757" stroke-width="1.2" opacity="0.2"/>
<polygon points="-3,104 3,104 0,110" fill="#D97757" opacity="0.25"/>
</g>
</g>
<!-- Central Core -->
<g class="central-core" transform="translate(200, 160)">
<circle r="40" class="core-bg" />
<circle r="32" fill="var(--vp-c-brand-1)" filter="url(#glow)" class="core-inner" />
<text y="8" text-anchor="middle" class="core-text">CCW</text>
<!-- Scanning Effect -->
<path d="M-32 0 A32 32 0 0 1 32 0" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" class="core-scanner" />
<!-- === Station: OpenAI (Build) === -->
<g transform="translate(180, 94)">
<g class="station" style="--sd:0.5s">
<rect x="-40" y="0" width="80" height="66" rx="12" class="station-box" style="--sc:#10A37F"/>
<circle cy="24" r="14" fill="#10A37F" opacity="0.05"/>
<!-- Official OpenAI icon -->
<svg x="-11" y="8" width="22" height="22" viewBox="0 0 16 16" fill="#10A37F">
<path d="M14.949 6.547a3.94 3.94 0 0 0-.348-3.273 4.11 4.11 0 0 0-4.4-1.934A4.1 4.1 0 0 0 8.423.2 4.15 4.15 0 0 0 6.305.086a4.1 4.1 0 0 0-1.891.948 4.04 4.04 0 0 0-1.158 1.753 4.1 4.1 0 0 0-1.563.679A4 4 0 0 0 .554 4.72a3.99 3.99 0 0 0 .502 4.731 3.94 3.94 0 0 0 .346 3.274 4.11 4.11 0 0 0 4.402 1.933c.382.425.852.764 1.377.995.526.231 1.095.35 1.67.346 1.78.002 3.358-1.132 3.901-2.804a4.1 4.1 0 0 0 1.563-.68 4 4 0 0 0 1.14-1.253 3.99 3.99 0 0 0-.506-4.716m-6.097 8.406a3.05 3.05 0 0 1-1.945-.694l.096-.054 3.23-1.838a.53.53 0 0 0 .265-.455v-4.49l1.366.778q.02.011.025.035v3.722c-.003 1.653-1.361 2.992-3.037 2.996m-6.53-2.75a2.95 2.95 0 0 1-.36-2.01l.095.057L5.29 12.09a.53.53 0 0 0 .527 0l3.949-2.246v1.555a.05.05 0 0 1-.022.041L6.473 13.3c-1.454.826-3.311.335-4.15-1.098m-.85-6.94A3.02 3.02 0 0 1 3.07 3.949v3.785a.51.51 0 0 0 .262.451l3.93 2.237-1.366.779a.05.05 0 0 1-.048 0L2.585 9.342a2.98 2.98 0 0 1-1.113-4.094zm11.216 2.571L8.747 5.576l1.362-.776a.05.05 0 0 1 .048 0l3.265 1.86a3 3 0 0 1 1.173 1.207 2.96 2.96 0 0 1-.27 3.2 3.05 3.05 0 0 1-1.36.997V8.279a.52.52 0 0 0-.276-.445m1.36-2.015-.097-.057-3.226-1.855a.53.53 0 0 0-.53 0L6.249 6.153V4.598a.04.04 0 0 1 .019-.04L9.533 2.7a3.07 3.07 0 0 1 3.257.139c.474.325.843.778 1.066 1.303.223.526.289 1.103.191 1.664zM5.503 8.575 4.139 7.8a.05.05 0 0 1-.026-.037V4.049c0-.57.166-1.127.476-1.607s.752-.864 1.275-1.105a3.08 3.08 0 0 1 3.234.41l-.096.054-3.23 1.838a.53.53 0 0 0-.265.455zm.742-1.577 1.758-1 1.762 1v2l-1.755 1-1.762-1z"/>
</svg>
<text y="46" text-anchor="middle" class="st-name" fill="#10A37F">OpenAI</text>
<text y="58" text-anchor="middle" class="st-role">Build</text>
<line x1="0" y1="66" x2="0" y2="104" stroke="#10A37F" stroke-width="1.2" opacity="0.2"/>
<polygon points="-3,104 3,104 0,110" fill="#10A37F" opacity="0.25"/>
</g>
</g>
<!-- === Station: Gemini (Verify) === -->
<g transform="translate(300, 94)">
<g class="station" style="--sd:0.7s">
<rect x="-40" y="0" width="80" height="66" rx="12" class="station-box" style="--sc:#4285F4"/>
<circle cy="24" r="14" fill="#4285F4" opacity="0.05"/>
<!-- Official Google Gemini icon (4-point star) -->
<svg x="-11" y="8" width="22" height="22" viewBox="0 0 24 24" fill="none">
<path d="M3 12a9 9 0 0 0 9-9a9 9 0 0 0 9 9a9 9 0 0 0-9 9a9 9 0 0 0-9-9" stroke="#4285F4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="#4285F4" fill-opacity="0.15"/>
</svg>
<text y="46" text-anchor="middle" class="st-name" fill="#4285F4">Gemini</text>
<text y="58" text-anchor="middle" class="st-role">Verify</text>
<line x1="0" y1="66" x2="0" y2="104" stroke="#4285F4" stroke-width="1.2" opacity="0.2"/>
<polygon points="-3,104 3,104 0,110" fill="#4285F4" opacity="0.25"/>
</g>
</g>
<!-- === Conveyor Belt === -->
<rect x="8" y="212" width="344" height="12" rx="6" fill="url(#beltFade)"/>
<line x1="18" y1="212" x2="342" y2="212" stroke="var(--vp-c-divider)" stroke-width="0.3" opacity="0.4"/>
<line x1="18" y1="224" x2="342" y2="224" stroke="var(--vp-c-divider)" stroke-width="0.3" opacity="0.4"/>
<!-- Rollers -->
<circle v-for="i in 9" :key="'r'+i" :cx="18 + i * 36" cy="218" r="2.5" class="roller"/>
<!-- Scrolling belt marks -->
<g clip-path="url(#beltClip)" class="belt-scroll">
<line v-for="i in 22" :key="'bm'+i" :x1="i * 18 - 18" y1="213" :x2="i * 18 - 18" y2="223"
stroke="var(--vp-c-text-3)" stroke-width="0.3" opacity="0.1"/>
</g>
<!-- Flow direction arrows -->
<g opacity="0.12">
<polygon points="116,215 116,221 121,218" fill="var(--vp-c-text-2)"/>
<polygon points="236,215 236,221 241,218" fill="var(--vp-c-text-2)"/>
<polygon points="340,215 340,221 345,218" fill="var(--vp-c-text-2)"/>
</g>
<!-- === Flow Items === -->
<g class="flow-items">
<g v-for="(item, i) in flowItems" :key="'fi'+i">
<rect :width="item.w" height="7" rx="2" :fill="item.color" :opacity="item.opacity">
<animateMotion :dur="item.dur + 's'" repeatCount="indefinite"
path="M6,215 L354,215" :begin="item.delay + 's'"/>
</rect>
</g>
</g>
<!-- Sparks at stations -->
<g v-for="(sp, i) in sparks" :key="'sp'+i">
<circle :r="sp.r" :fill="sp.color" class="spark">
<animateMotion :dur="sp.dur + 's'" repeatCount="indefinite" :path="sp.path" :begin="sp.delay + 's'"/>
</circle>
</g>
<!-- I/O labels -->
<text x="18" y="244" class="io-label">Tasks</text>
<text x="342" y="244" text-anchor="end" class="io-label">Done</text>
<!-- Output check -->
<g transform="translate(350, 218)">
<circle r="7" fill="#22C55E" opacity="0.06" class="check-pulse"/>
<polyline points="-3,1 -1,3 3,-1" fill="none" stroke="#22C55E" stroke-width="1.3" stroke-linecap="round" opacity="0.6"/>
</g>
<!-- Status row -->
<g class="status-row">
<g v-for="(s, i) in statusDots" :key="'sd'+i" :transform="`translate(${s.x}, 274)`">
<line x1="0" y1="-20" x2="0" y2="-14" :stroke="s.color" stroke-width="0.5" opacity="0.2"/>
<circle r="3" :fill="s.color" opacity="0.12"/>
<circle r="1.5" :fill="s.color" opacity="0.5" class="status-blink"/>
<text y="14" text-anchor="middle" class="status-text">{{ s.label }}</text>
</g>
</g>
<!-- Micro-particles -->
<circle v-for="(p, i) in particles" :key="'mp'+i"
:cx="p.x" :cy="p.y" :r="p.r"
fill="var(--vp-c-brand-1)" :opacity="p.opacity"
class="micro-dot" :style="{ '--fd': p.dur + 's', '--fdy': p.delay + 's' }"/>
</svg>
</div>
</template>
@@ -59,156 +193,112 @@ import { ref, onMounted } from 'vue'
const isVisible = ref(false)
onMounted(() => {
setTimeout(() => {
requestAnimationFrame(() => {
isVisible.value = true
}, 100)
})
})
const agents = [
{ name: 'Analyze', x: 200, y: 35, color: '#3B82F6' },
{ name: 'Plan', x: 315, y: 110, color: '#10B981' },
{ name: 'Code', x: 285, y: 245, color: '#8B5CF6' },
{ name: 'Test', x: 115, y: 245, color: '#F59E0B' },
{ name: 'Review', x: 85, y: 110, color: '#EF4444' }
const flowItems = [
{ w: 12, color: 'var(--vp-c-text-3)', opacity: 0.35, dur: 7, delay: 0 },
{ w: 10, color: '#D97757', opacity: 0.55, dur: 7, delay: 1.75 },
{ w: 14, color: '#10A37F', opacity: 0.55, dur: 7, delay: 3.5 },
{ w: 10, color: '#4285F4', opacity: 0.55, dur: 7, delay: 5.25 }
]
const paths = [
'M200,160 L200,35',
'M200,160 L315,110',
'M200,160 L285,245',
'M200,160 L115,245',
'M200,160 L85,110',
'M200,35 Q260,35 315,110',
'M315,110 Q315,180 285,245',
'M285,245 Q200,285 115,245',
'M115,245 Q85,180 85,110',
'M85,110 Q85,35 200,35'
const sparks = [
{ r: 1.5, color: '#D97757', dur: 2, delay: 0, path: 'M60,212 Q57,202 55,192' },
{ r: 1, color: '#D97757', dur: 2.5, delay: 0.5, path: 'M62,212 Q65,202 67,192' },
{ r: 1.5, color: '#10A37F', dur: 2, delay: 0.8, path: 'M180,212 Q177,202 175,192' },
{ r: 1, color: '#10A37F', dur: 2.5, delay: 1.3, path: 'M182,212 Q185,202 187,192' },
{ r: 1.5, color: '#4285F4', dur: 2, delay: 1.6, path: 'M300,212 Q297,202 295,192' },
{ r: 1, color: '#4285F4', dur: 2.5, delay: 2.1, path: 'M302,212 Q305,202 307,192' }
]
const statusDots = [
{ x: 60, color: '#D97757', label: 'Planning' },
{ x: 180, color: '#10A37F', label: 'Coding' },
{ x: 300, color: '#4285F4', label: 'Testing' }
]
const particles = Array.from({ length: 10 }, () => ({
x: 15 + Math.random() * 330,
y: 10 + Math.random() * 320,
r: 0.5 + Math.random() * 1,
opacity: 0.05 + Math.random() * 0.1,
dur: 3 + Math.random() * 4,
delay: Math.random() * 3
}))
</script>
<style scoped>
.hero-animation-container {
.hero-anim {
width: 100%;
max-width: 480px;
position: relative;
opacity: 0;
transform: scale(0.95);
transition: all 1s cubic-bezier(0.2, 0.8, 0.2, 1);
transform: scale(0.92) translateY(12px);
transition: all 0.8s cubic-bezier(0.16, 1, 0.3, 1);
}
.hero-animation-container.is-visible {
.hero-anim.is-visible {
opacity: 1;
transform: scale(1);
transform: scale(1) translateY(0);
}
.glow-bg {
position: absolute;
top: 50%;
left: 50%;
width: 150px;
height: 150px;
background: var(--vp-c-brand-1);
filter: blur(80px);
opacity: 0.15;
transform: translate(-50%, -50%);
pointer-events: none;
}
.hero-svg {
width: 100%;
height: auto;
overflow: visible;
display: block;
}
.orbit-ring {
fill: none;
stroke: var(--vp-c-brand-1);
stroke-width: 0.5;
opacity: 0.1;
stroke-dasharray: 4 4;
}
/* CCW */
.ccw-bg { fill: var(--vp-c-bg-soft); }
.ccw-ring1 { animation: ccwRing 4s ease-in-out infinite; }
.ccw-ring2 { animation: ccwRing 4s ease-in-out infinite 2s; }
.ccw-label { font-size: 15px; font-weight: 800; fill: var(--vp-c-brand-1); letter-spacing: 0.1em; }
.ccw-sub { font-size: 7px; fill: var(--vp-c-text-3); letter-spacing: 0.1em; text-transform: uppercase; font-weight: 500; }
.ccw-signal { animation: signalPulse 2s ease-in-out infinite; }
.line-pulse { opacity: 0.4; }
.ring-outer { animation: rotate 60s linear infinite; transform-origin: 200px 160px; }
.ring-inner { animation: rotate 40s linear infinite reverse; transform-origin: 200px 160px; }
/* Control lines */
.ctrl-line { fill: none; stroke: var(--vp-c-text-4); stroke-width: 0.8; stroke-dasharray: 3 3; opacity: 0; animation: fadeIn 0.6s ease forwards; animation-delay: var(--d); }
.connection-path {
fill: none;
stroke: var(--vp-c-brand-1);
stroke-width: 0.8;
opacity: 0.05;
}
/* Stations - animation only uses opacity, no transform conflict */
.station { opacity: 0; animation: stationAppear 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards; animation-delay: var(--sd); }
.station-box { fill: var(--vp-c-bg-soft); stroke: var(--sc); stroke-width: 1.2; transition: all 0.3s ease; }
.station:hover .station-box { stroke-width: 2; }
.st-name { font-size: 11px; font-weight: 700; letter-spacing: 0.06em; }
.st-role { font-size: 7.5px; fill: var(--vp-c-text-3); letter-spacing: 0.1em; text-transform: uppercase; font-weight: 500; }
.data-pulse {
fill: var(--vp-c-brand-2);
filter: drop-shadow(0 0 4px var(--vp-c-brand-2));
}
/* Belt */
.roller { fill: var(--vp-c-bg-soft); stroke: var(--vp-c-divider); stroke-width: 0.5; }
.belt-scroll { animation: beltScroll 1.2s linear infinite; }
.agent-group {
transition: all 0.3s ease;
}
/* Sparks */
.spark { opacity: 0.5; filter: drop-shadow(0 0 2px currentColor); }
.agent-circle {
transition: all 0.3s ease;
}
/* I/O */
.io-label { font-size: 8px; fill: var(--vp-c-text-3); letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600; opacity: 0.5; }
.check-pulse { animation: checkPulse 2.5s ease-in-out infinite; }
.agent-halo {
opacity: 0.2;
animation: agent-pulse 2s ease-in-out infinite;
transform-origin: center;
}
/* Status */
.status-blink { animation: blink 2.5s ease-in-out infinite; }
.status-text { font-size: 7.5px; fill: var(--vp-c-text-3); letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600; opacity: 0.5; }
.agent-label {
font-size: 10px;
fill: var(--vp-c-text-2);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
opacity: 0.7;
}
/* Particles */
.micro-dot { animation: microFloat var(--fd) ease-in-out infinite; animation-delay: var(--fdy); }
.core-bg {
fill: var(--vp-c-bg-soft);
stroke: var(--vp-c-brand-soft);
stroke-width: 1;
}
/* Keyframes */
@keyframes ccwRing { 0%, 100% { opacity: 0.06; } 50% { opacity: 0.18; } }
@keyframes signalPulse { 0%, 100% { opacity: 0.3; } 50% { opacity: 0.8; } }
@keyframes fadeIn { to { opacity: 0.25; } }
/* Only animate opacity - SVG positioning handled by parent <g> transform attribute */
@keyframes stationAppear { from { opacity: 0; } to { opacity: 1; } }
@keyframes beltScroll { from { transform: translateX(0); } to { transform: translateX(-18px); } }
@keyframes blink { 0%, 100% { opacity: 0.5; } 50% { opacity: 0.15; } }
@keyframes checkPulse { 0%, 100% { opacity: 0.06; } 50% { opacity: 0.14; } }
@keyframes microFloat { 0%, 100% { transform: translateY(0); opacity: 0.05; } 50% { transform: translateY(-5px); opacity: 0.14; } }
.core-inner {
opacity: 0.8;
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
.hero-anim.is-visible { opacity: 1; transform: none; }
.station { opacity: 1; }
.ctrl-line { opacity: 0.25; }
}
.core-text {
font-size: 14px;
font-weight: 800;
fill: white;
letter-spacing: 0.05em;
}
.core-scanner {
animation: rotate 3s linear infinite;
opacity: 0.6;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes agent-pulse {
0%, 100% { transform: scale(1); opacity: 0.2; }
50% { transform: scale(1.3); opacity: 0.1; }
}
.agent-node {
animation: agent-float 4s ease-in-out infinite;
animation-delay: var(--delay);
}
@keyframes agent-float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-5px); }
}
.hero-animation-container:hover .agent-circle {
filter: blur(2px) brightness(1.5);
}
</style>
</style>

View File

@@ -23,29 +23,18 @@
</div>
</div>
<div class="terminal-visual">
<div class="terminal-window">
<div class="terminal-header">
<div class="dots"><span></span><span></span><span></span></div>
<div class="title">workflow.json ccw run</div>
</div>
<div class="terminal-body font-mono">
<div v-for="(line, idx) in terminalLines.slice(0, terminalStep)" :key="idx" :class="line.color" class="line">
{{ line.text }}
</div>
<div v-if="terminalStep < terminalLines.length" class="cursor">_</div>
</div>
</div>
<div class="hero-visual">
<HeroAnimation />
</div>
</div>
</section>
<!-- Features Grid -->
<section class="features-section">
<section class="features-section" ref="featuresRef">
<div class="section-container">
<h2 class="section-title">{{ t.featureTitle }}</h2>
<h2 class="section-title reveal-text" :class="{ visible: featuresVisible }">{{ t.featureTitle }}</h2>
<div class="features-grid">
<div v-for="(f, i) in t.features" :key="i" class="feature-card">
<div v-for="(f, i) in t.features" :key="i" class="feature-card reveal-card" :class="{ visible: featuresVisible }" :style="{ '--card-delay': i * 0.1 + 's' }">
<div class="feature-icon-box">
<svg v-if="i === 0" viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0110 0v4"/><circle cx="12" cy="16" r="1"/>
@@ -68,9 +57,9 @@
</section>
<!-- Pipeline Visual -->
<section class="pipeline-section">
<section class="pipeline-section" ref="pipelineRef">
<div class="section-container">
<div class="section-header">
<div class="section-header reveal-text" :class="{ visible: pipelineVisible }">
<h2>{{ t.pipelineTitle }}</h2>
<p>{{ t.pipelineSubtitle }}</p>
</div>
@@ -147,9 +136,9 @@
</section>
<!-- JSON Demo -->
<section class="json-section">
<section class="json-section" ref="jsonRef">
<div class="section-container">
<div class="json-grid">
<div class="json-grid reveal-slide" :class="{ visible: jsonVisible }">
<div class="json-text">
<h2>{{ t.jsonTitle }}</h2>
<p>{{ t.jsonSubtitle }}</p>
@@ -172,9 +161,9 @@
</section>
<!-- Quick Start Section -->
<section class="quickstart-section">
<section class="quickstart-section" ref="quickstartRef">
<div class="section-container">
<div class="quickstart-layout">
<div class="quickstart-layout reveal-slide" :class="{ visible: quickstartVisible }">
<div class="quickstart-info">
<h2 class="quickstart-title">{{ t.quickStartTitle }}</h2>
<p class="quickstart-desc">{{ t.quickStartDesc }}</p>
@@ -225,9 +214,9 @@
</section>
<!-- CTA Footer -->
<section class="cta-section">
<section class="cta-section" ref="ctaRef">
<div class="section-container">
<div class="cta-card">
<div class="cta-card reveal-scale" :class="{ visible: ctaVisible }">
<h2>{{ t.ctaTitle }}</h2>
<p>{{ t.ctaDesc }}</p>
<div class="cta-actions">
@@ -248,6 +237,7 @@
<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue'
import HeroAnimation from './HeroAnimation.vue'
const props = defineProps({
lang: { type: String, default: 'en' }
@@ -256,6 +246,21 @@ const props = defineProps({
const localePath = computed(() => props.lang === 'zh' ? '/zh' : '')
const activeTab = ref(0)
// --- Scroll Reveal ---
const featuresRef = ref(null)
const pipelineRef = ref(null)
const jsonRef = ref(null)
const quickstartRef = ref(null)
const ctaRef = ref(null)
const featuresVisible = ref(false)
const pipelineVisible = ref(false)
const jsonVisible = ref(false)
const quickstartVisible = ref(false)
const ctaVisible = ref(false)
let observer = null
// --- Translations ---
const content = {
en: {
@@ -263,7 +268,7 @@ const content = {
heroTitle: 'JSON-Driven Multi-Agent <br/> <span class="gradient-text">Collaborative Framework</span>',
heroSubtitle: 'Industrial-grade orchestration blending 21 specialized agents and 32 core skills with precision cadence control.',
getStarted: 'Get Started',
featureTitle: 'The CCW Ecosystem',
featureTitle: 'The Claude Code Workflow Ecosystem',
features: [
{ title: "21 Specialized Agents", desc: "From CLI exploration to TDD implementation and UI design token management." },
{ title: "32 Core Skills", desc: "Standalone, Team, and Workflow skills including brainstorm, spec-gen, and code-review." },
@@ -336,7 +341,7 @@ const content = {
heroTitle: 'JSON 驱动的多智能体 <br/> <span class="gradient-text">协同开发框架</span>',
heroSubtitle: '融合 21 个专业代理与 32 项核心技能,通过精准节拍控制实现工业级自动化工作流处理。',
getStarted: '快速开始',
featureTitle: 'CCW 全局生态概览',
featureTitle: 'Claude Code Workflow 全局生态概览',
features: [
{ title: "21 个专业代理", desc: "涵盖 CLI 探索、TDD 实现、UI 设计令牌管理等全流程智能体。" },
{ title: "32 项核心技能", desc: "独立技能、团队技能与工作流技能,覆盖头脑风暴、规格生成、代码审查等场景。" },
@@ -465,11 +470,34 @@ onMounted(() => {
pipelineInterval = setInterval(() => {
currentTick.value = (currentTick.value + 1) % sequence.length
}, 3000)
// Scroll reveal observer
const sectionMap = new Map([
[featuresRef.value, featuresVisible],
[pipelineRef.value, pipelineVisible],
[jsonRef.value, jsonVisible],
[quickstartRef.value, quickstartVisible],
[ctaRef.value, ctaVisible]
])
observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const visRef = sectionMap.get(entry.target)
if (visRef) visRef.value = true
}
})
}, { threshold: 0.15, rootMargin: '0px 0px -60px 0px' })
sectionMap.forEach((_, el) => {
if (el) observer.observe(el)
})
})
onUnmounted(() => {
clearInterval(terminalInterval)
clearInterval(pipelineInterval)
if (observer) observer.disconnect()
})
</script>
@@ -483,7 +511,7 @@ onUnmounted(() => {
.section-container {
max-width: 1152px;
margin: 0 auto;
padding: 0 1.5rem;
padding: 0;
width: 100%;
}
@@ -493,7 +521,9 @@ onUnmounted(() => {
.hero-section {
width: 100%;
padding: 5rem 0 4rem;
background: radial-gradient(ellipse at 70% 20%, var(--vp-c-brand-soft) 0%, transparent 55%);
background:
radial-gradient(ellipse 120% 80% at 65% 30%, var(--vp-c-brand-soft) 0%, transparent 70%),
radial-gradient(ellipse 60% 50% at 90% 60%, color-mix(in srgb, var(--vp-c-brand-soft) 40%, transparent) 0%, transparent 100%);
display: flex;
justify-content: center;
}
@@ -502,13 +532,19 @@ onUnmounted(() => {
max-width: 1152px;
margin: 0 auto;
display: grid;
grid-template-columns: 1.1fr 0.9fr;
gap: 3rem;
grid-template-columns: 1fr 1fr;
gap: 2rem;
align-items: center;
padding: 0 1.5rem;
padding: 0;
width: 100%;
}
.hero-visual {
display: flex;
align-items: center;
justify-content: center;
}
.hero-badge {
display: inline-flex;
align-items: center;
@@ -539,12 +575,6 @@ onUnmounted(() => {
color: var(--vp-c-text-1);
}
.gradient-text {
background: linear-gradient(135deg, var(--vp-c-brand-1), var(--vp-c-brand-2));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.hero-subtitle {
font-size: 1.1rem;
color: var(--vp-c-text-2);
@@ -1045,9 +1075,94 @@ onUnmounted(() => {
position: relative;
}
/* ============================================
* Terminal cursor blink
* ============================================ */
.cursor-blink {
color: #10b981;
font-weight: bold;
animation: cursorBlink 1s step-end infinite;
}
.term-line {
animation: termLineReveal 0.3s ease-out both;
animation-delay: calc(var(--line-idx, 0) * 0.05s);
}
/* ============================================
* Scroll Reveal Animations
* ============================================ */
.reveal-text {
opacity: 0;
transform: translateY(24px);
transition: opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1), transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal-text.visible {
opacity: 1;
transform: translateY(0);
}
.reveal-card {
opacity: 0;
transform: translateY(32px);
transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1), transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
transition-delay: var(--card-delay, 0s);
}
.reveal-card.visible {
opacity: 1;
transform: translateY(0);
}
.reveal-slide {
opacity: 0;
transform: translateY(40px);
transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1), transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal-slide.visible {
opacity: 1;
transform: translateY(0);
}
.reveal-scale {
opacity: 0;
transform: scale(0.95) translateY(20px);
transition: opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1), transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal-scale.visible {
opacity: 1;
transform: scale(1) translateY(0);
}
/* ============================================
* Gradient text shimmer
* ============================================ */
.gradient-text {
background: linear-gradient(135deg, var(--vp-c-brand-1), #8B5CF6, var(--vp-c-brand-2));
background-size: 200% 200%;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: gradientShimmer 6s ease infinite;
}
/* ============================================
* Animations
* ============================================ */
@keyframes cursorBlink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
@keyframes termLineReveal {
from { opacity: 0; transform: translateX(-8px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes gradientShimmer {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4); }
70% { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); }
@@ -1063,8 +1178,13 @@ onUnmounted(() => {
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
.reveal-text, .reveal-card, .reveal-slide, .reveal-scale {
opacity: 1 !important;
transform: none !important;
}
}
/* ============================================

View File

@@ -2,10 +2,14 @@
<div class="workflow-animation">
<div class="workflow-container">
<div class="workflow-node coordinator">
<div class="node-icon">🎯</div>
<div class="node-icon">
<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"/><path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83"/>
</svg>
</div>
<div class="node-label">Coordinator</div>
</div>
<div class="workflow-paths">
<svg class="path-svg" viewBox="0 0 400 200">
<!-- Spec Path -->
@@ -13,13 +17,13 @@
<circle class="flow-dot dot-spec" r="6" fill="#3B82F6">
<animateMotion dur="3s" repeatCount="indefinite" path="M50,100 Q150,20 250,50"/>
</circle>
<!-- Impl Path -->
<path class="flow-path path-impl" d="M50,100 Q150,100 250,100" fill="none" stroke="#10B981" stroke-width="2"/>
<circle class="flow-dot dot-impl" r="6" fill="#10B981">
<animateMotion dur="2.5s" repeatCount="indefinite" path="M50,100 Q150,100 250,100"/>
</circle>
<!-- Test Path -->
<path class="flow-path path-test" d="M50,100 Q150,180 250,150" fill="none" stroke="#F59E0B" stroke-width="2"/>
<circle class="flow-dot dot-test" r="6" fill="#F59E0B">
@@ -27,27 +31,44 @@
</circle>
</svg>
</div>
<div class="workflow-nodes">
<div class="workflow-node analyst">
<div class="node-icon">📊</div>
<div class="node-icon">
<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 20V10M12 20V4M6 20v-6"/>
</svg>
</div>
<div class="node-label">Analyst</div>
</div>
<div class="workflow-node writer">
<div class="node-icon"></div>
<div class="node-icon">
<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M17 3a2.83 2.83 0 114 4L7.5 20.5 2 22l1.5-5.5L17 3z"/>
</svg>
</div>
<div class="node-label">Writer</div>
</div>
<div class="workflow-node executor">
<div class="node-icon"></div>
<div class="node-icon">
<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>
</svg>
</div>
<div class="node-label">Executor</div>
</div>
<div class="workflow-node tester">
<div class="node-icon">🧪</div>
<div class="node-icon">
<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 3h6M12 3v7l-4 8h8l-4-8"/>
<circle cx="8" cy="20" r="1"/><circle cx="16" cy="20" r="1"/><circle cx="12" cy="17" r="1"/>
</svg>
</div>
<div class="node-label">Tester</div>
</div>
</div>
</div>
<div class="workflow-legend">
<div class="legend-item"><span class="dot spec"></span> Spec Phase</div>
<div class="legend-item"><span class="dot impl"></span> Impl Phase</div>
@@ -57,10 +78,9 @@
</template>
<script setup>
import { onMounted, onUnmounted } from 'vue'
import { onMounted } from 'vue'
onMounted(() => {
// Add animation class after mount
document.querySelector('.workflow-animation')?.classList.add('animate')
})
</script>
@@ -129,6 +149,7 @@ onMounted(() => {
border-radius: 16px;
box-shadow: var(--vp-shadow-sm);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: default;
}
.workflow-node:hover {
@@ -138,8 +159,14 @@ onMounted(() => {
}
.node-icon {
font-size: 2rem;
width: 40px;
height: 40px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 0.5rem;
color: var(--vp-c-text-2);
}
.node-label {
@@ -155,10 +182,19 @@ onMounted(() => {
color: white;
}
.workflow-node.coordinator .node-icon {
color: white;
}
.workflow-node.coordinator .node-label {
color: white;
}
.workflow-node.analyst .node-icon { color: #3B82F6; }
.workflow-node.writer .node-icon { color: #8B5CF6; }
.workflow-node.executor .node-icon { color: #10B981; }
.workflow-node.tester .node-icon { color: #F59E0B; }
.workflow-legend {
display: flex;
justify-content: center;
@@ -186,15 +222,23 @@ onMounted(() => {
.dot.impl { background: var(--vp-c-secondary-500); }
.dot.test { background: var(--vp-c-accent-400); }
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
@media (max-width: 768px) {
.workflow-animation {
padding: 1.5rem;
}
.workflow-container {
flex-direction: column;
}
.workflow-nodes {
width: 100%;
}

View File

@@ -0,0 +1,209 @@
import { onMounted, onBeforeUnmount, ref, computed } from 'vue'
/**
* Theme color mappings for light and dark modes
*/
export const THEME_COLORS = {
blue: {
light: '#3B82F6',
dark: '#60A5FA'
},
green: {
light: '#22C55E',
dark: '#34D399'
},
orange: {
light: '#F59E0B',
dark: '#FBBF24'
},
purple: {
light: '#8B5CF6',
dark: '#A78BFA'
}
} as const
export type ThemeName = keyof typeof THEME_COLORS
/**
* Status dot colors (always green)
*/
export const STATUS_COLORS = {
light: '#22C55E',
dark: '#34D399'
} as const
const STORAGE_KEY_THEME = 'ccw-theme'
const STORAGE_KEY_COLOR_MODE = 'ccw-color-mode'
/**
* Get current theme from localStorage or default
*/
export function getCurrentTheme(): ThemeName {
const saved = localStorage.getItem(STORAGE_KEY_THEME)
if (saved && saved in THEME_COLORS) {
return saved as ThemeName
}
return 'blue'
}
/**
* Check if dark mode is active
*/
export function isDarkMode(): boolean {
const mode = localStorage.getItem(STORAGE_KEY_COLOR_MODE) || 'auto'
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
return mode === 'dark' || (mode === 'auto' && prefersDark)
}
/**
* Get the appropriate theme color based on current mode
*/
export function getThemeColor(theme: ThemeName, isDark: boolean): string {
return isDark ? THEME_COLORS[theme].dark : THEME_COLORS[theme].light
}
/**
* Get the appropriate status color based on current mode
*/
export function getStatusColor(isDark: boolean): string {
return isDark ? STATUS_COLORS.dark : STATUS_COLORS.light
}
/**
* Generate favicon SVG with dynamic colors (line style)
*/
export function generateFaviconSvg(theme: ThemeName, isDark: boolean): string {
const lineColor = getThemeColor(theme, isDark)
const dotColor = getThemeColor(theme, isDark) // Dot follows theme color
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<line x1="3" y1="6" x2="18" y2="6" stroke="${lineColor}" stroke-width="2" stroke-linecap="round"/>
<line x1="3" y1="12" x2="15" y2="12" stroke="${lineColor}" stroke-width="2" stroke-linecap="round"/>
<line x1="3" y1="18" x2="12" y2="18" stroke="${lineColor}" stroke-width="2" stroke-linecap="round"/>
<circle cx="19" cy="17" r="3" fill="${dotColor}"/>
</svg>`
}
/**
* Update the favicon with current theme colors
*/
export function updateFavicon(theme: ThemeName, isDark: boolean): void {
const svg = generateFaviconSvg(theme, isDark)
const dataUrl = `data:image/svg+xml,${encodeURIComponent(svg)}`
// Update existing favicon or create new one
let link = document.querySelector<HTMLLinkElement>('link[rel="icon"]')
if (!link) {
link = document.createElement('link')
link.rel = 'icon'
link.type = 'image/svg+xml'
document.head.appendChild(link)
}
link.href = dataUrl
// Also update apple-touch-icon if exists
const appleLink = document.querySelector<HTMLLinkElement>('link[rel="apple-touch-icon"]')
if (appleLink) {
appleLink.href = dataUrl
}
}
/**
* Composable for dynamic icon management
*/
export function useDynamicIcon() {
const currentTheme = ref<ThemeName>(getCurrentTheme())
const darkMode = ref(isDarkMode())
let mediaQuery: MediaQueryList | null = null
let mutationObserver: MutationObserver | null = null
let storageHandler: ((e: StorageEvent) => void) | null = null
/**
* Update favicon based on current state
*/
const updateIcon = () => {
updateFavicon(currentTheme.value, darkMode.value)
}
/**
* Check and update dark mode state
*/
const checkDarkMode = () => {
darkMode.value = isDarkMode()
updateIcon()
}
/**
* Check and update theme state
*/
const checkTheme = () => {
currentTheme.value = getCurrentTheme()
updateIcon()
}
onMounted(() => {
// Initial update
updateIcon()
// Listen for system color scheme changes
mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
mediaQuery.addEventListener('change', checkDarkMode)
// Listen for storage changes (cross-tab sync)
storageHandler = (e: StorageEvent) => {
if (e.key === STORAGE_KEY_THEME) checkTheme()
if (e.key === STORAGE_KEY_COLOR_MODE) checkDarkMode()
}
window.addEventListener('storage', storageHandler)
// Observe DOM changes for dark class and data-theme attribute
mutationObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'attributes') {
const target = mutation.target as HTMLElement
if (mutation.attributeName === 'class') {
const isDark = target.classList.contains('dark')
if (isDark !== darkMode.value) {
darkMode.value = isDark
updateIcon()
}
}
if (mutation.attributeName === 'data-theme') {
const theme = target.getAttribute('data-theme') as ThemeName
if (theme && theme !== currentTheme.value && theme in THEME_COLORS) {
currentTheme.value = theme
updateIcon()
}
}
}
}
})
mutationObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class', 'data-theme']
})
})
onBeforeUnmount(() => {
if (mediaQuery) {
mediaQuery.removeEventListener('change', checkDarkMode)
}
if (storageHandler) {
window.removeEventListener('storage', storageHandler)
}
if (mutationObserver) {
mutationObserver.disconnect()
}
})
return {
currentTheme,
darkMode,
updateIcon,
themeColors: THEME_COLORS
}
}
export type UseDynamicIconReturn = ReturnType<typeof useDynamicIcon>

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import DefaultTheme from 'vitepress/theme'
import { onBeforeUnmount, onMounted } from 'vue'
import { useDynamicIcon } from '../composables/useDynamicIcon'
let mediaQuery: MediaQueryList | null = null
let systemThemeChangeHandler: (() => void) | null = null
@@ -18,6 +19,9 @@ function applyColorMode() {
document.documentElement.classList.toggle('dark', isDark)
}
// Initialize dynamic favicon system
useDynamicIcon()
onMounted(() => {
applyTheme()
applyColorMode()

View File

@@ -56,14 +56,16 @@
/* ============================================
* Layout Container Adjustments
* ============================================ */
.VPDoc .content-container {
max-width: var(--vp-content-width);
.VPDoc .content-container,
.VPDoc.has-aside .content-container {
max-width: 90% !important;
padding: 0 32px;
}
/* Adjust sidebar and content layout */
.VPDoc {
padding-left: var(--vp-sidebar-width);
.VPDoc,
.VPDoc[data-v-343c73d6] {
padding-left: var(--vp-sidebar-width) !important;
}
/* Right side outline (TOC) adjustments */
@@ -84,6 +86,28 @@
padding-bottom: 0;
}
/* Remove horizontal padding for home page only (has .VPHome class) */
.VPHome .VPDoc {
padding-left: 0 !important;
padding-right: 0 !important;
}
.VPHome .VPDoc .content-container {
padding-left: 0 !important;
padding-right: 0 !important;
max-width: 100% !important;
}
.VPHome .VPDoc .content {
padding-left: 0 !important;
padding-right: 0 !important;
}
.VPHome .VPContent {
padding-left: 0 !important;
padding-right: 0 !important;
}
.VPHomeHero {
padding: 80px 24px;
background: linear-gradient(180deg, var(--vp-c-bg-soft) 0%, var(--vp-c-bg) 100%);

View File

@@ -183,7 +183,7 @@
.VPDoc .content-container {
padding: 0 24px;
max-width: var(--vp-content-width);
max-width: 90% !important;
}
.VPHomeHero {
@@ -225,7 +225,7 @@
}
.VPDoc .content-container {
max-width: var(--vp-content-width);
max-width: 90% !important;
padding: 0 40px;
}
@@ -272,6 +272,7 @@
}
.VPDoc .content-container {
max-width: 90% !important;
padding: 0 48px;
}

View File

@@ -9,6 +9,10 @@
* Color Scheme: Blue (Default)
* ============================================ */
/* Logo Colors - follow theme */
--logo-line-color: currentColor;
--logo-dot-color: var(--vp-c-primary);
/* Primary Colors */
--vp-c-primary-50: #eff6ff;
--vp-c-primary-100: #dbeafe;
@@ -38,14 +42,18 @@
--vp-c-secondary-900: #064e3b;
--vp-c-secondary: var(--vp-c-secondary-500);
/* Accent Colors */
--vp-c-accent-50: #fef3c7;
--vp-c-accent-100: #fde68a;
--vp-c-accent-200: #fcd34d;
--vp-c-accent-300: #fbbf24;
--vp-c-accent-400: #f59e0b;
--vp-c-accent-500: #d97706;
--vp-c-accent: var(--vp-c-accent-400);
/* Accent Colors (Orange) */
--vp-c-accent-50: #fffbeb;
--vp-c-accent-100: #fef3c7;
--vp-c-accent-200: #fde68a;
--vp-c-accent-300: #fcd34d;
--vp-c-accent-400: #fbbf24;
--vp-c-accent-500: #f59e0b;
--vp-c-accent-600: #d97706;
--vp-c-accent-700: #b45309;
--vp-c-accent-800: #92400e;
--vp-c-accent-900: #78350f;
--vp-c-accent: var(--vp-c-accent-500);
/* Background Colors (Light Mode) */
--vp-c-bg: #ffffff;
@@ -99,8 +107,8 @@
/* Shadows */
--vp-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--vp-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--vp-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
--vp-shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1);
--vp-shadow-lg: 0 10px 15px -3 rgb(0 0 0 / 0.1);
--vp-shadow-xl: 0 20px 25px -5 rgb(0 0 0 / 0.1);
/* Transitions */
--vp-transition-color: 0.2s ease;
@@ -120,6 +128,9 @@
* Dark Mode
* ============================================ */
.dark {
/* Logo Colors (Dark Mode) - follow theme */
--logo-dot-color: var(--vp-c-primary);
/* Background Colors (Dark Mode) */
--vp-c-bg: #111827;
--vp-c-bg-soft: #1f2937;
@@ -172,41 +183,136 @@
--vp-c-secondary-800: #d1fae5;
--vp-c-secondary-900: #ecfdf5;
--vp-c-secondary: var(--vp-c-secondary-400);
/* Accent Colors (adjusted for dark mode) */
--vp-c-accent-50: #451a03;
--vp-c-accent-100: #78350f;
--vp-c-accent-200: #92400e;
--vp-c-accent-300: #b45309;
--vp-c-accent-400: #d97706;
--vp-c-accent-500: #f59e0b;
--vp-c-accent-600: #fbbf24;
--vp-c-accent-700: #fcd34d;
--vp-c-accent-800: #fde68a;
--vp-c-accent-900: #fef3c7;
--vp-c-accent: var(--vp-c-accent-400);
}
/* ============================================
* Color Scheme: Green
* ============================================ */
/* Green - Light Mode */
[data-theme="green"] {
--vp-c-primary: var(--vp-c-secondary-500);
--vp-c-primary-50: #ecfdf5;
--vp-c-primary-100: #d1fae5;
--vp-c-primary-200: #a7f3d0;
--vp-c-primary-300: #6ee7b7;
--vp-c-primary-400: #34d399;
--vp-c-primary-500: #22c55e;
--vp-c-primary-600: #10b981;
--vp-c-primary-700: #059669;
--vp-c-primary-800: #047857;
--vp-c-primary-900: #065f46;
--vp-c-primary: var(--vp-c-primary-500);
--vp-c-brand-1: var(--vp-c-primary-500);
--vp-c-brand-soft: rgba(34, 197, 94, 0.1);
}
/* Green - Dark Mode */
.dark[data-theme="green"],
[data-theme="green"].dark {
--vp-c-primary: var(--vp-c-secondary-400);
--vp-c-primary-50: #064e3b;
--vp-c-primary-100: #065f46;
--vp-c-primary-200: #047857;
--vp-c-primary-300: #059669;
--vp-c-primary-400: #10b981;
--vp-c-primary-500: #34d399;
--vp-c-primary-600: #6ee7b7;
--vp-c-primary-700: #a7f3d0;
--vp-c-primary-800: #d1fae5;
--vp-c-primary-900: #ecfdf5;
--vp-c-primary: var(--vp-c-primary-400);
--vp-c-brand-1: var(--vp-c-primary-400);
--vp-c-brand-soft: rgba(52, 211, 153, 0.2);
}
/* ============================================
* Color Scheme: Orange
* ============================================ */
/* Orange - Light Mode */
[data-theme="orange"] {
--vp-c-primary: var(--vp-c-accent-500);
--vp-c-primary-50: #fffbeb;
--vp-c-primary-100: #fef3c7;
--vp-c-primary-200: #fde68a;
--vp-c-primary-300: #fcd34d;
--vp-c-primary-400: #fbbf24;
--vp-c-primary-500: #f59e0b;
--vp-c-primary-600: #d97706;
--vp-c-primary-700: #b45309;
--vp-c-primary-800: #92400e;
--vp-c-primary-900: #78350f;
--vp-c-primary: var(--vp-c-primary-500);
--vp-c-brand-1: var(--vp-c-primary-500);
--vp-c-brand-soft: rgba(245, 158, 11, 0.1);
}
/* Orange - Dark Mode */
.dark[data-theme="orange"],
[data-theme="orange"].dark {
--vp-c-primary: var(--vp-c-accent-400);
--vp-c-primary-50: #451a03;
--vp-c-primary-100: #78350f;
--vp-c-primary-200: #92400e;
--vp-c-primary-300: #b45309;
--vp-c-primary-400: #d97706;
--vp-c-primary-500: #f59e0b;
--vp-c-primary-600: #fbbf24;
--vp-c-primary-700: #fcd34d;
--vp-c-primary-800: #fde68a;
--vp-c-primary-900: #fef3c7;
--vp-c-primary: var(--vp-c-primary-400);
--vp-c-brand-1: var(--vp-c-primary-400);
--vp-c-brand-soft: rgba(251, 191, 36, 0.2);
}
/* ============================================
* Color Scheme: Purple
* ============================================ */
/* Purple - Light Mode */
[data-theme="purple"] {
--vp-c-primary-500: #8b5cf6;
--vp-c-primary-600: #7c3aed;
--vp-c-primary-700: #6d28d9;
--vp-c-primary: var(--vp-c-primary-500);
--vp-c-primary-50: #faf5ff;
--vp-c-primary-100: #f3e8ff;
--vp-c-primary-200: #e9d5ff;
--vp-c-primary-300: #d8b4fe;
--vp-c-primary-400: #c084fc;
--vp-c-primary-500: #a855f7;
--vp-c-primary-600: #8b5cf6;
--vp-c-primary-700: #7c3aed;
--vp-c-primary-800: #6d28d9;
--vp-c-primary-900: #5b21b6;
--vp-c-primary: var(--vp-c-primary-600);
--vp-c-brand-1: var(--vp-c-primary-600);
--vp-c-brand-soft: rgba(139, 92, 246, 0.1);
}
/* Purple - Dark Mode */
.dark[data-theme="purple"],
[data-theme="purple"].dark {
--vp-c-primary-400: #a78bfa;
--vp-c-primary-50: #2e1065;
--vp-c-primary-100: #3b0764;
--vp-c-primary-200: #581c87;
--vp-c-primary-300: #6d28d9;
--vp-c-primary-400: #7c3aed;
--vp-c-primary-500: #8b5cf6;
--vp-c-primary-600: #a78bfa;
--vp-c-primary-700: #c4b5fd;
--vp-c-primary-800: #ddd6fe;
--vp-c-primary-900: #f3e8ff;
--vp-c-primary: var(--vp-c-primary-400);
--vp-c-brand-1: var(--vp-c-primary-400);
--vp-c-brand-soft: rgba(167, 139, 250, 0.2);
}
/* ============================================