Skip to content

Commit a1e2df8

Browse files
committed
Redesign GitHub Pages site
1 parent e565588 commit a1e2df8

File tree

2 files changed

+588
-176
lines changed

2 files changed

+588
-176
lines changed

scripts/build-site.mjs

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ const tokens = md.parse(readme, {});
4040
const title = extractTitle(tokens) ?? "TPS Format Specification";
4141
const summary = extractSummary(tokens) ?? "Markdown-based teleprompter scripts with timing, pacing, emotion, and styling metadata.";
4242
const sections = extractSections(tokens);
43-
const articleHtml = md.render(readme);
43+
const stats = buildStats(readme, sections);
44+
const articleHtml = md.renderer.render(trimTitle(tokens), md.options, {});
45+
const heroTitle = buildHeroTitle(title);
4446
const builtAt = new Intl.DateTimeFormat("en", {
4547
dateStyle: "long",
4648
timeStyle: "short",
@@ -70,12 +72,23 @@ const page = `<!DOCTYPE html>
7072
<div class="page-shell">
7173
<header class="hero">
7274
<div class="hero-copy">
73-
<span class="eyebrow">ManagedCode Format Spec</span>
74-
<h1>${escapeHtml(title)}</h1>
75+
<span class="eyebrow">Managed Code / Open Spec</span>
76+
<p class="hero-kicker">A markdown-first teleprompter format built for natural delivery.</p>
77+
${heroTitle}
7578
<p class="hero-summary">${escapeHtml(summary)}</p>
79+
<ul class="hero-signals" aria-label="Format highlights">
80+
<li>Markdown-native authoring</li>
81+
<li>Actor and RSVP reading profiles</li>
82+
<li>Timing, pace, and emotion metadata</li>
83+
</ul>
7684
<div class="hero-actions">
77-
<a class="button button-primary" href="https://github.com/managedcode/TPS">Open Repository</a>
78-
<a class="button button-secondary" href="https://github.com/managedcode/TPS/blob/main/README.md">View Raw README</a>
85+
<a class="button button-primary" href="#specification">Read Specification</a>
86+
<a class="button button-secondary" href="https://github.com/managedcode/TPS">Open Repository</a>
87+
</div>
88+
<div class="hero-facts">
89+
<span><strong>${stats.sectionCount}</strong> major sections</span>
90+
<span><strong>${stats.subsectionCount}</strong> deep-dive subsections</span>
91+
<span><strong>${stats.wordCount.toLocaleString("en-US")}</strong> words of spec detail</span>
7992
</div>
8093
</div>
8194
</header>
@@ -86,17 +99,19 @@ const page = `<!DOCTYPE html>
8699
<p class="panel-label">Contents</p>
87100
<a href="#top">Back to top</a>
88101
</div>
102+
<p class="toc-summary">Jump straight to the part of the format you need.</p>
89103
<nav aria-label="Table of contents">
90104
<ol class="toc-list">
91105
${renderSections(sections)}
92106
</ol>
93107
</nav>
94108
</aside>
95109
96-
<article class="content-card">
110+
<article class="content-card" id="specification">
97111
<div class="content-meta" id="top">
98-
<span>Source of truth: <code>README.md</code></span>
99-
<span>Last site build: ${escapeHtml(builtAt)} UTC</span>
112+
<span class="meta-chip">Source of truth <code>README.md</code></span>
113+
<span class="meta-chip">${stats.sectionCount} sections / ${stats.subsectionCount} subsections</span>
114+
<span class="meta-chip">Built ${escapeHtml(builtAt)} UTC</span>
100115
</div>
101116
<div class="markdown-body">
102117
${articleHtml}
@@ -179,6 +194,44 @@ function extractSections(tokenList) {
179194
return sectionList;
180195
}
181196

197+
function buildStats(markdown, sectionList) {
198+
return {
199+
sectionCount: sectionList.filter((section) => section.depth === 2).length,
200+
subsectionCount: sectionList.filter((section) => section.depth === 3).length,
201+
wordCount: markdown.match(/\b[\p{L}\p{N}][\p{L}\p{N}'-]*\b/gu)?.length ?? 0
202+
};
203+
}
204+
205+
function trimTitle(tokenList) {
206+
if (
207+
tokenList[0]?.type === "heading_open" &&
208+
tokenList[0]?.tag === "h1" &&
209+
tokenList[2]?.type === "heading_close"
210+
) {
211+
return tokenList.slice(3);
212+
}
213+
214+
return tokenList;
215+
}
216+
217+
function buildHeroTitle(value) {
218+
const match = value.match(/^(.*?)\s*\((.*?)\)\s*$/);
219+
if (!match) {
220+
return `<h1>${escapeHtml(value)}</h1>`;
221+
}
222+
223+
const baseTitle = match[1].trim();
224+
const subtitle = match[2].trim();
225+
const [lead, ...rest] = baseTitle.split(/\s+/);
226+
const titleTail = rest.join(" ");
227+
228+
if (!lead || !titleTail) {
229+
return `<h1>${escapeHtml(baseTitle)}</h1><p class="hero-title-sub">${escapeHtml(subtitle)}</p>`;
230+
}
231+
232+
return `<h1><span class="hero-title-mark">${escapeHtml(lead)}</span><span class="hero-title-main">${escapeHtml(titleTail)}</span></h1><p class="hero-title-sub">${escapeHtml(subtitle)}</p>`;
233+
}
234+
182235
function renderSections(sectionList) {
183236
return sectionList
184237
.map((section) => {

0 commit comments

Comments
 (0)