This is an automated email from the ASF dual-hosted git repository.
moonming pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-website.git
The following commit(s) were added to refs/heads/master by this push:
new 12ae102154c fix(seo): point zh docs at the Chinese hub, drop
untranslated zh articles (#2087)
12ae102154c is described below
commit 12ae102154c0445f029eb84249e7e302d5d8b411
Author: Ming Wen <[email protected]>
AuthorDate: Thu Jul 30 11:35:26 2026 +0800
fix(seo): point zh docs at the Chinese hub, drop untranslated zh articles
(#2087)
Three fixes to the Chinese tree's indexing, where the polarity was
inverted: translated pages were suppressed and untranslated duplicates
were promoted.
Chinese plugin docs canonicalised to docs.api7.ai, which has no Chinese
edition — docs.api7.ai/zh/hub/limit-count is a 404. A genuinely translated
page (3,052 CJK characters) was telling search engines to index an English
page instead, so a reader searching "APISIX limit-count 限流" could not
find the translation that exists. They now canonicalise to
docs.apiseven.com, the Chinese edition of the same hub; all six sampled
targets return 200 and serve lang="zh" content. English pages are
unchanged. This also resolves the one canonical that pointed at a 404:
docs.api7.ai/hub/feishu-auth is dead, docs.apiseven.com/hub/feishu-auth
is not.
The 18 Chinese learning-center articles were never translated — 18-26 CJK
characters against 1,100-2,600 English words, titles byte-identical to
their English twins, self-canonical, and in the sitemap. They competed
with the site's most valuable commercial pages (apisix-vs-kong,
what-is-an-api-gateway) while giving Chinese readers English prose that
browsers would not offer to translate, because lang="zh-CN" claims it is
already Chinese. The pages are no longer built; /zh/learning-center/ lists
the English articles and says so, and the indexed URLs 301 to their
English equivalents.
Docs body images had no loading hints and no reserved space — eight
diagrams totalling 235 KB on one plugin page, all eager, all shifting the
layout as they arrived. A remark plugin adds loading="lazy" and
decoding="async"; the intrinsic size of a remote image is not knowable at
build time, so `.prose img` reserves a 16/9 block that a declared
width/height overrides.
---
.htaccess | 7 +++
next/astro.config.mjs | 3 +-
next/scripts/remark-image-loading.mjs | 21 +++++++++
next/src/components/CollectionPages.astro | 16 +++++--
next/src/components/StaticPages.astro | 2 +-
next/src/layouts/DocPage.astro | 15 +++++-
next/src/lib/content.ts | 17 +++++--
next/src/pages/learning-center/[slug].astro | 4 +-
next/src/pages/learning-center/archive.astro | 2 +-
next/src/pages/learning-center/index.astro | 2 +-
next/src/pages/learning-center/page/[n].astro | 4 +-
next/src/pages/learning-center/tags/[tag].astro | 2 +-
next/src/pages/learning-center/tags/index.astro | 2 +-
next/src/pages/zh/learning-center/[slug].astro | 53 ----------------------
next/src/pages/zh/learning-center/archive.astro | 5 --
next/src/pages/zh/learning-center/index.astro | 5 +-
next/src/pages/zh/learning-center/page/[n].astro | 17 -------
next/src/pages/zh/learning-center/tags/[tag].astro | 24 ----------
next/src/pages/zh/learning-center/tags/index.astro | 24 ----------
next/src/styles/global.css | 8 +++-
20 files changed, 88 insertions(+), 145 deletions(-)
diff --git a/.htaccess b/.htaccess
index 369c473fdba..ca349b60cbe 100644
--- a/.htaccess
+++ b/.htaccess
@@ -164,3 +164,10 @@ RedirectMatch 301
"^(/zh)?/docs/ingress-controller/(?:next/)?getting-started/$"
# Renamed docs and GitHub-repo-style paths that leak into search results
RedirectMatch 301 "^(/zh)?/docs/apisix/stand-alone/?$"
"$1/docs/apisix/deployment-modes/"
RedirectMatch 301 "^(/zh)?/docs/apisix/en/latest/deployment-modes(\.md)?/?$"
"$1/docs/apisix/deployment-modes/"
+
+# The Chinese learning-center articles were never translated: each served the
+# English body under lang="zh-CN" with a title identical to its English twin
+# and a self-referential canonical, so 18 near-duplicates competed with the
+# pages they duplicated. The listing at /zh/learning-center/ now links to the
+# English articles directly; these URLs are indexed, so send them there.
+RedirectMatch 301 "^/zh/learning-center/(.+)$" "/learning-center/$1"
diff --git a/next/astro.config.mjs b/next/astro.config.mjs
index 42c85749f73..89ee08ba81c 100644
--- a/next/astro.config.mjs
+++ b/next/astro.config.mjs
@@ -2,6 +2,7 @@ import { defineConfig } from 'astro/config';
import remarkDirective from 'remark-directive';
import { remarkAdmonitions } from './scripts/remark-admonitions.mjs';
import { remarkHeadingIds } from './scripts/remark-heading-ids.mjs';
+import { remarkImageLoading } from './scripts/remark-image-loading.mjs';
// Static-only rebuild of apisix.apache.org.
// URL contract: every public URL is identical to the current Docusaurus site
@@ -14,7 +15,7 @@ export default defineConfig({
// Vite's inline threshold, so the deploy overlay's _astro/ dependency holds.
build: { format: 'directory', inlineStylesheets: 'never' },
markdown: {
- remarkPlugins: [remarkDirective, remarkAdmonitions, remarkHeadingIds],
+ remarkPlugins: [remarkDirective, remarkAdmonitions, remarkHeadingIds,
remarkImageLoading],
shikiConfig: { theme: 'github-dark-default' },
},
});
diff --git a/next/scripts/remark-image-loading.mjs
b/next/scripts/remark-image-loading.mjs
new file mode 100644
index 00000000000..343aa097f6f
--- /dev/null
+++ b/next/scripts/remark-image-loading.mjs
@@ -0,0 +1,21 @@
+import { visit } from 'unist-util-visit';
+
+// Body images in synced docs are bare `` — no dimensions, no
+// loading hint — and most are remote (static.api7.ai), so the browser has
+// nothing to reserve space with and fetches every one eagerly. On a page like
+// /docs/apisix/plugins/openid-connect/ that is eight diagrams, ~235 KB, all
+// blocking and all shifting the layout as they arrive.
+//
+// The intrinsic size isn't knowable at build time for remote images, so the
+// space is reserved in CSS (`.prose img` carries an aspect-ratio placeholder);
+// here we add the loading hints the markup can carry.
+export function remarkImageLoading() {
+ return (tree) => {
+ visit(tree, 'image', (node) => {
+ const data = node.data || (node.data = {});
+ const props = data.hProperties || (data.hProperties = {});
+ if (props.loading === undefined) props.loading = 'lazy';
+ if (props.decoding === undefined) props.decoding = 'async';
+ });
+ };
+}
diff --git a/next/src/components/CollectionPages.astro
b/next/src/components/CollectionPages.astro
index f64d20150d2..7ad7e7031c7 100644
--- a/next/src/components/CollectionPages.astro
+++ b/next/src/components/CollectionPages.astro
@@ -16,9 +16,17 @@ interface Props {
sub?: string;
kicker?: string;
withTags?: boolean;
+ /** Locale prefix for archive/tags/pagination links; defaults to this
page's. */
+ linkPrefix?: string;
}
-const { locale, page, posts, urlBase, heading, sub, kicker, withTags = false }
= Astro.props;
+const {
+ locale, page, posts, urlBase, heading, sub, kicker, withTags = false,
linkPrefix,
+} = Astro.props;
const prefix = localePrefix(locale);
+// Where the archive/tags/pagination siblings live. Normally alongside this
+// listing, but a locale that shows another locale's articles (zh learning
+// center) must point at that locale's pages, not at routes it doesn't build.
+const sibling = linkPrefix ?? prefix;
const pages = paginate(posts);
const pagePosts = pages[page - 1] ?? [];
---
@@ -29,10 +37,10 @@ const pagePosts = pages[page - 1] ?? [];
kicker={kicker}
locale={locale}
path={page === 1 ? `${urlBase}/` : `${urlBase}/page/${page}/`}
- base={`${prefix}${urlBase}/`}
+ base={`${sibling}${urlBase}/`}
posts={pagePosts}
current={page}
total={pages.length}
- archiveUrl={`${prefix}${urlBase}/archive/`}
- tagsUrl={withTags ? `${prefix}${urlBase}/tags/` : undefined}
+ archiveUrl={`${sibling}${urlBase}/archive/`}
+ tagsUrl={withTags ? `${sibling}${urlBase}/tags/` : undefined}
/>
diff --git a/next/src/components/StaticPages.astro
b/next/src/components/StaticPages.astro
index 97451d47175..b741971d877 100644
--- a/next/src/components/StaticPages.astro
+++ b/next/src/components/StaticPages.astro
@@ -24,7 +24,7 @@ const DOCS_PROJECTS = [
];
const comparisons = kind === 'comparisons'
- ? getLearningPosts(locale).filter((post) => post.tags.includes('comparison'))
+ ? getLearningPosts().filter((post) => post.tags.includes('comparison'))
: [];
const aisixIndex = comparisons.findIndex((post) => post.slug ===
'apisix-vs-aisix');
const kongIndex = comparisons.findIndex((post) => post.slug ===
'apisix-vs-kong');
diff --git a/next/src/layouts/DocPage.astro b/next/src/layouts/DocPage.astro
index aa9733694e4..ed1daaeacb2 100644
--- a/next/src/layouts/DocPage.astro
+++ b/next/src/layouts/DocPage.astro
@@ -39,6 +39,19 @@ const jsonLd = [{
mainEntityOfPage: `${SITE}${locale === 'zh' ? '/zh' : ''}${path}`,
}];
+/**
+ * Upstream plugin docs carry a cross-site canonical to the English hub on
+ * docs.api7.ai. Emitting that unchanged on a translated Chinese page points
+ * search engines at a URL with no Chinese edition (docs.api7.ai/zh/hub/* is
+ * 404), so the translation we do have gets de-indexed in favour of a page
+ * that cannot rank for those queries. docs.apiseven.com is the Chinese
+ * edition of the same hub, so zh pages canonicalise there instead.
+ */
+const ZH_HUB = 'https://docs.apiseven.com';
+const rawCanonical = entry.mod.frontmatter.canonical;
+const canonicalOverride = locale === 'zh' &&
rawCanonical?.startsWith('https://docs.api7.ai/')
+ ? rawCanonical.replace('https://docs.api7.ai', ZH_HUB)
+ : rawCanonical;
---
<Base
title={entry.title}
@@ -46,7 +59,7 @@ const jsonLd = [{
locale={locale}
path={path}
jsonLd={jsonLd}
- canonicalOverride={entry.mod.frontmatter.canonical}
+ canonicalOverride={canonicalOverride}
search
>
<div class="container docs-layout">
diff --git a/next/src/lib/content.ts b/next/src/lib/content.ts
index 14f477d0db1..2cd6adb79e4 100644
--- a/next/src/lib/content.ts
+++ b/next/src/lib/content.ts
@@ -188,11 +188,20 @@ export function getBlogPosts(locale: Locale): Post[] {
.sort(byDateDesc);
}
-export function getLearningPosts(locale: Locale): Post[] {
- // learning-center content is EN-only today; zh URLs serve the same entries
- // (the current site does the same via Docusaurus i18n fallback).
+/**
+ * Learning-center articles. The content is English-only, so these always
+ * resolve to English URLs — the Chinese listing links straight to them.
+ *
+ * We used to publish /zh/learning-center/<slug>/ as well, serving the English
+ * body under lang="zh-CN" with a title identical to the English page and a
+ * self-referential canonical. That produced 18 near-duplicate competitors to
+ * the site's most valuable commercial pages, and told browsers the page was
+ * already Chinese so they would not offer to translate it. A reader is better
+ * served by an honest English URL.
+ */
+export function getLearningPosts(): Post[] {
return Object.entries(learningModules)
- .map(([p, mod]) => flatPost(p, mod, '/learning-center', locale))
+ .map(([p, mod]) => flatPost(p, mod, '/learning-center', 'en'))
.sort(byDateDesc);
}
diff --git a/next/src/pages/learning-center/[slug].astro
b/next/src/pages/learning-center/[slug].astro
index 3109de29a29..994203f469e 100644
--- a/next/src/pages/learning-center/[slug].astro
+++ b/next/src/pages/learning-center/[slug].astro
@@ -3,11 +3,11 @@ import Article from '../../layouts/Article.astro';
import { getLearningPosts, relatedPosts } from '../../lib/content';
export function getStaticPaths() {
- return getLearningPosts('en').map((post) => ({ params: { slug: post.slug },
props: { post } }));
+ return getLearningPosts().map((post) => ({ params: { slug: post.slug },
props: { post } }));
}
const { post } = Astro.props;
const path = `/learning-center/${post.slug}/`;
-const pool = getLearningPosts('en');
+const pool = getLearningPosts();
const related = relatedPosts(post, pool);
const idx = pool.findIndex((p) => p.url === post.url);
const newer = idx > 0 ? pool[idx - 1] : undefined;
diff --git a/next/src/pages/learning-center/archive.astro
b/next/src/pages/learning-center/archive.astro
index b141b99dea4..c0d3080daa0 100644
--- a/next/src/pages/learning-center/archive.astro
+++ b/next/src/pages/learning-center/archive.astro
@@ -2,4 +2,4 @@
import BlogArchive from '../../components/BlogArchive.astro';
import { getLearningPosts } from '../../lib/content';
---
-<BlogArchive locale="en" path="/learning-center/archive/"
posts={getLearningPosts('en')} heading="Learning Center Archive"
kicker="Learning Center" />
+<BlogArchive locale="en" path="/learning-center/archive/"
posts={getLearningPosts()} heading="Learning Center Archive" kicker="Learning
Center" />
diff --git a/next/src/pages/learning-center/index.astro
b/next/src/pages/learning-center/index.astro
index aaf183e0e18..79498f59306 100644
--- a/next/src/pages/learning-center/index.astro
+++ b/next/src/pages/learning-center/index.astro
@@ -5,7 +5,7 @@ import { getLearningPosts } from '../../lib/content';
<CollectionPages
locale="en"
page={1}
- posts={getLearningPosts('en')}
+ posts={getLearningPosts()}
urlBase="/learning-center"
heading="Learning Center"
kicker="Learning Center"
diff --git a/next/src/pages/learning-center/page/[n].astro
b/next/src/pages/learning-center/page/[n].astro
index e9b00775645..fc2a3c317f3 100644
--- a/next/src/pages/learning-center/page/[n].astro
+++ b/next/src/pages/learning-center/page/[n].astro
@@ -3,13 +3,13 @@ import CollectionPages from
'../../../components/CollectionPages.astro';
import { getLearningPosts, paginate } from '../../../lib/content';
export function getStaticPaths() {
- return paginate(getLearningPosts('en')).slice(1).map((_, i) => ({ params: {
n: String(i + 2) } }));
+ return paginate(getLearningPosts()).slice(1).map((_, i) => ({ params: { n:
String(i + 2) } }));
}
---
<CollectionPages
locale="en"
page={Number(Astro.params.n)}
- posts={getLearningPosts('en')}
+ posts={getLearningPosts()}
urlBase="/learning-center"
heading="Learning Center"
kicker="Learning Center"
diff --git a/next/src/pages/learning-center/tags/[tag].astro
b/next/src/pages/learning-center/tags/[tag].astro
index b759f807f88..83f28357561 100644
--- a/next/src/pages/learning-center/tags/[tag].astro
+++ b/next/src/pages/learning-center/tags/[tag].astro
@@ -3,7 +3,7 @@ import ListPage from '../../../layouts/ListPage.astro';
import { getLearningPosts, groupByTag } from '../../../lib/content';
export function getStaticPaths() {
- const tags = groupByTag(getLearningPosts('en'));
+ const tags = groupByTag(getLearningPosts());
return [...tags.entries()].map(([slug, group]) => ({ params: { tag: slug },
props: group }));
}
const { label, posts } = Astro.props;
diff --git a/next/src/pages/learning-center/tags/index.astro
b/next/src/pages/learning-center/tags/index.astro
index e32ef63bb34..bd6ad8ee08d 100644
--- a/next/src/pages/learning-center/tags/index.astro
+++ b/next/src/pages/learning-center/tags/index.astro
@@ -2,7 +2,7 @@
import Base from '../../../layouts/Base.astro';
import { getLearningPosts, groupByTag } from '../../../lib/content';
-const tags = groupByTag(getLearningPosts('en'));
+const tags = groupByTag(getLearningPosts());
const sorted = [...tags.entries()].sort((a, b) => b[1].posts.length -
a[1].posts.length || a[0].localeCompare(b[0]));
---
<Base title="Tags | Learning Center" locale="en" path="/learning-center/tags/">
diff --git a/next/src/pages/zh/learning-center/[slug].astro
b/next/src/pages/zh/learning-center/[slug].astro
deleted file mode 100644
index b8a763ac0d5..00000000000
--- a/next/src/pages/zh/learning-center/[slug].astro
+++ /dev/null
@@ -1,53 +0,0 @@
----
-import Article from '../../../layouts/Article.astro';
-import { getLearningPosts, relatedPosts } from '../../../lib/content';
-
-export function getStaticPaths() {
- return getLearningPosts('zh').map((post) => ({ params: { slug: post.slug },
props: { post } }));
-}
-const { post } = Astro.props;
-const path = `/learning-center/${post.slug}/`;
-const pool = getLearningPosts('zh');
-const related = relatedPosts(post, pool);
-const idx = pool.findIndex((p) => p.url === post.url);
-const newer = idx > 0 ? pool[idx - 1] : undefined;
-const older = idx >= 0 && idx < pool.length - 1 ? pool[idx + 1] : undefined;
-
-// learning-center pages carry structured FAQ data in frontmatter — emit
FAQPage
-// JSON-LD exactly like the current SEO setup.
-const faq = post.mod.frontmatter.faq;
-const jsonLdExtra: object[] = Array.isArray(faq) && faq.length
- ? [{
- '@context': 'https://schema.org',
- '@type': 'FAQPage',
- mainEntity: faq.map((f: any) => ({
- '@type': 'Question',
- name: f.q,
- acceptedAnswer: { '@type': 'Answer', text: f.a },
- })),
- }]
- : [];
----
-<Article
- post={post}
- locale="zh"
- path={path}
- tagBase="/zh/learning-center"
- schemaType="TechArticle"
- jsonLdExtra={jsonLdExtra}
- related={related}
- newer={newer}
- older={older}
->
- {Array.isArray(faq) && faq.length > 0 && (
- <section class="faq">
- <h2>常见问题</h2>
- {faq.map((f: any) => (
- <details>
- <summary>{f.q}</summary>
- <p>{f.a}</p>
- </details>
- ))}
- </section>
- )}
-</Article>
diff --git a/next/src/pages/zh/learning-center/archive.astro
b/next/src/pages/zh/learning-center/archive.astro
deleted file mode 100644
index b3c4f30a265..00000000000
--- a/next/src/pages/zh/learning-center/archive.astro
+++ /dev/null
@@ -1,5 +0,0 @@
----
-import BlogArchive from '../../../components/BlogArchive.astro';
-import { getLearningPosts } from '../../../lib/content';
----
-<BlogArchive locale="zh" path="/learning-center/archive/"
posts={getLearningPosts('zh')} heading="学习中心归档" kicker="学习中心" />
diff --git a/next/src/pages/zh/learning-center/index.astro
b/next/src/pages/zh/learning-center/index.astro
index 11b7221ad5c..606f3b02175 100644
--- a/next/src/pages/zh/learning-center/index.astro
+++ b/next/src/pages/zh/learning-center/index.astro
@@ -5,10 +5,11 @@ import { getLearningPosts } from '../../../lib/content';
<CollectionPages
locale="zh"
page={1}
- posts={getLearningPosts('zh')}
+ posts={getLearningPosts()}
urlBase="/learning-center"
heading="学习中心"
kicker="学习中心"
- sub="API 网关概念、安全、Kubernetes 与网关对比指南。"
+ sub="API 网关概念、安全、Kubernetes 与网关对比指南。文章内容为英文。"
withTags
+ linkPrefix=""
/>
diff --git a/next/src/pages/zh/learning-center/page/[n].astro
b/next/src/pages/zh/learning-center/page/[n].astro
deleted file mode 100644
index bc9a09a1f12..00000000000
--- a/next/src/pages/zh/learning-center/page/[n].astro
+++ /dev/null
@@ -1,17 +0,0 @@
----
-import CollectionPages from '../../../../components/CollectionPages.astro';
-import { getLearningPosts, paginate } from '../../../../lib/content';
-
-export function getStaticPaths() {
- return paginate(getLearningPosts('zh')).slice(1).map((_, i) => ({ params: {
n: String(i + 2) } }));
-}
----
-<CollectionPages
- locale="zh"
- page={Number(Astro.params.n)}
- posts={getLearningPosts('zh')}
- urlBase="/learning-center"
- heading="学习中心"
- kicker="学习中心"
- withTags
-/>
diff --git a/next/src/pages/zh/learning-center/tags/[tag].astro
b/next/src/pages/zh/learning-center/tags/[tag].astro
deleted file mode 100644
index 53ebed3912a..00000000000
--- a/next/src/pages/zh/learning-center/tags/[tag].astro
+++ /dev/null
@@ -1,24 +0,0 @@
----
-import ListPage from '../../../../layouts/ListPage.astro';
-import { getLearningPosts, groupByTag } from '../../../../lib/content';
-
-export function getStaticPaths() {
- const tags = groupByTag(getLearningPosts('zh'));
- return [...tags.entries()].map(([slug, group]) => ({ params: { tag: slug },
props: group }));
-}
-const { label, posts } = Astro.props;
-const tag = Astro.params.tag;
----
-<ListPage
- title={`${label} | 学习中心`}
- heading={label}
- kicker={'标签'}
- sub={`${posts.length} 篇文章`}
- locale="zh"
- path={`/learning-center/tags/${tag}/`}
- base={`/zh/learning-center/tags/${tag}/`}
- posts={posts}
- current={1}
- total={1}
- tagsUrl={'/zh/learning-center/tags/'}
-/>
diff --git a/next/src/pages/zh/learning-center/tags/index.astro
b/next/src/pages/zh/learning-center/tags/index.astro
deleted file mode 100644
index 34eee02132f..00000000000
--- a/next/src/pages/zh/learning-center/tags/index.astro
+++ /dev/null
@@ -1,24 +0,0 @@
----
-import Base from '../../../../layouts/Base.astro';
-import { getLearningPosts, groupByTag } from '../../../../lib/content';
-
-const tags = groupByTag(getLearningPosts('zh'));
-const sorted = [...tags.entries()].sort((a, b) => b[1].posts.length -
a[1].posts.length || a[0].localeCompare(b[0]));
----
-<Base title="标签 | 学习中心" locale="zh" path="/learning-center/tags/">
- <header class="page-head">
- <div class="container">
- <div class="page-title">
- <p class="kicker">学习中心</p>
- <h1>学习中心标签</h1>
- </div>
- </div>
- </header>
- <div class="container">
- <nav class="tag-row tag-cloud" aria-label="标签">
- {sorted.map(([slug, { label, posts }]) => (
- <a class="tag" href={`/zh/learning-center/tags/${slug}/`}>{label}
<span class="count">{posts.length}</span></a>
- ))}
- </nav>
- </div>
-</Base>
diff --git a/next/src/styles/global.css b/next/src/styles/global.css
index ab89ffd5a30..cc4362736f8 100644
--- a/next/src/styles/global.css
+++ b/next/src/styles/global.css
@@ -276,7 +276,13 @@ a.tag:focus-visible { outline: 2px solid
var(--color-primary); outline-offset: 2
.prose a:hover { color: var(--color-primary); text-decoration-color:
currentcolor; }
.prose :not(pre) > code { overflow-wrap: anywhere; }
.prose iframe { width: 100%; aspect-ratio: 16 / 9; height: auto; border: 0;
border-radius: 8px; }
-.prose img { border-radius: 8px; }
+/* Synced docs images are remote and carry no intrinsic size, so reserve a
+ plausible block before they load — without this the whole article reflows as
+ each diagram arrives. `height: auto` still wins once the real ratio is
known,
+ so correctly-proportioned images are unaffected. */
+.prose img { border-radius: 8px; aspect-ratio: 16 / 9; height: auto;
object-fit: contain; }
+/* Images that declare their own dimensions know better. */
+.prose img[width][height] { aspect-ratio: auto; }
.prose code { font-family: var(--font-mono); font-size: .86em; background:
var(--color-code-bg); padding: .12em .35em; border-radius: 4px; }
.prose pre { background: #0d1117; color: #e6edf3; border-radius: 10px;
padding: 1rem 1.2rem; overflow-x: auto; font-size: .86rem; line-height: 1.6; }
.prose .code-title { background: #21262d; color: #e6edf3; font-family:
var(--font-mono); font-size: .78rem; padding: .45rem 1.2rem; border-radius:
10px 10px 0 0; margin-bottom: 0; }