~/blog/2026-04-23-features.md
$ cat ./features.md

Blog feature reference

date: read: ~5 min

A single post that exercises every feature the blog pipeline supports. Everything below renders at build time via marked plus the custom extensions in app/vite/plugins/md-frontmatter.ts. Use this as a cheat sheet when writing future posts — if something on this page renders wrong on the deployed site, the renderer regressed, not the content.

Table of contents

The {{ toc }} token expands to a bullet list of the post's h2h6 headings:

Headings

Post titles come from frontmatter (title), so the body starts at h2. Use h3/h4 for subsections — anything deeper probably means the section should be split into its own post.

This is an h3

And this an h4

Text formatting

Standard emphasis: bold, italic, both, strikethrough. Inline code uses backticks.

Raw inline HTML passes through for semantic extras: highlight, HTML, — Mark Otto, deleted, inserted, H2O, E = mc2.

Plain markdown [text](url) works for every link type. Internal links that start with / are intercepted at runtime by useInternalLinkNav and navigate through React Router — no full page reload.

Reusable references

Reference-style links let you define a URL once and reuse it across the post. Define the label anywhere (convention: grouped at the bottom), then cite it with [text][label]:

Check out the marked docs for more info on how to get the most out of marked. File all bugs/feature requests at marked's GitHub repo. If you have questions, you can ask them on the marked discussions.

Site variables

Post content references central data through {{ path.to.key }} — expanded against content/site.yml at build. Keeps URLs, handles, and identity strings out of prose.

Router paths are derived from app/routes.ts and exposed as {{ urls.<route-file-basename> }} — e.g. {{ urls.home }}{{ urls.home }}, {{ urls.blog }}{{ urls.blog }}. Anything under the urls: section of site.yml (static artifacts like feed and not_found) wins over the router-derived map.

Lists

Unordered:

  • first item
  • second item
    • nested once
    • also nested
  • third item

Ordered:

  1. one
  2. two
  3. three

Blockquote

The best code is no code at all. Every new line of code you willingly bring into the world is code that has to be debugged, code that has to be read and understood, code that has to be supported.

— Jeff Atwood

Horizontal rule

Three or more hyphens on their own line render as a dashed divider:


Code

Inline snippets use backticks: marked.use() registers an extension.

Fenced without a language — plain preformatted:

$ pnpm run build
$ pnpm run preview

Fenced with a language — shiki highlights at build time using real TextMate grammars, with dual light/dark themes that follow the site's theme toggle:

export function slugify(text: string): string {
  return text
    .toLowerCase()
    .replace(/[^\w\s-]/g, '')
    .replace(/\s+/g, '-');
}

Append linenos to the info string for a two-column render with a line-number gutter:

export function expandTemplate(md: string, vars: Record<string, unknown>): string {
  return md.replace(/\{\{\s*([^}]+?)\s*\}\}/g, (match, raw) => {
    const inner = raw.trim();
    if (inner === 'toc') return buildTocList(md);
    if (inner === 'now') return formatNow();
    if (inner.startsWith('gist:')) return gistEmbed(inner.slice(5));
    return lookup(inner, vars) ?? match;
  });
}

Tables

GFM table syntax:

Feature Source Handler
Footnotes [^id] / [^id]: body marked-footnote
TOC {{ toc }} md-frontmatter.ts
Line numbers linenos fence flag md-frontmatter.ts
Gists {{ gist:ID }} md-frontmatter.ts
Route URLs {{ urls.<name> }} app/routes.ts
Site vars {{ path.to.key }} content/site.yml

Definition list

Raw HTML — marked doesn't parse the Kramdown term\n: def shorthand, so write <dl> directly:

HTML
HyperText Markup Language. Structure.
CSS
Cascading Style Sheets. Presentation.
JS
JavaScript. Behavior.

Images

External placeholders via placehold.co:

sample sample

Local images go under app/assets/ and are imported by the route component, not referenced from markdown.

Footnotes

Reference a footnote inline with [^id]1 and define the body anywhere in the document. On render, marked-footnote collects all definitions into a <section class="footnotes"> at the bottom and wires up back-links automatically.

Multiple references to the same id collapse to a single footnote with multiple back-arrows1.

Gist embed

{{ gist:ID }} expands to an iframe pointing at the gist's pibb URL (GitHub's iframe-safe render) — drops the embed inline without blocking on a <script> tag:

gist.github.com/b24679402957c63ec426
Good-CONTRIBUTING.md-template.md

Contributing

When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change.

Please note we have a code of conduct, please follow it in all your interactions with the project.

Pull Request Process

  1. Ensure any install or build dependencies are removed before the end of the layer when doing a build.
  2. Update the README.md with details of changes to the interface, this includes new environment variables, exposed ports, useful file locations and container parameters.
  3. Increase the version numbers in any examples files and the README.md to the new version that this Pull Request would represent. The versioning scheme we use is SemVer.
  4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you.

Code of Conduct

Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

Our Standards

Examples of behavior that contributes to creating a positive environment include:

  • Using welcoming and inclusive language
  • Being respectful of differing viewpoints and experiences
  • Gracefully accepting constructive criticism
  • Focusing on what is best for the community
  • Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

  • The use of sexualized language or imagery and unwelcome sexual attention or advances
  • Trolling, insulting/derogatory comments, and personal or political attacks
  • Public or private harassment
  • Publishing others' private information, such as a physical or electronic address, without explicit permission
  • Other conduct which could reasonably be considered inappropriate in a professional setting

Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [INSERT EMAIL ADDRESS]. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.

Attribution

This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at http://contributor-covenant.org/version/1/4

gist.github.com/dd5639089d837e95c22a706260b26706
readme.md

Sass Dark/Light Theme Mixin

This is a Sass mixin to handle a 3-way dark mode. It relies on a data-theme attribute on your <html> element with a value of light or dark. If data-theme is absent (i.e. it's neither light nor dark), the system's preferred mode is used.

body {
  // matches data-theme="light" or data-theme="auto" with system instructing light mode
  @include light {
    background: white;
    color: black;
  }
  
  // matches data-theme="dark" or data-theme="auto" with system instructing dark mode
  @include dark {
    background: black;
    color: white;
  }
}

See the example above on sassed

Caveat: This mixin targets modern browsers which support the :where() pseudo selector. There is an older revision of this Gist with more caveats, but also with support for older browsers, which you can find here.

theme-toggle.scss
@use 'sass:selector';
@use 'sass:string';
@use 'sass:list';

@mixin mode($mode) {
  $opposite-mode: '';
  @if $mode == 'light' {
    $opposite-mode: 'dark';
  } @else {
    $opposite-mode: 'light';
  }
  
  $individual-selectors: selector.parse(&);

  $new-selectors: ();

  // Split into individual selectors
  @each $individual-selector in $individual-selectors {
    // Split by :: to find pseudo elements
    $parts: string.split(#{$individual-selector}, '::', 2);

    @if list.length($parts) > 1 {
      // Selector has pseudo elements:
      // Split up the selector, apply mode modifier and stick the pseudo element on again
      $new-selectors: list.append(
        $new-selectors,
        "#{selector.append(list.nth($parts, 1), ':where(:root:not([data-theme=#{$mode}]):not([data-theme=#{$opposite-mode}]) *)')}::#{list.nth($parts, 2)}",
        comma
      );
    } @else {
      // Selector has no pseudo elements: Simply append the :where() clause
      $new-selectors: list.append(
        $new-selectors,
        selector.append($individual-selector, ':where(:root:not([data-theme=#{$mode}]):not([data-theme=#{$opposite-mode}]) *)'),
        comma
      );
    }
  }

  @media (prefers-color-scheme: $mode) {
    @at-root #{$new-selectors} {
      @content;
    }
  }
  @at-root #{$new-selectors} {
    @content;
  }
}

@mixin light {
  @include mode('light') {
    @content;
  }
}

@mixin dark {
  @include mode('dark') {
    @content;
  }
}

Frontmatter

Every post needs frontmatter at the top:

---
title: Post title
date: 2026-04-23
---

title drives the page <title> and the rendered h1. date can come from the filename prefix (YYYY-MM-DD-slug.md) or the frontmatter field — frontmatter wins when both are present. Slug is derived from the filename with any leading YYYY-MM-DD- stripped.


That's the full surface. To add a new feature, extend expandTemplate (build-time tokens), register a marked.use({ renderer }) override (HTML shape), or add styles under app/styles/terminal-content.scss (appearance).

Footnotes

  1. Footnote body. Marked-footnote moves this to the bottom and links back to the reference via the return arrow. 2

press ~ for terminal