Add Chinese documentation for custom skills development and reference guide

- Created a new document for custom skills development (`custom.md`) detailing the structure, creation, implementation, and best practices for developing custom CCW skills.
- Added an index document (`index.md`) summarizing all built-in skills, their categories, and usage examples.
- Introduced a reference guide (`reference.md`) providing a quick reference for all 33 built-in CCW skills, including triggers and purposes.
This commit is contained in:
catlog22
2026-03-01 13:08:12 +08:00
parent 2fb93d20e0
commit 8ceae6d6fd
78 changed files with 12352 additions and 3638 deletions

View File

@@ -264,7 +264,7 @@ function hideTooltip() {
}
}
@media (max-width: 640px) {
@media (max-width: 768px) {
.agent-orchestration {
padding: 2rem 1rem;
}

View File

@@ -1,12 +1,17 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue'
import { createRoot } from 'react-dom/client'
import React from 'react'
import type { Root } from 'react-dom/client'
import CodeViewer from './CodeViewer.vue'
interface Props {
name: string // Demo component name
id: string // Demo unique ID
name: string // Demo component name
file?: string // Optional: explicit source file
hasInlineCode?: boolean // Whether inline code is provided
inlineCode?: string // Base64 encoded inline code (for display)
virtualModule?: string // Virtual module path for inline demos
height?: string // Demo container height
expandable?: boolean // Allow expand/collapse
showCode?: boolean // Show code tab
@@ -14,6 +19,9 @@ interface Props {
}
const props = withDefaults(defineProps<Props>(), {
hasInlineCode: false,
inlineCode: '',
virtualModule: '',
height: 'auto',
expandable: true,
showCode: true,
@@ -33,25 +41,64 @@ const demoTitle = computed(() => props.title || props.name)
onMounted(async () => {
try {
// Dynamically import demo component
const demoModule = await import(`../demos/${props.name}.tsx`)
const DemoComponent = demoModule.default || demoModule[props.name]
// Handle inline code mode with virtual module
if (props.hasInlineCode && props.virtualModule) {
// Decode base64 for source code display
if (props.inlineCode) {
sourceCode.value = atob(props.inlineCode)
}
if (!DemoComponent) {
throw new Error(`Demo component "${props.name}" not found`)
}
// Dynamically import the virtual module
// @vite-ignore is needed for dynamic imports with variable paths
const inlineDemoModule = await import(/* @vite-ignore */ props.virtualModule)
const DemoComponent = inlineDemoModule.default
// Mount React component
if (demoRoot.value) {
reactRoot.value = createRoot(demoRoot.value)
reactRoot.value.render(DemoComponent)
if (!DemoComponent) {
throw new Error(`Inline demo component "${props.name}" not found in virtual module`)
}
// Extract source code
try {
const rawModule = await import(`../demos/${props.name}.tsx?raw`)
sourceCode.value = rawModule.default || rawModule
} catch {
sourceCode.value = '// Source code not available'
// Mount React component properly
if (demoRoot.value) {
reactRoot.value = createRoot(demoRoot.value)
reactRoot.value.render(React.createElement(DemoComponent))
}
} else if (props.hasInlineCode && props.inlineCode) {
// Fallback: inline code without virtual module (display only, no execution)
const decodedCode = atob(props.inlineCode)
sourceCode.value = decodedCode
if (demoRoot.value) {
// Show a message that preview is not available
const noticeEl = document.createElement('div')
noticeEl.className = 'inline-demo-notice'
noticeEl.innerHTML = `
<p><strong>Preview not available</strong></p>
<p>Inline demo "${props.name}" requires virtual module support.</p>
<p>Check the "Code" tab to see the source.</p>
`
demoRoot.value.appendChild(noticeEl)
}
} else {
// Dynamically import demo component from file
const demoModule = await import(`../demos/${props.name}.tsx`)
const DemoComponent = demoModule.default || demoModule[props.name]
if (!DemoComponent) {
throw new Error(`Demo component "${props.name}" not found`)
}
// Mount React component
if (demoRoot.value) {
reactRoot.value = createRoot(demoRoot.value)
reactRoot.value.render(DemoComponent)
// Extract source code
try {
const rawModule = await import(`../demos/${props.name}.tsx?raw`)
sourceCode.value = rawModule.default || rawModule
} catch {
sourceCode.value = '// Source code not available'
}
}
}
} catch (err) {
@@ -250,6 +297,43 @@ const switchTab = (tab: 'preview' | 'code') => {
height: auto !important;
}
/* Inline demo notice (fallback mode) */
:deep(.inline-demo-notice) {
padding: 20px;
background: var(--vp-c-warning-soft);
border-radius: 4px;
text-align: center;
}
:deep(.inline-demo-notice p) {
margin: 8px 0;
color: var(--vp-c-text-2);
}
:deep(.inline-demo-notice strong) {
color: var(--vp-c-warning-1);
}
/* Inline demo preview styles */
:deep(.inline-demo-preview) {
padding: 16px;
background: var(--vp-c-bg-soft);
border-radius: 4px;
}
:deep(.inline-demo-preview button) {
margin: 4px;
padding: 8px 16px;
border-radius: 4px;
border: 1px solid var(--vp-c-border);
background: var(--vp-c-bg);
cursor: pointer;
}
:deep(.inline-demo-preview button:hover) {
background: var(--vp-c-bg-soft);
}
/* Responsive */
@media (max-width: 768px) {
.demo-header {

View File

@@ -1,11 +1,13 @@
<script setup lang="ts">
import { computed, ref, onMounted } from 'vue'
import { computed, ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
import { useData } from 'vitepress'
const { site, lang, page } = useData()
const isOpen = ref(false)
const switcherRef = ref<HTMLElement>()
const buttonRef = ref<HTMLElement>()
const dropdownPosition = ref({ top: 0, left: 0 })
// Get available locales from VitePress config
const locales = computed(() => {
@@ -36,46 +38,101 @@ const getAltLink = (localeCode: string) => {
const switchLanguage = (localeCode: string) => {
const altLink = getAltLink(localeCode)
isOpen.value = false
window.location.href = altLink
}
// Close dropdown when clicking outside
onMounted(() => {
const handleClickOutside = (e: MouseEvent) => {
if (switcherRef.value && !switcherRef.value.contains(e.target as Node)) {
isOpen.value = false
// Calculate dropdown position
const updatePosition = () => {
if (buttonRef.value) {
const rect = buttonRef.value.getBoundingClientRect()
const isMobile = window.innerWidth <= 768
if (isMobile) {
dropdownPosition.value = {
top: rect.bottom + 8,
left: 12
}
} else {
dropdownPosition.value = {
top: rect.bottom + 4,
left: rect.right - 150
}
}
}
}
const toggleDropdown = async () => {
isOpen.value = !isOpen.value
if (isOpen.value) {
await nextTick()
updatePosition()
}
}
// Close dropdown when clicking outside
const handleClickOutside = (e: MouseEvent) => {
if (switcherRef.value && !switcherRef.value.contains(e.target as Node)) {
isOpen.value = false
}
}
// Handle scroll to close dropdown
const handleScroll = () => {
if (isOpen.value) {
isOpen.value = false
}
}
onMounted(() => {
document.addEventListener('click', handleClickOutside)
window.addEventListener('scroll', handleScroll, true)
window.addEventListener('resize', updatePosition)
})
onBeforeUnmount(() => {
document.removeEventListener('click', handleClickOutside)
window.removeEventListener('scroll', handleScroll, true)
window.removeEventListener('resize', updatePosition)
})
</script>
<template>
<div ref="switcherRef" class="language-switcher">
<button
ref="buttonRef"
class="switcher-button"
:aria-expanded="isOpen"
aria-label="Switch language"
@click="isOpen = !isOpen"
@click.stop="toggleDropdown"
>
<span class="current-locale">{{ currentLocale?.label }}</span>
<span class="dropdown-icon" :class="{ open: isOpen }"></span>
</button>
<Transition name="fade">
<ul v-if="isOpen" class="locale-list">
<li v-for="locale in locales" :key="locale.code">
<button
class="locale-button"
:class="{ active: locale.code === lang }"
@click="switchLanguage(locale.code)"
>
<span class="locale-label">{{ locale.label }}</span>
<span v-if="locale.code === lang" class="check-icon"></span>
</button>
</li>
</ul>
</Transition>
<Teleport to="body">
<Transition name="fade">
<ul
v-if="isOpen"
class="locale-list"
:style="{
top: dropdownPosition.top + 'px',
left: dropdownPosition.left + 'px'
}"
>
<li v-for="locale in locales" :key="locale.code">
<button
class="locale-button"
:class="{ active: locale.code === lang }"
@click="switchLanguage(locale.code)"
>
<span class="locale-label">{{ locale.label }}</span>
<span v-if="locale.code === lang" class="check-icon"></span>
</button>
</li>
</ul>
</Transition>
</Teleport>
</div>
</template>
@@ -117,19 +174,19 @@ onMounted(() => {
transform: rotate(180deg);
}
/* Locale list - rendered at body level via Teleport */
.locale-list {
position: absolute;
top: calc(100% + 4px);
right: 0;
position: fixed;
min-width: 150px;
max-width: calc(100vw - 24px);
list-style: none;
margin: 0;
padding: 4px;
padding: 8px 0;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-border);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 100;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
z-index: 9999;
}
.locale-button {
@@ -137,14 +194,14 @@ onMounted(() => {
align-items: center;
justify-content: space-between;
width: 100%;
padding: 8px 12px;
padding: 10px 16px;
border: none;
border-radius: 4px;
background: transparent;
color: var(--vp-c-text-1);
font-size: 14px;
cursor: pointer;
transition: background 0.2s;
text-align: left;
}
.locale-button:hover {
@@ -173,14 +230,24 @@ onMounted(() => {
.fade-enter-from,
.fade-leave-to {
opacity: 0;
transform: translateY(-4px);
transform: translateY(-8px);
}
/* Responsive */
@media (max-width: 768px) {
.locale-list {
right: auto;
left: 0;
width: calc(100vw - 24px) !important;
max-width: 300px !important;
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25) !important;
}
.switcher-button {
padding: 8px 12px;
font-size: 13px;
}
.locale-button {
padding: 12px 16px;
}
}
</style>

View File

@@ -1275,23 +1275,199 @@ onUnmounted(() => {
}
/* ============================================
* Responsive
* Responsive (Standardized Breakpoints)
* Mobile: < 768px | Tablet: 768px-1024px | Desktop: > 1024px
* WCAG 2.1 AA: Touch targets min 44x44px
* ============================================ */
@media (max-width: 1100px) {
/* Tablet (768px - 1024px) */
@media (max-width: 1024px) {
.features-grid { grid-template-columns: repeat(2, 1fr); }
.hero-container { gap: 1.5rem; }
.hero-title { font-size: 2.25rem; }
.section-container { padding: 0 1.5rem; }
}
@media (max-width: 960px) {
.hero-container, .json-grid, .quickstart-layout { grid-template-columns: 1fr; text-align: center; }
.hero-subtitle { margin-left: auto; margin-right: auto; }
.hero-actions { justify-content: center; }
.hero-title { font-size: 2.5rem; }
.logic-panel { grid-template-columns: 1fr; }
.features-grid { grid-template-columns: 1fr; max-width: 420px; margin: 0 auto; }
.feature-card { text-align: center; }
.feature-icon-box { margin-left: auto; margin-right: auto; }
.quickstart-info { text-align: center; }
.qs-step { flex-direction: column; align-items: center; text-align: center; }
.cta-actions { flex-direction: column; }
/* Mobile (< 768px) */
@media (max-width: 768px) {
/* Hero Section - add extra padding-top to clear fixed nav (56px) */
.hero-section {
padding: 4.5rem 0 2rem;
background:
radial-gradient(ellipse 150% 100% at 50% 20%, var(--vp-c-brand-soft) 0%, transparent 60%),
var(--vp-c-bg);
}
.hero-container {
grid-template-columns: 1fr;
text-align: center;
padding: 0 12px;
gap: 2rem;
max-width: 100%;
box-sizing: border-box;
}
.hero-content { order: 1; }
.hero-visual { order: 2; }
.hero-title {
font-size: 1.875rem;
line-height: 1.2;
margin-bottom: 1rem;
}
.hero-subtitle {
font-size: 1rem;
margin-left: auto;
margin-right: auto;
margin-bottom: 1.75rem;
max-width: 100%;
}
.hero-actions {
justify-content: center;
flex-wrap: wrap;
gap: 0.75rem;
}
.btn-primary,
.btn-secondary {
min-height: 44px;
min-width: 44px;
padding: 0.75rem 1.25rem;
font-size: 0.9rem;
}
.hero-visual { min-height: 200px; }
/* Section Container */
.section-container {
padding: 0 12px;
max-width: 100%;
box-sizing: border-box;
}
/* Features Grid */
.features-section { padding: 3rem 0; }
.section-title {
font-size: 1.5rem;
margin-bottom: 2rem;
}
.features-grid {
grid-template-columns: 1fr;
max-width: 100%;
margin: 0 auto;
gap: 1rem;
}
.feature-card {
text-align: center;
padding: 1.5rem;
min-height: auto;
}
.feature-icon-box {
width: 48px;
height: 48px;
margin: 0 auto 1rem;
}
.feature-card h3 { font-size: 1.05rem; }
.feature-card p { font-size: 0.9rem; }
/* Pipeline Section */
.pipeline-section { padding: 3rem 0; }
.section-header h2 { font-size: 1.5rem; }
.section-header p { font-size: 0.9rem; }
.pipeline-card {
padding: 1.25rem;
max-width: 100%;
box-sizing: border-box;
overflow-x: hidden;
}
.pipeline-flow {
flex-direction: column;
gap: 1rem;
margin-bottom: 2rem;
}
.stage-node { flex-direction: row; justify-content: flex-start; gap: 1rem; }
.stage-icon { width: 44px; height: 44px; min-width: 44px; }
.stage-info { text-align: left; }
.stage-info h4 { margin-top: 0; font-size: 0.9rem; }
.stage-info p { font-size: 0.8rem; }
.logic-panel { grid-template-columns: 1fr; gap: 1rem; }
.law-content, .log-content { padding: 1rem; font-size: 0.8rem; min-height: 60px; }
/* JSON Section */
.json-section { padding: 0; overflow-x: hidden; }
.json-grid {
grid-template-columns: 1fr;
text-align: center;
padding: 3rem 12px;
gap: 2rem;
max-width: 100%;
box-sizing: border-box;
}
.json-text h2 { font-size: 1.5rem; }
.json-text p { font-size: 0.95rem; }
.json-benefits { text-align: left; }
.json-benefits li { font-size: 0.9rem; }
.json-code {
padding: 1rem;
max-width: 100%;
box-sizing: border-box;
overflow-x: auto;
}
.json-pre { font-size: 0.75rem; }
/* Quick Start */
.quickstart-section { padding: 3rem 0; }
.quickstart-layout {
grid-template-columns: 1fr;
text-align: center;
gap: 2rem;
}
.quickstart-info { order: 2; }
.quickstart-terminal { order: 1; }
.qs-step {
flex-direction: column;
align-items: center;
text-align: center;
gap: 0.75rem;
}
.qs-step-num { width: 44px; height: 44px; min-width: 44px; }
.qs-terminal-body { min-height: 200px; padding: 1rem; }
.qs-terminal-header { padding: 0.4rem 0.6rem; }
.qs-tab { min-height: 36px; padding: 0.25rem 0.5rem; }
/* CTA Section */
.cta-section { padding: 3rem 0; }
.cta-card {
padding: 2rem 1rem;
margin: 0 12px;
max-width: calc(100% - 24px);
box-sizing: border-box;
}
.cta-card h2 { font-size: 1.5rem; }
.cta-card p { font-size: 0.95rem; margin-bottom: 1.5rem; }
.cta-actions {
flex-direction: column;
gap: 0.75rem;
width: 100%;
}
.cta-actions .btn-primary,
.cta-actions .btn-outline,
.cta-actions .btn-secondary {
width: 100%;
justify-content: center;
min-height: 48px;
}
}
/* Small Mobile (< 480px) */
@media (max-width: 480px) {
.hero-title { font-size: 1.5rem; }
.hero-actions { flex-direction: column; width: 100%; }
.hero-actions .btn-primary,
.hero-actions .btn-secondary {
width: 100%;
justify-content: center;
}
.section-title { font-size: 1.25rem; }
.pipeline-flow { gap: 0.75rem; }
.stage-icon { width: 40px; height: 40px; border-radius: 10px; }
.json-text h2 { font-size: 1.25rem; }
.cta-card { padding: 1.5rem 1rem; }
.cta-card h2 { font-size: 1.25rem; }
}
</style>