On this page

This opening paragraph sets the baseline: default font size, line height, color, and measure. It's intentionally long so you can judge reading rhythm and line length on desktop and mobile. A second sentence confirms spacing.

This second paragraph (after a blank line) checks p + p spacing.

1. Headings#

The renderer styles h1–h4; h5/h6 are smaller (h6 is an eyebrow label). Every heading also gets an id + a hover # anchor, and h2/h3 feed the TOC.

H1 — page-title scale#

H2 — section scale#

H3 — subsection scale#

H4 — minor heading#

H5 — small#
H6 — eyebrow label#

2. Inline formatting#

Bold, italic, bold italic, strikethrough, and inline code. Combined: bold with code inside, bold link, code with *no* emphasis (emphasis shouldn't apply inside code).

Escapes: literal asterisks *not italic*, a literal backtick ` and a literal hash # at line start. HTML entities: © — → & <tag>.

Hard line break (trailing backslash) —
this sits directly below the previous line.

Soft wrap: this line and this line collapse into ONE paragraph (no break — breaks is off).

4. Blockquotes#

A single-line quote — check left border, tint, and italic styling.

A multi-paragraph quote.

Second paragraph inside the quote (blockquote > * + * spacing).

A nested blockquote one level deeper.

— Attribution line

5. Lists#

Unordered + nested#

  • First item
  • Second item, deliberately long so a wrapped bullet's alignment against the marker indent is visible across the column width
    • Nested level two
      • Nested level three
  • Back to level one

Ordered, custom start, nested mixed#

  1. Starts at three (custom start)
  2. Fourth
    1. Sub-step a
    2. Sub-step b
      • mixed unordered child
  3. Fifth

Loose list (paragraph spacing between items)#

  • First item with its own paragraph.

    A second paragraph inside the same list item.

  • Second item, also loose.

Task list (GFM checkboxes)#

  • Completed task (aria-label "Completed")
  • Incomplete task (aria-label "Not completed")
  • Nested task lists:
    • sub-task done
    • sub-task pending

6. Tables (GFM)#

Basic table with the original gap (now styled): header fill, borders, zebra, hover, horizontal scroll on overflow.

Feature Supported Notes
Headings Yes h1–h4 tuned, h5/h6 smaller
Tables Yes styled
Footnotes Yes numbered + named
Math Yes KaTeX, server-rendered
Diagrams Yes Mermaid, lazy-loaded

Alignment + rich cells (inline formatting, code, links, math inside cells):

Left Center Right
bold cell code cell link
struck 🚀 a2+b2a^2+b^2
long left text centered 1,234.56

7. Code#

Inline: call renderArticle(source) then hydrate.

Fenced with language — note the lang label + Copy button (top-right):

ts
// TypeScript via Shiki (github-dark)
import { renderArticle } from "@/lib/markdown"

export async function preview(src: string): Promise<string> {
  const { html } = await renderArticle(src)
  return html.trim()
}

Several grammars to sanity-check highlighting breadth:

python
def fib(n: int) -> int:
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
    return a
css
.prose table { border-collapse: collapse; width: 100%; }
.prose th { background: var(--bg-soft); }
sql
SELECT title, published_at
FROM articles
WHERE status = 'PUBLISHED'
ORDER BY published_at DESC;
json
{ "name": "articles-goashins", "private": true }
diff
- const theme = "github-light"
+ const theme = "github-dark"

No-language block (no lang label, no colors):

plain preformatted text
  indentation preserved

Long line to test horizontal scroll inside the block:

bash
docker run --rm -it -v "$(pwd)":/app -w /app -e NODE_ENV=production node:22-alpine sh -c "npm ci && npm run build && npm run start"

8. Callouts#

Reminder: callout bodies render raw — markdown inside them does NOT format, only inline HTML does. The three styled types:

Unknown type (falls back to the info icon, class callout-note):

9. Footnotes#

Superscript references1 jump to a list at the bottom and back. Named labels work2, and a footnote can be cited twice1.

10. Emoji shortcodes#

:tada:🎉, :rocket:🚀, :white_check_mark:, :warning:⚠️, :bulb:💡. Emoji in a heading works too (see below). Unknown names stay literal: :not_a_real_emoji:.

Emoji in a heading #

Confirms shortcodes resolve inside headings (and the TOC text).

11. Math (KaTeX)#

Inline: mass–energy $E = mc^2$; golden ratio $\varphi = \frac{1+\sqrt{5}}{2}$; a sum i=1ni=n(n+1)2\sum_{i=1}^{n} i = \frac{n(n+1)}{2}.

Display equations:

ex2dx=π\int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi}

A matrix:

A=[abcd]A = \begin{bmatrix} a & b \\ c & d \end{bmatrix}

Aligned / cases:

f(x)={x2x0xx<0f(x) = \begin{cases} x^2 & x \ge 0 \\ -x & x < 0 \end{cases}

Wide expression (tests horizontal scroll):

f(x)=a0+n=1(ancosnπxL+bnsinnπxL)f(x) = a_0 + \sum_{n=1}^{\infty} \left( a_n \cos\frac{n\pi x}{L} + b_n \sin\frac{n\pi x}{L} \right)

12. Diagrams (Mermaid)#

These render client-side (lib loads only on pages with a diagram); the source stays visible as a fallback until rendered.

Flowchart:

flowchart LR
  A[Write markdown] --> B{Has diagram?}
  B -- yes --> C[Lazy-load mermaid]
  B -- no --> D[Ship nothing extra]
  C --> E[Render SVG]

Sequence diagram:

sequenceDiagram
  Reader->>Server: request article
  Server->>Server: renderArticle()
  Server-->>Reader: HTML + KaTeX

Pie chart:

pie title Time spent
  "Writing" : 45
  "Debugging" : 30
  "Reading docs" : 25

Class diagram:

classDiagram
  Article "1" --> "*" Category
  Article : +String title
  Article : +publish()

13. Collapsible details#

Native <details>/<summary> (raw HTML) — styled, good for FAQs / "show more":

Click to expand

Hidden content with bold, a list, and code:

  • point one
  • point two

inline code works here because this is real markdown (unlike callout bodies).

14. Images#

Inline (Cloudinary URLs get f_auto,q_auto,c_limit,w_1200; all lazy-load):

Descriptive alt text

With a caption — a standalone image whose markdown title is set renders as a <figure> with the title shown as a visible <figcaption> beneath it:

Alt text
This caption shows under the image

15. YouTube auto-embed#

A bare YouTube URL alone on a line becomes a responsive iframe:

16. X / Twitter auto-embed#

A bare X (or twitter.com) status URL alone on a line becomes an embedded post. An embed URL placed inline in a sentence — like https://x.com/jack/status/20 — stays an ordinary clickable link.

17. CodePen auto-embed#

A bare CodePen pen URL alone on a line becomes an embedded pen (result tab):

18. Horizontal rule#

Above the rule.


Below the rule.

19. Mixed real-world block#

Heterogeneous elements in sequence, to judge vertical rhythm:

Most work happens in renderArticle. The pipeline:

  1. Preprocess bare YouTube / X / CodePen URLs into embeds.
  2. Tokenize with marked (GFM + callout + emoji extensions).
  3. Highlight code via Shiki; render math with KaTeX.
  4. Post-process headings → ids, anchors, and the TOC.

The code, it turns out, was the easy part.

20. NOT supported (expected to render literally)#

These have no plugin, so they should appear as plain text — a gap here is intentional, not a bug:

  • Definition lists: Term then : definition (renders as plain text)
  • Superscript x^2^ / subscript H~2~O (no extension)
  • Highlight ==marked== (no extension)
  • Abbreviations *[HTML]: HyperText… (no extension)

That's the full set. This closing paragraph confirms spacing after the final section and before the end of the article body.

Footnotes

  1. First footnote — appears at the very bottom regardless of source order. 2

  2. Named footnotes are renumbered in output order, not by label.