@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.
Files changed (239) hide show
  1. package/README.md +43 -0
  2. package/dist/astro.mjs +233 -47
  3. package/dist/config.d.mts +6 -0
  4. package/dist/localization-LfIbIysE.mjs +356 -0
  5. package/dist/localization.d.mts +69 -0
  6. package/dist/localization.mjs +1 -0
  7. package/package.json +12 -9
  8. package/src/astro/content-pages.ts +30 -11
  9. package/src/astro/index.ts +11 -5
  10. package/src/astro/localized-mdx.ts +45 -0
  11. package/src/astro/routes.template.ts +4 -0
  12. package/src/astro/site-context.ts +10 -2
  13. package/src/astro/style-assets.ts +107 -0
  14. package/src/benchmarks/BenchmarkBarRows.astro +3 -2
  15. package/src/benchmarks/BenchmarkBars.astro +1 -1
  16. package/src/benchmarks/BenchmarkChart.astro +5 -1
  17. package/src/benchmarks/BenchmarkChartGroup.astro +1 -1
  18. package/src/benchmarks/BenchmarkDataTable.astro +1 -1
  19. package/src/benchmarks/BenchmarkFigure.astro +2 -1
  20. package/src/benchmarks/BenchmarkMetricTable.astro +2 -2
  21. package/src/benchmarks/BenchmarkMetrics.astro +1 -1
  22. package/src/benchmarks/BenchmarkSampleSummary.astro +1 -1
  23. package/src/benchmarks/BenchmarkScrollRegion.svelte +2 -1
  24. package/src/benchmarks/BenchmarkSource.astro +1 -1
  25. package/src/benchmarks/BenchmarkStackedBars.astro +4 -3
  26. package/src/benchmarks/BenchmarkStatus.astro +3 -1
  27. package/src/benchmarks/localization.ts +22 -0
  28. package/src/benchmarks/types.ts +2 -0
  29. package/src/blog/article/BlogArticleContents.svelte +1 -1
  30. package/src/blog/article/BlogArticleEnd.astro +6 -9
  31. package/src/blog/article/BlogArticleMasthead.astro +17 -9
  32. package/src/config.ts +3 -0
  33. package/src/css/NamespaceUtilityTable.astro +1 -1
  34. package/src/css/ThemeNumberVariableTable.astro +1 -1
  35. package/src/css/foundation-data/specimen-content.ts +2 -2
  36. package/src/css/markdown.ts +64 -14
  37. package/src/demo/Demo.svelte +10 -4
  38. package/src/demo/DemoAsset.astro +6 -3
  39. package/src/demo/DemoAxes.svelte +4 -2
  40. package/src/demo/DemoCatalog.astro +9 -6
  41. package/src/demo/DemoComparison.svelte +3 -0
  42. package/src/demo/DemoContainer.svelte +8 -3
  43. package/src/demo/DemoControls.svelte +5 -1
  44. package/src/demo/DemoCopyButton.svelte +9 -4
  45. package/src/demo/DemoCopyGroup.svelte +6 -2
  46. package/src/demo/DemoExample.astro +3 -1
  47. package/src/demo/DemoFeatureSupport.svelte +5 -1
  48. package/src/demo/DemoGeneratedCSS.svelte +5 -1
  49. package/src/demo/DemoIndex.astro +6 -3
  50. package/src/demo/DemoLegend.svelte +3 -1
  51. package/src/demo/DemoMeasure.svelte +4 -2
  52. package/src/demo/DemoMedia.svelte +3 -1
  53. package/src/demo/DemoMotion.svelte +6 -3
  54. package/src/demo/DemoPaletteGroups.svelte +4 -2
  55. package/src/demo/DemoScrollArea.svelte +2 -0
  56. package/src/demo/DemoStyleComparison.astro +6 -3
  57. package/src/demo/DemoSurface.svelte +2 -0
  58. package/src/demo/DemoThemeComparison.astro +5 -2
  59. package/src/demo/DemoTokenTable.astro +5 -3
  60. package/src/demo/DemoViewTransition.astro +3 -1
  61. package/src/demo/DemoViewport.svelte +18 -11
  62. package/src/demo/DemoWaterfall.astro +4 -2
  63. package/src/demo/ProjectStyleExample.astro +4 -1
  64. package/src/demo/foundations/FoundationDialog.astro +6 -2
  65. package/src/demo/foundations/FoundationMedia.astro +1 -0
  66. package/src/demo/foundations/FoundationShrink.astro +1 -0
  67. package/src/demo/foundations/FoundationTokensView.astro +16 -5
  68. package/src/demo/foundations/Palette.svelte +11 -7
  69. package/src/demo/foundations/dialog-copy.d.ts +1 -0
  70. package/src/demo/foundations/dialog-copy.js +5 -0
  71. package/src/demo/illustration-materials.ts +1 -1
  72. package/src/demo/illustration.css +218 -0
  73. package/src/docs-shell/common/i18n.config.d.ts +1 -1
  74. package/src/docs-shell/common/i18n.config.js +4 -12
  75. package/src/docs-shell/common/locales.d.ts +8 -0
  76. package/src/docs-shell/common/locales.js +18 -0
  77. package/src/docs-shell/common/terminology.json +32 -0
  78. package/src/docs-shell/common/translations.d.ts +3 -0
  79. package/src/docs-shell/common/translations.js +294 -0
  80. package/src/docs-shell/components/ArticleHeader.astro +5 -3
  81. package/src/docs-shell/components/Callout.astro +1 -0
  82. package/src/docs-shell/components/CodeCopyButton.astro +5 -1
  83. package/src/docs-shell/components/CodeTabBar.astro +7 -3
  84. package/src/docs-shell/components/CodeTabs.astro +2 -1
  85. package/src/docs-shell/components/CodeView.astro +1 -1
  86. package/src/docs-shell/components/DocHeader.svelte +13 -17
  87. package/src/docs-shell/components/DocHeading.astro +3 -1
  88. package/src/docs-shell/components/DocMenuButton.svelte +126 -61
  89. package/src/docs-shell/components/DocSidebar.svelte +3 -2
  90. package/src/docs-shell/components/DocVersionSelect.svelte +2 -0
  91. package/src/docs-shell/components/DocumentAPIIndex.astro +1 -1
  92. package/src/docs-shell/components/DocumentChoices.astro +1 -1
  93. package/src/docs-shell/components/DocumentCodeExampleView.astro +3 -1
  94. package/src/docs-shell/components/DocumentCodeTable.astro +4 -2
  95. package/src/docs-shell/components/DocumentCopyButton.svelte +3 -0
  96. package/src/docs-shell/components/DocumentDisclosure.astro +2 -0
  97. package/src/docs-shell/components/DocumentFileTree.astro +3 -0
  98. package/src/docs-shell/components/DocumentFlow.astro +4 -1
  99. package/src/docs-shell/components/DocumentIllustration.astro +8 -12
  100. package/src/docs-shell/components/DocumentKeyList.astro +6 -4
  101. package/src/docs-shell/components/DocumentNamespaceTable.astro +5 -3
  102. package/src/docs-shell/components/DocumentOptionEntry.astro +0 -1
  103. package/src/docs-shell/components/DocumentOptionList.astro +1 -1
  104. package/src/docs-shell/components/DocumentOptions.astro +3 -2
  105. package/src/docs-shell/components/DocumentParameters.astro +1 -1
  106. package/src/docs-shell/components/DocumentPrompt.astro +2 -0
  107. package/src/docs-shell/components/DocumentValueList.astro +1 -1
  108. package/src/docs-shell/components/DocumentationIndex.svelte +8 -7
  109. package/src/docs-shell/components/ExpandContent.svelte +3 -0
  110. package/src/docs-shell/components/ExplorerView.astro +1 -0
  111. package/src/docs-shell/components/GeneratedCSS.astro +4 -1
  112. package/src/docs-shell/components/Header.svelte +1 -1
  113. package/src/docs-shell/components/InlineCode.astro +1 -0
  114. package/src/docs-shell/components/InteractingIndicator.astro +1 -0
  115. package/src/docs-shell/components/LanguageButton.svelte +1 -0
  116. package/src/docs-shell/components/LanguageSelect.svelte +2 -0
  117. package/src/docs-shell/components/Link.svelte +3 -2
  118. package/src/docs-shell/components/NotFound.astro +6 -4
  119. package/src/docs-shell/components/PMAction.astro +1 -0
  120. package/src/docs-shell/components/PageContent.svelte +1 -1
  121. package/src/docs-shell/components/PageContentDisclosure.astro +2 -1
  122. package/src/docs-shell/components/PageContentList.svelte +2 -0
  123. package/src/docs-shell/components/PageNavs.astro +3 -3
  124. package/src/docs-shell/components/SearchButton.svelte +3 -0
  125. package/src/docs-shell/components/SearchDialog.svelte +18 -16
  126. package/src/docs-shell/components/Segments.svelte +2 -0
  127. package/src/docs-shell/components/StepSection.astro +1 -2
  128. package/src/docs-shell/components/Tabs.astro +1 -0
  129. package/src/docs-shell/components/ThemeSelect.svelte +2 -0
  130. package/src/docs-shell/components/TimeAgo.svelte +6 -6
  131. package/src/docs-shell/components/markdown/Table.astro +1 -0
  132. package/src/docs-shell/components/markdown-components.ts +0 -1
  133. package/src/docs-shell/contexts/search.ts +1 -1
  134. package/src/docs-shell/factories/Page.astro +10 -5
  135. package/src/docs-shell/factories/create-page.ts +33 -6
  136. package/src/docs-shell/layouts/BlogArticle.astro +3 -11
  137. package/src/docs-shell/remark/markdown-components.js +28 -7
  138. package/src/docs-shell/remark/slug-and-toc.js +5 -7
  139. package/src/docs-shell/scripts/code.ts +5 -2
  140. package/src/docs-shell/scripts/document.ts +1 -1
  141. package/src/docs-shell/site.ts +9 -1
  142. package/src/docs-shell/types/Metadata.ts +11 -0
  143. package/src/docs-shell/utils/document-copy.d.ts +2 -0
  144. package/src/docs-shell/utils/document-copy.js +18 -0
  145. package/src/docs-shell/utils/generate-metadata.ts +5 -1
  146. package/src/docs-shell/utils/head.ts +5 -2
  147. package/src/docs-shell/utils/i18n-pathname.ts +13 -2
  148. package/src/docs-shell/utils/i18n.ts +4 -1
  149. package/src/docs-shell/utils/localized-metadata.ts +22 -0
  150. package/src/docs-shell/utils/markdown-links.ts +61 -0
  151. package/src/docs-shell/utils/markdown-tree.ts +19 -6
  152. package/src/docs-shell/utils/resolve-heading.d.ts +1 -0
  153. package/src/docs-shell/utils/resolve-heading.js +21 -0
  154. package/src/docs-shell/utils/search-pages.ts +75 -22
  155. package/src/examples/components/ArticleTransitionScene.svelte +4 -0
  156. package/src/examples/layout-system/page.astro +0 -1
  157. package/src/home/HomeBentoFrame.astro +1 -0
  158. package/src/home/HomeBentoGrid.astro +1 -0
  159. package/src/home/bento.css +167 -0
  160. package/src/home/design.css +1 -1
  161. package/src/home/hero.css +5 -0
  162. package/src/home/home.css +4 -576
  163. package/src/home/segmented-control.css +94 -0
  164. package/src/layouts/Document.astro +4 -2
  165. package/src/public/icons/json.svg +1 -0
  166. package/src/scripts/atomic-class-authoring.ts +98 -0
  167. package/src/scripts/localization.ts +239 -0
  168. package/src/scripts/mdx-sample.ts +12 -0
  169. package/src/scripts/static-social.ts +17 -3
  170. package/src/scripts/style-delivery.mjs +120 -0
  171. package/src/sections/blog.astro +1 -1
  172. package/src/sections/guide.astro +4 -3
  173. package/src/styles/blog-article.css +10 -7
  174. package/src/styles/blog-materials.css +13 -0
  175. package/src/styles/demo.css +0 -1
  176. package/src/styles/docs-shell/code.css +5 -61
  177. package/src/styles/docs-shell/documentation-search.css +1 -0
  178. package/src/styles/docs-shell/header.css +2 -73
  179. package/src/styles/docs-shell/menu.css +37 -17
  180. package/src/styles/docs-shell/page-content.css +2 -37
  181. package/src/styles/docs-shell/prose.css +18 -9
  182. package/src/styles/docs-shell/steps.css +2 -39
  183. package/src/styles/document-comparison.css +5 -2
  184. package/src/styles/documentation-illustration.css +1 -9
  185. package/src/styles/documentation-index.css +2 -1
  186. package/src/styles/documents.css +2 -4
  187. package/src/styles/interface.css +5 -3
  188. package/src/styles/system/DESIGN.md +34 -14
  189. package/src/styles/system/browser.css +21 -0
  190. package/src/styles/system/button-feedback.css +13 -0
  191. package/src/styles/system/callout.css +14 -0
  192. package/src/styles/system/chart.css +18 -0
  193. package/src/styles/system/checks.css +18 -0
  194. package/src/styles/system/code-panel.css +82 -0
  195. package/src/styles/system/code-tabs.css +40 -0
  196. package/src/styles/system/code.css +8 -85
  197. package/src/styles/system/compare.css +7 -0
  198. package/src/styles/system/controls.css +10 -109
  199. package/src/styles/system/copy.css +32 -0
  200. package/src/styles/system/data.css +10 -134
  201. package/src/styles/system/dialog.css +16 -0
  202. package/src/styles/system/disclosure.css +19 -0
  203. package/src/styles/system/expand.css +11 -0
  204. package/src/styles/system/fields.css +25 -0
  205. package/src/styles/system/header-core.css +101 -0
  206. package/src/styles/system/hint.css +5 -0
  207. package/src/styles/system/index.css +13 -0
  208. package/src/styles/system/inline-code.css +4 -0
  209. package/src/styles/system/kbd.css +4 -0
  210. package/src/styles/system/logos.css +10 -0
  211. package/src/styles/system/menu.css +14 -0
  212. package/src/styles/system/metrics.css +22 -0
  213. package/src/styles/system/pagehead.css +13 -0
  214. package/src/styles/system/pager.css +13 -0
  215. package/src/styles/system/parameters.css +17 -0
  216. package/src/styles/system/prompt.css +7 -0
  217. package/src/styles/system/prose.css +4 -75
  218. package/src/styles/system/range.css +12 -0
  219. package/src/styles/system/reading.css +44 -0
  220. package/src/styles/system/scroll.css +6 -0
  221. package/src/styles/system/search-trigger.css +10 -0
  222. package/src/styles/system/segmented.css +22 -0
  223. package/src/styles/system/sheet.css +8 -0
  224. package/src/styles/system/shell.css +8 -102
  225. package/src/styles/system/sidebar.css +14 -0
  226. package/src/styles/system/source.css +5 -0
  227. package/src/styles/system/specimens.css +3 -0
  228. package/src/styles/system/stage.css +18 -0
  229. package/src/styles/system/steps.css +51 -0
  230. package/src/styles/system/surfaces.css +8 -104
  231. package/src/styles/system/table.css +23 -0
  232. package/src/styles/system/toc.css +59 -0
  233. package/src/styles/system/tooltip.css +6 -0
  234. package/src/styles/system/tray-code.css +6 -0
  235. package/src/styles/system/tray.css +20 -0
  236. package/src/styles/system/tree.css +15 -0
  237. package/src/styles/system/waterfall.css +14 -0
  238. package/src/virtual.d.ts +5 -1
  239. package/src/blog/article/language.ts +0 -29
@@ -1,4 +1,6 @@
1
1
  <script lang="ts">
2
+ import '@master/internal-site/styles/system/sheet.css'
3
+
2
4
  import '@master/internal-site/styles/system/specimens.css'
3
5
  import type { Snippet } from 'svelte'
4
6
  import type { HTMLAttributes } from 'svelte/elements'
@@ -1,4 +1,7 @@
1
1
  ---
2
+ import { siteOf } from '@master/internal-site/astro/site-context.ts'
3
+ const { t } = siteOf(Astro)
4
+ import '@master/internal-site/styles/system/compare.css'
2
5
  import Demo from '@master/internal-site/demo/Demo.svelte'
3
6
  import DemoViewport from '@master/internal-site/demo/DemoViewport.svelte'
4
7
  import { demoDocument } from '@master/internal-site/demo/reference/document.ts'
@@ -23,8 +26,8 @@ const document = (mode: 'light' | 'dark') => demoDocument({ page: 'foundations',
23
26
  <Demo title={title} frame="none" caption={caption} data-foundation-paint={name}>
24
27
  <div class="ui-compare">
25
28
  {(['light', 'dark'] as const).map(mode => <figure data-theme-specimen={mode}>
26
- <figcaption>{mode === 'light' ? 'Light' : 'Dark'}</figcaption>
27
- <DemoViewport client:idle={{ timeout: 500 }} title={`${title} · ${mode}`} document={document(mode)} initialTheme={mode} print={print} height={height} sizing="content" />
29
+ <figcaption>{mode === 'light' ? t('Light') : t('Dark')}</figcaption>
30
+ <DemoViewport client:idle={{ timeout: 500 }} title={`${t(title)} · ${mode === 'light' ? t('Light') : t('Dark')}`} document={document(mode)} initialTheme={mode} print={print} height={height} sizing="content" />
28
31
  </figure>)}
29
32
  </div>
30
33
  </Demo>
@@ -1,5 +1,7 @@
1
1
  ---
2
- import '@master/internal-site/styles/system/data.css'
2
+ import { siteOf } from '@master/internal-site/astro/site-context.ts'
3
+ const { t } = siteOf(Astro)
4
+ import '@master/internal-site/styles/system/table.css'
3
5
  import InlineCode from '@master/internal-site/components/InlineCode.astro'
4
6
  import RenderNode from '@master/internal-site/components/RenderNode.astro'
5
7
 
@@ -22,8 +24,8 @@ const { rows, descriptionTitle = 'Role' } = Astro.props
22
24
  ---
23
25
 
24
26
  {/* Narrow token inventories need native keyboard scrolling. */}
25
- <div class="ui-table" role="region" aria-label={`${descriptionTitle} token table`} tabindex="0"><table>
26
- <thead><tr><th scope="col">Token / class</th><th scope="col" class="(min-width:12rem)">{descriptionTitle}</th></tr></thead>
27
+ <div class="ui-table" role="region" aria-label={t('{label} token table').replace('{label}', t(descriptionTitle))} tabindex="0"><table>
28
+ <thead><tr><th scope="col">{t('Token / class')}</th><th scope="col" class="(min-width:12rem)">{t(descriptionTitle)}</th></tr></thead>
27
29
  <tbody>{rows.map(({ token, utilities, value, description, preview }) => <tr>
28
30
  <td>
29
31
  <div class="whitespace-nowrap">{preview != null && <RenderNode node={preview} />}<code>{token}</code></div>
@@ -1,4 +1,6 @@
1
1
  ---
2
+ import { siteOf } from '@master/internal-site/astro/site-context'
3
+ const { t } = siteOf(Astro)
2
4
  import Demo from '@master/internal-site/demo/Demo.svelte'
3
5
  import DemoViewport from '@master/internal-site/demo/DemoViewport.svelte'
4
6
 
@@ -14,5 +16,5 @@ const title = articles ? 'From collection to article' : 'A change of view'
14
16
 
15
17
  <Demo title={title} frame="none" data-view-transition={example}
16
18
  caption={articles ? 'Open an article, then return to its card. Each image and title keeps a unique snapshot name. Keyboard focus follows the view change.' : 'Choose a view. The panel and heading use named snapshots; unsupported browsers and reduced motion use the same immediate update.'}>
17
- <DemoViewport client:idle={{ timeout: 500 }} title={title} src={origin + (articles ? '/examples/article-transitions' : '/examples/view-transitions')} height={articles ? 640 : 340} sizing="viewport" />
19
+ <DemoViewport client:idle={{ timeout: 500 }} title={t(title)} src={origin + (articles ? '/examples/article-transitions' : '/examples/view-transitions')} height={articles ? 640 : 340} sizing="viewport" />
18
20
  </Demo>
@@ -28,6 +28,13 @@
28
28
  </script>
29
29
 
30
30
  <script lang="ts">
31
+ import { getSite } from '@master/internal-site/site'
32
+ const { t } = getSite()
33
+ import '@master/internal-site/styles/interface.css'
34
+ import '@master/internal-site/styles/system/browser.css'
35
+ import '@master/internal-site/styles/system/range.css'
36
+ import '@master/internal-site/styles/system/segmented.css'
37
+
31
38
  import { untrack } from 'svelte'
32
39
  import IconMoon from '@tabler/icons-svelte-runes/icons/moon'
33
40
  import IconPlayerPause from '@tabler/icons-svelte-runes/icons/player-pause'
@@ -287,38 +294,38 @@
287
294
  {#if chrome}<div class="ui-browser-bar" aria-hidden="true"><span class="ui-browser-dots"><i></i><i></i><i></i></span></div>{/if}
288
295
  <div bind:this={body} class="ui-browser-body">
289
296
  <div class="ui-browser-viewport" style:width={width ? `${width}px` : `min(100%, ${maxWidth}px)`}>
290
- <iframe bind:this={frame} {title} srcdoc={source} {src} loading="lazy" data-ready={ready} data-sizing={sizing}
297
+ <iframe bind:this={frame} title={t(title)} srcdoc={source} {src} loading="lazy" data-ready={ready} data-sizing={sizing}
291
298
  style:height={`${sizing === 'content' ? contentHeight ?? height : height}px`} onload={load}></iframe>
292
299
  {#if responsive}<span class="ui-resize" aria-hidden="true" onpointerdown={drag}></span>{/if}
293
300
  </div>
294
301
  </div>
295
302
  </div>
296
303
  {#if responsive || motion || print || theme || Boolean(inspect?.length)}
297
- <div class="ui-browser-tools" role="group" aria-label={`${title} controls`}>
304
+ <div class="ui-browser-tools" role="group" aria-label={t('{label} controls').replace('{label}', t(title))}>
298
305
  {#if responsive}
299
- {#if widthPresets?.length}<div class="ui-segmented" role="group" aria-label={`${rangeLabel} presets`}>
306
+ {#if widthPresets?.length}<div class="ui-segmented" role="group" aria-label={t('{label} presets').replace('{label}', t(rangeLabel))}>
300
307
  {#each widthPresets as preset (preset.label)}<button type="button" disabled={!ready}
301
- aria-pressed={width === preset.width} onclick={() => { width = preset.width }}>{preset.label}</button>{/each}
308
+ aria-pressed={width === preset.width} onclick={() => { width = preset.width }}>{t(preset.label)}</button>{/each}
302
309
  </div>{/if}
303
310
  <span class="ui-browser-range">
304
- <label class="ui-label" data-case="as-is" for={widthId}>{rangeLabel}</label>
311
+ <label class="ui-label" data-case="as-is" for={widthId}>{t(rangeLabel)}</label>
305
312
  <input id={widthId} class="ui-range" disabled={!ready} type="range" min={range.min} max={range.max} step="1" value={range.value}
306
313
  style:--value={`${((range.value - range.min) / Math.max(1, range.max - range.min)) * 100}%`}
307
314
  oninput={event => { width = Number(event.currentTarget.value) }} />
308
315
  {#if ready}<output class="ui-browser-size" for={widthId}>{fitWidth} px</output>{/if}
309
316
  </span>
310
- <button type="button" disabled={!ready} class="ui-button" data-size="sm" onclick={() => { width = undefined }}>Fit</button>
317
+ <button type="button" disabled={!ready} class="ui-button" data-size="sm" onclick={() => { width = undefined }}>{t('Fit')}</button>
311
318
  {/if}
312
- {#if theme}<button type="button" disabled={!ready} class="ui-button" data-size="sm" aria-pressed={isDark} title="Toggle dark theme" onclick={() => { dark = !isDark }}>
313
- {#if isDark}<IconMoon aria-hidden="true" />{:else}<IconSun aria-hidden="true" />{/if}Theme
319
+ {#if theme}<button type="button" disabled={!ready} class="ui-button" data-size="sm" aria-pressed={isDark} title={t('Toggle dark theme')} onclick={() => { dark = !isDark }}>
320
+ {#if isDark}<IconMoon aria-hidden="true" />{:else}<IconSun aria-hidden="true" />{/if}{t('Theme')}
314
321
  </button>{/if}
315
322
  {#if motion}
316
323
  <button type="button" disabled={!ready} class="ui-button" data-size="sm" onclick={() => animate(playing ? 'pause' : 'play')}>
317
- {#if playing}<IconPlayerPause aria-hidden="true" />{:else}<IconPlayerPlay aria-hidden="true" />{/if}{playing ? 'Pause' : 'Play'}
324
+ {#if playing}<IconPlayerPause aria-hidden="true" />{:else}<IconPlayerPlay aria-hidden="true" />{/if}{playing ? t('Pause') : t('Play')}
318
325
  </button>
319
- <button type="button" disabled={!ready} class="ui-button" data-size="sm" data-variant="quiet" onclick={() => animate('restart')}><IconRefresh aria-hidden="true" />Replay</button>
326
+ <button type="button" disabled={!ready} class="ui-button" data-size="sm" data-variant="quiet" onclick={() => animate('restart')}><IconRefresh aria-hidden="true" />{t('Replay')}</button>
320
327
  {/if}
321
- {#if print}<button type="button" disabled={!ready} class="ui-button" data-size="sm" onclick={() => frame?.contentWindow?.print()}><IconPrinter aria-hidden="true" />Print preview</button>{/if}
328
+ {#if print}<button type="button" disabled={!ready} class="ui-button" data-size="sm" onclick={() => frame?.contentWindow?.print()}><IconPrinter aria-hidden="true" />{t('Print preview')}</button>{/if}
322
329
  {#if properties}<output aria-live="polite">{properties}</output>{/if}
323
330
  </div>
324
331
  {/if}
@@ -1,5 +1,7 @@
1
1
  ---
2
- import '@master/internal-site/styles/system/data.css'
2
+ import { siteOf } from '@master/internal-site/astro/site-context'
3
+ const { t } = siteOf(Astro)
4
+ import '@master/internal-site/styles/system/waterfall.css'
3
5
  import Demo from '@master/internal-site/demo/Demo.svelte'
4
6
  import type { DemoWaterfallProps } from '@master/internal-site/demo/demo-waterfall.ts'
5
7
 
@@ -15,7 +17,7 @@ for (const { label, start, end } of rows) {
15
17
 
16
18
  <Demo title={title} description={description} caption={caption} padding="sm" aria-label={title}>
17
19
  <div class="w-full ui-waterfall">
18
- <div class="ui-waterfall-axis" aria-hidden="true"><span /><div class="relative"><span>Earlier</span>{marker && <span class="absolute font-medium fg-text-strong (translate:-50%)" style={{ left: `${marker.position}%` }}>{marker.label}</span>}<span>Later</span></div></div>
20
+ <div class="ui-waterfall-axis" aria-hidden="true"><span /><div class="relative"><span>{t("Earlier")}</span>{marker && <span class="absolute font-medium fg-text-strong (translate:-50%)" style={{ left: `${marker.position}%` }}>{marker.label}</span>}<span>{t("Later")}</span></div></div>
19
21
  {/* Preserve WebKit list semantics without list markers. */}
20
22
  <ul role="list" class="grid gap-2xs m(0) p(0) (list-style:none)">
21
23
  {rows.map(({ label, start, end, tone = 'blue' }) => <li class="ui-waterfall-row">
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  import DemoConfiguredExample from '@master/internal-site/demo/DemoConfiguredExample.astro'
3
3
  import { projectStyleExample, type ProjectStyleName } from '@master/internal-site/demo/project-style-examples.ts'
4
+ import { documentCopyOf, siteOf } from '@master/internal-site/astro/site-context'
5
+ import { demoCopy } from '@master/internal-site/utils/document-copy.js'
4
6
 
5
7
  interface Props {
6
8
  name: ProjectStyleName
@@ -8,6 +10,7 @@ interface Props {
8
10
  }
9
11
 
10
12
  const { name, code = true } = Astro.props
13
+ const example = demoCopy(documentCopyOf(Astro), siteOf(Astro).locale, `ProjectStyleExample:${name}`, projectStyleExample(name))
11
14
  ---
12
15
 
13
- <DemoConfiguredExample name={name} {...projectStyleExample(name)} code={code} />
16
+ <DemoConfiguredExample name={name} {...example} code={code} />
@@ -1,8 +1,12 @@
1
1
  ---
2
+ import '@master/internal-site/styles/interface.css'
2
3
  import FoundationSpecimen from '@master/internal-site/demo/foundations/FoundationSpecimen.astro'
4
+ import { dialogCopy } from './dialog-copy.js'
5
+ interface Props { title?: string; caption?: string }
6
+ const { title = dialogCopy.title, caption = dialogCopy.caption } = Astro.props
3
7
  ---
4
8
 
5
- <FoundationSpecimen name="dialog-entrance" title="Motion follows a real action" scene={{ systemButtons: true, sizing: 'viewport', height: 340, css: `@theme {
9
+ <FoundationSpecimen name="dialog-entrance" title={title} scene={{ systemButtons: true, sizing: 'viewport', height: 340, css: `@theme {
6
10
  --duration-enter: 180ms;
7
11
  --easing-emphasized: cubic-bezier(.16, 1, .3, 1);
8
12
  --animate-dialog-in: dialog-in var(--duration-enter) var(--easing-emphasized) both;
@@ -11,4 +15,4 @@ import FoundationSpecimen from '@master/internal-site/demo/foundations/Foundatio
11
15
  from { translate: 0 0.5rem; opacity: 0; }
12
16
  to { translate: 0; opacity: 1; }
13
17
  }
14
- `, html: "<article class=\"p-md r-sm b(1) b-solid b-line-divider bg-surface-raised\"><h2 class=\"m(0) text-lg font-regular\">Collection settings</h2><p class=\"mt-xs mb-md fg-text-muted\">Open a native dialog to preview its entrance.</p><button id=\"open\" type=\"button\" class=\"ui-button\">Open details</button></article>\n<dialog id=\"details\" aria-labelledby=\"dialog-title\" aria-describedby=\"dialog-description\" class=\"(width:calc(100%-32px)) max-w-xs m-auto p-md r-lg b(1) b-solid b-line-divider bg-surface-floating fg-text-body shadow-xl animate-dialog-in@media(screen)@motion-safe\">\n<h2 id=\"dialog-title\" class=\"m(0) text-lg font-regular fg-text-strong\">Collection details</h2><p id=\"dialog-description\" class=\"mt-sm mb-md\">This dialog has a finite entrance. Closing it is immediate.</p><form method=\"dialog\"><button class=\"ui-button\" autofocus>Close details</button></form>\n</dialog><script>const dialog = document.getElementById('details'); const openButton = document.getElementById('open'); openButton.addEventListener('click', () => dialog.showModal()); dialog.addEventListener('close', () => openButton.focus());</script>", caption: 'The dialog enters once on opening. Escape or Close details returns focus to its trigger. Reduced motion opens it immediately. Its modal boundary stays inside this preview.' }} />
18
+ `, html: "<article class=\"p-md r-sm b(1) b-solid b-line-divider bg-surface-raised\"><h2 class=\"m(0) text-lg font-regular\">Collection settings</h2><p class=\"mt-xs mb-md fg-text-muted\">Open a native dialog to preview its entrance.</p><button id=\"open\" type=\"button\" class=\"ui-button\">Open details</button></article>\n<dialog id=\"details\" aria-labelledby=\"dialog-title\" aria-describedby=\"dialog-description\" class=\"(width:calc(100%-32px)) max-w-xs m-auto p-md r-lg b(1) b-solid b-line-divider bg-surface-floating fg-text-body shadow-xl animate-dialog-in@media(screen)@motion-safe\">\n<h2 id=\"dialog-title\" class=\"m(0) text-lg font-regular fg-text-strong\">Collection details</h2><p id=\"dialog-description\" class=\"mt-sm mb-md\">This dialog has a finite entrance. Closing it is immediate.</p><form method=\"dialog\"><button class=\"ui-button\" autofocus>Close details</button></form>\n</dialog><script>const dialog = document.getElementById('details'); const openButton = document.getElementById('open'); openButton.addEventListener('click', () => dialog.showModal()); dialog.addEventListener('close', () => openButton.focus());</script>", caption }} />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import '@master/internal-site/styles/system/sheet.css'
2
3
  import DemoContainer from '@master/internal-site/demo/DemoContainer.svelte'
3
4
  import DemoLabel from '@master/internal-site/demo/DemoLabel.svelte'
4
5
  import DemoMedia from '@master/internal-site/demo/DemoMedia.svelte'
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import '@master/internal-site/styles/system/sheet.css'
2
3
  import DemoContainer from '@master/internal-site/demo/DemoContainer.svelte'
3
4
  import DemoItem from '@master/internal-site/demo/DemoItem.svelte'
4
5
  ---
@@ -1,4 +1,6 @@
1
1
  ---
2
+ import { siteOf } from '@master/internal-site/astro/site-context.ts'
3
+ const { t } = siteOf(Astro)
2
4
  import Demo from '@master/internal-site/demo/Demo.svelte'
3
5
  import DemoViewport from '@master/internal-site/demo/DemoViewport.svelte'
4
6
  import DemoThemeComparison from '@master/internal-site/demo/DemoThemeComparison.astro'
@@ -17,6 +19,13 @@ interface Props {
17
19
  const { namespace, keys, presentation = 'reference' } = Astro.props
18
20
  const tokens = namespace === 'breakpoints' ? [] : specimenTokens(namespace, keys)
19
21
  const tokenKeys = tokens.map(token => token.key).join(' ')
22
+ const namespaceTitles: Record<string, string> = {
23
+ 'color-surface': t('Surface colors'), 'color-line': t('Line colors'), 'color-text': t('Text colors'),
24
+ shadow: t('Shadow scale'), radius: t('Corner radius scale'), container: t('Container widths'),
25
+ 'font-family': t('Font families'), 'font-size': t('Font sizes'), 'font-weight': t('Font weights'),
26
+ text: t('Text styles'), leading: t('Line heights'), tracking: t('Letter spacing'),
27
+ animate: t('Animations'), duration: t('Animation durations'), easing: t('Timing functions'),
28
+ }
20
29
 
21
30
  function tokenScene(tokens: FoundationToken[], title: string, compare = false) {
22
31
  const scene = foundationScene(namespace, tokens.map(token => token.key))
@@ -39,22 +48,24 @@ if (namespace === 'color') {
39
48
  const aliases = rest.filter(token => defaultValue(token).startsWith('var('))
40
49
  const specials = rest.filter(token => !aliases.includes(token))
41
50
  scenes = [
42
- aliases.length > 0 ? tokenScene(aliases, 'Hue aliases', true) : undefined,
43
- specials.length > 0 ? tokenScene(specials, 'Special colors', true) : undefined,
51
+ aliases.length > 0 ? tokenScene(aliases, t('Hue aliases'), true) : undefined,
52
+ specials.length > 0 ? tokenScene(specials, t('Special colors'), true) : undefined,
44
53
  ].filter(scene => scene !== undefined)
45
54
  } else if (namespace !== 'breakpoints' && namespace !== 'spacing') {
46
- const scene = tokenScene(tokens, namespace === 'shadow' ? 'Shadow scale' : namespace === 'radius' ? 'Corner radius scale' : namespace.replaceAll('-', ' '), namespace.startsWith('color-') || (namespace === 'shadow' && presentation === 'reference'))
55
+ const title = namespaceTitles[namespace]
56
+ if (!title) throw new Error(`Missing foundation title: ${namespace}`)
57
+ const scene = tokenScene(tokens, title, namespace.startsWith('color-') || (namespace === 'shadow' && presentation === 'reference'))
47
58
  scenes = scene ? [scene] : []
48
59
  }
49
60
  ---
50
61
 
51
62
  {namespace === 'breakpoints' ? <ThemeNumberVariableTable namespace="breakpoint" />
52
63
  : namespace === 'color' ? <div data-foundation-tokens={namespace} data-token-keys={tokenKeys}>
53
- {palette.length > 0 && <Demo title="Fixed color steps" padding="sm" caption={specimenCaption.color}><Palette client:idle={{ timeout: 500 }} groups={palette} /></Demo>}
64
+ {palette.length > 0 && <Demo title={t('Fixed color steps')} padding="sm" caption={specimenCaption.color}><Palette client:idle={{ timeout: 500 }} groups={palette} /></Demo>}
54
65
  {scenes.map(({ scene, title }) => <DemoThemeComparison name={`tokens-${namespace}`} title={title} html={scene.html!} css={scene.css} caption={scene.caption} />)}
55
66
  </div>
56
67
  : <div data-foundation-tokens={namespace} data-token-keys={tokenKeys}>
57
- {namespace === 'spacing' ? <><ThemeNumberVariableTable namespace="spacing" keys={keys} representation="spacing" referenceUnits={presentation !== 'guide'} /><p class="m(0) font-xs fg-text-muted">{presentation === 'guide' ? 'The striped area is the actual gap between two equal blocks.' : specimenCaption.spacing}</p></>
68
+ {namespace === 'spacing' ? <><ThemeNumberVariableTable namespace="spacing" keys={keys} representation="spacing" referenceUnits={presentation !== 'guide'} /><p class="m(0) font-xs fg-text-muted">{presentation === 'guide' ? t('The striped area is the actual gap between two equal blocks.') : t(specimenCaption.spacing)}</p></>
58
69
  : scenes.map(({ scene, title, compare }) => compare
59
70
  ? <DemoThemeComparison name={`tokens-${namespace}`} title={title} html={scene.html!} css={scene.css} caption={scene.caption} />
60
71
  : <Demo title={title} frame="none" caption={scene.caption}>
@@ -3,6 +3,10 @@
3
3
  </script>
4
4
 
5
5
  <script lang="ts">
6
+ import { getSite } from '@master/internal-site/site'
7
+ const { t } = getSite()
8
+ import '@master/internal-site/styles/system/fields.css'
9
+
6
10
  import '@master/internal-site/styles/system/specimens.css'
7
11
  import '@master/internal-site/styles/demo.css'
8
12
  import DemoCopyButton from '@master/internal-site/demo/DemoCopyButton.svelte'
@@ -15,23 +19,23 @@
15
19
  const endId = `${id}-end`
16
20
  </script>
17
21
 
18
- <div class="w-full"><DemoCopyGroup instruction={format === 'value' ? 'Choose a swatch to copy its original CSS color value.' : 'Choose a swatch to copy its CSS variable reference.'}>
19
- <label class="w-fit ui-field" for={id}>Copy as
22
+ <div class="w-full"><DemoCopyGroup instruction={format === 'value' ? t('Choose a swatch to copy its original CSS color value.') : t('Choose a swatch to copy its CSS variable reference.')}>
23
+ <label class="w-fit ui-field" for={id}>{t('Copy as')}
20
24
  <select {id} class="ui-select" data-variant="raised" bind:value={format}>
21
- <option value="value">CSS value</option><option value="variable">CSS variable</option>
25
+ <option value="value">{t('CSS value')}</option><option value="variable">{t('CSS variable')}</option>
22
26
  </select>
23
27
  </label>
24
- <a class="ui-skip" href={`#${endId}`}>Skip color swatches</a>
28
+ <a class="ui-skip" href={`#${endId}`}>{t('Skip color swatches')}</a>
25
29
  <div class="ui-palette">
26
- {#each groups as group (group.family)}<div class="ui-palette-row" role="group" aria-label={`${group.family} palette`}>
30
+ {#each groups as group (group.family)}<div class="ui-palette-row" role="group" aria-label={t('{family} palette').replace('{family}', group.family)}>
27
31
  <span>{group.family}</span>
28
32
  {#each group.colors as color (color.name)}<div class="ui-palette-cell">
29
33
  <DemoCopyButton icon={null} class="ui-palette-chip" style={`background-color:${color.value}`}
30
34
  value={format === 'value' ? color.value : `var(--${color.name})`}
31
- label={`Copy ${color.name} ${format === 'value' ? 'CSS value' : 'CSS variable'}`} title={`${color.name}: ${color.value}`} />
35
+ label={format === 'value' ? t('Copy {name} CSS value').replace('{name}', color.name) : t('Copy {name} CSS variable').replace('{name}', color.name)} title={`${color.name}: ${color.value}`} />
32
36
  <span aria-hidden="true">{color.step}</span>
33
37
  </div>{/each}
34
38
  </div>{/each}
35
39
  </div>
36
- <div id={endId} tabindex="-1"><span class="sr-only">End of color palette</span></div>
40
+ <div id={endId} tabindex="-1"><span class="sr-only">{t('End of color palette')}</span></div>
37
41
  </DemoCopyGroup></div>
@@ -0,0 +1 @@
1
+ export declare const dialogCopy: { title: string; caption: string }
@@ -0,0 +1,5 @@
1
+ /** Reader-facing copy; the executable dialog specimen stays English. */
2
+ export const dialogCopy = {
3
+ title: 'Motion follows a real action',
4
+ caption: 'The dialog enters once on opening. Escape or Close details returns focus to its trigger. Reduced motion opens it immediately. Its modal boundary stays inside this preview.'
5
+ }
@@ -159,7 +159,7 @@ export function drawing(theme: Theme) {
159
159
  <head>
160
160
  <meta charset="utf-8">
161
161
  <title>${escapeHtml(title)}</title>
162
- <link rel="stylesheet" href="${asset('../../../../styles/system/tokens.css')}">
162
+ <link rel="stylesheet" href="${asset('../styles/system/tokens.css')}">
163
163
  <link rel="stylesheet" href="${asset('./illustration.css')}">
164
164
  </head>
165
165
  <body>
@@ -0,0 +1,218 @@
1
+ /*
2
+ * Illustration primitives drawn with the site's own tokens (`styles/system/tokens.css`), at 1:1 with the
3
+ * article: the 720px scene shows at the wide media width (712px on desktop), so a 14px label reads as 14px.
4
+ * Artifacts are sheets, operations are control-sized chips, groups are eyebrow captions over a hairline partition:
5
+ * the figure is a tray's stage, so nothing in it forms another nested frame. Nothing invents a colour.
6
+ */
7
+ @font-face { font-family: Geist; font-weight: 100 900; src: url("../public/fonts/GeistVariable.woff2") format("woff2"); }
8
+ @font-face { font-family: "IBM Plex Mono"; font-weight: 400; src: url("../public/fonts/IBMPlexMono-Regular.woff2") format("woff2"); }
9
+
10
+ :root { color-scheme: light; }
11
+ :root.dark { color-scheme: dark; }
12
+ * { box-sizing: border-box; }
13
+ body {
14
+ margin: 0; overflow: hidden;
15
+ background: var(--ui-stage-light), var(--ui-surface-stage);
16
+ color: var(--ui-text-body); font: var(--ui-weight-regular) 13px/20px Geist, sans-serif;
17
+ -webkit-font-smoothing: antialiased;
18
+ }
19
+ .scene { position: relative; overflow: hidden; }
20
+ .scene > * { position: absolute; }
21
+
22
+ /* Routes: one hairline ink, a chevron head, dashes only for a labelled condition */
23
+ .routes { inset: 0; overflow: visible; }
24
+ .routes path { fill: none; stroke: var(--ui-line-mark); stroke-width: 1; stroke-linecap: round; stroke-linejoin: round; }
25
+ .routes path[data-dashed] { stroke-dasharray: 3 4; }
26
+ .routes marker path { stroke: var(--ui-text-disabled); stroke-width: 1.25; }
27
+ .routes path[marker-end] { stroke: var(--ui-text-disabled); }
28
+
29
+ /* Type: the documentation's small sizes at its light regular weight */
30
+ .title { color: var(--ui-text-strong); font-size: 14px; font-weight: var(--ui-weight-medium); line-height: 20px; white-space: nowrap; }
31
+ .note { color: var(--ui-text-muted); font-size: 13px; line-height: 20px; white-space: nowrap; }
32
+ .body { color: var(--ui-text-body); font-size: 13px; line-height: 20px; white-space: nowrap; }
33
+ .edge { color: var(--ui-text-muted); font-size: 12px; line-height: 16px; white-space: nowrap; }
34
+ .code { color: var(--ui-text-body); font: 400 12.5px/20px "IBM Plex Mono", monospace; white-space: nowrap; }
35
+ .eyebrow { color: var(--ui-text-muted); font-size: 11px; font-weight: var(--ui-weight-medium); line-height: 16px; letter-spacing: .14em; text-transform: uppercase; white-space: nowrap; }
36
+ .center { translate: -50% 0; text-align: center; }
37
+ .end { translate: -100% 0; text-align: right; }
38
+ .icon { flex: none; width: 16px; height: 16px; color: var(--ui-text-muted); }
39
+ .icon svg { display: block; width: 100%; height: 100%; fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
40
+
41
+ /* An artifact: the sheet face at rise-1, the small card radius */
42
+ .sheet {
43
+ display: flex; flex-direction: column; width: max-content; gap: 4px; padding: 13px 16px 14px;
44
+ border-radius: var(--ui-radius-xl);
45
+ background: linear-gradient(180deg, var(--ui-surface-sheet), var(--ui-surface-sheet-lo));
46
+ box-shadow: var(--ui-elevation-rise-1);
47
+ }
48
+ .anchor-center { translate: -50% 0; }
49
+ .anchor-end { translate: -100% 0; }
50
+ .sheet > .row { display: flex; align-items: center; gap: 8px; min-height: 20px; }
51
+ .sheet > .rule { height: 1px; margin: 7px -16px 5px; background: var(--ui-line-hair); }
52
+ .sheet .bars { display: grid; gap: 9px; padding-top: 8px; }
53
+ .sheet .bars i { display: block; height: 5px; border-radius: 3px; background: var(--ui-line-divider); }
54
+
55
+ /* An operation: a control-sized chip, like a button at rest */
56
+ .chip {
57
+ display: flex; align-items: center; justify-content: center; gap: 8px; height: 32px; padding: 0 12px 0 10px;
58
+ border-radius: var(--ui-radius-md);
59
+ background: linear-gradient(180deg, var(--ui-surface-sheet), var(--ui-surface-sheet-lo));
60
+ box-shadow: var(--ui-elevation-rise-1);
61
+ color: var(--ui-text-strong); font-size: 13.5px; font-weight: var(--ui-weight-medium); white-space: nowrap;
62
+ }
63
+
64
+ /* A plain mark: an icon beside a title, with no frame */
65
+ .mark { display: flex; align-items: center; gap: 8px; }
66
+
67
+ /* A boundary between two places: one hairline partition, each side captioned by an eyebrow */
68
+ .divider { width: 1px; background: var(--ui-line-divider); }
69
+
70
+ /* A window: one contour, a tray-tone toolbar with its dots only */
71
+ .window { overflow: hidden; border-radius: var(--ui-radius-xl); background: var(--ui-surface-stage); box-shadow: var(--ui-elevation-rise-1); }
72
+ .window > .toolbar { display: flex; align-items: center; gap: 6px; height: 32px; padding: 0 12px; background: var(--ui-surface-tray); }
73
+ .window > .toolbar i { width: 7px; height: 7px; border-radius: 50%; background: var(--ui-line-strong); }
74
+ .window > .bars { display: grid; gap: 10px; padding: 18px 20px; }
75
+ .window > .bars i { display: block; height: 5px; border-radius: 3px; background: var(--ui-line-divider); }
76
+ .window > .bars i:first-child { height: 8px; border-radius: 4px; background: var(--ui-line-strong); }
77
+
78
+ /* Inspection guides expose a control's padding; they are annotations, not part of the taught button. */
79
+ .spacing-study { --sample-ink: var(--ui-text-muted); translate: -50% 0; }
80
+ .spacing-study.accented { --sample-ink: var(--ui-tone-blue); }
81
+ .spacing-control {
82
+ position: relative; display: flex; align-items: center; justify-content: center;
83
+ min-height: 38px; padding: 8px var(--sample-padding); border: 1px solid var(--ui-line-divider);
84
+ border-radius: var(--ui-radius-md); background: var(--ui-surface-sheet); box-shadow: var(--ui-elevation-rise-1);
85
+ }
86
+ .spacing-control > .body { color: var(--ui-text-strong); font-size: 14px; font-weight: 400; line-height: 1.2; }
87
+ .spacing-control > i { position: absolute; inset-block: 0; width: var(--sample-padding); background: color-mix(in oklch, var(--sample-ink) 10%, transparent); }
88
+ .spacing-control > .padding-start { left: 0; border-right: 1px dotted var(--sample-ink); border-radius: var(--ui-radius-md) 0 0 var(--ui-radius-md); }
89
+ .spacing-control > .padding-end { right: 0; border-left: 1px dotted var(--sample-ink); border-radius: 0 var(--ui-radius-md) var(--ui-radius-md) 0; }
90
+ .spacing-ruler { display: flex; align-items: center; justify-content: space-between; gap: 6px; margin: 16px 1px 0; }
91
+ .spacing-ruler > .edge { color: var(--sample-ink); }
92
+ .spacing-ruler > i { flex: none; position: relative; width: var(--sample-padding); height: 7px; border-inline: 1px solid var(--sample-ink); }
93
+ .spacing-ruler > i::after { content: ''; position: absolute; top: 3px; inset-inline: 0; border-top: 1px solid var(--sample-ink); }
94
+
95
+ /* Equal-area swatches show real color. Only the target outline belongs to the annotation. */
96
+ .color-scale { display: flex; gap: 16px; width: max-content; }
97
+ .color-scale-step { display: grid; justify-items: center; gap: 10px; }
98
+ .color-scale-step > i { display: block; width: 40px; aspect-ratio: 1; border-radius: var(--ui-radius-sm); box-shadow: inset 0 0 0 1px var(--ui-line-hair); }
99
+ .color-scale-step > i[data-selected] { outline: 1px solid var(--annotation-ink); outline-offset: 5px; }
100
+ .role-selection { display: grid; justify-items: center; gap: 18px; width: max-content; }
101
+ .role-selection > .code { color: var(--annotation-ink); }
102
+ .role-selection-sample { padding: 20px 24px; border-radius: var(--ui-radius-lg); box-shadow: var(--ui-elevation-rise-1); }
103
+ .role-selection-sample > span { font: var(--ui-weight-regular) 14px/1.5 Geist, sans-serif; }
104
+ .spacing-sample { width: max-content; outline: 1px dashed var(--annotation-ink); }
105
+ .spacing-sample[data-kind="padding"] { background: color-mix(in oklch, var(--annotation-ink) 12%, var(--ui-surface-stage)); }
106
+ .spacing-item { position: relative; padding: 10px 12px; background: var(--ui-surface-sheet); box-shadow: inset 0 0 0 1px var(--ui-line-divider); }
107
+ .spacing-sample[data-kind="gap"] > .spacing-item + .spacing-item::before { content: ''; position: absolute; inset-inline: 0; bottom: 100%; height: var(--spacing-md); background: color-mix(in oklch, var(--annotation-ink) 12%, var(--ui-surface-stage)); }
108
+ .sizing-study { display: grid; gap: 16px; }
109
+ .sizing-parent { outline: 1px dashed var(--ui-text-muted); }
110
+ .sizing-child { display: flex; justify-content: space-between; gap: 16px; padding: 16px; background: var(--ui-surface-sheet); box-shadow: inset 0 0 0 1px var(--ui-line-divider); }
111
+ .radius-sample { width: 224px; background: var(--ui-surface-sheet); outline: 1px dashed var(--ui-text-muted); }
112
+ .radius-child { padding: 16px 20px; background: color-mix(in oklch, var(--ui-tone-blue) 16%, var(--ui-surface-sheet)); }
113
+ .radius-parent-content { padding: 24px 20px; }
114
+ .shadow-study { display: grid; gap: 16px; width: max-content; }
115
+ .shadow-sample { display: grid; gap: 8px; padding: 24px; border-radius: var(--ui-radius-lg); background: var(--ui-surface-sheet); }
116
+ .shadow-following { position: relative; padding: 10px 24px; box-shadow: inset 0 1px 0 var(--ui-line-divider); }
117
+ .shadow-gap { position: absolute; right: -24px; top: -16px; width: 0; height: 16px; border-inline-start: 1px solid var(--ui-tone-blue); }
118
+ .shadow-gap::before, .shadow-gap::after { content: ''; position: absolute; left: -3px; width: 5px; border-top: 1px solid var(--ui-tone-blue); }
119
+ .shadow-gap::before { top: 0; }
120
+ .shadow-gap::after { bottom: 0; }
121
+ .shadow-gap > .edge { display: block; width: max-content; margin-inline-start: 8px; color: var(--ui-tone-blue); line-height: 16px; }
122
+ .font-comparison { display: grid; grid-template-columns: repeat(3, max-content); align-items: baseline; gap: 24px 36px; }
123
+ .font-specimen { line-height: 1.5; color: var(--ui-text-strong); }
124
+ .leading-study { display: grid; grid-template-columns: repeat(2, max-content); gap: 72px; }
125
+ .leading-column { display: grid; align-content: start; gap: 16px; }
126
+ .leading-lines { width: 12rem; color: var(--ui-text-strong); }
127
+ .leading-lines > span { display: block; background: color-mix(in oklch, var(--annotation-ink) 8%, transparent); box-shadow: inset 0 -1px 0 color-mix(in oklch, var(--annotation-ink) 30%, transparent); }
128
+ .leading-column > .edge { color: var(--annotation-ink); }
129
+ .tracking-scope { display: grid; grid-template-columns: repeat(2, max-content); align-items: baseline; gap: 12px 28px; padding: 24px; border: 1px solid var(--ui-line-divider); border-radius: var(--ui-radius-lg); }
130
+ .tracking-row { display: contents; }
131
+ .tracking-row > .code { grid-column: 1 / -1; }
132
+ .tracking-row + .tracking-row > .code { margin-top: 20px; }
133
+ .tracking-row > :not(.tracking-sample) { letter-spacing: normal; }
134
+ .tracking-sample { width: max-content; line-height: 1.5; color: var(--ui-text-strong); box-shadow: inset 0 -1px 0 var(--annotation-ink); }
135
+ .tracking-row > .edge { color: var(--annotation-ink); }
136
+
137
+ /* Syntax anatomy: one intact string with exact ranges and nearby, directly attached explanations. */
138
+ [data-tone="blue"] { --annotation-ink: var(--ui-tone-blue); }
139
+ [data-tone="violet"] { --annotation-ink: var(--ui-tone-violet); }
140
+ [data-tone="amber"] { --annotation-ink: color-mix(in srgb, var(--ui-tone-amber) 75%, var(--ui-text-strong)); }
141
+ .annotated-code { display: flex; width: max-content; }
142
+ .code-part { position: relative; padding-block: 4px; background: color-mix(in oklch, var(--annotation-ink) 8%, transparent); }
143
+ .code-part > .code { color: var(--annotation-ink); }
144
+ .code-part:first-child { border-radius: var(--ui-radius-sm) 0 0 var(--ui-radius-sm); }
145
+ .code-part:last-child { border-radius: 0 var(--ui-radius-sm) var(--ui-radius-sm) 0; }
146
+ .code-bracket { position: absolute; inset-inline: 1px; bottom: -9px; height: 5px; border: 1px solid var(--annotation-ink); border-top: 0; }
147
+ .annotation { display: flex; flex-direction: column; align-items: center; gap: 6px; width: max-content; }
148
+ .annotation > .title { color: var(--annotation-ink); }
149
+ .routes path[data-from] { stroke: color-mix(in srgb, var(--annotation-ink) 65%, var(--ui-line-mark)); }
150
+
151
+ /* A shared stylesheet reaches every consumer; conversion and verification have different scopes. */
152
+ .migration-reach { left: 50%; top: 50%; translate: -50% -50%; display: flex; flex-direction: column; align-items: center; width: max-content; }
153
+ .migration-reach > .sheet { position: static; }
154
+ .migration-trunk { position: relative; height: 76px; border-left: 1px solid var(--ui-line-mark); }
155
+ .migration-trunk > .edge { position: absolute; left: 12px; top: 24px; }
156
+ .migration-consumers { display: grid; grid-template-columns: repeat(3, 1fr); column-gap: 28px; }
157
+ .migration-consumer { position: relative; display: flex; flex-direction: column; align-items: center; gap: 10px; padding-top: 42px; }
158
+ .migration-consumer::before { content: ''; position: absolute; top: 0; height: 26px; left: 50%; border-left: 1px solid var(--ui-line-mark); }
159
+ .migration-consumer::after { content: ''; position: absolute; top: 0; left: -14px; right: -14px; border-top: 1px solid var(--ui-line-mark); }
160
+ .migration-consumer:first-child::after { left: 50%; }
161
+ .migration-consumer:last-child::after { right: 50%; }
162
+ .migration-screen { display: flex; align-items: center; gap: 8px; padding: 8px 12px; border-radius: var(--ui-radius-md); }
163
+ .migration-consumer.is-batch .migration-screen { background: color-mix(in oklch, var(--ui-tone-blue) 8%, transparent); outline: 1px solid color-mix(in oklch, var(--ui-tone-blue) 45%, transparent); }
164
+ .migration-consumer.is-batch .migration-screen > :is(.title, .icon) { color: var(--ui-tone-blue); }
165
+ .migration-verification { position: relative; align-self: stretch; display: flex; justify-content: center; margin-top: 28px; padding-top: 24px; }
166
+ .migration-verification::before { content: ''; position: absolute; top: 0; inset-inline: 0; height: 8px; border: 1px solid var(--ui-line-mark); border-top: 0; }
167
+
168
+ /* Same DOM and pointer state; only the suffix's test location changes. */
169
+ .selector-specimen > p { margin: 0 0 8px; }
170
+ .selector-specimen > a { position: relative; display: block; padding: 6px 26px 6px 0; border-radius: var(--ui-radius-xs); }
171
+ .selector-specimen[data-test="owner"] { outline: 1px dashed var(--ui-tone-blue); outline-offset: 3px; }
172
+ .selector-specimen[data-test="target"] > a:first-of-type { outline: 1px dashed var(--ui-tone-blue); outline-offset: 3px; }
173
+ .selector-specimen[data-test="owner"] > a > .body,
174
+ .selector-specimen[data-test="target"] > a:first-of-type > .body { text-decoration: underline; text-underline-offset: 3px; }
175
+ .selector-pointer { position: absolute; top: 15px; right: 4px; width: 20px; height: 20px; fill: var(--ui-surface-sheet); stroke: var(--ui-text-strong); stroke-width: 1.25; stroke-linejoin: round; }
176
+
177
+ /* Native code containment: an intrinsic block with indentation, braces and a measured scope guide. */
178
+ .code-scope { display: flex; flex-direction: column; width: max-content; }
179
+ .code-scope > .code { color: var(--annotation-ink, var(--ui-text-body)); }
180
+ .code-scope-body { padding-left: 16px; border-left: 1px solid color-mix(in srgb, var(--annotation-ink, var(--ui-text-muted)) 35%, transparent); }
181
+ .code-scope-body > .code-scope { --annotation-ink: var(--ui-text-body); }
182
+ .code-scope-body > .code-scope[data-tone="violet"] { --annotation-ink: var(--ui-tone-violet); }
183
+
184
+ /* Computed-variable comparison: nested boundaries mean DOM scopes; swatches are literal teaching results. */
185
+ .variable-scope { display: flex; flex-direction: column; gap: 4px; width: max-content; padding: 12px; border: 1px solid var(--ui-line-divider); border-radius: var(--ui-radius-lg); }
186
+ .variable-scope > .code:first-child { color: var(--ui-text-strong); margin-bottom: 3px; }
187
+ .variable-region { display: flex; flex-direction: column; gap: 4px; margin-top: 8px; padding: 10px; border-left: 1px solid var(--ui-line-strong); background: var(--ui-surface-tray); border-radius: 0 var(--ui-radius-sm) var(--ui-radius-sm) 0; }
188
+ .variable-sample { margin-top: 8px; padding: 8px 12px; border-radius: var(--ui-radius-sm); background: #1d4ed8; }
189
+ .variable-sample > .code { color: white; }
190
+ .variable-scope[data-inline="true"] .variable-sample { background: #86198f; }
191
+ .variable-scope .edge { line-height: 20px; }
192
+
193
+ /* Layer order is a ranked list. The final entry lies outside the five named layers. */
194
+ .priority-stack { display: flex; flex-direction: column; gap: 8px; width: max-content; }
195
+ .priority-stack > div { padding: 6px 28px; text-align: center; background: var(--ui-surface-sheet); border-radius: var(--ui-radius-sm); box-shadow: 0 0 0 1px var(--ui-line-hair); }
196
+ .priority-stack > .unlayered { position: relative; margin-top: 12px; background: none; box-shadow: none; }
197
+ .priority-stack > .unlayered::before { content: ''; position: absolute; top: -10px; inset-inline: 0; height: 1px; background: var(--ui-line-divider); }
198
+
199
+ .routes path[data-emphasis], .routes marker#arrow-emphasis path { stroke: var(--ui-text-muted); }
200
+
201
+ /* A removed input remains as context, with no live dependency edge. */
202
+ .source-contribution[data-removed] { background: none; box-shadow: none; outline: 1px dashed var(--ui-line-strong); }
203
+ .source-contribution[data-removed] .title { color: var(--ui-text-muted); text-decoration: line-through; }
204
+
205
+ /* A shared measured width scale distinguishes the viewport from a query container. */
206
+ .query-scopes { --query-ink: var(--ui-tone-blue); }
207
+ .query-ruler { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
208
+ .query-ruler::before, .query-ruler::after { content: ''; flex: 1; height: 8px; border-top: 1px solid var(--query-ink); }
209
+ .query-ruler::before { border-left: 1px solid var(--query-ink); }
210
+ .query-ruler::after { border-right: 1px solid var(--query-ink); }
211
+ .query-ruler > .code { color: var(--query-ink); }
212
+ .query-viewport { display: flex; flex-direction: column; align-items: center; gap: 22px; padding: 16px 0 22px; border: 1px solid var(--ui-line-strong); border-radius: var(--ui-radius-md); }
213
+ .query-scope-heading { display: flex; align-items: center; justify-content: space-between; width: 100%; padding-inline: 16px; }
214
+ .query-scope-heading > .code { color: var(--query-ink); }
215
+ .query-container-study { --query-ink: var(--ui-tone-violet); }
216
+ .query-container { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 14px 12px; border: 1px solid var(--ui-line-divider); border-radius: var(--ui-radius-sm); background: var(--ui-surface-sheet); }
217
+ .query-container > .code { color: var(--query-ink); }
218
+ .query-descendant { margin-top: 8px; padding: 8px 12px; border-radius: var(--ui-radius-sm); background: var(--ui-surface-tray); }
@@ -1,2 +1,2 @@
1
- declare const config: { defaultLocale: string; locales: readonly string[]; hreflangOfLocale: Record<string, string>; nameOfLocale: Record<string, string> }
1
+ declare const config: { defaultLocale: string; locales: readonly string[]; hreflangOfLocale: Record<string, string>; nameOfLocale: Record<string, string>; localizableOrigins: Record<string, readonly string[]> }
2
2
  export default config
@@ -1,13 +1,5 @@
1
- const i18n = {
2
- defaultLocale: 'en',
3
- locales: ['en', 'tw'],
4
- hreflangOfLocale: {
5
- tw: 'zh-TW'
6
- },
7
- nameOfLocale: {
8
- tw: '中文 (臺灣)',
9
- en: 'English'
10
- }
11
- }
1
+ import { defaultLocale, locales, hreflangOfLocale, nameOfLocale, localizableOrigins } from './locales.js'
12
2
 
13
- export default i18n
3
+ const i18n = { defaultLocale, locales, hreflangOfLocale, nameOfLocale, localizableOrigins }
4
+
5
+ export default i18n
@@ -0,0 +1,8 @@
1
+ export declare const locales: readonly ['en', 'tw', 'ja', 'ko', 'cn']
2
+ export declare const defaultLocale: 'en'
3
+ export type Locale = typeof locales[number]
4
+ export declare const hreflangOfLocale: Record<string, string>
5
+ export declare const nameOfLocale: Record<string, string>
6
+ export declare const localizableOrigins: Record<string, readonly string[]>
7
+ export declare function languageOf(locale: string): string
8
+ export declare function formatDate(value: string | number | Date, locale: string, options?: Intl.DateTimeFormatOptions): string
@@ -0,0 +1,18 @@
1
+ /** URL prefixes stay stable; document language tags use standard BCP 47 values. */
2
+ export const locales = ['en', 'tw', 'ja', 'ko', 'cn']
3
+ export const defaultLocale = 'en'
4
+ export const hreflangOfLocale = { en: 'en', tw: 'zh-TW', ja: 'ja', ko: 'ko', cn: 'zh-CN' }
5
+ export const nameOfLocale = { en: 'English', tw: '繁體中文(台灣)', ja: '日本語', ko: '한국어', cn: '简体中文' }
6
+ /** Only first-party page links inherit the reader's language across sites. */
7
+ export const localizableOrigins = {
8
+ 'https://master.co': ['blog', 'brand', 'design-system'],
9
+ 'https://css.master.co': ['guide', 'reference', 'benchmarks', 'roadmap'],
10
+ 'https://rc.css.master.co': ['guide', 'reference', 'benchmarks', 'roadmap']
11
+ }
12
+
13
+ export function languageOf(locale) { return hreflangOfLocale[locale] ?? locale }
14
+
15
+ /** UTC keeps publication dates identical in static builds and hydrated islands. */
16
+ export function formatDate(value, locale, options = {}) {
17
+ return new Intl.DateTimeFormat(languageOf(locale), { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC', ...options }).format(new Date(value))
18
+ }