diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
deleted file mode 100644
index 3535d88..0000000
--- a/.github/workflows/validate.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Validate content
-on:
- push:
- paths:
- - 'www/content/**'
- - 'www/public/*.txt'
- - 'www/public/config.yaml'
- pull_request:
- paths:
- - 'www/content/**'
- - 'www/public/*.txt'
- - 'www/public/config.yaml'
-
-jobs:
- validate:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: pnpm/action-setup@v4
- - uses: actions/setup-node@v4
- with:
- node-version: 22
- cache: pnpm
- - run: pnpm install --frozen-lockfile
- - run: pnpm validate:www
diff --git a/package.json b/package.json
index c870fd5..6ebe20f 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,6 @@
"dev:penfield": "pnpm --filter @ily/penfield dev",
"dev:www": "pnpm --filter @ily/www dev",
"build:penfield": "pnpm --filter @ily/penfield build",
- "build:www": "pnpm --filter @ily/www build",
- "validate:www": "pnpm --filter @ily/www validate"
+ "build:www": "pnpm --filter @ily/www build"
}
}
diff --git a/www/content/hello.md b/www/content/hello.md
index 4ef35ee..72cdeaf 100644
--- a/www/content/hello.md
+++ b/www/content/hello.md
@@ -1,7 +1,6 @@
---
title: hello
-date: 2023-02-26
-pinned: true
+date: 2026-02-26
---
i've always had some sort of homepage. it was originally on bebo, then that died and i didn't ever get into other social media, so i made a website
diff --git a/www/package.json b/www/package.json
index 5c7ad4d..cfa3cc7 100644
--- a/www/package.json
+++ b/www/package.json
@@ -5,8 +5,7 @@
"dev": "astro dev --port 4322",
"build": "astro build --remote && node scripts/generate-stats.js",
"preview": "astro preview",
- "serve": "pnpm build && npx serve .vercel/output/static -l 4322",
- "validate": "node scripts/validate-content.js"
+ "serve": "pnpm build && npx serve .vercel/output/static -l 4322"
},
"dependencies": {
"@astrojs/db": "^0.19.0",
diff --git a/www/public/changelog.txt b/www/public/changelog.txt
index a1b4dbe..5882854 100644
--- a/www/public/changelog.txt
+++ b/www/public/changelog.txt
@@ -1,3 +1,4 @@
+2026-03-27 - narrower layout (34rem), single-column entries, serif font, smaller muted text
2026-03-26 - inline section labels, compact layout
2026-02-07 - related posts !
2026-01-31 - text files now live at cleaner URLs (/*.txt instead of /txt/*.txt)
diff --git a/www/public/config.yaml b/www/public/config.yaml
index 2b7aa3a..c1c9ffa 100644
--- a/www/public/config.yaml
+++ b/www/public/config.yaml
@@ -1,4 +1,3 @@
-pinned: []
descriptions:
cv.txt: curriculum vitae
now.txt: what i'm doing now
diff --git a/www/public/js/params.js b/www/public/js/params.js
index c466e18..01fbf1b 100644
--- a/www/public/js/params.js
+++ b/www/public/js/params.js
@@ -4,8 +4,7 @@
var just = p.get('just');
if (just && /^[a-z0-9-]+$/.test(just)) {
document.documentElement.dataset.just = just;
- var css = 'section[data-section]:not([data-section="' + just + '"]){display:none}'
- + ' section[data-section="' + just + '"] .section-label{pointer-events:none;text-decoration:none;color:inherit}';
+ var css = 'section[data-section]:not([data-section="' + just + '"]){display:none}';
document.head.appendChild(Object.assign(document.createElement('style'), { textContent: css }));
}
@@ -30,7 +29,10 @@
if (has) {
document.documentElement.dataset.has = has;
has = has.toLowerCase();
- document.addEventListener('DOMContentLoaded', function() {
+ }
+
+ document.addEventListener('DOMContentLoaded', function() {
+ if (has) {
document.querySelectorAll('section[data-section] .entry').forEach(function(entry) {
if (entry.textContent.toLowerCase().indexOf(has) === -1) {
entry.style.display = 'none';
@@ -41,6 +43,22 @@
entry.style.display = 'none';
}
});
+ }
+
+ document.querySelectorAll('.section-label').forEach(function(a) {
+ var link = new URLSearchParams(a.search);
+ p.forEach(function(v, k) { if (!link.has(k)) link.set(k, v); });
+ a.href = '?' + link.toString();
});
- }
+
+ var find = document.getElementById('find');
+ if (find) find.addEventListener('click', function(e) {
+ e.preventDefault();
+ var term = prompt('find:');
+ if (!term) return;
+ var q = new URLSearchParams(location.search);
+ q.set('has', term);
+ location.search = q.toString();
+ });
+ });
}();
diff --git a/www/scripts/validate-content.js b/www/scripts/validate-content.js
deleted file mode 100644
index 3cce1f2..0000000
--- a/www/scripts/validate-content.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import fs from 'node:fs';
-import path from 'node:path';
-import yaml from 'js-yaml';
-
-const root = path.resolve(import.meta.dirname, '..');
-const contentDir = path.join(root, 'content');
-const publicDir = path.join(root, 'public');
-const errors = [];
-
-const mdFiles = fs.readdirSync(contentDir).filter(f => f.endsWith('.md'));
-for (const file of mdFiles) {
- const content = fs.readFileSync(path.join(contentDir, file), 'utf8');
- const match = content.match(/^---\n([\s\S]*?)\n---/);
- if (!match) {
- errors.push(`${file}: missing frontmatter`);
- continue;
- }
- const frontmatter = match[1];
- if (!/^date:\s*.+/m.test(frontmatter)) {
- errors.push(`${file}: missing required 'date' field`);
- }
-}
-
-const configPath = path.join(publicDir, 'config.yaml');
-const config = fs.existsSync(configPath)
- ? yaml.load(fs.readFileSync(configPath, 'utf8'))
- : {};
-const configDates = config.dates || {};
-const txtFiles = fs.readdirSync(publicDir).filter(f => f.endsWith('.txt'));
-for (const file of txtFiles) {
- if (!configDates[file]) {
- errors.push(`${file}: missing date in config.yaml`);
- }
-}
-
-if (errors.length) {
- console.error('Content validation failed:\n');
- for (const err of errors) console.error(` - ${err}`);
- console.error('');
- process.exit(1);
-} else {
- console.log(`Validated ${mdFiles.length} posts and ${txtFiles.length} txt files.`);
-}
diff --git a/www/src/content.config.ts b/www/src/content.config.ts
index 6499a0a..6efc211 100644
--- a/www/src/content.config.ts
+++ b/www/src/content.config.ts
@@ -3,13 +3,12 @@ import { glob, file } from 'astro/loaders';
import { z } from 'astro/zod';
import yaml from 'js-yaml';
-const md = defineCollection({
+const posts = defineCollection({
loader: glob({ pattern: '**/*.md', base: './content' }),
schema: z.object({
title: z.string(),
date: z.coerce.date(),
updated: z.coerce.date().optional(),
- pinned: z.boolean().optional(),
category: z.string().optional(),
related: z.array(z.string()).optional(),
})
@@ -29,4 +28,4 @@ const bookmarks = defineCollection({
})
});
-export const collections = { md, bookmarks };
+export const collections = { posts, bookmarks };
diff --git a/www/src/layouts/Layout.astro b/www/src/layouts/Layout.astro
index 4f59fe1..ee442c2 100644
--- a/www/src/layouts/Layout.astro
+++ b/www/src/layouts/Layout.astro
@@ -9,7 +9,7 @@ interface Props {
urls?: string[];
}
-const { title, description = 'personal website of lewis m.w.', showHeader = true, isHome = false, urls = [] } = Astro.props;
+const { title, description = 'personal website of ' + title, showHeader = true, isHome = false, urls = [] } = Astro.props;
---
@@ -28,7 +28,18 @@ const { title, description = 'personal website of lewis m.w.', showHeader = true
{showHeader && (
)}
diff --git a/www/src/lib/api.ts b/www/src/lib/api.ts
index 1961a01..6e4622e 100644
--- a/www/src/lib/api.ts
+++ b/www/src/lib/api.ts
@@ -1,5 +1,3 @@
-import { isAdmin } from './auth';
-
export function jsonResponse(data: unknown, status = 200): Response {
return new Response(JSON.stringify(data), {
status,
@@ -10,10 +8,3 @@ export function jsonResponse(data: unknown, status = 200): Response {
export function errorResponse(message: string, status: number): Response {
return jsonResponse({ error: message }, status);
}
-
-export function requireAdmin(session: { user?: { id?: string } } | null): Response | null {
- if (!session?.user?.id || !isAdmin(session.user.id)) {
- return errorResponse('Unauthorized', 403);
- }
- return null;
-}
diff --git a/www/src/lib/auth.ts b/www/src/lib/auth.ts
index 28b0d8b..eaac841 100644
--- a/www/src/lib/auth.ts
+++ b/www/src/lib/auth.ts
@@ -1,29 +1,26 @@
import { getSession } from 'auth-astro/server';
-type Session = { user?: { id?: string; name?: string | null } };
+export type Session = { user?: { id?: string; name?: string | null } };
-export function isAdmin(userId: string | undefined): boolean {
- return userId === import.meta.env.ADMIN_GITHUB_ID;
-}
+export type AuthResult =
+ | { status: 'admin'; session: Session }
+ | { status: 'unauthenticated' }
+ | { status: 'forbidden' }
+ | { status: 'error' };
-export async function requireAdminSession(request: Request): Promise<
- | { session: Session; error: null }
- | { session: null; error: Response | null }
-> {
+export async function getAdminSession(request: Request): Promise {
let session: Session | null;
try {
session = await getSession(request);
} catch {
- return { session: null, error: new Response('Auth not configured', { status: 500 }) };
+ return { status: 'error' };
}
- if (!session) {
- return { session: null, error: null };
+ if (!session) return { status: 'unauthenticated' };
+
+ if (session.user?.id !== import.meta.env.ADMIN_GITHUB_ID) {
+ return { status: 'forbidden' };
}
- if (!isAdmin(session.user?.id)) {
- return { session: null, error: new Response('Forbidden', { status: 403 }) };
- }
-
- return { session, error: null };
+ return { status: 'admin', session };
}
diff --git a/www/src/lib/consts.ts b/www/src/lib/consts.ts
index a19834c..a2e8d87 100644
--- a/www/src/lib/consts.ts
+++ b/www/src/lib/consts.ts
@@ -6,7 +6,7 @@ export const SUBDOMAINS = [
];
export const SECTIONS = {
- plaintext: 'plaintext',
+ files: 'files',
bookmarks: 'bookmarks',
guestbook: 'guestbook',
} as const;
diff --git a/www/src/lib/format.ts b/www/src/lib/format.ts
index cd07266..9257bde 100644
--- a/www/src/lib/format.ts
+++ b/www/src/lib/format.ts
@@ -26,25 +26,45 @@ export function formatDate(date: Date): string {
return `${d}/${m}/${y}`;
}
+export function wordCount(markdown: string | undefined): string {
+ if (!markdown) return '';
+ const words = markdown
+ .replace(/^---[\s\S]*?---/m, '')
+ .replace(/^#+\s+.*$/gm, '')
+ .replace(/!?\[([^\]]*)\]\([^)]*\)/g, '$1')
+ .replace(/[*_~`]/g, '')
+ .replace(/:[a-z]+\[([^\]]*)\]/g, '$1')
+ .trim()
+ .split(/\s+/)
+ .filter(Boolean).length;
+ if (words < 100) return `${words} words`;
+ const mins = Math.ceil(words / 200);
+ return `${mins} min`;
+}
+
+export function extractDomain(url: string): string {
+ try {
+ return new URL(url).hostname.replace(/^www\./, '');
+ } catch {
+ return '';
+ }
+}
+
export function formatListItem(
date: Date,
url: string,
title: string,
- options?: { pinned?: boolean }
+ options?: { suffix?: string }
): string {
- const pinnedBadge = options?.pinned ? ' [pinned]' : '';
- return `${formatDate(date)}${title}${pinnedBadge}`;
+ const suffixHtml = options?.suffix ? `${options.suffix}` : '';
+ return `${formatDate(date)}${title}${suffixHtml}`;
}
interface Sortable {
date: Date;
- pinned?: boolean;
}
-export function sortByPinnedThenDate(items: T[]): T[] {
- return items.slice().sort((a, b) => {
- if (a.pinned && !b.pinned) return -1;
- if (!a.pinned && b.pinned) return 1;
- return b.date.getTime() - a.date.getTime();
- });
+export function sortEntries(items: T[], key?: (item: T) => Sortable): T[] {
+ const get = key ?? (item => item as unknown as Sortable);
+ return items.slice().sort((a, b) => get(b).date.getTime() - get(a).date.getTime());
}
diff --git a/www/src/lib/md.ts b/www/src/lib/posts.ts
similarity index 63%
rename from www/src/lib/md.ts
rename to www/src/lib/posts.ts
index 378c2c6..6b44477 100644
--- a/www/src/lib/md.ts
+++ b/www/src/lib/posts.ts
@@ -1,22 +1,14 @@
import type { CollectionEntry } from 'astro:content';
import { DEFAULT_CATEGORY } from './consts';
+import { sortEntries } from './format';
-type Post = CollectionEntry<'md'>;
+export type Post = CollectionEntry<'posts'> & { body?: string };
export function getSlug(postId: string): string {
const parts = postId.split('/');
return parts[parts.length - 1];
}
-function sortPosts(posts: Post[], { alphabetically = false } = {}): Post[] {
- return posts.slice().sort((a, b) => {
- if (a.data.pinned && !b.data.pinned) return -1;
- if (!a.data.pinned && b.data.pinned) return 1;
- if (alphabetically) return a.data.title.localeCompare(b.data.title);
- return b.data.date.getTime() - a.data.date.getTime();
- });
-}
-
export function resolveRelatedPosts(
slugs: string[],
allPosts: T[],
@@ -25,7 +17,7 @@ export function resolveRelatedPosts(
return slugs.flatMap(s => bySlug.get(s) ?? []);
}
-export function organizePostsByCategory(posts: Post[], { sortAlphabetically = false } = {}): {
+export function organizePostsByCategory(posts: Post[]): {
grouped: Record;
categories: string[];
} {
@@ -43,7 +35,7 @@ export function organizePostsByCategory(posts: Post[], { sortAlphabetically = fa
});
for (const category of categories) {
- grouped[category] = sortPosts(grouped[category], { alphabetically: sortAlphabetically });
+ grouped[category] = sortEntries(grouped[category], p => p.data);
}
return { grouped, categories };
diff --git a/www/src/lib/txt.ts b/www/src/lib/txt.ts
index ee03984..ad13389 100644
--- a/www/src/lib/txt.ts
+++ b/www/src/lib/txt.ts
@@ -1,17 +1,15 @@
import fs from 'node:fs';
import path from 'node:path';
import yaml from 'js-yaml';
-import { sortByPinnedThenDate } from './format';
+import { sortEntries } from './format';
export interface TxtFile {
name: string;
date: Date;
- pinned: boolean;
description?: string;
}
export interface TxtConfig {
- pinned?: string[];
descriptions?: Record;
dates?: Record;
}
@@ -32,7 +30,6 @@ export function getTxtFiles(): TxtFile[] {
if (!fs.existsSync(txtDir)) return [];
const config = loadTxtConfig();
- const pinnedSet = new Set(config.pinned || []);
const descriptions = config.descriptions || {};
const dates = config.dates || {};
@@ -41,9 +38,8 @@ export function getTxtFiles(): TxtFile[] {
.map(name => ({
name,
date: dates[name] ? new Date(dates[name]) : new Date(0),
- pinned: pinnedSet.has(name),
description: descriptions[name],
}));
- return sortByPinnedThenDate(files);
+ return sortEntries(files);
}
diff --git a/www/src/pages/[slug].astro b/www/src/pages/[slug].astro
index c2a81a8..5f92dde 100644
--- a/www/src/pages/[slug].astro
+++ b/www/src/pages/[slug].astro
@@ -1,11 +1,11 @@
---
import { getCollection, render } from 'astro:content';
import Layout from '../layouts/Layout.astro';
-import { formatDate, formatListItem, excerpt } from '../lib/format';
-import { getSlug, resolveRelatedPosts } from '../lib/md';
+import { formatDate, formatListItem, excerpt, wordCount } from '../lib/format';
+import { getSlug, resolveRelatedPosts, type Post } from '../lib/posts';
export async function getStaticPaths() {
- const allPosts = await getCollection('md');
+ const allPosts = await getCollection('posts');
return allPosts.map(post => ({
params: { slug: getSlug(post.id) },
props: { post, allPosts }
@@ -15,13 +15,13 @@ export async function getStaticPaths() {
const { post, allPosts } = Astro.props;
const { Content } = await render(post);
const related = post.data.related ? resolveRelatedPosts(post.data.related, allPosts) : [];
-const description = excerpt((post as any).body) || undefined;
+const description = excerpt((post as Post).body) || undefined;
---
{post.data.title}
-{formatDate(post.data.date)}{post.data.updated && ` (updated ${formatDate(post.data.updated)})`}
+{formatDate(post.data.date)}{post.data.updated && ` (updated ${formatDate(post.data.updated)})`} · {wordCount((post as Post).body)}{post.data.category && ` · ${post.data.category}`}
{related.length > 0 && (
diff --git a/www/src/pages/admin.astro b/www/src/pages/admin.astro
index f540c6c..66ff49c 100644
--- a/www/src/pages/admin.astro
+++ b/www/src/pages/admin.astro
@@ -2,13 +2,15 @@
export const prerender = false;
import { getPendingEntries, type GuestbookEntry } from '../lib/db';
-import { requireAdminSession } from '../lib/auth';
+import { getAdminSession } from '../lib/auth';
import Layout from '../layouts/Layout.astro';
import { formatDate } from '../lib/format';
-const { session, error } = await requireAdminSession(Astro.request);
-if (error) return error;
-if (!session) return Astro.redirect('/api/auth/signin');
+const auth = await getAdminSession(Astro.request);
+if (auth.status === 'error') return new Response('Auth not configured', { status: 500 });
+if (auth.status === 'unauthenticated') return Astro.redirect('/api/auth/signin');
+if (auth.status !== 'admin') return new Response('Forbidden', { status: 403 });
+const { session } = auth;
let entries: GuestbookEntry[] = [];
try {
diff --git a/www/src/pages/api/deploy.ts b/www/src/pages/api/deploy.ts
index 57ad7ec..c6f7ab4 100644
--- a/www/src/pages/api/deploy.ts
+++ b/www/src/pages/api/deploy.ts
@@ -1,23 +1,18 @@
import type { APIRoute } from 'astro';
-import { getSession } from 'auth-astro/server';
-import { jsonResponse, errorResponse, requireAdmin } from '../../lib/api';
+import { jsonResponse, errorResponse } from '../../lib/api';
+import { getAdminSession } from '../../lib/auth';
export const prerender = false;
export const POST: APIRoute = async ({ request }) => {
- const session = await getSession(request);
- const authError = requireAdmin(session);
- if (authError) return authError;
+ const auth = await getAdminSession(request);
+ if (auth.status !== 'admin') return errorResponse('Unauthorized', 403);
const hookUrl = import.meta.env.VERCEL_DEPLOY_HOOK;
- if (!hookUrl) {
- return errorResponse('Deploy hook not configured', 500);
- }
+ if (!hookUrl) return errorResponse('Deploy hook not configured', 500);
const res = await fetch(hookUrl, { method: 'POST' });
- if (!res.ok) {
- return errorResponse('Failed to trigger deploy', 502);
- }
+ if (!res.ok) return errorResponse('Failed to trigger deploy', 502);
return jsonResponse({ success: true });
};
diff --git a/www/src/pages/api/guestbook/[id].ts b/www/src/pages/api/guestbook/[id].ts
index a8a91a6..32d92d9 100644
--- a/www/src/pages/api/guestbook/[id].ts
+++ b/www/src/pages/api/guestbook/[id].ts
@@ -1,33 +1,27 @@
import type { APIRoute } from 'astro';
-import { getSession } from 'auth-astro/server';
import { approveEntry, deleteEntry } from '../../../lib/db';
-import { jsonResponse, errorResponse, requireAdmin } from '../../../lib/api';
+import { jsonResponse, errorResponse } from '../../../lib/api';
+import { getAdminSession } from '../../../lib/auth';
export const prerender = false;
export const PATCH: APIRoute = async ({ params, request }) => {
- const session = await getSession(request);
- const authError = requireAdmin(session);
- if (authError) return authError;
+ const auth = await getAdminSession(request);
+ if (auth.status !== 'admin') return errorResponse('Unauthorized', 403);
const id = parseInt(params.id!, 10);
- if (isNaN(id)) {
- return errorResponse('Invalid ID', 400);
- }
+ if (isNaN(id)) return errorResponse('Invalid ID', 400);
await approveEntry(id);
return jsonResponse({ success: true });
};
export const DELETE: APIRoute = async ({ params, request }) => {
- const session = await getSession(request);
- const authError = requireAdmin(session);
- if (authError) return authError;
+ const auth = await getAdminSession(request);
+ if (auth.status !== 'admin') return errorResponse('Unauthorized', 403);
const id = parseInt(params.id!, 10);
- if (isNaN(id)) {
- return errorResponse('Invalid ID', 400);
- }
+ if (isNaN(id)) return errorResponse('Invalid ID', 400);
await deleteEntry(id);
return jsonResponse({ success: true });
diff --git a/www/src/pages/feed.xml.ts b/www/src/pages/feed.xml.ts
index d350661..1b26c9b 100644
--- a/www/src/pages/feed.xml.ts
+++ b/www/src/pages/feed.xml.ts
@@ -1,12 +1,12 @@
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
import type { APIContext } from 'astro';
-import { getSlug } from '../lib/md';
+import { getSlug, type Post } from '../lib/posts';
import { getTxtFiles } from '../lib/txt';
import { excerpt } from '../lib/format';
export async function GET(context: APIContext) {
- const posts = await getCollection('md');
+ const posts = await getCollection('posts');
const txtFiles = getTxtFiles();
const items = [
@@ -14,7 +14,7 @@ export async function GET(context: APIContext) {
title: post.data.title,
pubDate: post.data.date,
link: `/${getSlug(post.id)}`,
- description: excerpt((post as any).body) || post.data.title,
+ description: excerpt((post as Post).body) || post.data.title,
})),
...txtFiles.map(txt => ({
title: txt.name,
diff --git a/www/src/pages/index.astro b/www/src/pages/index.astro
index 75f592f..9f6f8ef 100644
--- a/www/src/pages/index.astro
+++ b/www/src/pages/index.astro
@@ -2,12 +2,12 @@
import { getCollection } from 'astro:content';
import Layout from '../layouts/Layout.astro';
import { getApprovedEntries, type GuestbookEntry } from '../lib/db';
-import { formatDate, formatListItem, escapeHtml } from '../lib/format';
-import { organizePostsByCategory, getSlug } from '../lib/md';
+import { formatDate, formatListItem, extractDomain, wordCount, escapeHtml } from '../lib/format';
+import { organizePostsByCategory, getSlug } from '../lib/posts';
import { getTxtFiles } from '../lib/txt';
import { DEFAULT_CATEGORY, SECTIONS, SUBDOMAINS } from '../lib/consts';
-const posts = await getCollection('md');
+const posts = await getCollection('posts');
const { grouped, categories: sortedCategories } = organizePostsByCategory(posts);
const bookmarksCollection = await getCollection('bookmarks');
@@ -37,24 +37,24 @@ const urls = [
{!isDefault && {category}}
- `
${formatListItem(post.data.date, `/${getSlug(post.id)}`, post.data.title, { pinned: post.data.pinned })}`
+ `
${formatListItem(post.data.date, `/${getSlug(post.id)}`, post.data.title, { suffix: wordCount(post.body) })}`
).join('')} />
);
})}
-
-{SECTIONS.plaintext}
+
+{SECTIONS.files}
{
const name = f.name.replace(/\.txt$/, '');
- return `
${formatListItem(f.date, `/${f.name}`, name, { pinned: f.pinned })}`;
+ return `
${formatListItem(f.date, `/${f.name}`, name, { suffix: f.description })}`;
}).join('')} />
{SECTIONS.bookmarks}
- `${formatListItem(b.data.date, b.data.url, b.data.title)}`
+ `${formatListItem(b.data.date, b.data.url, b.data.title, { suffix: extractDomain(b.data.url) })}`
).join('')} />
diff --git a/www/src/pages/sitemap.txt.ts b/www/src/pages/sitemap.txt.ts
index 2121a21..634b568 100644
--- a/www/src/pages/sitemap.txt.ts
+++ b/www/src/pages/sitemap.txt.ts
@@ -1,12 +1,12 @@
import { getCollection } from 'astro:content';
import type { APIContext } from 'astro';
-import { getSlug } from '../lib/md';
+import { getSlug } from '../lib/posts';
import { getTxtFiles } from '../lib/txt';
import { SUBDOMAINS } from '../lib/consts';
export async function GET(context: APIContext) {
const site = context.site?.origin ?? 'https://wynne.rs';
- const posts = await getCollection('md');
+ const posts = await getCollection('posts');
const txtFiles = getTxtFiles().map(f => f.name);
const urls = [
diff --git a/www/src/styles/global.css b/www/src/styles/global.css
index 4332b9c..4e595c6 100644
--- a/www/src/styles/global.css
+++ b/www/src/styles/global.css
@@ -1,9 +1,10 @@
body {
box-sizing: border-box;
- max-width: 48rem;
+ max-width: 34rem;
margin: 0 auto;
padding: 1rem;
text-align: justify;
+ font-family: 'Times New Roman', serif;
}
img {
@@ -17,6 +18,7 @@ h1, h2, h3, h4, h5, h6 {
.muted {
color: #888;
+ font-size: 0.9rem;
}
.left, .right {
@@ -24,7 +26,7 @@ h1, h2, h3, h4, h5, h6 {
font-size: 0.9rem;
}
-@media (min-width: 63rem) {
+@media (min-width: 58rem) {
.left, .right {
display: inline;
position: relative;
@@ -78,67 +80,77 @@ section {
margin: 1rem 0;
}
-section .section-label {
- font-family: monospace;
-}
-
-.home-name-link {
- display: none;
-}
-
-html[data-just] .home-name {
- display: none;
-}
-
-html[data-just] .home-name-link {
- display: inline;
-}
html[data-has] .guestbook-form {
display: none;
}
+header {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ gap: 2rem;
+}
+
+.header-name {
+ white-space: nowrap;
+}
+
+.header-links {
+ text-align: right;
+}
+
section pre {
margin: 0;
}
.entry-list {
- columns: 2 24ch;
- column-gap: 3ch;
- font-family: monospace;
margin: 0;
}
.entry {
display: grid;
- grid-template-columns: 10ch 1fr;
+ grid-template-columns: 4rem 1fr;
+ align-items: baseline;
break-inside: avoid;
}
.entry-content {
+ display: flex;
overflow: hidden;
white-space: nowrap;
+}
+
+.entry-content > a {
+ flex: 0 1 auto;
+ min-width: 0;
+ overflow: hidden;
text-overflow: ellipsis;
}
+.entry-suffix {
+ flex: 1 10000 0%;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ text-align: right;
+ padding-left: 0.5rem;
+}
+
.guestbook-entries {
- font-family: monospace;
- white-space: pre;
+ margin: 0;
}
.guestbook-entry {
display: grid;
- grid-template-columns: 10ch 1fr;
-}
-
-.guestbook-entry > span:last-child {
- white-space: normal;
+ grid-template-columns: 4rem 1fr;
+ align-items: baseline;
+ break-inside: avoid;
}
.guestbook-form {
margin-top: 0.5rem;
- margin-left: 10ch;
- font-family: monospace;
+ margin-left: 4rem;
}
html[data-compact] .list-meta {