meowpress 0.3.0-alpha.3 → 0.3.0-alpha.4

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/dist/src/site.js CHANGED
@@ -1,142 +1,114 @@
1
1
  import { themeAttributes } from "meowcss";
2
+ import { createRuntime, Fragment, h } from "meowjsx";
3
+ import { renderToHtmlWithRuntime } from "meowjsx/web";
4
+ import { Button, Container, Entry, EntryList, Inline, MetaRow, Page, PageHeader, Prose, Section, Tag, Text, createMeowUI, } from "meowtail";
2
5
  import { blogTheme } from "./theme.js";
3
- import { Fragment, h } from "meowjsx/runtime";
4
- import { renderToHtml } from "meowjsx/web";
5
6
  import { withoutLeadingHeading } from "./content.js";
6
7
  import { DEFAULT_SITE } from "./identity.js";
7
8
  import { sitePageAgentUrl } from "./pages.js";
8
9
  import { layoutSkillGraph } from "./skill-graph.js";
9
10
  /** The name of the theme stylesheet the package writes by default. */
10
11
  export const DEFAULT_THEME_STYLESHEET = "blog-theme.css";
11
- /** The href of the theme stylesheet for a page at a given depth. */
12
12
  function styleHref(depth, stylesheet) {
13
13
  return `${"../".repeat(depth)}${stylesheet}`;
14
14
  }
15
- /** The number of directories a slug puts a page below its section. */
16
15
  function slugDepth(slug) {
17
16
  return slug.split("/").filter(Boolean).length;
18
17
  }
19
18
  function SiteHeader({ activePath, site }) {
20
- return (h("header", { className: "site" },
19
+ const links = [
20
+ ["/posts/", "Posts"],
21
+ ["/thoughts/", "Thoughts"],
22
+ ["/skills/", "Skills"],
23
+ ["/about/", "About"],
24
+ ];
25
+ return (h("header", { className: "site-header" },
21
26
  h("a", { className: "site-name", href: "/" }, site.name),
22
- h("nav", { className: "site-nav", "aria-label": "Primary navigation" },
23
- h("a", { className: activePath === "/skills/" ? "active" : "", href: "/skills/" }, "Skills"),
24
- h("a", { className: activePath === "/about/" ? "active" : "", href: "/about/" }, "About"))));
27
+ h("nav", { className: "site-nav", "aria-label": "Primary navigation" }, links.map(([href, label]) => (h("a", { href: href, "aria-current": activePath === href ? "page" : undefined }, label))))));
25
28
  }
26
29
  function SiteFooter({ site }) {
27
- return (h("footer", { className: "site" },
28
- h("span", { className: "brand" },
30
+ return (h("footer", { className: "site-footer" },
31
+ h(Text, { variant: "caption" },
29
32
  "\u00A9 ",
30
33
  new Date().getFullYear(),
31
34
  " ",
32
35
  site.name),
33
- h("span", null,
36
+ h(Inline, { gap: "compact" },
34
37
  h("a", { href: "/llm.txt" }, "llm.txt"),
35
- " \u00B7 ",
36
38
  h("a", { href: "/agents.md" }, "agents.md"))));
37
39
  }
38
- function Document({ title, description, activePath, styleHref, baseHref, children, theme, site, }) {
39
- const documentTitle = title === site.name ? site.name : `${title} · ${site.name}`;
40
- return (h("html", { lang: "en", ...themeAttributes(theme) },
40
+ function Document({ title, description, activePath, styleHref, baseHref, wide, children, theme, site, }) {
41
+ return (h("html", { lang: "en", "data-meowui-page": "", ...themeAttributes(theme) },
41
42
  h("head", null,
42
43
  h("meta", { charSet: "UTF-8" }),
43
44
  h("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
44
45
  h("meta", { name: "description", content: description ?? site.description }),
45
- h("meta", { name: "theme-color", media: "(prefers-color-scheme: light)", content: "#faf8f3" }),
46
- h("meta", { name: "theme-color", media: "(prefers-color-scheme: dark)", content: "#191816" }),
47
46
  baseHref ? h("base", { href: baseHref }) : null,
48
47
  h("link", { rel: "stylesheet", href: styleHref }),
49
- h("title", null, documentTitle)),
48
+ h("title", null, title === site.name ? site.name : `${title} · ${site.name}`)),
50
49
  h("body", { "data-site": site.id },
51
- h("div", { className: "container" },
52
- h(SiteHeader, { activePath: activePath, site: site }),
53
- children,
54
- h(SiteFooter, { site: site })))));
50
+ h("a", { className: "skip-link", href: "#main-content" }, "Skip to content"),
51
+ h("div", { className: "site-shell" },
52
+ h(Container, { width: wide ? "wide" : "content" },
53
+ h(SiteHeader, { activePath: activePath, site: site }),
54
+ h("main", { id: "main-content", tabIndex: -1 }, children),
55
+ h(SiteFooter, { site: site }))))));
55
56
  }
56
57
  function pageHtml(props) {
57
- return `<!doctype html>\n${renderToHtml(h(Document, { ...props }))}`;
58
+ // Static rendering needs the same part/option adapter as a browser mount. No hydration is needed.
59
+ const runtime = createRuntime({
60
+ plugins: [
61
+ createMeowUI({
62
+ components: [
63
+ Button,
64
+ Container,
65
+ Entry,
66
+ EntryList,
67
+ Inline,
68
+ MetaRow,
69
+ Page,
70
+ PageHeader,
71
+ Prose,
72
+ Section,
73
+ Tag,
74
+ Text,
75
+ ],
76
+ }),
77
+ ],
78
+ });
79
+ return `<!doctype html>\n${renderToHtmlWithRuntime(runtime, h(Document, { ...props }))}`;
58
80
  }
59
81
  function dateLabel(dateKey) {
60
82
  const date = new Date(`${dateKey}T00:00:00Z`);
61
83
  if (Number.isNaN(date.getTime()))
62
84
  return { date: "Undated", weekday: "" };
63
- const weekday = new Intl.DateTimeFormat("en-US", {
64
- timeZone: "UTC",
65
- weekday: "short",
66
- }).format(date);
67
85
  return {
68
86
  date: new Intl.DateTimeFormat("en-US", {
69
87
  timeZone: "UTC",
70
88
  month: "long",
71
89
  day: "numeric",
72
90
  }).format(date),
73
- weekday: `${date.getUTCFullYear()} ${weekday}`,
91
+ weekday: `${date.getUTCFullYear()} ${new Intl.DateTimeFormat("en-US", { timeZone: "UTC", weekday: "short" }).format(date)}`,
74
92
  };
75
93
  }
76
94
  function itemUrl(item) {
77
95
  return `/${item.kind}s/${item.slug}/`;
78
96
  }
79
- function Hero({ image }) {
80
- return (h("section", { className: "hero", "aria-labelledby": "hero-title" },
81
- h("div", { className: "hero-copy" },
82
- h("h1", { id: "hero-title" },
83
- "Ten thousand years is too long;",
84
- h("br", null),
85
- h("em", null, "only this day is true."))),
86
- h("div", { className: "hero-work", "aria-hidden": "true" },
87
- h("svg", { className: "hero-doodle", viewBox: "0 0 260 190" },
88
- h("path", { className: "doodle-loop", d: "M24 96C20 45 58 18 122 20c72 2 116 33 114 84-2 48-48 70-113 68-65-2-96-27-99-76Z" }),
89
- h("circle", { className: "doodle-sun", cx: "194", cy: "55", r: "27" }),
90
- h("path", { className: "doodle-page", d: "M47 100c28-9 57-5 83 11v49c-24-13-52-16-83-7Z" }),
91
- h("path", { className: "doodle-page", d: "M130 111c24-15 52-19 82-9v51c-31-7-58-4-82 7Z" }),
92
- h("path", { className: "doodle-spine", d: "M130 111v49" }),
93
- h("path", { className: "doodle-writing", d: "M61 118c18-3 36-1 53 5M61 132c17-2 32 0 46 5M148 119c17-4 33-5 50-1M148 133c14-3 29-3 44 0" }),
94
- h("circle", { className: "doodle-dot", cx: "41", cy: "61", r: "5" }),
95
- h("path", { className: "doodle-spark", d: "M72 44v12M66 50h12" })),
96
- image ? h("img", { className: "hero-image", src: image, alt: "", decoding: "async" }) : null)));
97
- }
98
- function DoodleGithubIcon() {
99
- return (h("svg", { className: "github-icon", viewBox: "0 0 48 48", "aria-hidden": "true" },
100
- h("path", { className: "github-icon-ring", d: "M24 3.8C11.7 3.5 4.2 11.2 4.6 24.1c.3 12.1 7.8 20.1 19.6 20 12.2-.1 19.4-7.7 19.2-20C43.2 11.7 36.2 4.1 24 3.8Z" }),
101
- h("path", { className: "github-icon-cat", d: "M14.3 18.8c-.7-2.4-.3-5.1.8-7.1 3.2.1 5.4 1.3 6.6 2.2 1.5-.4 3.1-.6 4.8-.4 1.4-1 3.7-1.9 6.7-1.8 1 2.2 1.3 4.9.5 7.1 1.5 1.8 2.2 4.1 2 6.8-.4 5.2-3.2 8.2-8.2 8.8.8.7 1.3 1.9 1.3 3.8v4.3c-1.5.4-3.1.6-4.8.6-1.4 0-2.8-.1-4.2-.5v-3.4c0-1.8.5-3.1 1.4-4-4.8-.6-7.7-3.5-8.2-8.7-.3-2.7.4-5 1.3-6.7Z" }),
102
- h("path", { className: "github-icon-face", d: "M20.2 24.3h.1M29.2 24.3h.1M21.2 28.9c2.2 1.4 4.5 1.4 6.8 0" })));
103
- }
104
- function DoodleExternalArrow() {
105
- return (h("svg", { className: "external-arrow", viewBox: "0 0 28 28", "aria-hidden": "true" },
106
- h("path", { d: "M7 21 21 7M12 7h9v9" })));
107
- }
108
97
  function Tags({ item }) {
109
- if (item.tags.length === 0)
110
- return null;
111
- return (h("div", { className: "tags" }, item.tags.map((tag) => (h("span", null,
112
- "#",
113
- tag)))));
98
+ return item.tags.length ? (h(Inline, { gap: "compact" }, item.tags.map((tag) => (h(Tag, null, tag))))) : null;
114
99
  }
115
100
  function ThoughtCard({ item, linked = false }) {
116
- const body = (h(Fragment, null,
117
- h("div", { className: "time" },
118
- h("span", { className: "t" }, item.timeLabel || item.dateKey),
119
- " \u00B7 Thought"),
120
- h("div", { className: "body", dangerouslySetInnerHTML: { __html: withoutLeadingHeading(item.html) } }),
101
+ return (h(Entry, { className: "thought", "data-kind": "thought", title: item.title, href: linked ? itemUrl(item) : undefined, eyebrow: `${item.timeLabel || item.dateKey} · Thought` },
102
+ h(Prose, { dangerouslySetInnerHTML: { __html: withoutLeadingHeading(item.html) } }),
121
103
  h(Tags, { item: item })));
122
- if (!linked)
123
- return (h("article", { className: "thought", "data-kind": "thought" }, body));
124
- return (h("article", { className: "thought", "data-kind": "thought" },
125
- h("a", { className: "thought-link", href: itemUrl(item) }, body)));
126
104
  }
127
105
  function PostCard({ item }) {
128
106
  const category = typeof item.metadata.category === "string" ? item.metadata.category : "Post";
129
- return (h("a", { className: "post-card", href: itemUrl(item), "data-kind": "post" },
130
- h("div", { className: "card-top" },
131
- h("span", { className: "kind" }, category),
132
- h("span", { className: "meta" },
133
- "About ",
134
- item.readingMinutes,
135
- " min \u00B7 ",
136
- item.characterCount,
137
- " characters")),
138
- h("h3", null, item.title),
139
- h("p", { className: "excerpt" }, item.excerpt || "This post has no summary yet."),
107
+ return (h(Entry, { size: "feature", "data-kind": "post", title: item.title, href: itemUrl(item), eyebrow: category, description: item.excerpt || "This post has no summary yet.", meta: h(MetaRow, { items: [
108
+ { label: item.dateKey },
109
+ { label: `${item.readingMinutes} min read` },
110
+ { label: `${item.characterCount} characters` },
111
+ ] }) },
140
112
  h(Tags, { item: item })));
141
113
  }
142
114
  function groupByDate(items) {
@@ -148,108 +120,34 @@ function groupByDate(items) {
148
120
  }
149
121
  return groups;
150
122
  }
151
- function FeedDecor({ image }) {
152
- return (h("div", { className: "feed-decor", "aria-hidden": "true" },
153
- h("svg", { className: "yarn-doodle", viewBox: "0 0 88 70" },
154
- h("path", { className: "yarn-tail", d: "M42 48c-10 7-17 4-22 9-5 4-2 9 4 8 8-1 11-8 18-6 6 1 10 6 16 4" }),
155
- h("circle", { className: "yarn-fill", cx: "48", cy: "34", r: "22" }),
156
- h("path", { className: "yarn-line", d: "M31 23c11 1 22 7 34 18M28 34c12-7 24-9 39-5M34 49c7-14 16-24 28-31M28 42c13 1 24 5 33 12" })),
157
- h("svg", { className: "paw-trail", viewBox: "0 0 130 82" },
158
- h("g", { className: "paw-print", transform: "translate(12 54) rotate(-18)" },
159
- h("ellipse", { cx: "9", cy: "11", rx: "6", ry: "5" }),
160
- h("circle", { cx: "3", cy: "3", r: "2.5" }),
161
- h("circle", { cx: "9", cy: "1", r: "2.5" }),
162
- h("circle", { cx: "15", cy: "3", r: "2.5" })),
163
- h("g", { className: "paw-print", transform: "translate(45 37) rotate(8)" },
164
- h("ellipse", { cx: "9", cy: "11", rx: "6", ry: "5" }),
165
- h("circle", { cx: "3", cy: "3", r: "2.5" }),
166
- h("circle", { cx: "9", cy: "1", r: "2.5" }),
167
- h("circle", { cx: "15", cy: "3", r: "2.5" })),
168
- h("g", { className: "paw-print", transform: "translate(79 19) rotate(-12)" },
169
- h("ellipse", { cx: "9", cy: "11", rx: "6", ry: "5" }),
170
- h("circle", { cx: "3", cy: "3", r: "2.5" }),
171
- h("circle", { cx: "9", cy: "1", r: "2.5" }),
172
- h("circle", { cx: "15", cy: "3", r: "2.5" })),
173
- h("g", { className: "paw-print paw-print-accent", transform: "translate(111 5) rotate(11)" },
174
- h("ellipse", { cx: "7", cy: "9", rx: "5", ry: "4" }),
175
- h("circle", { cx: "2", cy: "2", r: "2" }),
176
- h("circle", { cx: "7", cy: "1", r: "2" }),
177
- h("circle", { cx: "12", cy: "2", r: "2" }))),
178
- image ? h("img", { className: "feed-image", src: image, alt: "", decoding: "async" }) : null));
179
- }
180
123
  function Feed({ items, image }) {
181
- if (items.length === 0) {
182
- return (h("main", { className: "feed feed-empty" },
183
- h("p", { className: "empty-state" }, "No public content yet."),
184
- h(FeedDecor, { image: image })));
185
- }
186
- return (h("main", { className: "feed" },
187
- [...groupByDate(items)].map(([dateKey, group]) => {
124
+ return (h("div", { className: "feed" },
125
+ items.length === 0 ? (h(Text, { variant: "secondary" }, "No public content yet.")) : ([...groupByDate(items)].map(([dateKey, group]) => {
188
126
  const label = dateLabel(dateKey);
189
- return (h("section", { className: "day" },
190
- h("h2", { className: "day-header" },
191
- label.date,
192
- " ",
193
- h("span", { className: "weekday" }, label.weekday)),
194
- group.map((item) => item.kind === "thought" ? (h(ThoughtCard, { item: item, linked: true })) : (h(PostCard, { item: item })))));
195
- }),
196
- h(FeedDecor, { image: image })));
127
+ return (h(Section, { className: "day", title: label.date, description: label.weekday },
128
+ h(EntryList, null, group.map((item) => item.kind === "thought" ? (h(ThoughtCard, { item: item, linked: true })) : (h(PostCard, { item: item }))))));
129
+ })),
130
+ image ? (h("img", { className: "feed-image", src: image, alt: "", loading: "lazy", decoding: "async" })) : null));
197
131
  }
198
132
  function Filter({ posts, thoughts }) {
199
- const total = posts + thoughts;
200
- return (h("div", { className: "tag-filter", "data-filter-group": "feed", role: "group", "aria-label": "Filter home feed" },
201
- h("button", { type: "button", "data-filter": "all", className: "active", "aria-pressed": "true" },
202
- "All",
203
- h("i", null, total)),
204
- h("button", { type: "button", "data-filter": "post", "aria-pressed": "false" },
205
- "Posts",
206
- h("i", null, posts)),
207
- h("button", { type: "button", "data-filter": "thought", "aria-pressed": "false" },
208
- "Thoughts",
209
- h("i", null, thoughts))));
133
+ return (h(Inline, { className: "feed-filter", gap: "compact", "data-filter-group": "feed", role: "group", "aria-label": "Filter home feed" }, [
134
+ ["all", "All", posts + thoughts],
135
+ ["post", "Posts", posts],
136
+ ["thought", "Thoughts", thoughts],
137
+ ].map(([value, label, count]) => (h(Button, { variant: "quiet", "data-filter": value, "aria-pressed": String(value === "all") },
138
+ label,
139
+ " ",
140
+ h("span", { className: "filter-count" }, count))))));
210
141
  }
211
142
  const FILTER_SCRIPT = `(() => {
212
143
  const links = document.querySelectorAll('[data-filter-group="feed"] [data-filter]');
213
144
  const items = document.querySelectorAll('.feed [data-kind]');
214
145
  const days = document.querySelectorAll('.feed .day');
215
- const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
216
- if ('IntersectionObserver' in window && !reduced) {
217
- const observer = new IntersectionObserver((entries) => {
218
- entries.forEach((entry) => {
219
- if (entry.isIntersecting) {
220
- entry.target.classList.add('in');
221
- observer.unobserve(entry.target);
222
- }
223
- });
224
- }, { threshold: 0.08 });
225
- days.forEach((day) => observer.observe(day));
226
- } else {
227
- days.forEach((day) => day.classList.add('in'));
228
- }
229
- links.forEach((link) => link.addEventListener('click', (event) => {
230
- event.preventDefault();
146
+ links.forEach((link) => link.addEventListener('click', () => {
231
147
  const filter = link.dataset.filter;
232
- links.forEach((item) => {
233
- const active = item === link;
234
- item.classList.toggle('active', active);
235
- item.setAttribute('aria-pressed', String(active));
236
- });
237
- items.forEach((item) => {
238
- item.style.display = filter === 'all' || item.dataset.kind === filter ? '' : 'none';
239
- });
240
- days.forEach((day) => {
241
- const visible = Array.from(day.querySelectorAll('[data-kind]'))
242
- .some((item) => item.style.display !== 'none');
243
- day.style.display = visible ? '' : 'none';
244
- if (!visible) day.classList.remove('in');
245
- });
246
- if (!reduced) {
247
- requestAnimationFrame(() => requestAnimationFrame(() => {
248
- days.forEach((day) => {
249
- if (day.style.display !== 'none') day.classList.add('in');
250
- });
251
- }));
252
- }
148
+ links.forEach((button) => button.setAttribute('aria-pressed', String(button === link)));
149
+ items.forEach((item) => { item.hidden = filter !== 'all' && item.dataset.kind !== filter; });
150
+ days.forEach((day) => { day.hidden = !Array.from(day.querySelectorAll('[data-kind]')).some((item) => !item.hidden); });
253
151
  }));
254
152
  })();`;
255
153
  export function homePage(items, theme = blogTheme, site = DEFAULT_SITE, stylesheet = DEFAULT_THEME_STYLESHEET) {
@@ -258,29 +156,27 @@ export function homePage(items, theme = blogTheme, site = DEFAULT_SITE, styleshe
258
156
  theme,
259
157
  title: site.name,
260
158
  styleHref: styleHref(0, stylesheet),
261
- children: (h(Fragment, null,
262
- h(Hero, { image: site.images?.hero ? `./${site.images.hero}` : undefined }),
263
- h(Filter, { posts: items.filter((item) => item.kind === "post").length, thoughts: items.filter((item) => item.kind === "thought").length }),
159
+ children: (h(Page, { header: h("div", { className: "home-heading" },
160
+ h("div", { className: "home-heading-copy" },
161
+ h(Text, { variant: "eyebrow" }, "Journal"),
162
+ h(PageHeader, { title: site.name, description: site.description })),
163
+ site.images?.hero ? (h("img", { className: "hero-image", src: `./${site.images.hero}`, alt: "", decoding: "async" })) : null), toolbar: h(Filter, { posts: items.filter((item) => item.kind === "post").length, thoughts: items.filter((item) => item.kind === "thought").length }) },
264
164
  h(Feed, { items: items, image: site.images?.feed ? `./${site.images.feed}` : undefined }),
265
165
  h("script", { dangerouslySetInnerHTML: { __html: FILTER_SCRIPT } }))),
266
166
  });
267
167
  }
268
168
  export function listPage(kind, items, theme = blogTheme, site = DEFAULT_SITE, stylesheet = DEFAULT_THEME_STYLESHEET) {
269
169
  const isPost = kind === "post";
170
+ const title = isPost ? "Posts" : "Thoughts";
270
171
  return pageHtml({
271
172
  site,
272
173
  theme,
273
- title: isPost ? "Posts" : "Thoughts",
174
+ title,
274
175
  activePath: isPost ? "/posts/" : "/thoughts/",
275
176
  styleHref: styleHref(1, stylesheet),
276
- children: (h("main", { className: "listing" },
277
- h("header", { className: "listing-head" },
278
- h("span", { className: "kind" }, isPost ? "Posts" : "Thoughts"),
279
- h("h1", null, isPost ? "Posts" : "Thoughts"),
280
- h("p", null, isPost
281
- ? "Complete arguments grown from fragments."
282
- : "Short, trimmed thoughts ready to show as they are.")),
283
- h("div", { className: isPost ? "post-list" : "thought-list" }, items.length === 0 ? (h("p", { className: "empty-state" }, "No public content yet.")) : (items.map((item) => isPost ? h(PostCard, { item: item }) : h(ThoughtCard, { item: item, linked: true })))))),
177
+ children: (h(Page, { title: title, eyebrow: "Archive", description: isPost
178
+ ? "Complete arguments grown from fragments."
179
+ : "Short, trimmed thoughts ready to show as they are." }, items.length ? (h(EntryList, null, items.map((item) => isPost ? h(PostCard, { item: item }) : h(ThoughtCard, { item: item, linked: true })))) : (h(Text, { variant: "secondary" }, "No public content yet.")))),
284
180
  });
285
181
  }
286
182
  export function notFoundPage(copy, theme, site, stylesheet) {
@@ -292,14 +188,9 @@ export function notFoundPage(copy, theme, site, stylesheet) {
292
188
  title,
293
189
  description: message,
294
190
  styleHref: styleHref(0, stylesheet),
295
- // Static hosts can serve 404.html at the missing URL, including deep paths.
296
191
  baseHref: "/",
297
- children: (h("main", { className: "listing" },
298
- h("header", { className: "listing-head" },
299
- h("span", { className: "kind" }, "404"),
300
- h("h1", null, title),
301
- h("p", null, message)),
302
- h("div", { className: "prose" },
192
+ children: (h(Page, { eyebrow: "404", title: title, description: message },
193
+ h(Prose, null,
303
194
  h("a", { href: "/" }, copy.homeLabel ?? "Back to home")))),
304
195
  });
305
196
  }
@@ -308,22 +199,13 @@ function PostNavigation({ item, posts }) {
308
199
  const older = index >= 0 ? posts[index + 1] : undefined;
309
200
  const newer = index > 0 ? posts[index - 1] : undefined;
310
201
  return (h("nav", { className: "post-nav", "aria-label": "Post navigation" },
311
- older ? (h("a", { href: itemUrl(older) },
312
- h("span", { className: "label" }, "\u2190 Previous post (older)"),
313
- older.title)) : (h("span", null)),
314
- newer ? (h("a", { className: "next", href: itemUrl(newer) },
315
- h("span", { className: "label" }, "Next post (newer) \u2192"),
316
- newer.title)) : (h("span", { className: "next" },
317
- h("span", { className: "label" }, "Next post (newer) \u2192"),
318
- "Nothing newer \u2014 this is the latest post."))));
202
+ older ? (h(Entry, { eyebrow: "\u2190 Previous post (older)", title: older.title, href: itemUrl(older) })) : (h("span", null)),
203
+ newer ? (h(Entry, { eyebrow: "Next post (newer) \u2192", title: newer.title, href: itemUrl(newer) })) : (h(Text, { variant: "caption" }, "Nothing newer \u2014 this is the latest post."))));
319
204
  }
320
205
  function RelatedThoughts({ item, thoughts, }) {
321
206
  const related = thoughts.filter((thought) => thought.dateKey === item.dateKey).slice(0, 3);
322
- if (related.length === 0)
323
- return null;
324
- return (h("aside", { className: "related-thoughts" },
325
- h("h2", null, "Thoughts from the same day"),
326
- related.map((thought) => (h(ThoughtCard, { item: thought, linked: true })))));
207
+ return related.length ? (h(Section, { title: "Thoughts from the same day" },
208
+ h(EntryList, null, related.map((thought) => (h(ThoughtCard, { item: thought, linked: true })))))) : null;
327
209
  }
328
210
  export function postPage(item, posts, thoughts, theme = blogTheme, site = DEFAULT_SITE, stylesheet = DEFAULT_THEME_STYLESHEET) {
329
211
  const category = typeof item.metadata.category === "string" ? item.metadata.category : "Post";
@@ -334,21 +216,16 @@ export function postPage(item, posts, thoughts, theme = blogTheme, site = DEFAUL
334
216
  description: item.excerpt,
335
217
  activePath: "/posts/",
336
218
  styleHref: styleHref(1 + slugDepth(item.slug), stylesheet),
337
- children: (h("main", null,
338
- h("article", null,
339
- h("header", { className: "post-head" },
340
- h("span", { className: "kind" }, category),
341
- h("h1", null, item.title),
342
- h("div", { className: "meta" },
343
- item.dateKey,
344
- " \u00B7 about ",
345
- item.readingMinutes,
346
- " min \u00B7 ",
347
- item.characterCount,
348
- " characters")),
349
- h("div", { className: "prose", dangerouslySetInnerHTML: { __html: item.html } }),
350
- h(RelatedThoughts, { item: item, thoughts: thoughts }),
351
- h(PostNavigation, { item: item, posts: posts })))),
219
+ children: (h(Page, { title: item.title, eyebrow: category },
220
+ h(MetaRow, { items: [
221
+ { label: item.dateKey },
222
+ { label: `${item.readingMinutes} min read` },
223
+ { label: `${item.characterCount} characters` },
224
+ ] }),
225
+ h(Prose, { as: "article", dangerouslySetInnerHTML: { __html: withoutLeadingHeading(item.html) } }),
226
+ h(Tags, { item: item }),
227
+ h(RelatedThoughts, { item: item, thoughts: thoughts }),
228
+ h(PostNavigation, { item: item, posts: posts }))),
352
229
  });
353
230
  }
354
231
  export function thoughtPage(item, theme = blogTheme, site = DEFAULT_SITE, stylesheet = DEFAULT_THEME_STYLESHEET) {
@@ -359,15 +236,10 @@ export function thoughtPage(item, theme = blogTheme, site = DEFAULT_SITE, styles
359
236
  description: item.excerpt,
360
237
  activePath: "/thoughts/",
361
238
  styleHref: styleHref(1 + slugDepth(item.slug), stylesheet),
362
- children: (h("main", null,
363
- h("article", null,
364
- h("header", { className: "post-head" },
365
- h("span", { className: "kind" }, "Thought"),
366
- h("h1", null, item.title),
367
- h("div", { className: "meta" },
368
- item.dateKey,
369
- item.timeLabel ? ` · ${item.timeLabel}` : "")),
370
- h("div", { className: "prose", dangerouslySetInnerHTML: { __html: item.html } })))),
239
+ children: (h(Page, { title: item.title, eyebrow: "Thought" },
240
+ h(MetaRow, { items: [{ label: item.dateKey }, ...(item.timeLabel ? [{ label: item.timeLabel }] : [])] }),
241
+ h(Prose, { as: "article", dangerouslySetInnerHTML: { __html: withoutLeadingHeading(item.html) } }),
242
+ h(Tags, { item: item }))),
371
243
  });
372
244
  }
373
245
  export function aboutPage(theme = blogTheme, site = DEFAULT_SITE, stylesheet = DEFAULT_THEME_STYLESHEET) {
@@ -378,19 +250,13 @@ export function aboutPage(theme = blogTheme, site = DEFAULT_SITE, stylesheet = D
378
250
  title: "About",
379
251
  activePath: "/about/",
380
252
  styleHref: styleHref(1, stylesheet),
381
- children: (h("main", { className: "about" },
382
- h("div", { className: "avatar", "aria-hidden": "true" }, site.images?.about ? (h("img", { src: `../${site.images.about}`, alt: "", decoding: "async" })) : (h("span", { className: "avatar-initial" }, Array.from(site.name.trim())[0] ?? "M"))),
383
- about ? (h(Fragment, null,
384
- h("h1", null, about.heading),
385
- about.paragraphs.map((paragraph) => (h("p", null, paragraph))),
386
- about.links && about.links.length > 0 ? (h(Fragment, null,
387
- h("h2", null, about.linksHeading ?? "Find me"),
388
- about.links.map((link) => (h("a", { className: "github-link", href: link.href, target: "_blank", rel: "me noopener noreferrer" },
389
- h(DoodleGithubIcon, null),
390
- h("span", { className: "github-copy" },
391
- h("span", null, link.label),
392
- h("strong", null, link.handle)),
393
- h(DoodleExternalArrow, null)))))) : null)) : (h("h1", null, site.name)))),
253
+ children: (h(Page, { eyebrow: "About", title: about?.heading ?? site.name },
254
+ h("div", { className: "site-avatar", "aria-hidden": "true" }, site.images?.about ? (h("img", { src: `../${site.images.about}`, alt: "", decoding: "async" })) : (h("span", { className: "avatar-initial" }, Array.from(site.name.trim())[0] ?? "M"))),
255
+ about ? (h(Prose, null, about.paragraphs.map((paragraph) => (h("p", null, paragraph))))) : null,
256
+ about?.links?.length ? (h(Section, { title: about.linksHeading ?? "Find me" },
257
+ h(EntryList, null, about.links.map((link) => (h(Entry, { title: h("a", { href: link.href, target: "_blank", rel: "me noopener noreferrer" },
258
+ link.handle,
259
+ " \u2197"), eyebrow: link.label })))))) : null)),
394
260
  });
395
261
  }
396
262
  function skillUrl(skill) {
@@ -402,20 +268,12 @@ function skillInstallCommand(site, skill) {
402
268
  : skill.sourceBasePath;
403
269
  }
404
270
  function SkillCard({ skill, site }) {
405
- return (h("a", { className: `skill-card${skill.scope === "project" ? " project-skill" : ""}`, href: skillUrl(skill) },
406
- h("span", { className: "skill-card-source" },
407
- h("span", { className: "skill-card-path" }, skill.scope === "public" ? skillInstallCommand(site, skill) : skill.sourceBasePath),
408
- skill.scope === "project" ? (h("span", { className: "skill-scope-badge" }, "Project Skill")) : null),
409
- h("span", { className: "skill-card-main" },
410
- h("strong", null, skill.name),
411
- h("span", null, skill.description)),
412
- h("span", { className: "skill-card-foot" },
413
- h("span", null,
414
- skill.files.length,
415
- " Markdown files \u00B7 ",
416
- skill.relations.length,
417
- " references"),
418
- h("span", { className: "skill-open" }, "Open directory\u00A0 \u2192"))));
271
+ return (h(Entry, { href: skillUrl(skill), title: skill.name, description: skill.description, eyebrow: skill.scope === "project" ? "Project Skill" : "Public Skill", meta: h(MetaRow, { items: [
272
+ { label: `${skill.files.length} Markdown files` },
273
+ { label: `${skill.relations.length} references` },
274
+ ] }) },
275
+ h(Text, { variant: "caption" },
276
+ h("code", { className: "skill-command-text" }, skill.scope === "public" ? skillInstallCommand(site, skill) : skill.sourceBasePath))));
419
277
  }
420
278
  function skillTree(files) {
421
279
  const root = { name: "", children: [] };
@@ -459,7 +317,7 @@ function SkillGraphNodeView({ node }) {
459
317
  function SkillRelationGraph({ skill, direction }) {
460
318
  const graph = layoutSkillGraph(skill, direction);
461
319
  const markerId = `skill-arrow-${skill.slug}-${direction.toLowerCase()}`;
462
- return (h("svg", { className: `skill-graph-svg skill-graph-${direction.toLowerCase()}`, viewBox: `0 0 ${graph.width} ${graph.height}`, "aria-label": `${skill.name} file reference graph` },
320
+ return (h("svg", { className: `skill-graph-svg skill-graph-${direction.toLowerCase()}`, viewBox: `0 0 ${graph.width} ${graph.height}`, width: graph.width, height: graph.height, "aria-label": `${skill.name} file reference graph` },
463
321
  h("defs", null,
464
322
  h("marker", { id: markerId, viewBox: "0 0 10 10", refX: "8", refY: "5", markerWidth: "5", markerHeight: "5", orient: "auto-start-reverse" },
465
323
  h("path", { className: "skill-graph-arrow", d: "M 0 0 L 10 5 L 0 10 z" }))),
@@ -499,9 +357,8 @@ function SkillRelations({ skill }) {
499
357
  h("strong", null, missingNodes.length),
500
358
  " missing targets")),
501
359
  hasRelations ? (h(Fragment, null,
502
- h("div", { className: `skill-graph-canvas${compactGraph ? " compact" : ""}` },
503
- h(SkillRelationGraph, { skill: skill, direction: "LR" }),
504
- h(SkillRelationGraph, { skill: skill, direction: "TB" })),
360
+ h("div", { className: `skill-graph-canvas${compactGraph ? " compact" : ""}`, tabIndex: 0, role: "region", "aria-label": "Scrollable file reference graph" },
361
+ h(SkillRelationGraph, { skill: skill, direction: "LR" })),
505
362
  h("section", { className: "skill-reading-paths", "aria-labelledby": `${skill.slug}-paths` },
506
363
  h("h3", { id: `${skill.slug}-paths` }, "Where references happen"),
507
364
  h("ol", null, skill.relations.map((relation) => {
@@ -636,24 +493,11 @@ export function skillsIndexPage(skills, theme = blogTheme, site = DEFAULT_SITE,
636
493
  description: "Methods and working conventions that can be handed to an Agent.",
637
494
  activePath: "/skills/",
638
495
  styleHref: styleHref(1, stylesheet),
639
- children: (h("main", { className: "skills-index" },
640
- h("header", { className: "skills-head" },
641
- h("span", { className: "skill-path" }, "~/skills"),
642
- h("h1", null, "Skills"),
643
- h("p", null, "Methods shared publicly and usable directly by an Agent.")),
644
- h("section", { className: "skill-group", "aria-labelledby": "public-skills-heading" },
645
- h("header", { className: "skill-group-head" },
646
- h("h2", { id: "public-skills-heading" }, "Public Skills"),
647
- h("span", null, "Public content")),
648
- publicSkills.length > 0 ? (h("div", { className: "skill-list" }, publicSkills.map((skill) => (h(SkillCard, { skill: skill, site: site }))))) : (h("p", { className: "skill-group-empty" }, "Public Skills have no content yet."))),
649
- projectSkills.length > 0 ? (h("section", { className: "skill-group project-skills", "aria-labelledby": "project-skills-heading" },
650
- h("header", { className: "skill-group-head" },
651
- h("div", null,
652
- h("h2", { id: "project-skills-heading" }, "Project Skills"),
653
- h("small", null, "Development and content workflows used inside this project")),
654
- h("span", null, ".agents/skills/")),
655
- h("div", { className: "skill-list" }, projectSkills.map((skill) => (h(SkillCard, { skill: skill, site: site })))))) : null,
656
- h("p", { className: "skills-prototype-note" }, "The reference graph starts from SKILL.md and is generated at build time from Markdown links."))),
496
+ children: (h(Page, { eyebrow: "~/skills", title: "Skills", description: "Methods shared publicly and usable directly by an Agent." },
497
+ h(Section, { title: "Public Skills" }, publicSkills.length ? (h(EntryList, null, publicSkills.map((skill) => (h(SkillCard, { skill: skill, site: site }))))) : (h(Text, { variant: "secondary" }, "Public Skills have no content yet."))),
498
+ projectSkills.length ? (h(Section, { title: "Project Skills", description: "Development and content workflows used inside this project" },
499
+ h(EntryList, null, projectSkills.map((skill) => (h(SkillCard, { skill: skill, site: site })))))) : null,
500
+ h(Text, { variant: "caption" }, "The reference graph starts from SKILL.md and is generated at build time from Markdown links."))),
657
501
  });
658
502
  }
659
503
  export function skillPage(skill, theme = blogTheme, site = DEFAULT_SITE, stylesheet = DEFAULT_THEME_STYLESHEET) {
@@ -663,12 +507,13 @@ export function skillPage(skill, theme = blogTheme, site = DEFAULT_SITE, stylesh
663
507
  site,
664
508
  theme,
665
509
  title: skill.name,
510
+ wide: true,
666
511
  description: skill.description,
667
512
  activePath: "/skills/",
668
513
  styleHref: skill.scope === "public"
669
514
  ? styleHref(1 + slugDepth(skill.slug), stylesheet)
670
515
  : styleHref(2 + slugDepth(skill.slug), stylesheet),
671
- children: (h("main", { className: "skill-page" },
516
+ children: (h(Page, { width: "full", className: "skill-page", title: skill.name, eyebrow: "Skill", description: skill.description },
672
517
  h("a", { className: "skill-back", href: "/skills/" }, "\u2190 All Skills"),
673
518
  h("header", { className: "skill-browser-head" },
674
519
  h("span", { className: "skill-command" },
@@ -687,8 +532,11 @@ export function skillPage(skill, theme = blogTheme, site = DEFAULT_SITE, stylesh
687
532
  h("header", { className: "skill-viewer-bar" },
688
533
  h("span", { "data-current-file": true }, firstFile.path),
689
534
  h("span", null, "Markdown")),
690
- skill.files.map((file, index) => (h("article", { className: `skill-file-panel prose${index === 0 ? " active" : ""}`, "data-skill-panel": file.path, hidden: index !== 0, dangerouslySetInnerHTML: {
691
- __html: Bun.markdown.html(file.content, { tagFilter: true }),
535
+ skill.files.map((file, index) => (h(Prose, { as: "article", className: `skill-file-panel${index === 0 ? " active" : ""}`, "data-skill-panel": file.path, hidden: index !== 0, dangerouslySetInnerHTML: {
536
+ // The page owns h1; file headings nest under it without dropping any content.
537
+ __html: Bun.markdown
538
+ .html(file.content, { tagFilter: true })
539
+ .replace(/<(\/?)h([1-6])(\s[^>]*)?>/g, (_, closing, level, attributes = "") => `<${closing}h${Math.min(6, Number(level) + 1)}${attributes}>`),
692
540
  } }))))),
693
541
  h(SkillRelations, { skill: skill })),
694
542
  h("script", { dangerouslySetInnerHTML: { __html: SKILL_BROWSER_SCRIPT } }))),