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>

View File

@@ -35,10 +35,16 @@ export const STATUS_COLORS = {
const STORAGE_KEY_THEME = 'ccw-theme'
const STORAGE_KEY_COLOR_MODE = 'ccw-color-mode'
/**
* Check if running in browser environment
*/
const isBrowser = typeof window !== 'undefined' && typeof localStorage !== 'undefined'
/**
* Get current theme from localStorage or default
*/
export function getCurrentTheme(): ThemeName {
if (!isBrowser) return 'blue'
const saved = localStorage.getItem(STORAGE_KEY_THEME)
if (saved && saved in THEME_COLORS) {
return saved as ThemeName
@@ -50,6 +56,7 @@ export function getCurrentTheme(): ThemeName {
* Check if dark mode is active
*/
export function isDarkMode(): boolean {
if (!isBrowser) return false
const mode = localStorage.getItem(STORAGE_KEY_COLOR_MODE) || 'auto'
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
return mode === 'dark' || (mode === 'auto' && prefersDark)

View File

@@ -0,0 +1,130 @@
/**
* Vite plugin for handling inline demo blocks as virtual modules.
* This allows React JSX code embedded in markdown :::demo blocks to be
* dynamically compiled and executed as proper React components.
*/
import type { Plugin } from 'vite'
import type { DemoBlockMeta } from './markdownTransform'
// Global registry for inline demos (populated during markdown transform)
const inlineDemoRegistry = new Map<string, { code: string; name: string }>()
// Virtual module prefix
const VIRTUAL_PREFIX = 'virtual:inline-demo:'
const VIRTUAL_PREFIX_FULL = '\0' + VIRTUAL_PREFIX
/**
* Register an inline demo during markdown transformation.
* Returns a virtual module ID that can be imported.
*/
export function registerInlineDemo(
demoId: string,
code: string,
name: string
): string {
inlineDemoRegistry.set(demoId, { code, name })
return VIRTUAL_PREFIX + demoId
}
/**
* Get registered inline demo by ID
*/
export function getInlineDemo(demoId: string) {
return inlineDemoRegistry.get(demoId)
}
/**
* Clear all registered demos (useful for rebuilds)
*/
export function clearInlineDemos() {
inlineDemoRegistry.clear()
}
/**
* Vite plugin that resolves virtual inline-demo modules.
* These modules contain the React/JSX code from markdown :::demo blocks.
*/
export function inlineDemoPlugin(): Plugin {
return {
name: 'vitepress-inline-demo-plugin',
enforce: 'pre',
resolveId(id) {
// Handle virtual module resolution
if (id.startsWith(VIRTUAL_PREFIX)) {
return VIRTUAL_PREFIX_FULL + id.slice(VIRTUAL_PREFIX.length)
}
return null
},
load(id) {
// Load virtual module content
if (id.startsWith(VIRTUAL_PREFIX_FULL)) {
const demoId = id.slice(VIRTUAL_PREFIX_FULL.length)
const demo = inlineDemoRegistry.get(demoId)
if (!demo) {
return `export default function MissingDemo() {
return React.createElement('div', { className: 'demo-error' },
'Demo not found: ${demoId}'
)
}`
}
// Wrap the inline code in an ESM module
// The code should export a React component
return `
import React from 'react';
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
// User's inline demo code:
${demo.code}
// Auto-export fallback if no default export
export default ${demo.name} || (() => React.createElement('div', null, 'Demo component "${demo.name}" not found'));
`
}
return null
},
// Handle HMR for inline demos
handleHotUpdate({ file, server }) {
// When markdown files change, clear the registry
if (file.endsWith('.md')) {
clearInlineDemos()
server.ws.send({
type: 'full-reload',
path: file
})
}
}
}
}
/**
* Transform inline demo code to ensure it has proper exports.
* This wraps bare JSX in a function component if needed.
*/
export function wrapDemoCode(code: string, componentName: string): string {
// If code already has export, return as-is
if (code.includes('export ')) {
return code
}
// If code is just JSX (starts with <), wrap it
const trimmedCode = code.trim()
if (trimmedCode.startsWith('<') || trimmedCode.startsWith('React.createElement')) {
return `
function ${componentName}() {
return (
${trimmedCode}
);
}
export default ${componentName};
`
}
// Otherwise return as-is and hope it defines the component
return code + `\nexport default ${componentName};`
}

View File

@@ -82,25 +82,61 @@ onBeforeUnmount(() => {
<template #nav-bar-content-after>
<div class="nav-extensions">
<DocSearch />
<DarkModeToggle />
<ThemeSwitcher />
<LanguageSwitcher />
<DocSearch class="nav-item-always" />
<DarkModeToggle class="nav-item-desktop" />
<ThemeSwitcher class="nav-item-desktop" />
<LanguageSwitcher class="nav-item-desktop" />
</div>
</template>
</DefaultTheme.Layout>
</template>
<style scoped>
/* ============================================
* Container Query Context Definitions
* Enables component-level responsive design
* ============================================ */
/* Set container context on layout root */
:deep(.Layout) {
container-type: inline-size;
container-name: layout;
}
/* Sidebar container context */
:deep(.VPSidebar) {
container-type: inline-size;
container-name: sidebar;
}
/* Main content container context */
:deep(.VPContent) {
container-type: inline-size;
container-name: content;
}
/* Document outline container context */
:deep(.VPDocOutline) {
container-type: inline-size;
container-name: outline;
}
/* Navigation container context */
:deep(.VPNav) {
container-type: inline-size;
container-name: nav;
}
/* Hero section with fluid spacing */
.hero-extensions {
margin-top: 40px;
margin-top: var(--spacing-fluid-lg);
text-align: center;
}
.hero-stats {
display: flex;
justify-content: center;
gap: 48px;
gap: var(--spacing-fluid-xl);
flex-wrap: wrap;
}
@@ -109,23 +145,23 @@ onBeforeUnmount(() => {
}
.stat-value {
font-size: 32px;
font-size: clamp(1.5rem, 1.25rem + 1.25vw, 2rem);
font-weight: 700;
color: var(--vp-c-primary);
}
.stat-label {
font-size: 14px;
font-size: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
color: var(--vp-c-text-2);
margin-top: 4px;
margin-top: var(--spacing-fluid-xs);
}
.nav-extensions {
display: flex;
align-items: center;
gap: 12px;
gap: var(--spacing-fluid-sm);
margin-left: auto;
padding-left: 16px;
padding-left: var(--spacing-fluid-sm);
}
.nav-logo {
@@ -156,22 +192,64 @@ onBeforeUnmount(() => {
top: 0;
}
/* Mobile overrides now handled by fluid spacing variables */
/* Container queries in mobile.css provide additional responsiveness */
/* Mobile-specific styles */
@media (max-width: 768px) {
.hero-extensions {
margin-top: 1rem;
padding: 0 12px;
max-width: 100vw;
box-sizing: border-box;
overflow-x: hidden;
}
.hero-stats {
gap: 24px;
gap: 1rem;
}
.stat-value {
font-size: 24px;
font-size: 1.5rem;
}
.stat-label {
font-size: 12px;
font-size: 0.75rem;
}
.nav-extensions {
gap: 8px;
padding-left: 8px;
gap: 0.25rem;
padding-left: 0.25rem;
overflow: visible !important;
}
/* Hide desktop-only nav items on mobile */
.nav-item-desktop {
display: none !important;
}
/* Keep always-visible items */
.nav-item-always {
display: flex !important;
}
/* Ensure nav bar allows dropdown overflow */
:deep(.VPNavBar) {
overflow: visible !important;
}
:deep(.VPNavBar .content) {
overflow: visible !important;
}
/* Fix dropdown positioning for mobile */
:deep(.VPNavBarMenuGroup .items) {
position: fixed !important;
left: 12px !important;
right: 12px !important;
top: 56px !important;
max-width: none !important;
width: auto !important;
}
}
</style>

View File

@@ -1,10 +1,15 @@
import type { MarkdownTransformContext } from 'vitepress'
const demoBlockRE = /:::\s*demo\s+(.+?)\s*:::/g
// Multi-line demo block: :::demo Name #file ...code... :::
const demoBlockRE = /:::\s*demo\s+([^\n]+)\n([\s\S]*?)\n:::/g
// Single-line demo block: :::demo Name #file :::
const demoBlockSingleRE = /:::\s*demo\s+(\S+)\s*(#\S+)?\s*:::/g
export interface DemoBlockMeta {
name: string
file?: string
code?: string
height?: string
expandable?: boolean
showCode?: boolean
@@ -15,47 +20,71 @@ export function transformDemoBlocks(
code: string,
ctx: MarkdownTransformContext
): string {
return code.replace(demoBlockRE, (match, content) => {
const meta = parseDemoBlock(content)
const demoId = `demo-${ctx.path.replace(/[^a-z0-9]/gi, '-')}-${Math.random().toString(36).slice(2, 8)}`
// First handle multi-line demo blocks with inline code
let result = code.replace(demoBlockRE, (match, headerLine, codeContent) => {
const meta = parseDemoHeader(headerLine)
const demoId = `demo-${ctx.path.replace(/[^a-z0-9]/gi, '-')}-${meta.name}-${Date.now().toString(36)}`
const props = [
`id="${demoId}"`,
`name="${meta.name}"`,
meta.file ? `file="${meta.file}"` : '',
meta.height ? `height="${meta.height}"` : '',
meta.expandable === false ? ':expandable="false"' : ':expandable="true"',
meta.showCode === false ? ':show-code="false"' : ':show-code="true"',
meta.expandable === false ? ':expandable="false"' : '',
meta.showCode === false ? ':show-code="false"' : '',
meta.title ? `title="${meta.title}"` : ''
].filter(Boolean).join(' ')
// Return a simple comment placeholder - the inline code will be ignored
// This avoids Vue parsing issues with JSX in markdown
return `<DemoContainer ${props} />`
})
// Then handle single-line demo blocks (file references only)
result = result.replace(demoBlockSingleRE, (match, name, fileRef) => {
const demoId = `demo-${ctx.path.replace(/[^a-z0-9]/gi, '-')}-${name}-${Date.now().toString(36)}`
const file = fileRef ? fileRef.slice(1) : undefined
const props = [
`id="${demoId}"`,
`name="${name}"`,
file ? `file="${file}"` : '',
':expandable="true"',
':show-code="true"'
].filter(Boolean).join(' ')
return `<DemoContainer ${props} />`
})
return result
}
function parseDemoBlock(content: string): DemoBlockMeta {
const lines = content.trim().split('\n')
const firstLine = lines[0] || ''
function parseDemoHeader(headerLine: string): DemoBlockMeta {
const parts = headerLine.trim().split(/\s+/)
const name = parts[0] || ''
const file = parts.find(p => p.startsWith('#'))?.slice(1)
// Parse: name or # file
const [name, ...rest] = firstLine.split(/\s+/)
const file = rest.find(l => l.startsWith('#'))?.slice(1)
// Extract props from remaining parts
const props: Record<string, string> = {}
for (const part of parts.slice(1)) {
if (part.includes(':')) {
const [key, value] = part.split(':', 2)
props[key] = value
}
}
return {
name: name.trim(),
name,
file,
height: extractProp(lines, 'height'),
expandable: extractProp(lines, 'expandable') !== 'false',
showCode: extractProp(lines, 'showCode') !== 'false',
title: extractProp(lines, 'title')
height: props.height,
expandable: props.expandable !== 'false',
showCode: props.showCode !== 'false',
title: props.title
}
}
function extractProp(lines: string[], prop: string): string | undefined {
const line = lines.find(l => l.trim().startsWith(`${prop}:`))
return line?.split(':', 2)[1]?.trim()
}
// VitePress markdown configuration hook
export function markdownTransformSetup() {
return {

View File

@@ -85,11 +85,26 @@
.VPContent:has(.pro-home) {
padding: 0 !important;
margin: 0 !important;
max-width: 100% !important;
max-width: 100vw !important;
overflow-x: hidden !important;
}
.Layout:has(.pro-home) {
max-width: 100% !important;
max-width: 100vw !important;
overflow-x: hidden !important;
}
/* Ensure VPNav doesn't cause overflow */
.Layout:has(.pro-home) .VPNav {
max-width: 100vw;
}
/* Ensure VPFooter doesn't cause overflow */
.Layout:has(.pro-home) .VPFooter {
max-width: 100vw;
padding-left: 1rem;
padding-right: 1rem;
box-sizing: border-box;
}
/* ProfessionalHome component full width */
@@ -98,6 +113,8 @@
padding: 0 !important;
margin: 0 !important;
width: 100% !important;
overflow-x: hidden !important;
box-sizing: border-box !important;
}
/* Ensure all sections extend to full width */
@@ -111,6 +128,8 @@
margin: 0 !important;
padding-left: 0 !important;
padding-right: 0 !important;
box-sizing: border-box !important;
max-width: 100vw !important;
}
.VPHomeHero {

View File

@@ -1,9 +1,250 @@
/**
* Mobile-Responsive Styles
* Breakpoints: 320px-768px (mobile), 768px-1024px (tablet), 1024px+ (desktop)
* Uses CSS custom properties from variables.css: --bp-mobile, --bp-tablet, --bp-desktop
* WCAG 2.1 AA compliant
*/
/* ============================================
* Container Query Support
* Enable component-level responsive design
* Fallback: Uses @supports to provide media query fallbacks
* ============================================ */
/* Fallback for browsers without container query support */
@supports not (container-type: inline-size) {
/* Sidebar fallback */
.VPSidebar {
width: 100%;
max-width: 320px;
}
@media (min-width: 768px) {
.VPSidebar {
width: var(--vp-sidebar-width, 272px);
max-width: none;
}
}
/* Content fallback */
.VPContent {
padding: 16px;
}
@media (min-width: 768px) {
.VPContent {
padding: 24px;
}
}
@media (min-width: 1024px) {
.VPContent {
padding: 32px 48px;
}
}
/* Outline fallback */
.VPDocOutline {
display: none;
}
@media (min-width: 768px) {
.VPDocOutline {
display: block;
width: 200px;
}
}
@media (min-width: 1024px) {
.VPDocOutline {
width: 256px;
}
}
}
/* Container Query Rules (modern browsers) */
@supports (container-type: inline-size) {
/* Sidebar Container Queries */
@container sidebar (max-width: 480px) {
.VPSidebar .group {
padding: 12px 16px;
}
.VPSidebar .title {
font-size: 13px;
}
}
@container sidebar (min-width: 480px) and (max-width: 768px) {
.VPSidebar .group {
padding: 16px 20px;
}
.VPSidebar .title {
font-size: 14px;
}
}
@container sidebar (min-width: 768px) {
.VPSidebar .group {
padding: 16px 24px;
}
.VPSidebar .title {
font-size: 14px;
font-weight: 600;
}
}
/* Content Container Queries */
@container content (max-width: 640px) {
.VPDoc .content-container {
padding: 0 var(--spacing-fluid-sm);
}
.vp-doc h1 {
font-size: 1.75rem;
}
.vp-doc h2 {
font-size: 1.375rem;
}
.vp-doc pre {
font-size: 12px;
padding: 12px 16px;
}
}
@container content (min-width: 640px) and (max-width: 960px) {
.VPDoc .content-container {
padding: 0 var(--spacing-fluid-md);
}
.vp-doc h1 {
font-size: 2rem;
}
.vp-doc h2 {
font-size: 1.5rem;
}
.vp-doc pre {
font-size: 13px;
padding: 16px 20px;
}
}
@container content (min-width: 960px) {
.VPDoc .content-container {
padding: 0 var(--spacing-fluid-lg);
}
.vp-doc h1 {
font-size: 2.25rem;
}
.vp-doc h2 {
font-size: 1.625rem;
}
.vp-doc pre {
font-size: 14px;
padding: 20px 24px;
}
}
/* Outline Container Queries */
@container outline (max-width: 200px) {
.VPDocOutline .outline-link {
font-size: 11px;
padding: 3px 8px;
}
.VPDocOutline .outline-marker {
width: 2px;
}
}
@container outline (min-width: 200px) and (max-width: 280px) {
.VPDocOutline .outline-link {
font-size: 12px;
padding: 4px 10px;
}
.VPDocOutline .outline-marker {
width: 3px;
}
}
@container outline (min-width: 280px) {
.VPDocOutline .outline-link {
font-size: 13px;
padding: 4px 12px;
}
.VPDocOutline .outline-marker {
width: 4px;
}
}
/* Navigation Container Queries */
@container nav (max-width: 640px) {
.VPNavBar {
padding: 0 12px;
}
.VPNavBar .nav-extensions {
gap: 8px;
}
}
@container nav (min-width: 640px) and (max-width: 960px) {
.VPNavBar {
padding: 0 20px;
}
.VPNavBar .nav-extensions {
gap: 12px;
}
}
@container nav (min-width: 960px) {
.VPNavBar {
padding: 0 32px;
}
.VPNavBar .nav-extensions {
gap: 16px;
}
}
}
/* Generic Container-Responsive Utility Class */
@container (max-width: 480px) {
.container-responsive {
padding: 0 var(--spacing-fluid-xs);
}
}
@container (min-width: 480px) and (max-width: 768px) {
.container-responsive {
padding: 0 var(--spacing-fluid-sm);
}
}
@container (min-width: 768px) and (max-width: 1024px) {
.container-responsive {
padding: 0 var(--spacing-fluid-md);
}
}
@container (min-width: 1024px) {
.container-responsive {
padding: 0 var(--spacing-fluid-lg);
}
}
/* ============================================
* Mobile First Approach
* ============================================ */
@@ -18,32 +259,218 @@
/* Container */
.container {
padding: 0 16px;
padding: 0 12px;
}
/* Navigation */
/* Navigation - ensure hamburger menu is visible */
.VPNav {
height: 56px;
overflow: visible !important;
}
.VPNavBar {
padding: 0 16px;
padding: 0 12px;
overflow: visible !important;
}
/* Sidebar */
/* Navigation bar content wrapper */
.VPNavBar .content {
overflow: visible !important;
}
/* Show hamburger menu button on mobile */
.VPNavBar .VPNavBarHamburger {
display: flex !important;
}
/* Hide desktop nav links on mobile, use hamburger menu */
.VPNavBar .VPNavBarMenu {
display: none;
}
/* Ensure nav title is visible */
.VPNavBar .VPNavBarTitle {
flex: 1;
}
/* Reduce nav-extensions gap on mobile */
.VPNavBar .nav-extensions {
gap: 0.25rem;
padding-left: 0.25rem;
overflow: visible !important;
}
/* Hide non-essential nav items on mobile */
.nav-extensions .nav-item-desktop {
display: none !important;
}
/* Style search button for mobile */
.nav-extensions .DocSearch {
min-height: 40px;
}
/* Ensure VitePress social link is hidden on mobile (uses sidebar) */
.VPNavBar .VPNavBarSocialLinks {
display: none;
}
/* Fix dropdown menus overflow on mobile */
.VPNavBar .VPNavBarMenuGroup {
position: relative;
overflow: visible !important;
}
.VPNavBar .VPNavBarMenuGroup .items {
position: absolute;
top: 100%;
left: 0;
min-width: 180px;
max-width: calc(100vw - 24px);
max-height: 60vh;
overflow-y: auto;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
z-index: 100;
padding: 8px 0;
}
/* Language switcher dropdown fix */
.language-switcher {
position: relative !important;
overflow: visible !important;
}
.language-switcher .locale-list {
position: fixed !important;
top: auto !important;
left: 50% !important;
transform: translateX(-50%) !important;
right: auto !important;
min-width: 200px !important;
max-width: calc(100vw - 24px) !important;
z-index: 1000 !important;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2) !important;
}
/* Sidebar - fix display issues */
.VPSidebar {
width: 100%;
max-width: 320px;
width: 100% !important;
max-width: 320px !important;
padding-top: 0 !important;
top: 56px !important;
height: calc(100vh - 56px) !important;
max-height: calc(100vh - 56px) !important;
overflow: visible !important;
position: fixed !important;
left: 0 !important;
z-index: 40 !important;
background: var(--vp-c-bg) !important;
transition: transform 0.25s ease !important;
}
/* Content */
/* Sidebar when open */
.VPSidebar.open,
.sidebar-open .VPSidebar {
transform: translateX(0) !important;
}
/* Sidebar when closed */
.VPSidebar:not(.open) {
transform: translateX(-100%) !important;
}
/* Sidebar nav container */
.VPSidebar .VPSidebarNav {
padding: 12px 0;
height: 100%;
min-height: auto;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
/* Sidebar groups */
.VPSidebar .VPSidebarGroup {
padding: 8px 16px;
}
/* Sidebar items */
.VPSidebar .VPSidebarItem {
padding: 6px 0;
}
/* Ensure sidebar links are properly sized */
.VPSidebar .link {
padding: 8px 12px;
display: block;
}
/* Local nav for mobile */
.VPLocalNav {
display: flex !important;
position: sticky;
top: 56px;
z-index: 10;
}
/* Sidebar curtain/backdrop */
.VPSidebar curtain,
.VPSidebar .curtain {
display: none;
}
/* Sidebar scroll container */
.VPSidebar .sidebar-container,
.VPSidebar nav {
height: 100%;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
/* Make sure all sidebar content is visible */
.VPSidebar .group {
margin: 0;
padding: 12px 16px;
}
.VPSidebar .title {
font-size: 14px;
font-weight: 600;
padding: 4px 0;
color: var(--vp-c-text-1);
}
/* Sidebar text styling */
.VPSidebar .text {
font-size: 14px;
line-height: 1.5;
padding: 6px 12px;
}
/* Ensure nested items are visible */
.VPSidebar .items {
padding: 0;
}
/* Backdrop for sidebar */
.VPBackdrop {
position: fixed;
inset: 0;
top: 56px;
background: rgba(0, 0, 0, 0.5);
z-index: 39;
}
/* Content - reduce padding for better space usage */
.VPContent {
padding: 16px;
padding: 12px;
}
/* Doc content adjustments */
/* Doc content adjustments - reduce padding */
.VPDoc .content-container {
padding: 0 16px;
padding: 0 12px;
}
/* Hide outline on mobile */
@@ -53,7 +480,7 @@
/* Hero Section */
.VPHomeHero {
padding: 40px 16px;
padding: 40px 12px;
}
.VPHomeHero h1 {
@@ -65,14 +492,14 @@
font-size: 14px;
}
/* Code Blocks */
/* Code Blocks - reduce margins */
div[class*='language-'] {
margin: 12px -16px;
margin: 12px -12px;
border-radius: 0;
}
div[class*='language-'] pre {
padding: 12px 16px;
padding: 12px;
font-size: 12px;
}
@@ -319,6 +746,230 @@
}
}
/* ============================================
* ProfessionalHome Component - Mobile Optimizations
* ============================================ */
@media (max-width: 768px) {
/* Root level overflow prevention */
html, body {
overflow-x: hidden;
max-width: 100vw;
}
/* VitePress Layout container fix */
.Layout {
max-width: 100vw;
overflow-x: hidden;
}
/* VPContent container fix */
.VPContent {
max-width: 100vw;
overflow-x: hidden;
padding: 0 !important;
}
/* Hero extensions in Layout.vue - add proper padding */
.hero-extensions {
padding: 0 12px;
box-sizing: border-box;
max-width: 100vw;
overflow-x: hidden;
}
.hero-stats {
gap: 1rem;
}
/* ProfessionalHome - Hero Section */
.pro-home .hero-section {
min-height: auto;
padding-top: 4.5rem; /* Clear fixed nav (56px) */
padding-bottom: 1.5rem;
}
/* Prevent horizontal scroll */
.pro-home {
overflow-x: hidden;
max-width: 100vw;
}
.pro-home .hero-container {
max-width: 100%;
padding-left: 12px;
padding-right: 12px;
box-sizing: border-box;
}
/* Fix section containers */
.pro-home .section-container {
max-width: 100%;
box-sizing: border-box;
padding-left: 12px;
padding-right: 12px;
}
/* ProfessionalHome - Feature Cards */
.pro-home .feature-card {
border-radius: 12px;
touch-action: manipulation;
}
.pro-home .feature-card:active {
transform: scale(0.98);
transition: transform 0.1s ease;
}
/* ProfessionalHome - Pipeline Animation */
.pro-home .cadence-track {
margin: 1rem 0 2rem;
}
.pro-home .tick-node {
width: 12px;
height: 12px;
min-width: 12px;
}
/* ProfessionalHome - Terminal Window */
.pro-home .terminal-window,
.pro-home .qs-terminal-window {
font-size: 0.75rem;
border-radius: 8px;
}
.pro-home .terminal-header,
.pro-home .qs-terminal-header {
padding: 0.5rem 0.75rem;
}
/* ProfessionalHome - Code Blocks */
.pro-home .json-code {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
margin: 0 -1rem;
border-radius: 0;
border-left: none;
border-right: none;
}
/* ProfessionalHome - Buttons touch targets */
.pro-home .btn-primary,
.pro-home .btn-secondary,
.pro-home .btn-outline,
.pro-home .btn-ghost {
min-height: 44px;
min-width: 44px;
touch-action: manipulation;
}
/* ProfessionalHome - CTA Section */
.pro-home .cta-card {
margin: 0 0.5rem;
border-radius: 16px;
max-width: calc(100% - 1rem);
box-sizing: border-box;
}
/* ProfessionalHome - Animation adjustments */
.pro-home .reveal-text,
.pro-home .reveal-card,
.pro-home .reveal-slide {
opacity: 1;
transform: none;
transition: none;
}
/* ProfessionalHome - Stage nodes in pipeline */
.pro-home .stage-node {
touch-action: manipulation;
}
/* Quick Start Section - prevent overflow */
.pro-home .quickstart-section {
padding: 3rem 0;
max-width: 100vw;
overflow-x: hidden;
}
.pro-home .quickstart-layout {
padding: 0 12px;
max-width: 100%;
box-sizing: border-box;
}
.pro-home .quickstart-info,
.pro-home .quickstart-terminal {
max-width: 100%;
box-sizing: border-box;
}
/* Ensure all text content wraps properly */
.pro-home .json-text,
.pro-home .json-benefits,
.pro-home .qs-step-content {
max-width: 100%;
box-sizing: border-box;
word-wrap: break-word;
overflow-wrap: break-word;
}
/* Features section overflow fix */
.pro-home .features-section {
max-width: 100vw;
overflow-x: hidden;
padding: 3rem 0;
}
/* Pipeline section overflow fix */
.pro-home .pipeline-section {
max-width: 100vw;
overflow-x: hidden;
}
}
/* ProfessionalHome - Tablet Optimizations */
@media (min-width: 768px) and (max-width: 1024px) {
.pro-home .hero-section {
padding: 3rem 0 2.5rem;
}
.pro-home .hero-title {
font-size: 2.25rem;
}
.pro-home .features-grid {
gap: 1.25rem;
}
.pro-home .json-grid {
gap: 2rem;
padding: 3rem 1.5rem;
}
}
/* ProfessionalHome - Small Mobile (< 480px) */
@media (max-width: 480px) {
.pro-home .hero-badge {
font-size: 0.7rem;
padding: 0.2rem 0.5rem;
}
.pro-home .section-title {
font-size: 1.25rem;
}
.pro-home .pipeline-card {
padding: 1rem;
}
/* Ensure touch targets */
.pro-home .btn-primary,
.pro-home .btn-secondary {
padding: 0.875rem 1rem;
}
}
/* ============================================
* Dark Mode Specific
* ============================================ */

View File

@@ -89,7 +89,7 @@
--vp-font-size-lg: 18px;
--vp-font-size-xl: 20px;
/* Spacing */
/* Spacing (Fixed) */
--vp-spacing-xs: 0.25rem; /* 4px */
--vp-spacing-sm: 0.5rem; /* 8px */
--vp-spacing-md: 1rem; /* 16px */
@@ -97,6 +97,25 @@
--vp-spacing-xl: 2rem; /* 32px */
--vp-spacing-2xl: 3rem; /* 48px */
/* Fluid Spacing (Responsive with clamp())
* Scales smoothly between viewport widths
* Usage: padding: var(--spacing-fluid-md);
*/
--spacing-fluid-xs: clamp(0.25rem, 0.2rem + 0.25vw, 0.375rem); /* 4-6px */
--spacing-fluid-sm: clamp(0.5rem, 0.4rem + 0.5vw, 0.75rem); /* 8-12px */
--spacing-fluid-md: clamp(0.75rem, 0.6rem + 0.75vw, 1.25rem); /* 12-20px */
--spacing-fluid-lg: clamp(1rem, 0.8rem + 1vw, 1.75rem); /* 16-28px */
--spacing-fluid-xl: clamp(1.5rem, 1.2rem + 1.5vw, 2.5rem); /* 24-40px */
--spacing-fluid-2xl: clamp(2rem, 1.5rem + 2.5vw, 3.5rem); /* 32-56px */
/* Container Query Names
* Usage: container-name: var(--container-sidebar);
*/
--container-sidebar: sidebar;
--container-content: content;
--container-outline: outline;
--container-nav: nav;
/* Border Radius */
--vp-radius-sm: 0.25rem; /* 4px */
--vp-radius-md: 0.375rem; /* 6px */
@@ -122,6 +141,19 @@
--vp-z-index-fixed: 50;
--vp-z-index-modal: 100;
--vp-z-index-toast: 200;
/* Responsive Breakpoints (VitePress standard) */
--bp-mobile: 768px; /* Mobile: < 768px */
--bp-tablet: 1024px; /* Tablet: 768px - 1024px */
--bp-desktop: 1440px; /* Desktop: > 1024px, large: > 1440px */
/* Container Query Breakpoints
* Aligned with media query breakpoints for consistency
*/
--container-bp-sm: 480px; /* Small container */
--container-bp-md: 768px; /* Medium container */
--container-bp-lg: 1024px; /* Large container */
--container-bp-xl: 1280px; /* Extra large container */
}
/* ============================================