diff --git a/assets/landing.css b/assets/landing.css new file mode 100644 index 0000000..68846fc --- /dev/null +++ b/assets/landing.css @@ -0,0 +1,101 @@ +/* + * Landing page styles, layered on top of TypeDoc's generated style.css + * (copied to assets/style.css at build time). TypeDoc wraps its rules in + * @layer typedoc, so these unlayered rules win without specificity hacks. + * Only TypeDoc's unprefixed --color-* variables are used, so the landing + * page tracks the docs' light/dark/os theming automatically. + */ + +.landing-cards { + list-style: none; + margin: 1rem 0 0; + padding: 0; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 1rem; +} + +.landing-card { + display: flex; + flex-direction: column; + padding: 1rem; + background-color: var(--color-background-secondary); + border: 1px solid var(--color-accent); + border-radius: 0.5rem; +} + +.landing-card h3 { + margin: 0 0 0.75rem; + font-size: 1rem; +} + +.landing-card-version { + display: inline-block; + margin-left: 0.25rem; + padding: 0 0.4rem; + border: 1px solid var(--color-accent); + border-radius: 0.25rem; + color: var(--color-text-aside); + font-size: 0.75rem; + font-weight: 400; + line-height: 1.25rem; + vertical-align: middle; + white-space: nowrap; +} + +.landing-card p { + flex: 1; + margin: 0 0 1rem; + color: var(--color-text-aside); + font-size: 0.875rem; +} + +.landing-card-links { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.landing-link { + display: inline-block; + padding: 0.375rem 0.75rem; + border: 1px solid transparent; + border-radius: 0.25rem; + background-color: var(--color-link); + color: var(--color-background); + font-size: 0.875rem; +} + +.landing-link:hover { + filter: brightness(1.1); +} + +.landing-link.legacy { + background-color: transparent; + border-color: var(--color-accent); + color: var(--color-text-aside); +} + +/* Single β†— indicator in the button's text color; suppress TypeDoc's + black/white a.external[target="_blank"] background icon */ +.landing-link.external { + background-image: none; + padding-right: 0.75rem; +} + +.landing-link.external::after { + content: " β†—"; +} + +.landing-nav-category { + margin: 1rem 0 0.25rem; + color: var(--color-text-aside); + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.site-menu .landing-nav-category:first-child { + margin-top: 0; +} diff --git a/build.mjs b/build.mjs index fd38743..c644712 100644 --- a/build.mjs +++ b/build.mjs @@ -25,9 +25,12 @@ try { */ function parseBranchArgs() { const args = process.argv.slice(2); - + // Process arguments in the format: repo=branch (e.g., engine=dev) for (const arg of args) { + if (arg === '--landing-only') { + continue; + } const match = arg.match(/^([^=]+)=(.+)$/); if (match) { const [, repoName, branchName] = match; @@ -96,6 +99,149 @@ function copyDirContents(src, dest) { } } +/** + * Read the version of each cloned repository from its package.json + */ +function getRepoVersions() { + const versions = {}; + + for (const repo of REPOS) { + const packagePath = path.join('repos', repo.name, 'package.json'); + try { + const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8')); + versions[repo.name] = packageJson.version || ''; + } catch (error) { + console.warn(`Warning: Could not read version for ${repo.name}: ${error.message}`); + versions[repo.name] = ''; + } + } + + return versions; +} + +/** + * Generate the landing page from the index.html template, injecting the + * build date and per-repository version numbers + */ +function generateLandingPage(versions) { + const sourceIndexPath = path.join(__dirname, 'index.html'); + if (!fs.existsSync(sourceIndexPath)) { + throw new Error(`Source index.html not found: ${sourceIndexPath}`); + } + + let html = fs.readFileSync(sourceIndexPath, 'utf8'); + + const buildDate = new Date().toLocaleDateString('en-US', { + year: 'numeric', month: 'long', day: 'numeric' + }); + html = html.replace(/\{\{BUILD_DATE\}\}/g, buildDate); + + html = html.replace(/v\{\{VERSION:([\w-]+)\}\}/g, (match, name) => { + return versions[name] ? `v${versions[name]}` : ''; + }); + + // Remove version badges left empty by missing versions + html = html.replace(/\s*\s*<\/span>/g, ''); + + fs.writeFileSync(path.join('docs', 'index.html'), html); + console.log('Generated landing page with build date and versions'); +} + +/** + * Copy TypeDoc's generated stylesheet and icons into docs/assets so the + * landing page shares the exact theme of the product references + */ +function copySharedAssets() { + const engineAssets = path.join('docs', 'engine', 'assets'); + const stylePath = path.join(engineAssets, 'style.css'); + if (!fs.existsSync(stylePath)) { + throw new Error(`TypeDoc stylesheet not found: ${stylePath}. Run a full build first.`); + } + + ensureDir(path.join('docs', 'assets')); + fs.copyFileSync(stylePath, path.join('docs', 'assets', 'style.css')); + fs.copyFileSync(path.join(engineAssets, 'icons.svg'), path.join('docs', 'assets', 'icons.svg')); + console.log('Copied shared TypeDoc assets (style.css, icons.svg)'); +} + +/** + * Post-process the generated TypeDoc pages so the way back to the landing + * page is obvious: turn the toolbar title into a breadcrumb (PlayCanvas / + * ) and rename the sidebar "Home" link. All replacements are + * idempotent so this can re-run over already-processed docs. + */ +function postProcessProductDocs() { + const breadcrumb = 'Home'; + const breadcrumbCss = ` +/* api-reference: toolbar breadcrumb back to the landing page */ +.tsd-toolbar-contents > .title-home { + font-weight: bold; + white-space: nowrap; +} +.tsd-toolbar-contents > .title-sep { + margin: 0 0.5rem; + color: var(--color-text-aside); +} +@media (max-width: 769px) { + .tsd-toolbar-contents > .title-home, + .tsd-toolbar-contents > .title-sep { + display: none; + } +} +`; + + const processHtml = (file) => { + const html = fs.readFileSync(file, 'utf8'); + let updated = html; + + // Each replacement is independently idempotent + if (!updated.includes('class="title-home"')) { + updated = updated.replace(//, match => breadcrumb + match); + } + updated = updated.replace( + /(