diff --git a/docs/.vitepress/tests/pages-build.test.mjs b/docs/.vitepress/tests/pages-build.test.mjs index 6ce2c41..6818b6a 100644 --- a/docs/.vitepress/tests/pages-build.test.mjs +++ b/docs/.vitepress/tests/pages-build.test.mjs @@ -14,6 +14,12 @@ const distRoot = path.resolve(docsRoot, '.vitepress', 'dist') const cachedLandingPages = new Map() +function extractLandingHeroSection(html) { + const heroMatch = html.match(/
/) + assert.ok(heroMatch, 'expected built homepage LandingHero section') + return heroMatch[0] +} + function readBuiltLandingPages(base = '/cpp-high-performance-guide/') { if (!cachedLandingPages.has(base)) { const result = spawnSync(process.execPath, [buildPagesScriptPath], { @@ -58,6 +64,8 @@ test('build:pages uses a cross-platform Node wrapper while preserving external b test('build:pages prefixes homepage landing links with the GitHub Pages base and .html leaf routes', () => { const { en, zh } = readBuiltLandingPages() + const enHero = extractLandingHeroSection(en) + const zhHero = extractLandingHeroSection(zh) for (const href of [ '/cpp-high-performance-guide/en/reference/', @@ -81,6 +89,20 @@ test('build:pages prefixes homepage landing links with the GitHub Pages base and assert.match(zh, new RegExp(`href="${href.replaceAll('/', '\\/')}"`)) } + assert.match(enHero, /href="\/cpp-high-performance-guide\/en\/getting-started\/quickstart(?:\.html)?"/) + assert.match(enHero, /href="\/cpp-high-performance-guide\/en\/academy\/"/) + assert.match(enHero, /href="\/cpp-high-performance-guide\/en\/academy\/module-atlas(?:\.html)?"/) + assert.match(enHero, /href="\/cpp-high-performance-guide\/en\/academy\/validation-doctrine(?:\.html)?"/) + assert.match(enHero, /href="\/cpp-high-performance-guide\/en\/architecture\/performance-methodology(?:\.html)?"/) + assert.doesNotMatch(enHero, /href="\/(?:en|zh)\//) + + assert.match(zhHero, /href="\/cpp-high-performance-guide\/zh\/getting-started\/quickstart(?:\.html)?"/) + assert.match(zhHero, /href="\/cpp-high-performance-guide\/zh\/academy\/"/) + assert.match(zhHero, /href="\/cpp-high-performance-guide\/zh\/academy\/module-atlas(?:\.html)?"/) + assert.match(zhHero, /href="\/cpp-high-performance-guide\/zh\/academy\/validation-doctrine(?:\.html)?"/) + assert.match(zhHero, /href="\/cpp-high-performance-guide\/zh\/architecture\/performance-methodology(?:\.html)?"/) + assert.doesNotMatch(zhHero, /href="\/(?:en|zh)\//) + for (const rawHref of [ 'href="/en/reference/"', 'href="/en/getting-started/quickstart"', diff --git a/docs/.vitepress/tests/theme-primitives.test.mjs b/docs/.vitepress/tests/theme-primitives.test.mjs index 716c76c..9ce7cd7 100644 --- a/docs/.vitepress/tests/theme-primitives.test.mjs +++ b/docs/.vitepress/tests/theme-primitives.test.mjs @@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url' import { createSSRApp, h } from 'vue' import { renderToString } from 'vue/server-renderer' import { compileScript, compileTemplate, parse } from '@vue/compiler-sfc' +import { transformWithEsbuild } from 'vite' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const themeDir = path.resolve(__dirname, '..', 'theme') @@ -21,20 +22,48 @@ function readAllCss() { let allCss = mainCss if (fs.existsSync(tokensDir)) { - const tokenFiles = fs.readdirSync(tokensDir).filter(f => f.endsWith('.css')) + const tokenFiles = fs.readdirSync(tokensDir).filter(file => file.endsWith('.css')) for (const file of tokenFiles) { - allCss += '\n' + fs.readFileSync(path.join(tokensDir, file), 'utf8') + allCss += `\n${fs.readFileSync(path.join(tokensDir, file), 'utf8')}` } } return allCss } -function extractHeroLinks(content) { - const linksMatch = content.match(/:links='\[(.*?)\]'/s) - assert.ok(linksMatch, 'expected SectionHero links array') +function extractLandingArray(content, propName) { + const match = content.match(new RegExp(`:${propName}='\\[(.*?)\\]'`, 's')) - return [...linksMatch[1].matchAll(/href:\s*"([^"]+)"/g)].map(([, href]) => href) + assert.ok(match, `expected LandingHero ${propName} array`) + return match[1] +} + +function extractLandingActions(content) { + return [...extractLandingArray(content, 'actions').matchAll(/href:\s*"([^"]+)"/g)].map(([, href]) => href) +} + +function extractLandingGuides(content) { + return [...extractLandingArray(content, 'guides').matchAll(/href:\s*"([^"]+)"/g)].map(([, href]) => href) +} + +function extractLandingMetricValues(content) { + return [...extractLandingArray(content, 'metrics').matchAll(/value:\s*"([^"]+)"/g)].map(([, value]) => value) +} + +function extractSectionIndexLinkGroups(content) { + return [...content.matchAll(//g)].map(([block]) => { + return [...block.matchAll(/href:\s*"([^"]+)"/g)].map(([, href]) => href) + }) +} + +function countModuleMapRows(content, startHeading, endHeading) { + const escapedStart = startHeading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + const escapedEnd = endHeading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + const sectionMatch = content.match(new RegExp(`## ${escapedStart}[\\s\\S]*?## ${escapedEnd}`)) + + assert.ok(sectionMatch, `expected section between ${startHeading} and ${endHeading}`) + + return [...sectionMatch[0].matchAll(/^\| (?:0[1-9]|[1-9][0-9]*)\./gm)].length } function normalizeHeroLink(href) { @@ -45,37 +74,36 @@ function normalizeHeroLink(href) { return href.replace(/^\/(?:en|zh)(?=\/|$)/, '') || '/' } -async function renderComponent(relativePath, props) { - const source = read(relativePath) - const { descriptor } = parse(source, { filename: relativePath }) - const script = compileScript(descriptor, { - id: relativePath, - inlineTemplate: false, - }) +async function loadSfcComponent(relativePath) { + const filename = path.join(themeDir, relativePath) + const source = fs.readFileSync(filename, 'utf8') + const { descriptor } = parse(source, { filename }) + const script = compileScript(descriptor, { id: relativePath }) const template = compileTemplate({ id: relativePath, + filename, source: descriptor.template.content, - filename: relativePath, - }) - const moduleSource = `${script.content - .replace(/^type\s+\w+\s*=\s*\{[\s\S]*?^\}\n*/gm, '') - .replace(/__props:\s*any/g, '__props') - .replace('export default', 'const component =')} -${template.code.replace('export function render', 'function render')} -component.render = render -export default component` - .replaceAll("'vue'", `'${vueModuleUrl}'`) - .replaceAll('"vue"', `"${vueModuleUrl}"`) - const dataUrl = `data:text/javascript;base64,${Buffer.from(moduleSource).toString('base64')}` - const { default: component } = await import(dataUrl) - const app = createSSRApp(component, props) - app.component('BaseAwareLink', { - props: ['href'], - setup(linkProps, { slots, attrs }) { - return () => h('a', { ...attrs, href: linkProps.href }, slots.default?.()) + compilerOptions: { + bindingMetadata: script.bindings, }, }) - return renderToString(app) + + const combined = [ + script.content.replace('export default', 'const __sfc__ ='), + template.code, + 'const __component__ = __sfc__', + '__component__.render = render', + 'export default __component__', + ].join('\n') + + const transformed = await transformWithEsbuild(combined, filename, { + loader: 'ts', + format: 'esm', + target: 'es2022', + }) + + const moduleSource = transformed.code.replace(/from\s+['"]vue['"]/g, `from '${vueModuleUrl}'`) + return import(`data:text/javascript;charset=utf-8,${encodeURIComponent(moduleSource)}`) } test('style.css keeps tokenized selectors for the live homepage, Mermaid, and SVG surfaces', () => { @@ -84,9 +112,11 @@ test('style.css keeps tokenized selectors for the live homepage, Mermaid, and SV for (const token of [ '--wp-paper-1', '--wp-ink-1', - '--wp-surface-meta', '--wp-surface-figure', + '--wp-surface-meta', '--wp-surface-section-index', + '--wp-surface-reference', + '--wp-line-1', '--wp-surface-1', '--wp-surface-2', '--wp-pill-bg', @@ -97,8 +127,7 @@ test('style.css keeps tokenized selectors for the live homepage, Mermaid, and SV } for (const selector of [ - '.wp-hero', - '.wp-metric-strip', + '.landing-hero', '.wp-section-index', '.wp-figure-shell', '.wp-meta-strip', @@ -122,6 +151,15 @@ test('style.css suppresses VitePress built-in translation widgets in favor of th } }) +test('style.css includes dedicated nav-density selectors for the redesigned homepage', () => { + const css = read('style.css') + + assert.match(css, /\.VPNavBarMenu\s*\{[\s\S]*?gap:\s*clamp\(0\.45rem,\s*1vw,\s*1rem\);/) + assert.match(css, /\.VPNavBarMenuLink,[\s\S]*?\.VPNavBarMenuGroup \.button\s*\{[\s\S]*?font-size:\s*0\.94rem;/) + assert.match(css, /\.VPNavBarExtra\s*\{[\s\S]*?gap:\s*0\.55rem;/) + assert.match(css, /\.landing-hero__shell\s*\{/) +}) + test('language switcher markup keeps native navigation when JavaScript is unavailable', () => { const switcher = read('LanguageSwitcher.vue') @@ -137,9 +175,13 @@ test('language switcher markup keeps native navigation when JavaScript is unavai test('theme index wires only the active language chrome', () => { const themeIndex = read('index.ts') - for (const componentName of ['BaseAwareLink', 'LanguageRedirect', 'LanguageSwitcher', 'SectionHero', 'MetricStrip', 'SectionIndex', 'LandingHero']) { + for (const componentName of ['BaseAwareLink', 'LanguageRedirect', 'LanguageSwitcher', 'LandingHero', 'SectionIndex']) { assert.match(themeIndex, new RegExp(componentName)) } + + for (const componentName of ['SectionHero', 'MetricStrip']) { + assert.doesNotMatch(themeIndex, new RegExp(componentName)) + } }) test('bilingual landing pages preserve copy while using shared whitepaper primitives', () => { @@ -148,59 +190,20 @@ test('bilingual landing pages preserve copy while using shared whitepaper primit const zhIndex = fs.readFileSync(path.join(docsRoot, 'zh', 'index.md'), 'utf8') for (const content of [enIndex, zhIndex]) { - // LandingHero is used instead of SectionHero+MetricStrip in current implementation assert.match(content, /Quick Start guide<\/BaseAwareLink>/) assert.match(zhIndex, /快速开始指南<\/BaseAwareLink>/) + assert.match(read('SectionIndex.vue'), /BaseAwareLink/) + assert.match(enIndex, /:actions='/) + assert.match(zhIndex, /:actions='/) assert.doesNotMatch(enIndex, /href: "\.\//) assert.doesNotMatch(enIndex, /href: "\.\.\//) assert.doesNotMatch(zhIndex, /href: "\.\//) @@ -209,32 +212,101 @@ test('bilingual landing pages preserve copy while using shared whitepaper primit assert.doesNotMatch(zhIndex, / { +test('bilingual landing pages keep equivalent guide routes and summary metrics', () => { const docsRoot = path.resolve(themeDir, '..', '..') const enIndex = fs.readFileSync(path.join(docsRoot, 'en', 'index.md'), 'utf8') const zhIndex = fs.readFileSync(path.join(docsRoot, 'zh', 'index.md'), 'utf8') - const enHeroLinks = extractHeroLinks(enIndex).map(normalizeHeroLink) - const zhHeroLinks = extractHeroLinks(zhIndex).map(normalizeHeroLink) + assert.deepEqual( + extractLandingGuides(zhIndex).map(normalizeHeroLink), + extractLandingGuides(enIndex).map(normalizeHeroLink), + ) + + assert.deepEqual(extractLandingMetricValues(zhIndex), extractLandingMetricValues(enIndex)) +}) + +test('bilingual landing pages keep equivalent section index routes and module map rows', () => { + const docsRoot = path.resolve(themeDir, '..', '..') + const enIndex = fs.readFileSync(path.join(docsRoot, 'en', 'index.md'), 'utf8') + const zhIndex = fs.readFileSync(path.join(docsRoot, 'zh', 'index.md'), 'utf8') + + const enSectionIndexGroups = extractSectionIndexLinkGroups(enIndex) + const zhSectionIndexGroups = extractSectionIndexLinkGroups(zhIndex) + + assert.equal(enSectionIndexGroups.length, 2) + assert.equal(zhSectionIndexGroups.length, 2) + assert.deepEqual( + zhSectionIndexGroups.map(group => group.map(normalizeHeroLink)), + enSectionIndexGroups.map(group => group.map(normalizeHeroLink)), + ) + + assert.equal(countModuleMapRows(enIndex, 'Module map', 'Expert reader callouts'), 5) + assert.equal(countModuleMapRows(zhIndex, '模块地图', '面向熟练读者的提示'), 5) +}) + +test('LandingHero renders split hero props and base-aware links through shared link nodes', async () => { + const { default: LandingHero } = await loadSfcComponent('LandingHero.vue') + + const app = createSSRApp({ + render() { + return h(LandingHero, { + badge: 'Updated for 2026', + title: 'Learn modern C++', + titleAccent: 'systematically', + subtitle: 'A practical path from foundations to validation.', + intro: 'Use the guided module sequence, then branch into reference material.', + actionsAriaLabel: 'Primary learning actions', + actions: [ + { href: '/en/getting-started/quickstart', label: 'Start here', primary: true }, + { href: '/en/reference/', label: 'Browse references' }, + ], + guidesAriaLabel: 'Recommended guide sequence', + guides: [ + { + href: '/en/guides/learning-path', + title: 'Learn the module sequence', + description: 'Follow the staged reading order.', + }, + ], + metricsAriaLabel: 'Learning metrics', + metrics: [ + { value: '12+', label: 'Core modules' }, + { value: '3', label: 'Practice tracks' }, + ], + }) + }, + }) + + app.component('BaseAwareLink', { + props: { + href: { + type: String, + required: true, + }, + }, + setup(props, { slots, attrs }) { + return () => h('a', { ...attrs, href: `resolved:${props.href}` }, slots.default?.()) + }, + }) + + const html = await renderToString(app) - assert.deepEqual(zhHeroLinks, enHeroLinks) + assert.match(html, /Primary learning actions/) + assert.match(html, /Recommended guide sequence/) + assert.match(html, /Learning metrics/) + assert.match(html, /Learn the module sequence/) + assert.match(html, /href="resolved:\/en\/getting-started\/quickstart"/) + assert.match(html, /href="resolved:\/en\/guides\/learning-path"/) + assert.match(html, /class="landing-hero__guide-title"/) }) diff --git a/docs/.vitepress/tests/whitepaper-content.test.mjs b/docs/.vitepress/tests/whitepaper-content.test.mjs index 708dfad..d6d6f20 100644 --- a/docs/.vitepress/tests/whitepaper-content.test.mjs +++ b/docs/.vitepress/tests/whitepaper-content.test.mjs @@ -62,11 +62,31 @@ test('landing page states the stronger thesis and validation ladder', () => { const content = read('en/index.md') assert.match(content, /performance advice as something to compile, test, benchmark, and falsify/i) - assert.match(content, /Debug→UBSan/) + assert.match(content, /debug→ubsan/i) + assert.match(content, /## Who should read this guide\?/) + assert.match(content, /## Recommended study path/) + assert.match(content, /## First-run route/) assert.match(content, /Validation claims/) assert.match(content, /Expert reader callouts/) }) +test('homepages expose the new learning-first whitepaper headings in both languages', () => { + const enContent = read('en/index.md') + const zhContent = read('zh/index.md') + + for (const heading of [ + '## Who should read this guide?', + '## Recommended study path', + '## First-run route', + ]) { + assert.match(enContent, new RegExp(heading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) + } + + for (const heading of ['## 这份指南适合谁?', '## 建议学习顺序', '## 首次运行路线']) { + assert.match(zhContent, new RegExp(heading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) + } +}) + test('architecture pages describe preset-driven validation and profiling methodology', () => { const architectureIndex = read('en/architecture/index.md') const methodology = read('en/architecture/performance-methodology.md') diff --git a/docs/.vitepress/theme/LandingHero.vue b/docs/.vitepress/theme/LandingHero.vue index 2ed46c5..ba84188 100644 --- a/docs/.vitepress/theme/LandingHero.vue +++ b/docs/.vitepress/theme/LandingHero.vue @@ -1,41 +1,47 @@ - - diff --git a/docs/.vitepress/theme/SectionHero.vue b/docs/.vitepress/theme/SectionHero.vue deleted file mode 100644 index 2df6ffb..0000000 --- a/docs/.vitepress/theme/SectionHero.vue +++ /dev/null @@ -1,47 +0,0 @@ - - - diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index 149c535..e866d5d 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -5,8 +5,6 @@ import './style.css' import BaseAwareLink from './BaseAwareLink.vue' import LanguageRedirect from './LanguageRedirect.vue' import LanguageSwitcher from './LanguageSwitcher.vue' -import MetricStrip from './MetricStrip.vue' -import SectionHero from './SectionHero.vue' import SectionIndex from './SectionIndex.vue' import Citation from './Citation.vue' import CodeCompare from './CodeCompare.vue' @@ -23,8 +21,6 @@ export default { enhanceApp({ app }) { // Register custom components globally app.component('BaseAwareLink', BaseAwareLink) - app.component('SectionHero', SectionHero) - app.component('MetricStrip', MetricStrip) app.component('SectionIndex', SectionIndex) // New components for v2.0 app.component('Citation', Citation) diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css index b84c0ab..919fd43 100644 --- a/docs/.vitepress/theme/style.css +++ b/docs/.vitepress/theme/style.css @@ -123,6 +123,23 @@ color: var(--wp-accent-primary); } + .VPNavBarMenu { + gap: clamp(0.45rem, 1vw, 1rem); + } + + .VPNavBarMenuLink, + .VPNavBarMenuGroup .button { + font-size: 0.94rem; + font-weight: 600; + letter-spacing: -0.01em; + } + + .VPNavBarExtra { + display: flex; + align-items: center; + gap: 0.55rem; + } + /* === Sidebar === */ .VPSidebar { background: var(--wp-paper-2); @@ -1183,10 +1200,9 @@ /* === Landing Hero Component === */ .landing-hero { position: relative; - min-height: 85vh; - display: flex; - flex-direction: column; - justify-content: center; + min-height: min(85vh, 56rem); + display: grid; + align-items: center; padding: var(--wp-space-8) 0; overflow: hidden; } @@ -1222,29 +1238,52 @@ filter: blur(80px); } - .landing-hero__content { + .landing-hero__shell { position: relative; z-index: 1; - max-width: 56rem; + display: grid; + grid-template-columns: minmax(0, 1.5fr) minmax(18rem, 1fr); + gap: var(--wp-space-6); + align-items: start; + } + + .landing-hero__content, + .landing-hero__rail { + display: grid; + align-content: start; + } + + .landing-hero__content { + gap: var(--wp-space-4); + max-width: 42rem; + } + + .landing-hero__rail { + gap: var(--wp-space-4); } .landing-hero__badge { display: inline-flex; align-items: center; - gap: var(--wp-space-2); - padding: 0.35rem 0.85rem; + justify-self: start; + max-width: 100%; + min-height: 2rem; + padding: 0.4rem 0.9rem; border-radius: var(--wp-radius-full); - background: var(--wp-accent-soft); + border: 1px solid color-mix(in oklch, var(--wp-accent-primary) 22%, var(--wp-line-1)); + background: color-mix(in oklch, var(--wp-paper-1) 82%, var(--wp-accent-primary) 8%); color: var(--wp-accent-primary); font-size: 0.8rem; font-weight: 700; - letter-spacing: 0.05em; + letter-spacing: 0.08em; + line-height: 1.2; text-transform: uppercase; - margin-bottom: var(--wp-space-5); } .landing-hero__title { - margin: 0 0 var(--wp-space-4); + display: grid; + gap: 0.15em; + margin: 0; font-size: clamp(2.5rem, 6vw, 4rem); font-weight: 800; line-height: 1.05; @@ -1252,35 +1291,33 @@ } .landing-hero__title-accent { - background: linear-gradient( - 135deg, - var(--wp-accent-primary) 0%, - var(--wp-accent-secondary) 50%, - var(--wp-accent-tertiary) 100% - ); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; + color: var(--wp-accent-primary); } .landing-hero__subtitle { - margin: 0 0 var(--wp-space-6); + margin: 0; color: var(--wp-ink-2); font-size: clamp(1.1rem, 2vw, 1.35rem); - line-height: 1.6; - max-width: 40rem; + line-height: 1.55; + } + + .landing-hero__intro { + margin: 0; + color: var(--wp-ink-3); + font-size: 1rem; + line-height: 1.8; } .landing-hero__actions { display: flex; flex-wrap: wrap; gap: var(--wp-space-3); - margin-bottom: var(--wp-space-8); } .landing-hero__cta { display: inline-flex; align-items: center; + justify-content: center; gap: var(--wp-space-2); padding: 0.7rem 1.25rem; border-radius: var(--wp-radius-2); @@ -1291,11 +1328,7 @@ } .landing-hero__cta--primary { - background: linear-gradient( - 135deg, - var(--wp-accent-primary) 0%, - var(--wp-accent-secondary) 100% - ); + background: var(--wp-accent-primary); color: white; box-shadow: var(--wp-shadow-2); } @@ -1307,7 +1340,7 @@ .landing-hero__cta:not(.landing-hero__cta--primary) { border: 1px solid var(--wp-line-2); - background: var(--wp-paper-1); + background: color-mix(in oklch, var(--wp-paper-1) 90%, var(--wp-paper-2)); color: var(--wp-ink-2); } @@ -1316,27 +1349,74 @@ color: var(--wp-accent-primary); } + .landing-hero__guides, .landing-hero__metrics { - display: flex; - flex-wrap: wrap; - gap: var(--wp-space-6); + display: grid; + gap: var(--wp-space-3); } + .landing-hero__guide, .landing-hero__metric { - display: flex; - flex-direction: column; + display: grid; gap: var(--wp-space-1); + padding: var(--wp-space-4); + border: 1px solid var(--wp-line-1); + border-radius: var(--wp-radius-2); + background: color-mix(in oklch, var(--wp-surface-1) 92%, var(--wp-paper-1)); + box-shadow: var(--wp-shadow-card); + text-decoration: none; + transition: + border-color var(--wp-transition-fast), + box-shadow var(--wp-transition-fast), + transform var(--wp-transition-fast), + background-color var(--wp-transition-slow); + } + + .landing-hero__guide:hover { + border-color: color-mix(in oklch, var(--wp-accent-primary) 42%, var(--wp-line-2)); + transform: translateY(-2px); + box-shadow: var(--wp-shadow-2); + } + + .dark .landing-hero__guide:hover { + box-shadow: var(--wp-shadow-glow); + } + + .landing-hero__guide-title { + color: var(--wp-ink-1); + font-size: 1rem; + font-weight: 700; + line-height: 1.35; + } + + .landing-hero__guide-description { + color: var(--wp-ink-2); + font-size: 0.92rem; + line-height: 1.65; + } + + .landing-hero__metrics { + grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); + } + + .landing-hero__metric { + flex-wrap: wrap; + align-content: start; } .landing-hero__metric-value { + margin: 0; font-size: 1.5rem; font-weight: 800; color: var(--wp-ink-1); + line-height: 1.1; } .landing-hero__metric-label { + margin: 0; font-size: 0.85rem; color: var(--wp-ink-3); + line-height: 1.5; } /* Landing Hero Animations */ @@ -1627,6 +1707,10 @@ min-height: auto; padding: var(--wp-space-6) 0; } + + .landing-hero__shell { + grid-template-columns: 1fr; + } } @media (max-width: 720px) { @@ -1657,13 +1741,12 @@ } .landing-hero__cta { - justify-content: center; width: 100%; } + .landing-hero__guides, .landing-hero__metrics { - flex-direction: column; - gap: var(--wp-space-4); + grid-template-columns: 1fr; } } diff --git a/docs/en/index.md b/docs/en/index.md index f680dad..3d3a51c 100644 --- a/docs/en/index.md +++ b/docs/en/index.md @@ -3,33 +3,78 @@ layout: home --- -## Thesis +## Who should read this guide? + +This guide is for readers who want more than isolated tips: + +- engineers revisiting modern C++ performance work and wanting a structured re-entry point +- reviewers or interviewers who need to inspect claims against runnable code and verification surfaces +- maintainers who must understand how examples, tests, presets, and whitepaper narrative fit together + +The repository treats performance advice as something to compile, test, benchmark, and falsify. An expert reader should be able to move from a claim on this page to the corresponding source file, preset, benchmark, or test target without guesswork. + +## Recommended study path -Performance guidance becomes credible only when the argumentative chain stays visible. In this repository, each layer is inspectable: +Start with orientation before specialization: -- **code** lives in runnable example modules under `examples/` -- **evidence** lives in CMake presets, unit tests, property tests, sanitizer runs, and benchmark executables -- **interpretation** lives in the academy, architecture, playbook, reference, and research sections -- **governance** lives in `openspec/`, where non-trivial changes record intent before they land +1. Read the [Academy overview](/en/academy/) to learn the repository's teaching model. +2. Use [Module Atlas](/en/academy/module-atlas) to map each module to concrete code surfaces. +3. Read [Validation Doctrine](/en/academy/validation-doctrine) before trusting any optimization claim. +4. Continue into [Architecture](/en/architecture/) once you want repository topology and methodology. +5. Keep [Playbook](/en/playbook/) and [Reference](/en/reference/) nearby for operational detail. -An expert reader should be able to move from a claim on this page to the corresponding source file, preset, benchmark, or test target without guesswork. +## First-run route + +Use the preset-driven path first. It is the shortest route that still preserves architectural intent. + +
+
Quick Start
+
+

Run the baseline before browsing advanced optimization pages. That keeps the learning sequence anchored to a known-good build and test loop.

+
+ cmake --preset=debug && cmake --build build/debug && ctest --preset=debug +
+ For a broader operational path, continue with the Quick Start guide. +
+
## Validation claims diff --git a/docs/zh/index.md b/docs/zh/index.md index 49b9177..c10c22a 100644 --- a/docs/zh/index.md +++ b/docs/zh/index.md @@ -3,33 +3,78 @@ layout: home --- -## 论点 +## 这份指南适合谁? + +如果你需要的不是零散技巧,而是一条可核验的学习入口,这份指南就是为你准备的: + +- 想系统复习现代 C++ 性能主题、但不希望只看到结论的工程师 +- 需要把页面论断追溯到代码、测试和 preset 的评审者或面试官 +- 未来会维护这个仓库,需要先建立整体地图的接手者 + +这个仓库把性能建议视为必须能够编译、测试、基准比较并被证伪的对象。理想状态下,你应当能从首页上的任何结论直接追到对应的源文件、preset、基准或测试目标,而不需要猜。 + +## 建议学习顺序 -只有当论证链条保持可见时,性能指导才有可信度。在这个仓库里,每一层都可以被检查: +建议先建立理解框架,再进入专题深水区: -- **代码** 位于 `examples/` 下可运行的示例模块中 -- **证据** 位于 CMake preset、单元测试、性质测试、sanitizer 运行与基准可执行文件中 -- **解释** 位于学院、架构、实践手册、参考与研究部分中 -- **治理** 位于 `openspec/` 中,在非平凡改动落地之前先记录意图 +1. 先看[学院概览](/zh/academy/),理解整套内容的教学组织方式。 +2. 再读[模块总览](/zh/academy/module-atlas),把模块与真实代码位置对上。 +3. 接着看[验证原则](/zh/academy/validation-doctrine),先知道什么才算证据充分。 +4. 需要理解仓库结构与测量方法时,再进入[架构](/zh/architecture/)。 +5. 日常查命令、排路线时,把[实践手册](/zh/playbook/)和[参考](/zh/reference/)当作随手册。 -一个熟练读者应当可以从本页上的任何结论,直接追到对应的源文件、preset、基准或测试目标,而不需要猜测。 +## 首次运行路线 + +第一次进入仓库时,优先走 preset 驱动的基线路线。这样最短,也最能保留项目原本的工程语义。 + +
+
快速开始
+
+

先把基线构建与测试跑通,再去读更激进的优化页面。这样学习顺序会稳,也更容易判断后续结论是不是可信。

+
+ cmake --preset=debug && cmake --build build/debug && ctest --preset=debug +
+ 如需更完整的操作路线,请继续阅读快速开始指南。 +
+
## 验证主张