@master/internal-site 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/dist/astro.mjs +233 -47
- package/dist/config.d.mts +6 -0
- package/dist/localization-LfIbIysE.mjs +356 -0
- package/dist/localization.d.mts +69 -0
- package/dist/localization.mjs +1 -0
- package/package.json +12 -9
- package/src/astro/content-pages.ts +30 -11
- package/src/astro/index.ts +11 -5
- package/src/astro/localized-mdx.ts +45 -0
- package/src/astro/routes.template.ts +4 -0
- package/src/astro/site-context.ts +10 -2
- package/src/astro/style-assets.ts +107 -0
- package/src/benchmarks/BenchmarkBarRows.astro +3 -2
- package/src/benchmarks/BenchmarkBars.astro +1 -1
- package/src/benchmarks/BenchmarkChart.astro +5 -1
- package/src/benchmarks/BenchmarkChartGroup.astro +1 -1
- package/src/benchmarks/BenchmarkDataTable.astro +1 -1
- package/src/benchmarks/BenchmarkFigure.astro +2 -1
- package/src/benchmarks/BenchmarkMetricTable.astro +2 -2
- package/src/benchmarks/BenchmarkMetrics.astro +1 -1
- package/src/benchmarks/BenchmarkSampleSummary.astro +1 -1
- package/src/benchmarks/BenchmarkScrollRegion.svelte +2 -1
- package/src/benchmarks/BenchmarkSource.astro +1 -1
- package/src/benchmarks/BenchmarkStackedBars.astro +4 -3
- package/src/benchmarks/BenchmarkStatus.astro +3 -1
- package/src/benchmarks/localization.ts +22 -0
- package/src/benchmarks/types.ts +2 -0
- package/src/blog/article/BlogArticleContents.svelte +1 -1
- package/src/blog/article/BlogArticleEnd.astro +6 -9
- package/src/blog/article/BlogArticleMasthead.astro +17 -9
- package/src/config.ts +3 -0
- package/src/css/NamespaceUtilityTable.astro +1 -1
- package/src/css/ThemeNumberVariableTable.astro +1 -1
- package/src/css/foundation-data/specimen-content.ts +2 -2
- package/src/css/markdown.ts +64 -14
- package/src/demo/Demo.svelte +10 -4
- package/src/demo/DemoAsset.astro +6 -3
- package/src/demo/DemoAxes.svelte +4 -2
- package/src/demo/DemoCatalog.astro +9 -6
- package/src/demo/DemoComparison.svelte +3 -0
- package/src/demo/DemoContainer.svelte +8 -3
- package/src/demo/DemoControls.svelte +5 -1
- package/src/demo/DemoCopyButton.svelte +9 -4
- package/src/demo/DemoCopyGroup.svelte +6 -2
- package/src/demo/DemoExample.astro +3 -1
- package/src/demo/DemoFeatureSupport.svelte +5 -1
- package/src/demo/DemoGeneratedCSS.svelte +5 -1
- package/src/demo/DemoIndex.astro +6 -3
- package/src/demo/DemoLegend.svelte +3 -1
- package/src/demo/DemoMeasure.svelte +4 -2
- package/src/demo/DemoMedia.svelte +3 -1
- package/src/demo/DemoMotion.svelte +6 -3
- package/src/demo/DemoPaletteGroups.svelte +4 -2
- package/src/demo/DemoScrollArea.svelte +2 -0
- package/src/demo/DemoStyleComparison.astro +6 -3
- package/src/demo/DemoSurface.svelte +2 -0
- package/src/demo/DemoThemeComparison.astro +5 -2
- package/src/demo/DemoTokenTable.astro +5 -3
- package/src/demo/DemoViewTransition.astro +3 -1
- package/src/demo/DemoViewport.svelte +18 -11
- package/src/demo/DemoWaterfall.astro +4 -2
- package/src/demo/ProjectStyleExample.astro +4 -1
- package/src/demo/foundations/FoundationDialog.astro +6 -2
- package/src/demo/foundations/FoundationMedia.astro +1 -0
- package/src/demo/foundations/FoundationShrink.astro +1 -0
- package/src/demo/foundations/FoundationTokensView.astro +16 -5
- package/src/demo/foundations/Palette.svelte +11 -7
- package/src/demo/foundations/dialog-copy.d.ts +1 -0
- package/src/demo/foundations/dialog-copy.js +5 -0
- package/src/demo/illustration-materials.ts +1 -1
- package/src/demo/illustration.css +218 -0
- package/src/docs-shell/common/i18n.config.d.ts +1 -1
- package/src/docs-shell/common/i18n.config.js +4 -12
- package/src/docs-shell/common/locales.d.ts +8 -0
- package/src/docs-shell/common/locales.js +18 -0
- package/src/docs-shell/common/terminology.json +32 -0
- package/src/docs-shell/common/translations.d.ts +3 -0
- package/src/docs-shell/common/translations.js +294 -0
- package/src/docs-shell/components/ArticleHeader.astro +5 -3
- package/src/docs-shell/components/Callout.astro +1 -0
- package/src/docs-shell/components/CodeCopyButton.astro +5 -1
- package/src/docs-shell/components/CodeTabBar.astro +7 -3
- package/src/docs-shell/components/CodeTabs.astro +2 -1
- package/src/docs-shell/components/CodeView.astro +1 -1
- package/src/docs-shell/components/DocHeader.svelte +13 -17
- package/src/docs-shell/components/DocHeading.astro +3 -1
- package/src/docs-shell/components/DocMenuButton.svelte +126 -61
- package/src/docs-shell/components/DocSidebar.svelte +3 -2
- package/src/docs-shell/components/DocVersionSelect.svelte +2 -0
- package/src/docs-shell/components/DocumentAPIIndex.astro +1 -1
- package/src/docs-shell/components/DocumentChoices.astro +1 -1
- package/src/docs-shell/components/DocumentCodeExampleView.astro +3 -1
- package/src/docs-shell/components/DocumentCodeTable.astro +4 -2
- package/src/docs-shell/components/DocumentCopyButton.svelte +3 -0
- package/src/docs-shell/components/DocumentDisclosure.astro +2 -0
- package/src/docs-shell/components/DocumentFileTree.astro +3 -0
- package/src/docs-shell/components/DocumentFlow.astro +4 -1
- package/src/docs-shell/components/DocumentIllustration.astro +8 -12
- package/src/docs-shell/components/DocumentKeyList.astro +6 -4
- package/src/docs-shell/components/DocumentNamespaceTable.astro +5 -3
- package/src/docs-shell/components/DocumentOptionEntry.astro +0 -1
- package/src/docs-shell/components/DocumentOptionList.astro +1 -1
- package/src/docs-shell/components/DocumentOptions.astro +3 -2
- package/src/docs-shell/components/DocumentParameters.astro +1 -1
- package/src/docs-shell/components/DocumentPrompt.astro +2 -0
- package/src/docs-shell/components/DocumentValueList.astro +1 -1
- package/src/docs-shell/components/DocumentationIndex.svelte +8 -7
- package/src/docs-shell/components/ExpandContent.svelte +3 -0
- package/src/docs-shell/components/ExplorerView.astro +1 -0
- package/src/docs-shell/components/GeneratedCSS.astro +4 -1
- package/src/docs-shell/components/Header.svelte +1 -1
- package/src/docs-shell/components/InlineCode.astro +1 -0
- package/src/docs-shell/components/InteractingIndicator.astro +1 -0
- package/src/docs-shell/components/LanguageButton.svelte +1 -0
- package/src/docs-shell/components/LanguageSelect.svelte +2 -0
- package/src/docs-shell/components/Link.svelte +3 -2
- package/src/docs-shell/components/NotFound.astro +6 -4
- package/src/docs-shell/components/PMAction.astro +1 -0
- package/src/docs-shell/components/PageContent.svelte +1 -1
- package/src/docs-shell/components/PageContentDisclosure.astro +2 -1
- package/src/docs-shell/components/PageContentList.svelte +2 -0
- package/src/docs-shell/components/PageNavs.astro +3 -3
- package/src/docs-shell/components/SearchButton.svelte +3 -0
- package/src/docs-shell/components/SearchDialog.svelte +18 -16
- package/src/docs-shell/components/Segments.svelte +2 -0
- package/src/docs-shell/components/StepSection.astro +1 -2
- package/src/docs-shell/components/Tabs.astro +1 -0
- package/src/docs-shell/components/ThemeSelect.svelte +2 -0
- package/src/docs-shell/components/TimeAgo.svelte +6 -6
- package/src/docs-shell/components/markdown/Table.astro +1 -0
- package/src/docs-shell/components/markdown-components.ts +0 -1
- package/src/docs-shell/contexts/search.ts +1 -1
- package/src/docs-shell/factories/Page.astro +10 -5
- package/src/docs-shell/factories/create-page.ts +33 -6
- package/src/docs-shell/layouts/BlogArticle.astro +3 -11
- package/src/docs-shell/remark/markdown-components.js +28 -7
- package/src/docs-shell/remark/slug-and-toc.js +5 -7
- package/src/docs-shell/scripts/code.ts +5 -2
- package/src/docs-shell/scripts/document.ts +1 -1
- package/src/docs-shell/site.ts +9 -1
- package/src/docs-shell/types/Metadata.ts +11 -0
- package/src/docs-shell/utils/document-copy.d.ts +2 -0
- package/src/docs-shell/utils/document-copy.js +18 -0
- package/src/docs-shell/utils/generate-metadata.ts +5 -1
- package/src/docs-shell/utils/head.ts +5 -2
- package/src/docs-shell/utils/i18n-pathname.ts +13 -2
- package/src/docs-shell/utils/i18n.ts +4 -1
- package/src/docs-shell/utils/localized-metadata.ts +22 -0
- package/src/docs-shell/utils/markdown-links.ts +61 -0
- package/src/docs-shell/utils/markdown-tree.ts +19 -6
- package/src/docs-shell/utils/resolve-heading.d.ts +1 -0
- package/src/docs-shell/utils/resolve-heading.js +21 -0
- package/src/docs-shell/utils/search-pages.ts +75 -22
- package/src/examples/components/ArticleTransitionScene.svelte +4 -0
- package/src/examples/layout-system/page.astro +0 -1
- package/src/home/HomeBentoFrame.astro +1 -0
- package/src/home/HomeBentoGrid.astro +1 -0
- package/src/home/bento.css +167 -0
- package/src/home/design.css +1 -1
- package/src/home/hero.css +5 -0
- package/src/home/home.css +4 -576
- package/src/home/segmented-control.css +94 -0
- package/src/layouts/Document.astro +4 -2
- package/src/public/icons/json.svg +1 -0
- package/src/scripts/atomic-class-authoring.ts +98 -0
- package/src/scripts/localization.ts +239 -0
- package/src/scripts/mdx-sample.ts +12 -0
- package/src/scripts/static-social.ts +17 -3
- package/src/scripts/style-delivery.mjs +120 -0
- package/src/sections/blog.astro +1 -1
- package/src/sections/guide.astro +4 -3
- package/src/styles/blog-article.css +10 -7
- package/src/styles/blog-materials.css +13 -0
- package/src/styles/demo.css +0 -1
- package/src/styles/docs-shell/code.css +5 -61
- package/src/styles/docs-shell/documentation-search.css +1 -0
- package/src/styles/docs-shell/header.css +2 -73
- package/src/styles/docs-shell/menu.css +37 -17
- package/src/styles/docs-shell/page-content.css +2 -37
- package/src/styles/docs-shell/prose.css +18 -9
- package/src/styles/docs-shell/steps.css +2 -39
- package/src/styles/document-comparison.css +5 -2
- package/src/styles/documentation-illustration.css +1 -9
- package/src/styles/documentation-index.css +2 -1
- package/src/styles/documents.css +2 -4
- package/src/styles/interface.css +5 -3
- package/src/styles/system/DESIGN.md +34 -14
- package/src/styles/system/browser.css +21 -0
- package/src/styles/system/button-feedback.css +13 -0
- package/src/styles/system/callout.css +14 -0
- package/src/styles/system/chart.css +18 -0
- package/src/styles/system/checks.css +18 -0
- package/src/styles/system/code-panel.css +82 -0
- package/src/styles/system/code-tabs.css +40 -0
- package/src/styles/system/code.css +8 -85
- package/src/styles/system/compare.css +7 -0
- package/src/styles/system/controls.css +10 -109
- package/src/styles/system/copy.css +32 -0
- package/src/styles/system/data.css +10 -134
- package/src/styles/system/dialog.css +16 -0
- package/src/styles/system/disclosure.css +19 -0
- package/src/styles/system/expand.css +11 -0
- package/src/styles/system/fields.css +25 -0
- package/src/styles/system/header-core.css +101 -0
- package/src/styles/system/hint.css +5 -0
- package/src/styles/system/index.css +13 -0
- package/src/styles/system/inline-code.css +4 -0
- package/src/styles/system/kbd.css +4 -0
- package/src/styles/system/logos.css +10 -0
- package/src/styles/system/menu.css +14 -0
- package/src/styles/system/metrics.css +22 -0
- package/src/styles/system/pagehead.css +13 -0
- package/src/styles/system/pager.css +13 -0
- package/src/styles/system/parameters.css +17 -0
- package/src/styles/system/prompt.css +7 -0
- package/src/styles/system/prose.css +4 -75
- package/src/styles/system/range.css +12 -0
- package/src/styles/system/reading.css +44 -0
- package/src/styles/system/scroll.css +6 -0
- package/src/styles/system/search-trigger.css +10 -0
- package/src/styles/system/segmented.css +22 -0
- package/src/styles/system/sheet.css +8 -0
- package/src/styles/system/shell.css +8 -102
- package/src/styles/system/sidebar.css +14 -0
- package/src/styles/system/source.css +5 -0
- package/src/styles/system/specimens.css +3 -0
- package/src/styles/system/stage.css +18 -0
- package/src/styles/system/steps.css +51 -0
- package/src/styles/system/surfaces.css +8 -104
- package/src/styles/system/table.css +23 -0
- package/src/styles/system/toc.css +59 -0
- package/src/styles/system/tooltip.css +6 -0
- package/src/styles/system/tray-code.css +6 -0
- package/src/styles/system/tray.css +20 -0
- package/src/styles/system/tree.css +15 -0
- package/src/styles/system/waterfall.css +14 -0
- package/src/virtual.d.ts +5 -1
- package/src/blog/article/language.ts +0 -29
|
@@ -3,6 +3,7 @@ import { mergeAssets, type PageAssets, type PropagatedModule } from '@master/int
|
|
|
3
3
|
import { createStaticPaths, directoriesOf, localesOf, sectionOf, type ParamsModule, type RouteProps, type Section } from '@master/internal-site/astro/route-paths'
|
|
4
4
|
import type { Metadata } from '@master/internal-site/types/Metadata'
|
|
5
5
|
import type { Props } from '@master/internal-site/types/Props'
|
|
6
|
+
import { loadLocalizedContent } from '@master/internal-site/factories/create-page'
|
|
6
7
|
|
|
7
8
|
export { localizePathname, sectionOf } from '@master/internal-site/astro/route-paths'
|
|
8
9
|
export type { RouteProps, Section } from '@master/internal-site/astro/route-paths'
|
|
@@ -18,6 +19,7 @@ interface PageModule {
|
|
|
18
19
|
metadata?: Partial<Metadata>
|
|
19
20
|
generateMetadata?: (props: Pick<Props, 'params'>) => Promise<Partial<Metadata>> | Partial<Metadata>
|
|
20
21
|
/** `false` for pages that render no document surfaces, code or prose, such as indexes. */
|
|
22
|
+
/** Article typography only; feature components own their CSS dependencies. */
|
|
21
23
|
documentStyles?: boolean
|
|
22
24
|
}
|
|
23
25
|
|
|
@@ -76,6 +78,8 @@ export async function loadRoute({ file, locale, params }: RouteProps): Promise<L
|
|
|
76
78
|
const metadata = (module.generateMetadata ? await module.generateMetadata(props) : module.metadata) ?? {}
|
|
77
79
|
const layers = [module.default]
|
|
78
80
|
const propagated: PropagatedModule<unknown>[] = [page.propagated]
|
|
81
|
+
const localized = await loadLocalizedContent(locale, metadata)
|
|
82
|
+
if (localized) { await localized.getMod(); propagated.push(localized) }
|
|
79
83
|
const directories = directoriesOf(file, contentRoot)
|
|
80
84
|
for (let depth = directories.length; depth > 0; depth--) {
|
|
81
85
|
const layoutFile = `${contentRoot}${directories.slice(0, depth).join('/')}/layout.astro`
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import config from 'virtual:master-site/config'
|
|
2
2
|
import { createSite, type Site, type SiteContextValue } from '../docs-shell/site'
|
|
3
|
+
import sharedTranslations from '../docs-shell/common/translations'
|
|
3
4
|
|
|
4
5
|
export function localeOfPathname(pathname: string): string {
|
|
5
6
|
const segment = pathname.split('/')[1]
|
|
@@ -18,10 +19,17 @@ export function canonicalPathname(pathname: string) {
|
|
|
18
19
|
export function siteContextOf(url: URL): SiteContextValue {
|
|
19
20
|
const pathname = canonicalPathname(url.pathname)
|
|
20
21
|
const locale = localeOfPathname(pathname)
|
|
21
|
-
const { translations,
|
|
22
|
-
|
|
22
|
+
const { translations, defaultLocale, locales, hreflangOfLocale, nameOfLocale, localizablePathnameRoots, localizableOrigins } = config.i18n
|
|
23
|
+
// Only public navigation settings belong in the serialized island context.
|
|
24
|
+
const i18n = { defaultLocale, locales, hreflangOfLocale, nameOfLocale, localizablePathnameRoots, localizableOrigins }
|
|
25
|
+
return { locale, pathname, translations: { ...sharedTranslations[locale], ...translations[locale] }, i18n }
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
export function siteOf(astro: { url: URL }): Site {
|
|
26
29
|
return createSite(siteContextOf(astro.url))
|
|
27
30
|
}
|
|
31
|
+
|
|
32
|
+
/** Request-local authored figure captions and other article-specific copy. */
|
|
33
|
+
export function documentCopyOf(astro: { locals: object }): Record<string, any> {
|
|
34
|
+
return (astro.locals as Record<string, any>).masterDocument ?? {}
|
|
35
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { readdir, readFile, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { dirname, resolve } from 'node:path'
|
|
4
|
+
|
|
5
|
+
/** Collapse only the compiler's unconditional, same-directory asset links.
|
|
6
|
+
* This is concatenation of already compiled CSS, not a second CSS compiler.
|
|
7
|
+
* Keep conditions, layers, URLs, repeated occurrences and unrelated imports intact.
|
|
8
|
+
*/
|
|
9
|
+
export async function inlineGeneratedStyles(file: string, ancestors: string[] = []): Promise<string> {
|
|
10
|
+
if (ancestors.includes(file)) throw new Error(`Cyclic generated stylesheet: ${file}`)
|
|
11
|
+
const source = await readFile(file, 'utf8')
|
|
12
|
+
let code = '', cursor = 0
|
|
13
|
+
// Generated imports form a leading prologue. Never search declarations or
|
|
14
|
+
// comments for import-looking text (for example a teaching sample's content).
|
|
15
|
+
while (cursor < source.length) {
|
|
16
|
+
const rest = source.slice(cursor)
|
|
17
|
+
const trivia = /^(?:\s+|\/\*[\s\S]*?\*\/|@layer\s+[\w,\s-]+;)/.exec(rest)
|
|
18
|
+
if (trivia) { code += trivia[0]; cursor += trivia[0].length; continue }
|
|
19
|
+
const match = /^@import\s+["'](\.\/master-css-[\w-]+\.css)["']\s*;/.exec(rest)
|
|
20
|
+
if (!match) break
|
|
21
|
+
code += await inlineGeneratedStyles(resolve(dirname(file), match[1]), [...ancestors, file]) + '\n'
|
|
22
|
+
cursor += match[0].length
|
|
23
|
+
}
|
|
24
|
+
const expanded = code + source.slice(cursor)
|
|
25
|
+
// A remaining import would become invalid after an inlined declaration.
|
|
26
|
+
// Retain this entry intact, including its original import prologue. A match
|
|
27
|
+
// inside a comment/string merely skips this optional optimization.
|
|
28
|
+
return /@(?:import|charset|namespace)\b/.test(expanded) ? source : expanded
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Preserve HTML link order and each reusable entry's cache identity. Originals
|
|
32
|
+
* remain available to any JS/CSS references; no client loading logic is added. */
|
|
33
|
+
export async function compactStyleAssets(output: string) {
|
|
34
|
+
const root = resolve(output), replacements = new Map<string, string>()
|
|
35
|
+
const documents = new Map<string, string>(), users = new Map<string, string[]>()
|
|
36
|
+
const boundaries = new Set<string>()
|
|
37
|
+
async function emit(code: string) {
|
|
38
|
+
const hash = createHash('sha256').update(code).digest('hex').slice(0, 20)
|
|
39
|
+
const href = `/_astro/site-style-${hash}.css`
|
|
40
|
+
await writeFile(resolve(root, '.' + href), code)
|
|
41
|
+
return href
|
|
42
|
+
}
|
|
43
|
+
async function walk(directory: string): Promise<void> {
|
|
44
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
45
|
+
const file = resolve(directory, entry.name)
|
|
46
|
+
if (entry.isDirectory()) { await walk(file); continue }
|
|
47
|
+
if (!entry.name.endsWith('.html')) continue
|
|
48
|
+
let html = await readFile(file, 'utf8')
|
|
49
|
+
const links = [...html.matchAll(/<link\b(?=[^>]*\brel="stylesheet")(?=[^>]*\bhref="(\/_astro\/[^"/]+\.css)")[^>]*>/g)]
|
|
50
|
+
for (const match of links) {
|
|
51
|
+
const href = match[1]
|
|
52
|
+
if (!replacements.has(href)) {
|
|
53
|
+
const input = resolve(root, '.' + href)
|
|
54
|
+
const source = await readFile(input, 'utf8')
|
|
55
|
+
const code = await inlineGeneratedStyles(input)
|
|
56
|
+
const next = source === code ? href : await emit(code)
|
|
57
|
+
replacements.set(href, next)
|
|
58
|
+
}
|
|
59
|
+
const next = replacements.get(href)!
|
|
60
|
+
if (/\/(site|interface|documents)\./.test(href)) boundaries.add(next)
|
|
61
|
+
const pages = users.get(next) ?? []
|
|
62
|
+
if (!pages.includes(file)) pages.push(file)
|
|
63
|
+
users.set(next, pages)
|
|
64
|
+
html = html.replace(match[0], match[0].replace(`href="${href}"`, `href="${next}"`))
|
|
65
|
+
}
|
|
66
|
+
documents.set(file, html)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
await walk(root)
|
|
70
|
+
// Co-delivered feature assets share a cache boundary. Merge only adjacent,
|
|
71
|
+
// plain links with identical route usage; never reorder a rule or pull a
|
|
72
|
+
// feature onto a route that did not already load it.
|
|
73
|
+
const signatures = new Map([...users].map(([href, pages]) => [href, pages.sort().join('\n')]))
|
|
74
|
+
const adjacent = new Map<string, Set<string>>()
|
|
75
|
+
for (const [file, html] of documents) {
|
|
76
|
+
for (const match of html.matchAll(/(?=<link rel="stylesheet" href="([^"\s]+)">\s*<link rel="stylesheet" href="([^"\s]+)">)/g)) {
|
|
77
|
+
const key = `${match[1]}\n${match[2]}`
|
|
78
|
+
const pages = adjacent.get(key) ?? new Set<string>()
|
|
79
|
+
pages.add(file); adjacent.set(key, pages)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for (const [file, source] of documents) {
|
|
83
|
+
let html = source
|
|
84
|
+
const runs = [...source.matchAll(/(?:<link rel="stylesheet" href="\/_astro\/[^"/]+\.css">\s*){2,}/g)]
|
|
85
|
+
for (const run of runs) {
|
|
86
|
+
const links = [...run[0].matchAll(/<link rel="stylesheet" href="([^"\s]+)">\s*/g)]
|
|
87
|
+
let replacement = '', group: typeof links = []
|
|
88
|
+
async function flush() {
|
|
89
|
+
if (group.length > 1) {
|
|
90
|
+
const code = (await Promise.all(group.map(link => readFile(resolve(root, '.' + link[1]), 'utf8')))).join('\n')
|
|
91
|
+
// An unresolved import must stay at the start of its own stylesheet.
|
|
92
|
+
replacement += code.includes('@import') ? group.map(link => link[0]).join('') : `<link rel="stylesheet" href="${await emit(code)}">`
|
|
93
|
+
} else replacement += group.map(link => link[0]).join('')
|
|
94
|
+
group = []
|
|
95
|
+
}
|
|
96
|
+
for (const link of links) {
|
|
97
|
+
if (boundaries.has(link[1])) { await flush(); replacement += link[0]; continue }
|
|
98
|
+
if (group.length && (signatures.get(group[0][1]) !== signatures.get(link[1])
|
|
99
|
+
|| adjacent.get(`${group.at(-1)![1]}\n${link[1]}`)?.size !== users.get(link[1])?.length)) await flush()
|
|
100
|
+
group.push(link)
|
|
101
|
+
}
|
|
102
|
+
await flush()
|
|
103
|
+
html = html.replace(run[0], replacement)
|
|
104
|
+
}
|
|
105
|
+
await writeFile(file, html)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
import '@master/internal-site/styles/system/
|
|
2
|
+
import '@master/internal-site/styles/system/chart.css'
|
|
3
|
+
import '@master/internal-site/styles/system/metrics.css'
|
|
3
4
|
import '@master/internal-site/styles/benchmarks.css'
|
|
4
5
|
import { siteOf } from '@master/internal-site/astro/site-context'
|
|
5
6
|
import { uniqueId } from '@master/internal-site/utils/unique-id'
|
|
@@ -27,7 +28,7 @@ const resolvedMax = max ?? Math.max(0, ...items.map((item) => item.max ?? item.v
|
|
|
27
28
|
{item.detail && <small>{t(item.detail)}</small>}
|
|
28
29
|
</span>
|
|
29
30
|
<div class="ui-bar" role="meter" aria-labelledby={`${labelId} ${valueId}`} aria-valuemin={0} aria-valuemax={itemMax} aria-valuenow={item.value}>
|
|
30
|
-
<i data-tone={item.color === 'neutral' ? 'muted' : undefined} style={{ width: `${clampPercent(item.value, itemMax)}
|
|
31
|
+
<i data-tone={item.color === 'neutral' ? 'muted' : undefined} style={{ width: `${clampPercent(item.value, itemMax)}%`, background: item.fill }} />
|
|
31
32
|
</div>
|
|
32
33
|
<output id={valueId}>{valueLabel}</output>
|
|
33
34
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import '@master/internal-site/styles/system/
|
|
2
|
+
import '@master/internal-site/styles/system/chart.css'
|
|
3
3
|
import '@master/internal-site/styles/benchmarks.css'
|
|
4
4
|
import BenchmarkBarRows from '@master/internal-site/benchmarks/BenchmarkBarRows.astro'
|
|
5
5
|
import type { BenchmarkBarsProps } from '@master/internal-site/benchmarks/types.ts'
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { siteOf } from '@master/internal-site/astro/site-context'
|
|
3
|
+
import { localizeBenchmarkChart } from './localization'
|
|
4
|
+
import '@master/internal-site/styles/system/table.css'
|
|
2
5
|
import ExpandContent from '@master/internal-site/components/ExpandContent.svelte'
|
|
3
6
|
import BenchmarkBars from '@master/internal-site/benchmarks/BenchmarkBars.astro'
|
|
4
7
|
import BenchmarkMetricTable from '@master/internal-site/benchmarks/BenchmarkMetricTable.astro'
|
|
@@ -10,7 +13,8 @@ interface Props {
|
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
/** Renders an authored chart model; `utils/benchmark-content` exports the same model as Markdown. */
|
|
13
|
-
const {
|
|
16
|
+
const { t } = siteOf(Astro)
|
|
17
|
+
const chart = localizeBenchmarkChart(Astro.props.chart, t)
|
|
14
18
|
---
|
|
15
19
|
|
|
16
20
|
{chart.kind === 'bars' && <BenchmarkBars items={chart.items} max={chart.max} unit={chart.unit} valueFormatter={chart.valueFormatter} />}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import '@master/internal-site/styles/system/
|
|
2
|
+
import '@master/internal-site/styles/system/chart.css'
|
|
3
3
|
import { siteOf } from '@master/internal-site/astro/site-context'
|
|
4
4
|
import BenchmarkBarRows from '@master/internal-site/benchmarks/BenchmarkBarRows.astro'
|
|
5
5
|
import type { BenchmarkBarItem } from '@master/internal-site/benchmarks/types.ts'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import '@master/internal-site/styles/system/
|
|
2
|
+
import '@master/internal-site/styles/system/disclosure.css'
|
|
3
3
|
import '@master/internal-site/styles/benchmarks.css'
|
|
4
4
|
import { siteOf } from '@master/internal-site/astro/site-context'
|
|
5
5
|
import BenchmarkScrollRegion from '@master/internal-site/benchmarks/BenchmarkScrollRegion.svelte'
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
import '@master/internal-site/styles/system/
|
|
2
|
+
import '@master/internal-site/styles/system/tray.css'
|
|
3
|
+
import '@master/internal-site/styles/system/tray-code.css'
|
|
3
4
|
import { siteOf } from '@master/internal-site/astro/site-context'
|
|
4
5
|
import RenderNode from '@master/internal-site/components/RenderNode.astro'
|
|
5
6
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import '@master/internal-site/styles/system/
|
|
2
|
+
import '@master/internal-site/styles/system/table.css'
|
|
3
3
|
import { siteOf } from '@master/internal-site/astro/site-context'
|
|
4
4
|
import BenchmarkStatus from '@master/internal-site/benchmarks/BenchmarkStatus.astro'
|
|
5
5
|
import type { BenchmarkMetric } from '@master/internal-site/benchmarks/types.ts'
|
|
@@ -16,7 +16,7 @@ const hasDetails = metrics.some((metric) => metric.detail)
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
{/* Preserve keyboard access to wide metric tables. */}
|
|
19
|
-
<div class="ui-table" role="region" aria-label={label} tabindex="0">
|
|
19
|
+
<div class="ui-table" role="region" aria-label={t(label)} tabindex="0">
|
|
20
20
|
<table>
|
|
21
21
|
<thead>
|
|
22
22
|
<tr>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import '@master/internal-site/styles/system/
|
|
2
|
+
import '@master/internal-site/styles/system/metrics.css'
|
|
3
3
|
import { siteOf } from '@master/internal-site/astro/site-context'
|
|
4
4
|
import BenchmarkStatus from '@master/internal-site/benchmarks/BenchmarkStatus.astro'
|
|
5
5
|
import type { BenchmarkMetric } from '@master/internal-site/benchmarks/types.ts'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import '@master/internal-site/styles/system/
|
|
2
|
+
import '@master/internal-site/styles/system/metrics.css'
|
|
3
3
|
import { siteOf } from '@master/internal-site/astro/site-context'
|
|
4
4
|
import type { BenchmarkSampleSummaryStats } from '@master/internal-site/benchmarks/types.ts'
|
|
5
5
|
import { formatMetricValue } from '@master/internal-site/benchmarks/utils.ts'
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
import '@master/internal-site/styles/system/
|
|
2
|
+
import '@master/internal-site/styles/system/chart.css'
|
|
3
|
+
import '@master/internal-site/styles/system/metrics.css'
|
|
3
4
|
import '@master/internal-site/styles/system/specimens.css'
|
|
4
5
|
import '@master/internal-site/styles/benchmarks.css'
|
|
5
6
|
import { siteOf } from '@master/internal-site/astro/site-context'
|
|
@@ -34,11 +35,11 @@ const labelPrefix = uniqueId(Astro, 'benchmark-stacked-bars')
|
|
|
34
35
|
<div class="ui-bar" data-variant="stacked" role="img" aria-labelledby={`${labelId} ${valueId}`}>
|
|
35
36
|
{item.segments.map((segment, segmentIndex) => <i
|
|
36
37
|
data-tone={benchmarkTone(segment.color ?? benchmarkColors[segmentIndex % benchmarkColors.length])}
|
|
37
|
-
title={`${segment.label}: ${segment.valueLabel ?? formatMetricValue(segment.value, unit)}`}
|
|
38
|
+
title={`${t(segment.label)}: ${segment.valueLabel ?? formatMetricValue(segment.value, unit)}`}
|
|
38
39
|
style={{ width: `${clampPercent(segment.value, max ?? total)}%` }} />)}
|
|
39
40
|
</div>
|
|
40
41
|
<output id={valueId}>{totalLabel}</output>
|
|
41
|
-
<ul class="ui-legend" role="group" aria-label={
|
|
42
|
+
<ul class="ui-legend" role="group" aria-label={t('{label} breakdown').replace('{label}', t(item.label))}>
|
|
42
43
|
{item.segments.map((segment, segmentIndex) => {
|
|
43
44
|
const valueLabel = segment.valueLabel ?? (valueFormatter ? valueFormatter(segment.value) : formatMetricValue(segment.value, unit))
|
|
44
45
|
return <li>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { siteOf } from '@master/internal-site/astro/site-context'
|
|
3
|
+
const { t } = siteOf(Astro)
|
|
2
4
|
import '@master/internal-site/styles/system/specimens.css'
|
|
3
5
|
import type { BenchmarkTone } from '@master/internal-site/benchmarks/types.ts'
|
|
4
6
|
import { benchmarkStatusTones } from '@master/internal-site/benchmarks/utils.ts'
|
|
@@ -13,4 +15,4 @@ const names: Record<Exclude<BenchmarkTone, 'neutral'>, string> = { good: 'Good',
|
|
|
13
15
|
const { tone } = Astro.props
|
|
14
16
|
---
|
|
15
17
|
|
|
16
|
-
{tone && tone !== 'neutral' && <span class="ui-badge" data-tone={benchmarkStatusTones[tone]} data-variant="dot" data-size="sm">{names[tone]}</span>}
|
|
18
|
+
{tone && tone !== 'neutral' && <span class="ui-badge" data-tone={benchmarkStatusTones[tone]} data-variant="dot" data-size="sm">{t(names[tone])}</span>}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Reader copy only: numeric values, technical units, fixture rows and formatters stay canonical. */
|
|
2
|
+
const proseFields = new Set(['label', 'labelNote', 'title', 'detail', 'description', 'caption', 'headers'])
|
|
3
|
+
|
|
4
|
+
function mapCopy<T>(model: T, translate: (text: string) => string): T {
|
|
5
|
+
const visit = (value: unknown, field: string): unknown => {
|
|
6
|
+
if (typeof value === 'string') return proseFields.has(field) && value.trim() ? translate(value) : value
|
|
7
|
+
if (Array.isArray(value)) return value.map(item => visit(item, field))
|
|
8
|
+
if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, visit(item, key)]))
|
|
9
|
+
return value
|
|
10
|
+
}
|
|
11
|
+
return visit(model, '') as T
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function benchmarkCopyKeys(model: unknown): string[] {
|
|
15
|
+
const keys = new Set<string>()
|
|
16
|
+
mapCopy(model, text => { keys.add(text); return text })
|
|
17
|
+
return [...keys].sort()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function localizeBenchmarkChart<T>(model: T, translate: (text: string) => string): T {
|
|
21
|
+
return mapCopy(model, translate)
|
|
22
|
+
}
|
package/src/benchmarks/types.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface BenchmarkBarItem {
|
|
|
20
20
|
detail?: string
|
|
21
21
|
icon?: BenchmarkIcon
|
|
22
22
|
color?: BenchmarkColor
|
|
23
|
+
/** Explicit series identity, such as a brand's CSS color. Overrides the neutral fill. */
|
|
24
|
+
fill?: string
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export interface BenchmarkSegment {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import '@master/internal-site/styles/
|
|
2
|
+
import '@master/internal-site/styles/system/toc.css'
|
|
3
3
|
import PageContentList from '@master/internal-site/components/PageContentList.svelte'
|
|
4
4
|
import PageContentProvider from '@master/internal-site/components/PageContentProvider.svelte'
|
|
5
5
|
import type { PageContentItem } from '@master/internal-site/components/page-content.svelte'
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
2
|
+
import { formatDate } from '../../docs-shell/common/locales'
|
|
3
3
|
import IconArrowLeft from '@tabler/icons-svelte-runes/icons/arrow-left'
|
|
4
4
|
import IconArrowRight from '@tabler/icons-svelte-runes/icons/arrow-right'
|
|
5
5
|
import Link from '@master/internal-site/components/Link.svelte'
|
|
6
6
|
import { siteOf } from '@master/internal-site/astro/site-context'
|
|
7
7
|
import TitleLines from './title-lines.astro'
|
|
8
|
-
import { sourceLanguageOf } from './language'
|
|
9
8
|
import type { BlogArticleNeighbor } from './neighbors'
|
|
10
9
|
|
|
11
10
|
interface Props {
|
|
@@ -20,14 +19,12 @@ interface Props {
|
|
|
20
19
|
* article as quiet links.
|
|
21
20
|
*/
|
|
22
21
|
const { newer, older, showCategory = true } = Astro.props
|
|
23
|
-
const { t: $,
|
|
24
|
-
const tw = locale === 'tw'
|
|
25
|
-
const languageOf = (text?: string) => sourceLanguageOf(text, { ...i18n, locale })
|
|
22
|
+
const { t: $, locale } = siteOf(Astro)
|
|
26
23
|
// Readers who finish a post usually continue backwards through the archive; the newest post leads with the one before it.
|
|
27
24
|
const lead = older ?? newer
|
|
28
25
|
const other = lead === older ? newer : undefined
|
|
29
26
|
// Written out in full, as on the post's own header and on the index's featured sheet.
|
|
30
|
-
const date = (value: string) =>
|
|
27
|
+
const date = (value: string) => formatDate(value, locale)
|
|
31
28
|
---
|
|
32
29
|
|
|
33
30
|
<nav class="blog-article-end blog-rails blog-rails-rule" aria-labelledby="blog-article-end-title">
|
|
@@ -38,8 +35,8 @@ const date = (value: string) => dayjs(value).format(tw ? 'YYYY 年 M 月 D 日'
|
|
|
38
35
|
{showCategory && lead.category && <span>{$(lead.category)}</span>}
|
|
39
36
|
{lead.date && <time datetime={lead.date}>{date(lead.date)}</time>}
|
|
40
37
|
</span>
|
|
41
|
-
<span class="blog-article-next-title"
|
|
42
|
-
{lead.description && <span class="blog-article-next-description"
|
|
38
|
+
<span class="blog-article-next-title"><TitleLines title={$(lead.title)} class="blog-article-next-title-line" /><IconArrowRight class="blog-article-next-arrow" aria-hidden="true" size={22} stroke={1.25} /></span>
|
|
39
|
+
{lead.description && <span class="blog-article-next-description">{$(lead.description)}</span>}
|
|
43
40
|
</Link>
|
|
44
41
|
)}
|
|
45
42
|
<div class="blog-article-end-links">
|
|
@@ -47,7 +44,7 @@ const date = (value: string) => dayjs(value).format(tw ? 'YYYY 年 M 月 D 日'
|
|
|
47
44
|
{other && (
|
|
48
45
|
<Link class="blog-article-end-link" href={other.pathname}>
|
|
49
46
|
<span class="blog-article-end-link-label">{$('Newer')}</span>
|
|
50
|
-
<span class="blog-article-end-link-title"
|
|
47
|
+
<span class="blog-article-end-link-title">{$(other.title)}</span>
|
|
51
48
|
<IconArrowRight aria-hidden="true" size={14} stroke={1.75} />
|
|
52
49
|
</Link>
|
|
53
50
|
)}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
2
|
+
import { formatDate } from '../../docs-shell/common/locales'
|
|
3
3
|
import authors from '@master/internal-site/data/authors'
|
|
4
4
|
import Image from '@master/internal-site/components/Image.svelte'
|
|
5
5
|
import Link from '@master/internal-site/components/Link.svelte'
|
|
6
6
|
import { siteOf } from '@master/internal-site/astro/site-context'
|
|
7
7
|
import TitleLines from './title-lines.astro'
|
|
8
|
-
import {
|
|
8
|
+
import type { DefinedMetadata } from '@master/internal-site/types/Metadata'
|
|
9
9
|
|
|
10
10
|
interface Props {
|
|
11
|
-
metadata:
|
|
11
|
+
metadata: DefinedMetadata
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -17,11 +17,9 @@ interface Props {
|
|
|
17
17
|
* falls on it from above and to the left.
|
|
18
18
|
*/
|
|
19
19
|
const { metadata } = Astro.props
|
|
20
|
-
const { t: $,
|
|
21
|
-
const tw = locale === 'tw'
|
|
20
|
+
const { t: $, locale } = siteOf(Astro)
|
|
22
21
|
const source: string = metadata.title?.absolute || metadata.title
|
|
23
22
|
const title: string = $(source)
|
|
24
|
-
const languageOf = (text?: string) => sourceLanguageOf(text, { ...i18n, locale })
|
|
25
23
|
const people = (metadata.authors ?? [])
|
|
26
24
|
.map(({ name }: { name: string }) => authors.find((author) => author.name === name))
|
|
27
25
|
.filter(Boolean) as typeof authors
|
|
@@ -31,11 +29,11 @@ const people = (metadata.authors ?? [])
|
|
|
31
29
|
{(metadata.category || metadata.date) && (
|
|
32
30
|
<p class="blog-article-meta">
|
|
33
31
|
{metadata.category && <span>{$(metadata.category)}</span>}
|
|
34
|
-
{metadata.date && <time datetime={metadata.date}>{
|
|
32
|
+
{metadata.date && <time datetime={metadata.date}>{formatDate(metadata.date, locale)}</time>}
|
|
35
33
|
</p>
|
|
36
34
|
)}
|
|
37
|
-
<h1 class="blog-article-title blog-stone-text"
|
|
38
|
-
{metadata.description && <p class="blog-article-dek"
|
|
35
|
+
<h1 class="blog-article-title blog-stone-text"><TitleLines title={title} class="blog-article-title-line" /></h1>
|
|
36
|
+
{metadata.description && <p class="blog-article-dek">{$(metadata.description)}</p>}
|
|
39
37
|
{people.length > 0 && (
|
|
40
38
|
<div class="blog-article-byline">
|
|
41
39
|
<span class="blog-article-byline-label">{$('Written by')}</span>
|
|
@@ -58,4 +56,14 @@ const people = (metadata.authors ?? [])
|
|
|
58
56
|
</ul>
|
|
59
57
|
</div>
|
|
60
58
|
)}
|
|
59
|
+
{metadata.cover && (
|
|
60
|
+
<img
|
|
61
|
+
class="blog-article-cover"
|
|
62
|
+
src={metadata.cover.src}
|
|
63
|
+
alt={metadata.cover.alt}
|
|
64
|
+
width={metadata.cover.width}
|
|
65
|
+
height={metadata.cover.height}
|
|
66
|
+
fetchpriority="high"
|
|
67
|
+
/>
|
|
68
|
+
)}
|
|
61
69
|
</header>
|
package/src/config.ts
CHANGED
|
@@ -28,10 +28,13 @@ export interface SiteI18nConfig {
|
|
|
28
28
|
hreflangOfLocale: Record<string, string>
|
|
29
29
|
nameOfLocale: Record<string, string>
|
|
30
30
|
localizablePathnameRoots: string[]
|
|
31
|
+
localizableOrigins?: Record<string, readonly string[]>
|
|
31
32
|
translations: Record<string, Record<string, string>>
|
|
32
33
|
sectionLocales?: Record<string, readonly string[]>
|
|
33
34
|
/** These sections emit translated URLs only when translated MDX exists. */
|
|
34
35
|
translatedOnly?: readonly string[]
|
|
36
|
+
/** Build-only resource loader and version-controlled explicit translation reviews. */
|
|
37
|
+
validation?: { reviews: URL; resources: URL }
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
export interface DocumentConfig {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import '@master/internal-site/styles/system/
|
|
2
|
+
import '@master/internal-site/styles/system/table.css'
|
|
3
3
|
import { siteOf } from '@master/internal-site/astro/site-context'
|
|
4
4
|
import { uniqueId } from '@master/internal-site/utils/unique-id'
|
|
5
5
|
import InlineCode from '@master/internal-site/components/InlineCode.astro'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import '@master/internal-site/styles/system/
|
|
2
|
+
import '@master/internal-site/styles/system/table.css'
|
|
3
3
|
import { siteOf } from '@master/internal-site/astro/site-context'
|
|
4
4
|
import { uniqueId } from '@master/internal-site/utils/unique-id'
|
|
5
5
|
import InlineCode from '@master/internal-site/components/InlineCode.astro'
|
|
@@ -4,7 +4,7 @@ import { configuredMarkupMarkdown } from '@master/internal-site/css/configured-e
|
|
|
4
4
|
export function specimenFactsMarkdown(namespace: string, keys?: string[]) {
|
|
5
5
|
return specimenTokens(namespace, keys).map(token => `- \`--${token.name}\`: ${token.values.map(value => `\`${value.value}\` (${value.path.join(' → ')})`).join('; ')}${tokenAdvice(namespace, token.key) ? ` — ${tokenAdvice(namespace, token.key)}` : ''}`).join('\n')
|
|
6
6
|
}
|
|
7
|
-
export function selectedSpecimensMarkdown(namespace: string, keys: string[]) {
|
|
7
|
+
export function selectedSpecimensMarkdown(namespace: string, keys: string[], t: (text: string) => string = text => text) {
|
|
8
8
|
const scene = foundationScene(namespace, keys)
|
|
9
|
-
return `${scene.caption}\n\n${specimenFactsMarkdown(namespace, keys)}${scene.html && namespace !== 'color' ? `\n\n${configuredMarkupMarkdown(scene.css ?? '', scene.html)}` : ''}`
|
|
9
|
+
return `${t(scene.caption ?? '')}\n\n${specimenFactsMarkdown(namespace, keys)}${scene.html && namespace !== 'color' ? `\n\n${configuredMarkupMarkdown(scene.css ?? '', scene.html)}` : ''}`
|
|
10
10
|
}
|