Skip to content

Commit 87042db

Browse files
feat(site): 钢蓝色魂 + 精致组件 + OnlineCompilerDemo 弹出式 IDE 重构 (#90)
色魂(品牌色 identity,治"无色魂"短板): - :root 钢蓝品牌色 #2563EB 系 + 暖中性骨白底,消除 4 套散落配色 (1:1 镜像 C-Journey 已验证的 :root 模板,只把锈橙换钢蓝) - 终端硬编码色抽 var(--term-*),::selection/滚动条美化,blockquote/ HomeTipBanner/HomeRoadmap chip 配色统一到品牌色,补 theme-color meta 精致组件 + 脆弱性清理: - ReadingProgress 顶部阅读进度条(rAF 节流 + 钢蓝 glow + 路由双重重算) - .VPFeature 卡片 rail(::before 伪元素 + 三档 tier 着色) - 代码折叠按钮优化(brand-soft 容器 chevron + 行数 chip + 通知条质感) - .VPFeature .title 字号冲突修(:where 零特异性)+ !important 74→61 - OnlineCompilerDemo dup-output 修复(extractExecutionText/extractAsmText 改 || 取 first-non-empty,对齐 C-Journey f85300b) OnlineCompilerDemo 弹出式重构: - 默认只读源码 + "动手试一试" CTA,点开 Teleport 浮层(灰色遮罩 + 比页面小一圈的 1280px 卡片 + 完整 IDE),✕/遮罩/Esc 关闭 + 锁滚 - 编辑态高亮 overlay(shiki backdrop + 透明 textarea + 像素 line-height 对齐 + 边打边高亮 + 滚动同步) - 编译错误信息友好化(透出 stderr 真实诊断 + ❌ 编译失败 前缀) - 结果区 shimmer 骨架屏 修复: - sidebar 超大字体下长标题溢出视口(VitePress .text 默认 min-width:auto 不收缩,补 min-width:0 + ellipsis 优雅截断)
1 parent 4c9648b commit 87042db

8 files changed

Lines changed: 730 additions & 79 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
---
2121

2222
<!-- COVERAGE_START -->
23-
![English Coverage](https://img.shields.io/badge/en_coverage-89%25-green.svg) 494/552 docs translated
23+
![English Coverage](https://img.shields.io/badge/en_coverage-97%25-green.svg) 494/510 docs translated
2424
<!-- COVERAGE_END -->
2525

2626
## 这是什么项目

site/.vitepress/config/shared.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ export const sharedBase = {
6666

6767
head: [
6868
['link', { rel: 'icon', href: '/Tutorial_AwesomeModernCPP/favicon.ico' }],
69+
// 浏览器地址栏/壁纸融合(明暗双值,与站点暖中性底一致)
70+
['meta', { name: 'theme-color', content: '#F7F3EC' }],
71+
['meta', { name: 'theme-color', content: '#17120E', media: '(prefers-color-scheme: dark)' }],
6972
// 首屏立即应用字号档(从 localStorage 读,默认 medium),防刷新闪烁。
7073
// 与 FontSizeSwitcher.vue 的 STORAGE_KEY('vp-font-size')保持一致。
7174
[

site/.vitepress/theme/components/HomeHeroVisual.vue

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,12 @@ onBeforeUnmount(() => {
148148
.terminal {
149149
position: relative;
150150
box-sizing: border-box;
151-
border: 1px solid rgba(99, 102, 241, 0.35);
151+
border: 1px solid var(--term-border);
152152
border-radius: 14px;
153153
overflow: hidden;
154-
background: linear-gradient(135deg, #1a1a2e 0%, #0f3460 100%);
154+
background: linear-gradient(135deg, var(--term-bg-from) 0%, var(--term-bg-to) 100%);
155155
box-shadow:
156-
0 22px 56px rgba(15, 52, 96, 0.4),
156+
0 22px 56px var(--term-shadow),
157157
0 6px 14px rgba(0, 0, 0, 0.22);
158158
animation: terminal-glow 4s ease-in-out infinite;
159159
}
@@ -179,7 +179,7 @@ onBeforeUnmount(() => {
179179
180180
.terminal__title {
181181
margin-left: 10px;
182-
color: rgba(226, 232, 240, 0.55);
182+
color: var(--term-title);
183183
font-family: var(--vp-font-family-mono);
184184
font-size: 13px;
185185
letter-spacing: 0.3px;
@@ -200,17 +200,17 @@ onBeforeUnmount(() => {
200200
min-height: 1.75em;
201201
}
202202
203-
.ln--code { color: #cbd5e1; }
204-
.ln--cmd { color: #42a5f5; } /* shell prompt */
205-
.ln--ok { color: #009688; } /* 编译成功 / brand teal */
206-
.ln--save { color: #fbbf24; } /* 保存提示 */
207-
.ln--out { color: #e2e8f0; } /* 程序输出 */
208-
.ln--prompt { color: #009688; }
203+
.ln--code { color: var(--term-code); }
204+
.ln--cmd { color: var(--term-cmd); } /* shell prompt */
205+
.ln--ok { color: var(--term-ok); } /* 编译成功(语义绿) */
206+
.ln--save { color: var(--term-save); } /* 保存提示 */
207+
.ln--out { color: var(--term-out); } /* 程序输出 */
208+
.ln--prompt { color: var(--term-ok); }
209209
210210
.cursor {
211211
display: inline-block;
212212
margin-left: 2px;
213-
color: #009688;
213+
color: var(--term-ok);
214214
animation: blink 1.05s step-end infinite;
215215
}
216216
@@ -223,15 +223,15 @@ onBeforeUnmount(() => {
223223
@keyframes terminal-glow {
224224
0%, 100% {
225225
box-shadow:
226-
0 22px 56px rgba(15, 52, 96, 0.4),
226+
0 22px 56px var(--term-shadow),
227227
0 6px 14px rgba(0, 0, 0, 0.22),
228-
0 0 0 0 rgba(99, 102, 241, 0);
228+
0 0 0 0 rgba(96, 165, 250, 0);
229229
}
230230
50% {
231231
box-shadow:
232-
0 22px 56px rgba(15, 52, 96, 0.45),
232+
0 22px 56px var(--term-shadow),
233233
0 6px 14px rgba(0, 0, 0, 0.25),
234-
0 0 26px 3px rgba(99, 102, 241, 0.24);
234+
0 0 26px 3px var(--term-glow);
235235
}
236236
}
237237

site/.vitepress/theme/components/HomeRoadmap.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ const t = computed(() =>
166166
}
167167
168168
.rm-chip--done .rm-chip__mark { color: var(--vp-c-green-1); }
169-
.rm-chip--doing .rm-chip__mark { color: #ffc107; }
169+
.rm-chip--doing .rm-chip__mark { color: var(--vp-c-yellow-1, #f59e0b); }
170170
.rm-chip--todo .rm-chip__mark { color: var(--vp-c-text-3); }
171171
172172
.home-roadmap__next {

site/.vitepress/theme/components/OnlineCompilerDemo.vue

Lines changed: 186 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,76 @@
2727
</a>
2828
</div>
2929

30-
<div class="online-compiler-demo__split">
30+
<!-- 默认态:只读源码 + "动手试一试" CTA。源码占满正文宽,不再两栏挤压 -->
31+
<div class="online-compiler-demo__inline">
32+
<div v-if="armSourcePath" class="online-compiler-demo__source-tabs">
33+
<button
34+
type="button"
35+
class="online-compiler-demo__button online-compiler-demo__button--secondary online-compiler-demo__button--tab"
36+
:class="{ 'is-active': displayKind !== 'arm' }"
37+
@click="switchSourceKind('default')"
38+
>
39+
源码
40+
</button>
41+
<button
42+
type="button"
43+
class="online-compiler-demo__button online-compiler-demo__button--secondary online-compiler-demo__button--tab"
44+
:class="{ 'is-active': displayKind === 'arm' }"
45+
@click="switchSourceKind('arm')"
46+
>
47+
ARM 精简源码
48+
</button>
49+
</div>
50+
51+
<div class="online-compiler-demo__source-view">
52+
<div
53+
v-if="highlightedHtml"
54+
class="online-compiler-demo__source-highlight"
55+
v-html="highlightedHtml"
56+
/>
57+
<pre v-else class="online-compiler-demo__source-code"><code>{{ displaySource }}</code></pre>
58+
</div>
59+
60+
<p
61+
v-if="sourceLoadState === 'loading' || sourceLoadState === 'error'"
62+
class="online-compiler-demo__source-hint"
63+
>
64+
{{ sourceLoadState === 'error' ? '源码加载失败,可点上方源码链接查看' : '加载源码中…' }}
65+
</p>
66+
67+
<button class="online-compiler-demo__cta" type="button" @click="openModal">
68+
<span class="online-compiler-demo__cta-text">动手试一试</span>
69+
<span class="online-compiler-demo__cta-arrow" aria-hidden="true">→</span>
70+
</button>
71+
</div>
72+
73+
<!-- 浮层态:Teleport 到 body,灰色遮罩 + 比页面小一圈的卡片,完整 IDE -->
74+
<Teleport to="body">
75+
<div
76+
v-if="modalOpen"
77+
class="online-compiler-demo__overlay"
78+
@click.self="closeModal"
79+
>
80+
<div
81+
class="online-compiler-demo__modal"
82+
role="dialog"
83+
aria-modal="true"
84+
aria-labelledby="ocd-modal-title"
85+
>
86+
<div class="online-compiler-demo__modal-header">
87+
<div>
88+
<p class="online-compiler-demo__eyebrow">Compiler Explorer</p>
89+
<strong id="ocd-modal-title">{{ title }}</strong>
90+
</div>
91+
<button
92+
class="online-compiler-demo__modal-close"
93+
type="button"
94+
aria-label="关闭"
95+
@click="closeModal"
96+
>✕</button>
97+
</div>
98+
99+
<div class="online-compiler-demo__split">
31100
<div class="online-compiler-demo__source-pane">
32101
<div v-if="armSourcePath" class="online-compiler-demo__source-tabs">
33102
<button
@@ -56,15 +125,23 @@
56125
/>
57126
<pre v-else class="online-compiler-demo__source-code"><code>{{ displaySource }}</code></pre>
58127
</div>
59-
<textarea
60-
v-else
61-
v-model="editorSource"
62-
class="online-compiler-demo__textarea"
63-
spellcheck="false"
64-
autocomplete="off"
65-
autocorrect="off"
66-
autocapitalize="off"
67-
/>
128+
<div v-else class="online-compiler-demo__editor-wrap">
129+
<div
130+
ref="editorBackdropRef"
131+
class="online-compiler-demo__source-highlight online-compiler-demo__editor-backdrop"
132+
v-html="editorHighlightedHtml"
133+
/>
134+
<textarea
135+
ref="editorTextareaRef"
136+
v-model="editorSource"
137+
class="online-compiler-demo__textarea online-compiler-demo__editor-textarea"
138+
spellcheck="false"
139+
autocomplete="off"
140+
autocorrect="off"
141+
autocapitalize="off"
142+
@scroll="onEditorScroll"
143+
/>
144+
</div>
68145

69146
<p
70147
v-if="!editorOpen && (sourceLoadState === 'loading' || sourceLoadState === 'error')"
@@ -195,8 +272,26 @@
195272
</div>
196273
<pre><code>{{ result.text }}</code></pre>
197274
</div>
275+
<!-- 编译进行中(activeAction 置位、result 尚未返回):shimmer 骨架,避免结果区整块消失干等 -->
276+
<div v-else-if="activeAction" class="online-compiler-demo__result online-compiler-demo__result--skeleton">
277+
<div class="online-compiler-demo__result-header">
278+
<span class="skel skel--title" />
279+
<span class="skel skel--meta" />
280+
</div>
281+
<div class="online-compiler-demo__skel-lines">
282+
<span
283+
v-for="(w, i) in [92, 78, 86, 58, 90, 72, 95, 48]"
284+
:key="i"
285+
class="skel skel--line"
286+
:style="{ width: w + '%' }"
287+
/>
288+
</div>
289+
</div>
198290
</div>
199291
</div>
292+
</div>
293+
</div>
294+
</Teleport>
200295

201296
<p v-if="error" class="online-compiler-demo__error">
202297
{{ error }}
@@ -280,6 +375,10 @@ const error = ref('')
280375
const result = ref<CompileResult | null>(null)
281376
const editorOpen = ref(false)
282377
const highlightedHtml = ref('')
378+
// 编辑态高亮(overlay 技巧):编辑框背后垫一层 shiki 高亮,文字透明,光标/选区由 textarea 接管
379+
const editorHighlightedHtml = ref('')
380+
const editorBackdropRef = ref<HTMLElement | null>(null)
381+
const editorTextareaRef = ref<HTMLTextAreaElement | null>(null)
283382
const optionsOpen = ref(false)
284383
const editorSourceKind = ref<SourceKind>('default')
285384
const editorSource = ref('')
@@ -312,6 +411,26 @@ watch(displaySource, async (code) => {
312411
}
313412
})
314413
414+
// 编辑态:边打边重新高亮(overlay backdrop 跟随 editorSource)
415+
watch(editorSource, async (code) => {
416+
editorHighlightedHtml.value = ''
417+
if (!code) return
418+
try {
419+
editorHighlightedHtml.value = await highlightCpp(code)
420+
} catch {
421+
editorHighlightedHtml.value = ''
422+
}
423+
})
424+
425+
// textarea 与 backdrop 滚动同步(两层重叠,滚动必须一致,否则错位)
426+
function onEditorScroll() {
427+
const ta = editorTextareaRef.value
428+
const bd = editorBackdropRef.value
429+
if (!ta || !bd) return
430+
bd.scrollTop = ta.scrollTop
431+
bd.scrollLeft = ta.scrollLeft
432+
}
433+
315434
const actions = computed<DemoAction[]>(() => {
316435
const available: DemoAction[] = []
317436
if (props.allowRun) {
@@ -423,6 +542,33 @@ function closeEditor(): void {
423542
editorOpen.value = false
424543
}
425544
545+
// ── 浮层(模态):默认只显源码,点"动手试一试"弹完整 IDE ──
546+
const modalOpen = ref(false)
547+
548+
function onModalEsc(e: KeyboardEvent) {
549+
if (e.key === 'Escape' && modalOpen.value) closeModal()
550+
}
551+
552+
async function openModal(): Promise<void> {
553+
modalOpen.value = true
554+
document.body.style.overflow = 'hidden'
555+
window.addEventListener('keydown', onModalEsc)
556+
await openEditor() // 加载可编辑源码、置 editorOpen=true
557+
}
558+
559+
function closeModal(): void {
560+
// closeEditor 把编辑内容回写源码缓存 + editorOpen=false
561+
closeEditor()
562+
modalOpen.value = false
563+
document.body.style.overflow = ''
564+
window.removeEventListener('keydown', onModalEsc)
565+
}
566+
567+
onBeforeUnmount(() => {
568+
window.removeEventListener('keydown', onModalEsc)
569+
if (modalOpen.value) document.body.style.overflow = ''
570+
})
571+
426572
async function resetEditor(): Promise<void> {
427573
activeAction.value = 'source'
428574
error.value = ''
@@ -472,33 +618,43 @@ function stripAnsi(value: string): string {
472618
473619
function extractExecutionText(payload: any): string {
474620
const exec = payload.execResult ?? payload.executionResult ?? payload
475-
const chunks = [
476-
linesToText(exec.stdout),
477-
linesToText(exec.stderr),
478-
linesToText(payload.stdout),
479-
linesToText(payload.stderr),
480-
linesToText(payload.buildResult?.stdout),
481-
linesToText(payload.buildResult?.stderr),
482-
].filter(Boolean)
621+
// godbolt executor 响应常把程序输出同时放在 execResult 和顶层(此时 exec===payload),
622+
// 每路只取第一份非空的,避免把同一份输出拼两遍。(对齐 C-Journey f85300b 修复)
623+
if (isCompilationFailure(payload, linesToText(payload.asm))) {
624+
const diag = gatherDiagnostics(payload)
625+
return diag
626+
? `❌ 编译失败:\n${diag}`
627+
: '❌ 编译失败,但 Compiler Explorer 没有返回诊断信息。检查源码语法、编译器 id 与参数,或点「打开 Godbolt」看完整输出。'
628+
}
629+
const out = linesToText(exec.stdout) || linesToText(payload.stdout) || linesToText(payload.buildResult?.stdout)
630+
const err = linesToText(exec.stderr) || linesToText(payload.stderr) || linesToText(payload.buildResult?.stderr)
631+
const chunks = [out, err].filter(Boolean)
632+
633+
if (exec.code !== undefined && exec.code !== 0) chunks.push(`exit code: ${exec.code}`)
634+
else if (payload.code !== undefined && payload.code !== 0) chunks.push(`exit code: ${payload.code}`)
635+
return chunks.join('\n').trim() || '(程序无输出)'
636+
}
483637
484-
if (exec.code !== undefined) chunks.push(`exit code: ${exec.code}`)
485-
return chunks.join('\n').trim()
638+
// 收集编译/运行诊断(execResult → 顶层 → buildResult,stderr 优先再 stdout),每路第一份非空避免重复
639+
function gatherDiagnostics(payload: any): string {
640+
const exec = payload.execResult ?? payload.executionResult ?? payload
641+
const err = linesToText(exec.stderr) || linesToText(payload.stderr) || linesToText(payload.buildResult?.stderr)
642+
const out = linesToText(exec.stdout) || linesToText(payload.stdout) || linesToText(payload.buildResult?.stdout)
643+
return [err, out].filter(Boolean).join('\n')
486644
}
487645
488646
function extractAsmText(payload: any): string {
489647
const asm = linesToText(payload.asm)
490-
const diagnostics = [
491-
linesToText(payload.stdout),
492-
linesToText(payload.stderr),
493-
linesToText(payload.buildResult?.stdout),
494-
linesToText(payload.buildResult?.stderr),
495-
].filter(Boolean).join('\n')
496-
497648
if (isCompilationFailure(payload, asm)) {
498-
return (diagnostics || asm || '编译失败,但 Compiler Explorer 没有返回诊断信息。').trim()
649+
// 顶层与 buildResult 可能同源,每路只取第一份非空。
650+
const err = linesToText(payload.stderr) || linesToText(payload.buildResult?.stderr)
651+
const out = linesToText(payload.stdout) || linesToText(payload.buildResult?.stdout)
652+
const diag = [err, out].filter(Boolean).join('\n')
653+
return diag
654+
? `❌ 编译失败:\n${diag}`
655+
: '❌ 编译失败,但 Compiler Explorer 没有返回诊断信息。检查源码语法、编译器 id 与参数,或点「打开 Godbolt」看完整输出。'
499656
}
500-
501-
return (asm || diagnostics || 'Compiler Explorer 没有返回可显示的输出。').trim()
657+
return (asm || 'Compiler Explorer 没有返回可显示的汇编输出。').trim()
502658
}
503659
504660
function isCompilationFailure(payload: any, asm: string): boolean {

0 commit comments

Comments
 (0)