feat(ai-protocols): add MCP and A2A protocol demos and documentation
docs(ai-protocols): update AI protocols page with visual demos and detailed explanations style(git-demos): improve responsive design and layout for git visualization components refactor(ai-history): simplify and clean up demo components chore: update config to register new AI protocol components
This commit is contained in:
@@ -1,697 +1,41 @@
|
||||
<template>
|
||||
<div class="evolution-demo">
|
||||
<el-card
|
||||
class="main-card"
|
||||
shadow="hover"
|
||||
>
|
||||
<template #header>
|
||||
<div class="header-container">
|
||||
<div class="title-area">
|
||||
<span class="main-title">AI 进化模拟器</span>
|
||||
</div>
|
||||
<el-steps
|
||||
:active="currentStage"
|
||||
finish-status="success"
|
||||
align-center
|
||||
class="compact-steps"
|
||||
simple
|
||||
>
|
||||
<el-step
|
||||
v-for="stage in stages"
|
||||
:key="stage.id"
|
||||
:title="stage.label"
|
||||
/>
|
||||
</el-steps>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Stage 1: Rule Based (Traffic Light Example) -->
|
||||
<div
|
||||
v-if="currentStage === 0"
|
||||
class="stage-pane"
|
||||
>
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="compact-alert mb-2"
|
||||
>
|
||||
<template #title>
|
||||
<span class="alert-title">阶段一:规则时代 (Rule-Based)</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<span class="alert-desc">就像教小孩:如果看到红灯,就停下。</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<div class="game-area-grid">
|
||||
<div class="panel left-panel">
|
||||
<div class="panel-header">
|
||||
规则库 (Code)
|
||||
</div>
|
||||
<div class="code-block">
|
||||
<div class="code-line">
|
||||
<span class="keyword">function</span> <span class="function">decideTrafficLight</span>(color) {
|
||||
</div>
|
||||
<div class="code-line indent">
|
||||
<span class="keyword">if</span> (color === <span class="string">'red'</span>) <span class="keyword">return</span> <span class="string">'stop'</span>
|
||||
</div>
|
||||
<div class="code-line indent">
|
||||
<span class="keyword">else if</span> (color === <span class="string">'yellow'</span>) <span class="keyword">return</span> <span class="string">'caution'</span>
|
||||
</div>
|
||||
<div class="code-line indent">
|
||||
<span class="keyword">else if</span> (color === <span class="string">'green'</span>) <span class="keyword">return</span> <span class="string">'go'</span>
|
||||
</div>
|
||||
<div class="code-line">
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel right-panel">
|
||||
<div class="panel-header">
|
||||
测试输入
|
||||
</div>
|
||||
<div class="input-controls">
|
||||
<el-select
|
||||
v-model="ruleColor"
|
||||
size="small"
|
||||
style="width: 120px;"
|
||||
>
|
||||
<el-option
|
||||
value="red"
|
||||
label="🔴 红灯"
|
||||
/>
|
||||
<el-option
|
||||
value="yellow"
|
||||
label="🟡 黄灯"
|
||||
/>
|
||||
<el-option
|
||||
value="green"
|
||||
label="🟢 绿灯"
|
||||
/>
|
||||
<el-option
|
||||
value="blue"
|
||||
label="🔵 蓝灯"
|
||||
/>
|
||||
</el-select>
|
||||
<div class="arrow">
|
||||
→
|
||||
</div>
|
||||
<el-tag :type="ruleResult === 'stop' ? 'danger' : ruleResult === 'caution' ? 'warning' : ruleResult === 'go' ? 'success' : 'info'">
|
||||
{{ ruleResult }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div
|
||||
v-if="ruleResult === 'Unknown'"
|
||||
class="hint-text"
|
||||
>
|
||||
规则库中没有定义"蓝灯",所以系统不知道该做什么。这就是规则系统的局限性:无法处理未定义的规则。
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="hint-text"
|
||||
>
|
||||
系统严格按照预定义的规则执行指令。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="demo-card">
|
||||
<div class="timeline-visual">
|
||||
<div class="era" v-for="era in eras" :key="era.label" :style="{ flex: era.flex, background: era.bg }">
|
||||
<div class="era-label">{{ era.label }}</div>
|
||||
<div class="era-years">{{ era.years }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Stage 2: Machine Learning (Interactive 2D Plot) -->
|
||||
<div
|
||||
v-else-if="currentStage === 1"
|
||||
class="stage-pane"
|
||||
>
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="compact-alert mb-2"
|
||||
>
|
||||
<template #title>
|
||||
<span class="alert-title">阶段二:机器学习 (Machine Learning)</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<span class="alert-desc">点击画布添加数据点,训练模型自动寻找分类边界 (Decision Boundary)。</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<div class="game-area-grid">
|
||||
<div
|
||||
class="panel left-panel canvas-container"
|
||||
@click="addPoint"
|
||||
>
|
||||
<!-- Simple SVG Plot -->
|
||||
<svg
|
||||
width="100%"
|
||||
height="200"
|
||||
class="ml-plot"
|
||||
>
|
||||
<!-- Background Regions (Visible after training) -->
|
||||
<rect
|
||||
v-if="modelTrained"
|
||||
x="0"
|
||||
y="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
:fill="boundaryColor"
|
||||
/>
|
||||
|
||||
<!-- Decision Line -->
|
||||
<line
|
||||
v-if="modelTrained"
|
||||
:x1="line.x1"
|
||||
:y1="line.y1"
|
||||
:x2="line.x2"
|
||||
:y2="line.y2"
|
||||
stroke="#333"
|
||||
stroke-width="2"
|
||||
stroke-dasharray="4"
|
||||
/>
|
||||
|
||||
<!-- Points -->
|
||||
<circle
|
||||
v-for="(p, i) in points"
|
||||
:key="i"
|
||||
:cx="p.x"
|
||||
:cy="p.y"
|
||||
r="6"
|
||||
:fill="p.type === 'A' ? '#409eff' : '#e6a23c'"
|
||||
stroke="white"
|
||||
stroke-width="2"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
v-if="points.length === 0"
|
||||
class="canvas-hint"
|
||||
>
|
||||
👆 点击此处添加数据点
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel right-panel">
|
||||
<div class="panel-header">
|
||||
控制面板
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<span class="label">当前类别:</span>
|
||||
<el-radio-group
|
||||
v-model="currentClass"
|
||||
size="small"
|
||||
>
|
||||
<el-radio-button label="A">
|
||||
<span style="color: #409eff">● 蓝类</span>
|
||||
</el-radio-button>
|
||||
<el-radio-button label="B">
|
||||
<span style="color: #e6a23c">● 橙类</span>
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="control-group mt-2">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:disabled="points.length < 2"
|
||||
@click="trainLinearModel"
|
||||
>
|
||||
⚡ 开始训练 (Fit)
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
:icon="Delete"
|
||||
circle
|
||||
@click="clearPoints"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="stats-info mt-2">
|
||||
<p
|
||||
v-if="!modelTrained"
|
||||
class="text-desc"
|
||||
>
|
||||
机器学习不再依赖硬编码规则,而是通过统计学方法(如寻找中心点或线性回归)在数据之间划出一条"界线"。试试在不同位置添加点,看看界线如何变化。
|
||||
</p>
|
||||
<p
|
||||
v-else
|
||||
class="text-desc"
|
||||
>
|
||||
模型已训练!它找到了一条最佳分割线。新进来的数据将根据它在红区还是蓝区被自动分类。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stage 3: Deep Learning (3x3 Grid Feature Extraction) -->
|
||||
<div
|
||||
v-else
|
||||
class="stage-pane"
|
||||
>
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="compact-alert mb-2"
|
||||
>
|
||||
<template #title>
|
||||
<span class="alert-title">阶段三:深度学习 (Deep Learning)</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<span class="alert-desc">神经网络通过多层结构自动提取特征(Feature Extraction)。点击格子绘制图案。</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<div class="game-area-grid">
|
||||
<div class="panel left-panel grid-container">
|
||||
<div class="pixel-grid">
|
||||
<div
|
||||
v-for="(pixel, i) in pixels"
|
||||
:key="i"
|
||||
class="pixel"
|
||||
:class="{ active: pixel }"
|
||||
@click="togglePixel(i)"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid-actions">
|
||||
<el-button
|
||||
size="small"
|
||||
link
|
||||
@click="preset('x')"
|
||||
>
|
||||
❌ X型
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
link
|
||||
@click="preset('plus')"
|
||||
>
|
||||
➕ 十字
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
link
|
||||
@click="clearPixels"
|
||||
>
|
||||
清空
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel right-panel">
|
||||
<div class="panel-header">
|
||||
神经网络层级透视
|
||||
</div>
|
||||
|
||||
<!-- Visualization of Layers -->
|
||||
<div class="network-viz">
|
||||
<div class="layer input-layer">
|
||||
<div class="layer-label">
|
||||
输入层 (Pixels)
|
||||
</div>
|
||||
<div class="nodes">
|
||||
<span
|
||||
v-for="n in 9"
|
||||
:key="n"
|
||||
class="node mini"
|
||||
:class="{active: pixels[n-1]}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="arrow-down">
|
||||
⬇️ 卷积/提取特征
|
||||
</div>
|
||||
|
||||
<div class="layer hidden-layer">
|
||||
<div class="layer-label">
|
||||
隐藏层 (Features)
|
||||
</div>
|
||||
<div class="feature-detectors">
|
||||
<div
|
||||
class="feature"
|
||||
:class="{detected: features.center}"
|
||||
>
|
||||
<span class="f-icon">⏺</span> 中心点
|
||||
</div>
|
||||
<div
|
||||
class="feature"
|
||||
:class="{detected: features.corners}"
|
||||
>
|
||||
<span class="f-icon">Corners</span> 四角
|
||||
</div>
|
||||
<div
|
||||
class="feature"
|
||||
:class="{detected: features.cross}"
|
||||
>
|
||||
<span class="f-icon">➕</span> 交叉
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="arrow-down">
|
||||
⬇️ 输出层
|
||||
</div>
|
||||
|
||||
<div class="layer output-layer">
|
||||
<div class="prediction-box">
|
||||
识别结果: <span class="result-text">{{ prediction }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Navigation -->
|
||||
<div class="footer-nav mt-2 flex justify-end">
|
||||
<el-button-group>
|
||||
<el-button
|
||||
size="small"
|
||||
:disabled="currentStage === 0"
|
||||
@click="currentStage--"
|
||||
>
|
||||
上一步
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
:disabled="currentStage === 2"
|
||||
@click="currentStage++"
|
||||
>
|
||||
下一步
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<div class="legend">
|
||||
<span class="legend-item"><span class="dot" style="background:#059669"></span>技术浪潮</span>
|
||||
<span class="legend-item"><span class="dot" style="background:#94a3b8"></span>❄️ AI 寒冬</span>
|
||||
<span class="legend-item"><span class="dot" style="background:#7c3aed"></span>大模型时代</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { Delete } from '@element-plus/icons-vue'
|
||||
|
||||
const currentStage = ref(0)
|
||||
const stages = [
|
||||
{ id: 0, label: '规则', desc: '人工规则' },
|
||||
{ id: 1, label: '机器学习', desc: '统计特征' },
|
||||
{ id: 2, label: '深度学习', desc: '自动特征' }
|
||||
const eras = [
|
||||
{ label: '理论奠基', years: '1940s-50s', flex: 1.5, bg: 'linear-gradient(135deg, #dbeafe, #bfdbfe)' },
|
||||
{ label: '第一次浪潮', years: '1960s-70s', flex: 1.5, bg: 'linear-gradient(135deg, #d1fae5, #a7f3d0)' },
|
||||
{ label: '❄️ 寒冬 I', years: '1974-80', flex: 0.7, bg: 'linear-gradient(135deg, #e2e8f0, #cbd5e1)' },
|
||||
{ label: '第二次浪潮', years: '1980s', flex: 1, bg: 'linear-gradient(135deg, #d1fae5, #a7f3d0)' },
|
||||
{ label: '❄️ 寒冬 II', years: '1987-93', flex: 0.7, bg: 'linear-gradient(135deg, #e2e8f0, #cbd5e1)' },
|
||||
{ label: 'ML 崛起', years: '1990s-2000s', flex: 1.5, bg: 'linear-gradient(135deg, #d1fae5, #6ee7b7)' },
|
||||
{ label: '深度学习', years: '2010s', flex: 1.2, bg: 'linear-gradient(135deg, #a7f3d0, #34d399)' },
|
||||
{ label: '大模型时代', years: '2018+', flex: 1.2, bg: 'linear-gradient(135deg, #c4b5fd, #a78bfa)' },
|
||||
]
|
||||
|
||||
// --- Stage 1: Rule Based ---
|
||||
const ruleColor = ref('red')
|
||||
const ruleResult = computed(() => {
|
||||
if (ruleColor.value === 'red') return 'stop'
|
||||
if (ruleColor.value === 'yellow') return 'caution'
|
||||
if (ruleColor.value === 'green') return 'go'
|
||||
return 'Unknown'
|
||||
})
|
||||
|
||||
// --- Stage 2: Machine Learning ---
|
||||
const points = ref([])
|
||||
const currentClass = ref('A')
|
||||
const modelTrained = ref(false)
|
||||
const line = reactive({ x1: 0, y1: 0, x2: 0, y2: 0 })
|
||||
// SVG click coordinates are relative to the SVG element
|
||||
// We'll use a simple approximation for the demo
|
||||
// x, y are percentages (0-100)
|
||||
const addPoint = (e) => {
|
||||
const rect = e.target.getBoundingClientRect()
|
||||
// Ensure we are clicking on the SVG or its children
|
||||
// Best to put event on wrapper
|
||||
// But event target might be circle.
|
||||
// Use currentTarget
|
||||
const x = e.offsetX
|
||||
const y = e.offsetY
|
||||
// Convert to % for responsiveness if needed, but pixel is easier for calc
|
||||
// Let's stick to pixel for this simple demo, assuming fixed height 200
|
||||
// width varies.
|
||||
points.value.push({
|
||||
x, y,
|
||||
type: currentClass.value
|
||||
})
|
||||
modelTrained.value = false
|
||||
}
|
||||
|
||||
const clearPoints = () => {
|
||||
points.value = []
|
||||
modelTrained.value = false
|
||||
}
|
||||
|
||||
const trainLinearModel = () => {
|
||||
// Simple Nearest Centroid Classifier
|
||||
const groupA = points.value.filter(p => p.type === 'A')
|
||||
const groupB = points.value.filter(p => p.type === 'B')
|
||||
|
||||
if (groupA.length === 0 || groupB.length === 0) return
|
||||
|
||||
const avgA = {
|
||||
x: groupA.reduce((sum, p) => sum + p.x, 0) / groupA.length,
|
||||
y: groupA.reduce((sum, p) => sum + p.y, 0) / groupA.length
|
||||
}
|
||||
const avgB = {
|
||||
x: groupB.reduce((sum, p) => sum + p.x, 0) / groupB.length,
|
||||
y: groupB.reduce((sum, p) => sum + p.y, 0) / groupB.length
|
||||
}
|
||||
|
||||
// Midpoint
|
||||
const midX = (avgA.x + avgB.x) / 2
|
||||
const midY = (avgA.y + avgB.y) / 2
|
||||
|
||||
// Normal vector (from A to B)
|
||||
const dx = avgB.x - avgA.x
|
||||
const dy = avgB.y - avgA.y
|
||||
|
||||
// Perpendicular line: dx*x + dy*y = C
|
||||
// Slope of normal is dy/dx. Slope of perp line is -dx/dy
|
||||
|
||||
// Let's just draw a line perpendicular to the segment AB passing through Midpoint
|
||||
// Slope m = -dx/dy
|
||||
|
||||
// Calculate line coordinates for visualization
|
||||
// y - midY = m * (x - midX)
|
||||
// if dy is close to 0, vertical line x = midX
|
||||
|
||||
const width = 1000 // ample width
|
||||
|
||||
if (Math.abs(dy) < 0.001) {
|
||||
// Vertical line
|
||||
line.x1 = midX
|
||||
line.x2 = midX
|
||||
line.y1 = 0
|
||||
line.y2 = 200
|
||||
} else {
|
||||
const m = -dx / dy
|
||||
// At x=0
|
||||
const y0 = midY + m * (0 - midX)
|
||||
// At x=width
|
||||
const y1 = midY + m * (width - midX)
|
||||
|
||||
line.x1 = 0
|
||||
line.y1 = y0
|
||||
line.x2 = width
|
||||
line.y2 = y1
|
||||
}
|
||||
|
||||
modelTrained.value = true
|
||||
}
|
||||
|
||||
// Simple visual background
|
||||
// If A is left/top, background is blue-ish
|
||||
// SVG doesn't support "half plane fill" easily without path math
|
||||
// For this demo, we won't fill the background perfectly, just draw the line.
|
||||
const boundaryColor = computed(() => 'transparent')
|
||||
|
||||
|
||||
// --- Stage 3: Deep Learning ---
|
||||
const pixels = ref(Array(9).fill(false))
|
||||
|
||||
const togglePixel = (index) => {
|
||||
pixels.value[index] = !pixels.value[index]
|
||||
}
|
||||
|
||||
const clearPixels = () => {
|
||||
pixels.value = pixels.value.map(() => false)
|
||||
}
|
||||
|
||||
const preset = (type) => {
|
||||
clearPixels()
|
||||
if (type === 'x') {
|
||||
[0, 2, 4, 6, 8].forEach(i => pixels.value[i] = true)
|
||||
} else if (type === 'plus') {
|
||||
[1, 3, 4, 5, 7].forEach(i => pixels.value[i] = true)
|
||||
}
|
||||
}
|
||||
|
||||
const features = computed(() => {
|
||||
// Simple heuristics to simulate feature detection
|
||||
const p = pixels.value
|
||||
const center = p[4]
|
||||
const corners = p[0] && p[2] && p[6] && p[8]
|
||||
const cross = p[1] && p[3] && p[5] && p[7]
|
||||
|
||||
return { center, corners, cross }
|
||||
})
|
||||
|
||||
const prediction = computed(() => {
|
||||
const f = features.value
|
||||
if (f.corners && f.center) return 'X 型图案 (X-Shape)'
|
||||
if (f.cross && f.center) return '十字型 (Plus-Shape)'
|
||||
if (f.corners && !f.center) return '四角 (Corners)'
|
||||
if (pixels.value.filter(Boolean).length === 0) return '无输入'
|
||||
return '未知图案'
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.evolution-demo { margin: 10px 0; }
|
||||
.header-container { margin-bottom: 5px; }
|
||||
.main-title { font-weight: bold; font-size: 16px; }
|
||||
.compact-steps { padding: 5px 0; margin-bottom: 10px; }
|
||||
.compact-alert { padding: 5px 10px; }
|
||||
.alert-title { font-weight: bold; font-size: 13px; }
|
||||
.alert-desc { font-size: 12px; }
|
||||
|
||||
.game-area-grid {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.panel {
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
}
|
||||
.left-panel { flex: 1; }
|
||||
.right-panel { flex: 1; background-color: #fcfcfc; }
|
||||
.panel-header {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: #606266;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
/* Stage 1 */
|
||||
.code-block {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
background: #282c34;
|
||||
color: #abb2bf;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.keyword { color: #c678dd; }
|
||||
.string { color: #98c379; }
|
||||
.function { color: #61afef; }
|
||||
.indent { padding-left: 15px; }
|
||||
.input-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.hint-text {
|
||||
margin-top: 10px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* Stage 2 */
|
||||
.canvas-container {
|
||||
height: 220px;
|
||||
background-color: #f5f7fa;
|
||||
position: relative;
|
||||
cursor: crosshair;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.ml-plot {
|
||||
display: block;
|
||||
}
|
||||
.canvas-hint {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.text-desc {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Stage 3 */
|
||||
.grid-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.pixel-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 40px);
|
||||
gap: 4px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.pixel {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: #eee;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.pixel:hover { background-color: #d9d9d9; }
|
||||
.pixel.active { background-color: #333; }
|
||||
|
||||
.network-viz {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.layer {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
.layer-label { font-size: 11px; color: #909399; margin-bottom: 4px; }
|
||||
.nodes { display: flex; gap: 2px; justify-content: center; flex-wrap: wrap; width: 60px; margin: 0 auto; }
|
||||
.node.mini { width: 6px; height: 6px; border-radius: 50%; background: #ddd; }
|
||||
.node.mini.active { background: #333; }
|
||||
.arrow-down { font-size: 10px; color: #ccc; }
|
||||
|
||||
.feature-detectors {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
font-size: 11px;
|
||||
}
|
||||
.feature {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
opacity: 0.3;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
.feature.detected { opacity: 1; color: #409eff; font-weight: bold; }
|
||||
.f-icon { font-size: 14px; margin-bottom: 2px; }
|
||||
|
||||
.prediction-box { font-weight: bold; font-size: 13px; color: #303133; }
|
||||
.result-text { color: #67c23a; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.game-area-grid { flex-direction: column; }
|
||||
}
|
||||
.flex { display: flex; }
|
||||
.justify-end { justify-content: flex-end; }
|
||||
.mt-2 { margin-top: 8px; }
|
||||
.mb-2 { margin-bottom: 8px; }
|
||||
.demo-card { border: 1px solid var(--vp-c-divider); border-radius: 8px; background: var(--vp-c-bg-soft); padding: 1rem; margin: 1rem 0; }
|
||||
.timeline-visual { display: flex; border-radius: 6px; overflow: hidden; border: 1px solid var(--vp-c-divider); min-height: 60px; }
|
||||
.era { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 0.4rem 0.2rem; text-align: center; border-right: 1px solid rgba(255,255,255,0.4); }
|
||||
.era:last-child { border-right: none; }
|
||||
.era-label { font-size: 0.65rem; font-weight: bold; color: #1e293b; line-height: 1.2; }
|
||||
.era-years { font-size: 0.55rem; color: #475569; margin-top: 0.15rem; }
|
||||
.legend { display: flex; gap: 1rem; margin-top: 0.6rem; flex-wrap: wrap; }
|
||||
.legend-item { display: flex; align-items: center; gap: 0.3rem; font-size: 0.72rem; color: var(--vp-c-text-2); }
|
||||
.dot { width: 8px; height: 8px; border-radius: 2px; }
|
||||
@media (max-width: 640px) { .era-label { font-size: 0.58rem; } .era-years { display: none; } }
|
||||
</style>
|
||||
Reference in New Issue
Block a user