From 6250a4e5848fbbbb330724ce58c584344d5a3082 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 18:10:42 +0000 Subject: [PATCH 1/2] Draw the share images and the favicon at build time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each article gets its own 1200x630 share image (its title, date and reading time) at /articles//share.png, and Home and About share /share.png, with the label "Projects · Writing", the home intro and the article and project counts. Activity logs use the site's image. They replace og-default.jpg as og:image and twitter:image, now with their size and alt text. The favicon is the paper tile: favicon.svg (the 32px tile), a favicon.ico at 16px and 32px and a 180px apple-touch-icon. They replace Astro's default logo on every page, the 404 and /documents/ included. Everything is drawn from the site's DejaVu Sans Mono subset, read with fontkitten and turned into SVG paths laid out as the browser lays out text, then rasterised with sharp. The values are those of docs/redesign/paper/README.md. src/data/projects.ts is a minimal project list for the count, until #32's replaces it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013MdjUerTCBDeF5DG9puDd2 --- bun.lock | 2 + package.json | 4 +- public/favicon.svg | 9 -- public/og-default.jpg | Bin 78296 -> 0 bytes scripts/subset-fonts.py | 3 +- src/components/Favicons.astro | 8 ++ src/data/projects.ts | 7 ++ src/layouts/Layout.astro | 13 ++- src/lib/favicon.ts | 73 +++++++++++++++ src/lib/mono.ts | 89 +++++++++++++++++++ src/lib/reading-time.mjs | 11 +++ src/lib/share-images.ts | 118 +++++++++++++++++++++++++ src/lib/share.ts | 52 +++++++++++ src/pages/404.astro | 3 +- src/pages/apple-touch-icon.png.ts | 6 ++ src/pages/articles/[slug].astro | 5 +- src/pages/articles/[slug]/share.png.ts | 14 +++ src/pages/documents.astro | 3 +- src/pages/favicon.ico.ts | 6 ++ src/pages/favicon.svg.ts | 5 ++ src/pages/share.png.ts | 10 +++ 21 files changed, 424 insertions(+), 17 deletions(-) delete mode 100644 public/favicon.svg delete mode 100644 public/og-default.jpg create mode 100644 src/components/Favicons.astro create mode 100644 src/data/projects.ts create mode 100644 src/lib/favicon.ts create mode 100644 src/lib/mono.ts create mode 100644 src/lib/reading-time.mjs create mode 100644 src/lib/share-images.ts create mode 100644 src/lib/share.ts create mode 100644 src/pages/apple-touch-icon.png.ts create mode 100644 src/pages/articles/[slug]/share.png.ts create mode 100644 src/pages/favicon.ico.ts create mode 100644 src/pages/favicon.svg.ts create mode 100644 src/pages/share.png.ts diff --git a/bun.lock b/bun.lock index 37833d0..eca2ecb 100644 --- a/bun.lock +++ b/bun.lock @@ -8,7 +8,9 @@ "@astrojs/markdown-remark": "^7.3.0", "@astrojs/rss": "^4.0.19", "astro": "^7.4.3", + "fontkitten": "^1.0.3", "remark-breaks": "^4.0.0", + "sharp": "^0.35.4", }, "devDependencies": { "@astrojs/sitemap": "^3.7.4", diff --git a/package.json b/package.json index e2d2201..e2d57d9 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "@astrojs/markdown-remark": "^7.3.0", "@astrojs/rss": "^4.0.19", "astro": "^7.4.3", - "remark-breaks": "^4.0.0" + "fontkitten": "^1.0.3", + "remark-breaks": "^4.0.0", + "sharp": "^0.35.4" }, "scripts": { "test:documents": "node --test tests/*.test.mjs", diff --git a/public/favicon.svg b/public/favicon.svg deleted file mode 100644 index f157bd1..0000000 --- a/public/favicon.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - diff --git a/public/og-default.jpg b/public/og-default.jpg deleted file mode 100644 index d86e0366216e27d3ba83acffce421b8cf4dd1b1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78296 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 z zRLAn7%m6!b&ffTrLPeZgilT3#$YfkVwarO30QI3&XI0DLfc0w?&Kz8dys`rSS_V2C zyk<%#^6?G8Qk^->tAjc_U#*FF)9R@DJPDMO)59oKEgKSb2)V_49Abx9szRsR!Z%I8 cD6F%`fb%vWWEfb2%}k&gL*zc*O8v$AKeUPhzyJUM diff --git a/scripts/subset-fonts.py b/scripts/subset-fonts.py index 1ef1ca0..bc145fd 100644 --- a/scripts/subset-fonts.py +++ b/scripts/subset-fonts.py @@ -1,6 +1,7 @@ """Make the site's self-hosted fonts in src/fonts/ from the original font files. -- DejaVu Sans Mono 2.37, Book: the text of everything but articles and About. +- DejaVu Sans Mono 2.37, Book: the text of everything but articles and About. The build also draws the share images + and the favicon from it (src/lib/mono.ts), so a character missing from it shows as a box in a share image. - Newsreader 1.003, regular and italic: the text of articles and About. Its weight is fixed at 400 and its optical size axis (6 to 72) is kept, as Google Fonts serves it to the prototype. diff --git a/src/components/Favicons.astro b/src/components/Favicons.astro new file mode 100644 index 0000000..823cd14 --- /dev/null +++ b/src/components/Favicons.astro @@ -0,0 +1,8 @@ +--- +// The favicon links, drawn at build time by src/lib/favicon.ts: the SVG for the browsers that take one, favicon.ico +// for the others (its sizes keep Chrome on the SVG), and the icon iPhones and iPads put on the home screen. +--- + + + + diff --git a/src/data/projects.ts b/src/data/projects.ts new file mode 100644 index 0000000..08a9da5 --- /dev/null +++ b/src/data/projects.ts @@ -0,0 +1,7 @@ +// The projects the home shows, one entry each. For now only what the share image's project count needs; the home and +// article pages (#31, #32) add each project's details. +export const projects = [ + { slug: 'voice-todos', name: 'Voice Todos' }, + { slug: 'agent-sdks', name: 'Agent SDKs for the Rest of Us' }, + { slug: 'data-lineage', name: 'Data Lineage' }, +]; diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index 21f7a94..dd7dfc5 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -2,15 +2,18 @@ import '../styles/global.css'; import '../styles/legacy.css'; import Frame from '../components/Frame.astro'; +import Favicons from '../components/Favicons.astro'; import monoFont from '../fonts/dejavu-sans-mono.woff2?url'; +import { SITE_SHARE, type Share } from '../lib/share'; -// Accept props for meta tags +// Accept props for meta tags. `share` is the page's share image, the site's unless it has its own. const { title = 'meaningfool', description = 'Personal site of Josselin Perrus, product manager in Paris', - ogImage = new URL('/og-default.jpg', Astro.site).toString(), + share = SITE_SHARE as Share, type = 'website' } = Astro.props; +const ogImage = new URL(share.path, Astro.site).toString(); // Use Astro's built-in URL handling for canonical URLs const canonicalURL = new URL(Astro.url.pathname, Astro.site); @@ -22,7 +25,7 @@ const canonical = canonicalURL.toString(); - + {title} @@ -35,6 +38,9 @@ const canonical = canonicalURL.toString(); + + + @@ -43,6 +49,7 @@ const canonical = canonicalURL.toString(); + diff --git a/src/lib/favicon.ts b/src/lib/favicon.ts new file mode 100644 index 0000000..6913f57 --- /dev/null +++ b/src/lib/favicon.ts @@ -0,0 +1,73 @@ +// The favicon, the paper tile: "m." in the mono font on the panel colour, inside a 1px border, the "m" in ink and the +// full stop in the accent colour. The design and its values are in docs/redesign/paper/README.md. +// +// The glyphs have a line height equal to their size and -0.12em of letter-spacing, and are centred in the tile with +// some space kept under them. Each size has its own values. +// +// The small sizes are kept sharp the way a font's hinting would: the glyphs move by less than half a pixel so the "m" +// starts on a pixel and stands on one, and the full stop, a plain rectangle in DejaVu Sans Mono, is fitted to whole +// pixels, at least 2px a side so it still shows as a square at 16px. +import sharp from 'sharp'; +import { baseline, charWidth, inkBox, textPath, type TextStyle } from './mono'; + +const PANEL = '#f3f3f0'; +const BORDER = '#dcdcd6'; +const INK = '#151515'; +const ACCENT = '#b64b00'; + +// Tile size: the glyphs' size and the space under them, in px. The 180px tile, the apple-touch-icon, is the design's +// 144px tile (108px glyphs, 14px under them) scaled by 1.25. +const TILES: Record = { + 16: { glyphs: 12, under: 1 }, + 32: { glyphs: 24, under: 3 }, + 180: { glyphs: 135, under: 17.5 }, +}; + +/** The tile as an SVG, `size` px wide, with the glyphs converted to paths. */ +export function tileSvg(size: number): string { + const { glyphs, under } = TILES[size]; + const style: TextStyle = { size: glyphs, tracking: -0.12 }; + const m = inkBox('m'); + const ink = (size - 2 * charWidth(style)) / 2 + m.minX * glyphs; // where the "m" starts, centred + const x = Math.round(ink) - m.minX * glyphs; + const y = Math.round(baseline((size - under - glyphs) / 2, glyphs, glyphs)); + + const stop = inkBox('.'); + const width = Math.max(2, Math.round((stop.maxX - stop.minX) * glyphs)); + const height = Math.max(2, Math.round((stop.maxY - stop.minY) * glyphs)); + const centre = x + charWidth(style) + ((stop.minX + stop.maxX) / 2) * glyphs; + const left = Math.round(centre - width / 2); + return ( + `` + + `` + + `` + + `` + + `` + + '' + ); +} + +/** The tile as a PNG. */ +export function tilePng(size: number): Promise { + return sharp(Buffer.from(tileSvg(size))).ensureAlpha().png({ compressionLevel: 9 }).toBuffer(); +} + +/** An .ico file holding the tile at each size, each as a PNG, which every browser reads. */ +export async function tileIco(sizes: number[]): Promise { + const images = await Promise.all(sizes.map(tilePng)); + const header = Buffer.alloc(6 + 16 * sizes.length); + header.writeUInt16LE(1, 2); // an icon + header.writeUInt16LE(sizes.length, 4); + let offset = header.length; + sizes.forEach((size, i) => { + const entry = 6 + 16 * i; + header.writeUInt8(size % 256, entry); // width and height, 0 meaning 256 + header.writeUInt8(size % 256, entry + 1); + header.writeUInt16LE(1, entry + 4); // colour planes + header.writeUInt16LE(32, entry + 6); // bits per pixel + header.writeUInt32LE(images[i].length, entry + 8); + header.writeUInt32LE(offset, entry + 12); + offset += images[i].length; + }); + return Buffer.concat([header, ...images]); +} diff --git a/src/lib/mono.ts b/src/lib/mono.ts new file mode 100644 index 0000000..82c8860 --- /dev/null +++ b/src/lib/mono.ts @@ -0,0 +1,89 @@ +// DejaVu Sans Mono as SVG paths, for the images the build draws: the share images and the favicon. They use the +// site's own subset (src/fonts/), so they show the same letters as the pages, and as paths they need no font where +// they are shown. Text is laid out as a browser lays it out: every character takes the same width plus the +// letter-spacing, lines break at spaces and after hyphens, and a line box puts its baseline where CSS puts it. +import { create, type Font } from 'fontkitten'; +import fontData from '../fonts/dejavu-sans-mono.woff2?inline'; + +const font = create(Buffer.from(fontData.slice(fontData.indexOf(',') + 1), 'base64')) as Font; +const EM = font.unitsPerEm; +const ASCENT = font.ascent / EM; +const DESCENT = -font.descent / EM; +const ADVANCE = font.glyphForCodePoint(0x6d).advanceWidth / EM; // 0.602em: in a mono font, every character's width + +const COMMANDS = { moveTo: 'M', lineTo: 'L', quadraticCurveTo: 'Q', bezierCurveTo: 'C', closePath: 'Z' }; + +export interface TextStyle { + size: number; // font size, in px + tracking?: number; // letter-spacing, in em +} + +const chars = (text: string) => Array.from(text); +const num = (value: number) => String(Math.round(value * 100) / 100); + +/** The width one character takes, letter-spacing included. */ +export function charWidth({ size, tracking = 0 }: TextStyle): number { + return size * (ADVANCE + tracking); +} + +/** The width of a line of text. As in Chrome, the last character keeps its letter-spacing. */ +export function textWidth(text: string, style: TextStyle): number { + return chars(text).length * charWidth(style); +} + +/** Where CSS puts the baseline in a line box `lineHeight` tall whose top is at `top`. */ +export function baseline(top: number, lineHeight: number, size: number): number { + return top + (lineHeight - (ASCENT + DESCENT) * size) / 2 + ASCENT * size; +} + +/** A character's ink, in em from its origin on the baseline, y going up. */ +export function inkBox(char: string): { minX: number; minY: number; maxX: number; maxY: number } { + const { minX, minY, maxX, maxY } = font.glyphForCodePoint(char.codePointAt(0)!).bbox; + return { minX: minX / EM, minY: minY / EM, maxX: maxX / EM, maxY: maxY / EM }; +} + +/** SVG path data for one line of text, starting at `x` on the baseline `y`. */ +export function textPath(text: string, x: number, y: number, style: TextStyle): string { + const scale = style.size / EM; + const step = charWidth(style); + const missing = new Set(); + let d = ''; + chars(text).forEach((char, i) => { + const glyph = font.glyphForCodePoint(char.codePointAt(0)!); + if (glyph.id === 0 && char.trim()) missing.add(char); + const left = x + i * step; + for (const { command, args } of glyph.path.commands) { + d += COMMANDS[command] + args.map((v, j) => num(j % 2 ? y - v * scale : left + v * scale)).join(' '); + } + }); + if (missing.size) { + // The subset only holds Latin, punctuation, arrows and box drawing (scripts/subset-fonts.py). + console.warn(`[mono] DejaVu Sans Mono's subset has no ${[...missing].join(' ')}, drawn as a box in "${text}"`); + } + return d; +} + +/** + * Break text into lines at most `maxWidth` wide, as a browser does: at spaces and after a hyphen between letters, + * the space at the end of a line not counting. A word longer than a line breaks where it reaches the end. + */ +export function wrap(text: string, style: TextStyle, maxWidth: number): string[] { + const max = Math.max(1, Math.floor(maxWidth / charWidth(style) + 1e-9)); + const fits = (line: string) => chars(line.trimEnd()).length <= max; + const pieces = text.replace(/\s+/g, ' ').trim().split(/(?<= )|(?<=[\p{L}\p{N}]-)(?=[\p{L}\p{N}])/u); + const lines: string[] = []; + let line = ''; + for (let piece of pieces) { + if (line && !fits(line + piece)) { + lines.push(line.trimEnd()); + line = ''; + } + while (!fits(piece)) { + lines.push(chars(piece).slice(0, max).join('')); + piece = chars(piece).slice(max).join(''); + } + line += piece; + } + if (line.trim()) lines.push(line.trimEnd()); + return lines; +} diff --git a/src/lib/reading-time.mjs b/src/lib/reading-time.mjs new file mode 100644 index 0000000..875237c --- /dev/null +++ b/src/lib/reading-time.mjs @@ -0,0 +1,11 @@ +// Reading time, as the redesign shows it next to a post's date. + +/** + * Minutes to read a post: the words of its Markdown body at 230 a minute, rounded up, and at least 1. This gives the + * prototype's reading times (docs/redesign/prototype/maps.js) for all its articles but the longest, 40 minutes + * instead of 39. + */ +export function readingMinutes(markdown = '') { + const words = markdown.split(/\s+/).filter(Boolean).length; + return Math.max(1, Math.ceil(words / 230)); +} diff --git a/src/lib/share-images.ts b/src/lib/share-images.ts new file mode 100644 index 0000000..ab34b22 --- /dev/null +++ b/src/lib/share-images.ts @@ -0,0 +1,118 @@ +// The share images, 1200×630 PNGs drawn at build time: one per article and one for Home and About. The design and its +// values are in docs/redesign/paper/README.md. All the text is DejaVu Sans Mono, drawn as paths (src/lib/mono.ts). +// +// The frame is white, padded 64px at the top, 72px at the sides and 56px at the bottom, with three rows spread from top +// to bottom: the wordmark and a label tab, the middle, and a line over two pieces of text. +import sharp from 'sharp'; +import { baseline, charWidth, textPath, textWidth, wrap, type TextStyle } from './mono'; + +const WIDTH = 1200; +const HEIGHT = 630; +const TOP = 64; +const SIDE = 72; +const BOTTOM = 56; +const RIGHT = WIDTH - SIDE; + +// The colours of src/styles/tokens.css. +const PAPER = '#ffffff'; +const INK = '#151515'; +const MUTED = '#686862'; +const LINE = '#e5e5e2'; +const TINT = '#fff9f4'; +const ACCENT = '#b64b00'; + +interface Block { + height: number; + draw: (top: number) => string; +} + +const rect = (x: number, y: number, width: number, height: number, fill: string) => + ``; +const path = (d: string, fill: string) => (d ? `` : ''); + +/** One line of text in a line box `lineHeight` tall whose top is at `top`, from `x`. */ +const line = (text: string, x: number, top: number, lineHeight: number, style: TextStyle, fill: string) => + path(textPath(text, x, baseline(top, lineHeight, style.size), style), fill); + +/** Lines of text, one under the other. */ +function lines(texts: string[], top: number, lineHeight: number, style: TextStyle, fill: string): string { + return texts.map((text, i) => line(text, SIDE, top + i * lineHeight, lineHeight, style, fill)).join(''); +} + +function frame(label: string, middle: Block, left: string, right: string): string { + // Top row, its items centred on each other. Left, the wordmark: mono 36px/36px, -0.055em, the dot in the accent + // colour. Right, the label tab: mono 20px/28px on the tint, padding 10px 20px 9px, with a 3px accent line under it. + const mark: TextStyle = { size: 36, tracking: -0.055 }; + const tab: TextStyle = { size: 20 }; + const tabWidth = textWidth(label, tab) + 40; + const tabHeight = 10 + 28 + 9 + 3; + const markTop = TOP + (tabHeight - 36) / 2; + const tabLeft = RIGHT - tabWidth; + const top = + line('meaningfool', SIDE, markTop, 36, mark, INK) + + line('.', SIDE + 11 * charWidth(mark), markTop, 36, mark, ACCENT) + + rect(tabLeft, TOP, tabWidth, tabHeight - 3, TINT) + + rect(tabLeft, TOP + tabHeight - 3, tabWidth, 3, ACCENT) + + line(label, tabLeft + 20, TOP + 10, 28, tab, INK); + + // Bottom row: a 2px line, 24px of space, then mono 22px/30px, ink on the left and muted on the right. + const foot: TextStyle = { size: 22 }; + const footTop = HEIGHT - BOTTOM - (2 + 24 + 30); + const bottom = + rect(SIDE, footTop, RIGHT - SIDE, 2, LINE) + + line(left, SIDE, footTop + 26, 30, foot, INK) + + line(right, RIGHT - textWidth(right, foot), footTop + 26, 30, foot, MUTED); + + // The middle, with as much space above as below. + const space = footTop - (TOP + tabHeight); + const centre = middle.draw(TOP + tabHeight + (space - middle.height) / 2); + + return ( + `` + + rect(0, 0, WIDTH, HEIGHT, PAPER) + + top + + centre + + bottom + + '' + ); +} + +const png = (svg: string) => sharp(Buffer.from(svg)).removeAlpha().png({ compressionLevel: 9 }).toBuffer(); + +/** + * An article's image: "Article" on the tab, the title in mono 68px/80px, -0.05em, at most 1020px wide, and the name, + * date and reading time at the bottom. A title longer than four lines gets smaller until it fits in four. + */ +function articleImageSvg({ title, meta }: { title: string; meta: string }): string { + let style: TextStyle = { size: 68, tracking: -0.05 }; + let texts = wrap(title, style, 1020); + while (texts.length > 4 && style.size > 36) { + style = { ...style, size: style.size - 4 }; + texts = wrap(title, style, 1020); + } + if (texts.length > 4) texts = [...texts.slice(0, 3), `${texts[3].slice(0, -1)}…`]; + const lineHeight = Math.round((style.size * 80) / 68); + const middle = { + height: texts.length * lineHeight, + draw: (top: number) => lines(texts, top, lineHeight, style, INK), + }; + return frame('Article', middle, 'Josselin Perrus', meta); +} + +/** + * The image of Home and About: "Projects · Writing" on the tab, the name in mono 84px/92px, -0.05em, and 20px under it + * the home's intro in mono 28px/42px, -0.02em, muted, at most 920px wide. At the bottom, the site and its counts. + */ +function siteImageSvg({ intro, counts }: { intro: string; counts: string }): string { + const name: TextStyle = { size: 84, tracking: -0.05 }; + const lede: TextStyle = { size: 28, tracking: -0.02 }; + const texts = wrap(intro, lede, 920); + const middle = { + height: 92 + 20 + texts.length * 42, + draw: (top: number) => line('Josselin Perrus', SIDE, top, 92, name, INK) + lines(texts, top + 112, 42, lede, MUTED), + }; + return frame('Projects · Writing', middle, 'meaningfool.net', counts); +} + +export const articleImage = (text: { title: string; meta: string }) => png(articleImageSvg(text)); +export const siteImage = (text: { intro: string; counts: string }) => png(siteImageSvg(text)); diff --git a/src/lib/share.ts b/src/lib/share.ts new file mode 100644 index 0000000..fea66b4 --- /dev/null +++ b/src/lib/share.ts @@ -0,0 +1,52 @@ +// What the share images say and where they are served, for the endpoints that draw them (src/lib/share-images.ts) +// and the pages whose og:image they are. +import type { CollectionEntry } from 'astro:content'; +import { isDailyLog } from './posts.mjs'; +import { readingMinutes } from './reading-time.mjs'; +import { projects } from '../data/projects'; + +type Post = CollectionEntry<'writing'>; + +export interface Share { + path: string; + alt: string; +} + +/** The home's intro, which the share image of Home and About repeats under the name. */ +export const HOME_INTRO = + "I'm a product manager building AI products with AI. My projects are an opportunity to test ideas and learn in public."; + +/** The share image of Home and About. The activity logs, which have none of their own, use it too. */ +export const SITE_SHARE: Share = { path: '/share.png', alt: `Josselin Perrus. ${HOME_INTRO}` }; + +const plural = (count: number, word: string) => `${count} ${word}${count === 1 ? '' : 's'}`; + +/** "May 27, 2026": the date in the content is a day, which UTC keeps whatever the build machine's time zone. */ +export function formatDate(date: Date): string { + return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' }); +} + +/** The articles, not the activity logs. */ +export const articlesOnly = (posts: Post[]) => posts.filter((post) => !isDailyLog(post)); + +/** "18 articles · 3 projects", for the share image of Home and About. */ +export function siteCounts(posts: Post[]): string { + return `${plural(articlesOnly(posts).length, 'article')} · ${plural(projects.length, 'project')}`; +} + +/** An article's date and reading time, as its share image shows them: "May 27, 2026 · 3 min read". */ +export function articleMeta(article: Post): { date: string; minutes: number; line: string } { + const date = formatDate(article.data.date); + const minutes = readingMinutes(article.body); + return { date, minutes, line: `${date} · ${minutes} min read` }; +} + +/** A post's share image: its own for an article, the site's for an activity log. */ +export function postShare(post: Post): Share { + if (isDailyLog(post)) return SITE_SHARE; + const { date, minutes } = articleMeta(post); + return { + path: `/articles/${post.id}/share.png`, + alt: `${post.data.title}. Josselin Perrus, ${date}, ${minutes} min read.`, + }; +} diff --git a/src/pages/404.astro b/src/pages/404.astro index 6e3d663..72baa37 100644 --- a/src/pages/404.astro +++ b/src/pages/404.astro @@ -2,6 +2,7 @@ import '../styles/global.css'; import Frame from '../components/Frame.astro'; import NotFound from '../components/NotFound.astro'; +import Favicons from '../components/Favicons.astro'; import monoFont from '../fonts/dejavu-sans-mono.woff2?url'; --- @@ -12,7 +13,7 @@ import monoFont from '../fonts/dejavu-sans-mono.woff2?url'; 404 - Page Not Found | meaningfool - + diff --git a/src/pages/apple-touch-icon.png.ts b/src/pages/apple-touch-icon.png.ts new file mode 100644 index 0000000..c53523c --- /dev/null +++ b/src/pages/apple-touch-icon.png.ts @@ -0,0 +1,6 @@ +// The 180px icon that iPhones and iPads put on the home screen. +import type { APIRoute } from 'astro'; +import { tilePng } from '../lib/favicon'; + +export const GET: APIRoute = async () => + new Response(new Uint8Array(await tilePng(180)), { headers: { 'Content-Type': 'image/png' } }); diff --git a/src/pages/articles/[slug].astro b/src/pages/articles/[slug].astro index be96275..7d71e55 100644 --- a/src/pages/articles/[slug].astro +++ b/src/pages/articles/[slug].astro @@ -3,6 +3,7 @@ import type { GetStaticPaths } from 'astro'; import { getCollection, render, type CollectionEntry } from 'astro:content'; import Layout from '../../layouts/Layout.astro'; import JsonLd from '../../components/JsonLd.astro'; +import { postShare } from '../../lib/share'; export const getStaticPaths: GetStaticPaths = async () => { const articles = await getCollection('writing'); @@ -14,6 +15,7 @@ export const getStaticPaths: GetStaticPaths = async () => { const article = Astro.props as CollectionEntry<'writing'>; const { Content } = await render(article); +const share = postShare(article); // JSON-LD structured data for BlogPosting const canonical = new URL(Astro.url.pathname, Astro.site).toString(); @@ -27,7 +29,7 @@ const articleSchema = { "@type": "BlogPosting", "headline": article.data.title, "description": article.data.description || article.data.title, - "image": new URL('/og-default.jpg', Astro.site).toString(), + "image": new URL(share.path, Astro.site).toString(), "author": author, "publisher": author, "datePublished": article.data.date.toISOString(), @@ -41,6 +43,7 @@ const articleSchema = { title={`${article.data.title} | meaningfool`} description={article.data.description || article.data.title} type="article" + share={share} >

diff --git a/src/pages/articles/[slug]/share.png.ts b/src/pages/articles/[slug]/share.png.ts new file mode 100644 index 0000000..45467cd --- /dev/null +++ b/src/pages/articles/[slug]/share.png.ts @@ -0,0 +1,14 @@ +// Each article's share image, /articles//share.png: its title, date and reading time. +import type { APIRoute, GetStaticPaths } from 'astro'; +import { getCollection, type CollectionEntry } from 'astro:content'; +import { articleImage } from '../../../lib/share-images'; +import { articleMeta, articlesOnly } from '../../../lib/share'; + +export const getStaticPaths: GetStaticPaths = async () => + articlesOnly(await getCollection('writing')).map((article) => ({ params: { slug: article.id }, props: { article } })); + +export const GET: APIRoute = async ({ props }) => { + const { article } = props as { article: CollectionEntry<'writing'> }; + const image = await articleImage({ title: article.data.title, meta: articleMeta(article).line }); + return new Response(new Uint8Array(image), { headers: { 'Content-Type': 'image/png' } }); +}; diff --git a/src/pages/documents.astro b/src/pages/documents.astro index beaa3ed..44c9d39 100644 --- a/src/pages/documents.astro +++ b/src/pages/documents.astro @@ -2,6 +2,7 @@ import '../styles/tokens.css'; import Frame from '../components/Frame.astro'; import NotFound from '../components/NotFound.astro'; +import Favicons from '../components/Favicons.astro'; --- @@ -16,7 +17,7 @@ import NotFound from '../components/NotFound.astro'; content="default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; connect-src 'self'; frame-src blob:; img-src 'self' data:; object-src 'none'; base-uri 'none'; form-action 'none'" /> 404 - Page Not Found | meaningfool - +