Everything this blog can do

One post that renders every style the blog supports — paragraphs, callouts, code, diagrams, images, video, tables, footnotes and maths. Keep it around as a reference.

On this page

This is the reference sheet. Everything the blog can render appears below — actually rendered, not just described — so when you forget how to drop in a diagram six months from now, this is the page to open.

Paragraph styles

Ordinary paragraphs need nothing special. Body text is set in Source Serif 4, self-hosted, at about 71 characters a line — inside the 60–75 band that reads comfortably. The type scale is fluid, so it grows with the viewport rather than snapping between breakpoints. Interface text — the nav, dates, tags, this site’s furniture — stays in the system sans, because you scan that rather than read it.

Inline formatting

You get the usual: bold, italic, inline code, links, strikethrough, highlighted text, H2O, 106, CDN with a tooltip, and Ctrl + C for keys.

Quotes and callouts

Blockquotes sit behind a quiet rule, set roman rather than italic — a long quote in italics is slower to read. Good for quoting someone else; less good for things you want the reader to actually stop and notice.

— someone, probably

For those, use a callout.

Lists

Unordered, with nesting:

  • A top-level item
  • Another one
    • A nested item
    • And its sibling
      • Three deep still reads fine
  • Back to the top level

Ordered, for steps that happen in sequence:

  1. Reproduce the failure first
  2. Write the test that captures it
  3. Fix it
  4. Watch the test go green

Task lists work too, via GFM:

  • Set the site up
  • Write the reference post
  • Actually write something worth reading

And definition-style pairs, using a plain table when you need two columns:

Term : A description list is not in GFM, so use a table or a bold run-in instead.

Code blocks

Fenced code is rendered by Expressive Code, so it gets a copy button, a file title, and line-level annotation — all at build time, with no JavaScript shipped for the highlighting itself.

src/lib/cache.ts
type Entry<T> = { value: T; expires: number };
export function createCache<T>(ttlMs: number) {
const store = new Map<string, Entry<T>>();
return {
get(key: string): T | undefined {
const hit = store.get(key);
if (!hit) return undefined;
if (hit.expires < Date.now()) {
store.delete(key);
return undefined;
}
return hit.value;
},
set(key: string, value: T) {
store.set(key, { value, expires: Date.now() + ttlMs });
},
};
}

Marking the lines that matter

Highlight ranges with {4-6}, and pick out a term by quoting it:

train.py
import torch
def configure(model, learning_rate: float):
# These three lines are the whole point of the example.
optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=100)
return optimizer, scheduler

Showing a change

nginx.conf
server {
listen 443 ssl http2;
server_name atyasar.dev;
gzip on;
brotli on;
brotli_types text/html text/css application/javascript;
}

ins and del work inside a normal language block too, so you keep syntax colours while still showing the edit:

rollup.config.js
export default {
input: 'src/index.js',
output: {
format: 'cjs',
format: 'esm',
},
};

Line numbers, folding and wrapping

Long files can start folded with collapse, and line numbers are opt-in:

server.js
import http from 'node:http';
const PORT = process.env.PORT ?? 3000;
const routes = {
'/': () => ({ status: 200, body: 'ok' }),
'/health': () => ({ status: 200, body: JSON.stringify({ up: true }) }),
'/version': () => ({ status: 200, body: process.version }),
};
function handle(url) {
const route = routes[url];
return route ? route() : { status: 404, body: 'not found' };
}
http.createServer((req, res) => {
const { status, body } = handle(req.url);
res.writeHead(status).end(body);
}).listen(PORT);

wrap stops a long line scrolling off the side — handy for log output:

Terminal window
ERROR Failed to resolve import "./components/Header.astro" from "src/pages/index.astro". Does the file exist? The import was added in commit ac76eb7 and the file was renamed in 5511c9b, so either the import or the rename needs to change.

Terminal blocks get their own frame automatically, with no file title:

Terminal window
npm run build && npx serve dist

Diagrams

Write a mermaid fence and it renders as a diagram. Anything after the language becomes the caption. The raw source stays in the HTML as a <pre> fallback, so it still reads sensibly without JavaScript — and mermaid itself is only downloaded on pages that actually contain a diagram.

Flowcharts

How a request moves through the stack

Sequence diagrams

A token refresh, in order

State machines

The lifecycle of a background job

Entity relationships

The shape of the data

Gantt charts, pie charts, git graphs and class diagrams all work the same way — anything mermaid supports.

Images

Import the file, then hand it to Figure. Astro generates a responsive srcset, converts to modern formats, and sets the intrinsic size so nothing shifts while the page loads.

A blue gradient placeholder with a grid overlay
Captions are optional, and render centred under the image.

Pass wide to break out past the text measure on large screens — useful for screenshots and wide charts.

The same placeholder, rendered wider than the text column
Same image, wide.

Several at once. Set sizes on gallery children so the browser doesn’t fetch a full-width file for a half-width slot:

Three across works too:

Video

For screen recordings, drop the file in /public/video/ and use Video. With loop set it autoplays muted and loops, the way a GIF would, but at a fraction of the size — the clip below is 38 KB for four seconds.

Silent loop mode: autoplays, no controls, no sound.

Drop loop and you get a normal player with controls, which is what you want for anything with narration:

The same clip with controls, and nothing loaded until you press play.

Pass alt_src to offer a second encoding — a .webm alongside an .mp4, say. The browser takes the first one it can play, and the MIME type is worked out from the extension.

For YouTube, YouTube renders a thumbnail and loads nothing from Google until the reader clicks play. A page with five embeds still costs no third-party JavaScript on first paint.

A placeholder embed — swap the id for a real one. Nothing loads from YouTube until you click.

Tables

ApproachFirst paintJS shippedNotes
Static HTML~40 ms0 KBWhat this site does
Islands~60 ms~15 KBFine for a search box
Full SPA~400 ms~180 KBHard to justify for a blog

Columns align with :---, :---: and ---:. Wide tables scroll horizontally on small screens instead of breaking the layout.

Footnotes and maths

Footnotes use standard GFM syntax and collect at the bottom of the post.1 You can have more than one, and they renumber themselves.2

Maths is available inline — , or — and as a display block:

Frontmatter

Every post starts with this block. Only the first three fields are required:

---
title: 'Everything this blog can do'
description: 'One line. Used in listings, RSS, search results and social cards.'
pubDate: 2026-09-20
# optional from here down
updatedDate: 2026-09-22
tags: ['meta', 'astro']
draft: false # true hides it from the build
cover: './cover.jpg' # social card, and hero if showCover is true
coverAlt: 'Describe the image'
showCover: false
toc: true # false hides the table of contents
canonicalUrl: 'https://example.com/original' # for cross-posts
---

Reading time and word count are computed at build time — no need to write them down.

What you don’t have to do

No imports for any of the components above; they’re injected into every post. No manual table of contents. No <div> wrappers for layout. Write markdown, reach for a component when markdown runs out, and that’s the whole workflow.

Footnotes

  1. Like this. The arrow at the end takes you back to where you were reading. ↩

  2. Footnotes can contain code, links and several sentences, so they are a reasonable place to park a caveat. ↩