Site Features and Widgets
I’ve re-done a bunch of the internals to be more pleasing-to-me. Posts now use cool widgets rather than ugly large inline html constructs. This post is a reference for these new sleek features. Everything here is processed at build time with zero client-side JavaScript.
The site is built with Hakyll, a static site generator. Posts are written in Markdown. During the build, custom Pandoc transforms convert shorthand syntax (like ::: chat or [Charizard]{.card}) into the styled HTML you see on the page.
Japanese Text Support
This site automatically detects Japanese text and marks it for screen readers. For example, this sentence contains 日本語 which gets wrapped in a lang="ja" attribute.
Mixed text works naturally: English and 日本語 can appear in the same paragraph without any special markup.
English and 日本語 can appear in the same paragraph without any special markup.The generated HTML:
<span lang="ja">日本語</span>Ruby/Furigana
Use {kanji|reading} syntax for furigana: 漢字 renders as ruby text.
{漢|かん}{字|じ} renders as ruby text.The generated HTML:
<span lang="ja"><ruby>漢<rt>かん</rt></ruby><ruby>字<rt>じ</rt></ruby></span>Mixed ruby and plain CJK: 日本語を勉強する
{日|に}{本|ほん}{語|ご}を勉強するSingle character: The word 気 means “spirit” or “energy”.
The word {気|き} means "spirit" or "energy".Game Text
For in-game text that needs preserved spacing and special styling, use the .game class:
1337!!!? おはよ
[1337!!!? おはよ ]{.game}The underline extends through trailing spaces: トレーリングスペーステスト
[トレーリングスペーステスト ]{.game}Admonitions
::: note
This is a **note** admonition. Use it for general information.
:::::: warning
This is a **warning** admonition. Use it for important cautionary information.
:::::: tip
This is a **tip** admonition. Use it for helpful suggestions.
:::::: danger
This is a **danger** admonition. Use it for critical warnings.
:::The type determines the icon and color scheme. Types are defined in config/admonitions.toml.
:::: {.note title="Custom Title"}
You can also set a custom title for any admonition type.
::::Card Previews
Hover to see card images (desktop only). Alt text contains full card data for accessibility.
🖛 This post contains ✨on-hover card images✨, indicated by the hanafuda symbol, 「🎴」, like so: 🎴Thalia, Guardian of Thraben
🖚
Alt text contains card data for screen readers.
::: cards
:::The generated HTML includes complete card data in the alt text:
<img class="card-image"
src="../../images/cards/mtg/thalia-guardian-of-thraben.jpg"
alt="Thalia, Guardian of Thraben {1}{W} - Legendary Creature
— Human Soldier - First strike Noncreature spells cost
{1} more to cast. - 2/1"
loading="lazy">Pokemon: 🎴Charizard
and 🎴Mewtwo![Mewtwo - 60 HP - Psychic - Psychic [PsychicColorless] 10+: Does 10 damage plus 10 more damage for each Energy card attached to the Defending Pokémon. - Barrier [PsychicPsychic]: Discard 1 Psychic Energy card attached to Mewtwo in order to prevent all effects of attacks, including damage, done to Mewtwo during your opponent's next turn. - Weakness: Psychic ×2](../../images/cards/pokemon/base1-10.webp)
[Charizard]{.card} and [Mewtwo]{.card}Pokemon with set disambiguation: 🎴Mewtwo![Mewtwo - 70 HP - Psychic - Energy Absorption [Psychic]: Choose up to 2 Energy cards from your discard pile and attach them to Mewtwo. - Psyburn [PsychicPsychicColorless] 40 - Weakness: Psychic ×2](../../images/cards/pokemon/basep-14.webp)
[Mewtwo]{.card set="basep"}Yu-Gi-Oh: 🎴Blue-Eyes White Dragon
and 🎴Dark Magician![Dark Magician - Normal Monster (Spellcaster) [DARK] - Level 7 - ATK 2500 / DEF 2100 - ''The ultimate wizard in terms of attack and defense.''](../../images/cards/yugioh/46986414.jpg)
MTG: 🎴Lightning Axe
and 🎴Arclight Phoenix
Name collisions: Some names exist in multiple games (both Yu-Gi-Oh and MTG have “Mountain”). When this happens, the default priority is MTG > Pokemon > Yu-Gi-Oh. Override with source:
- 🎴Mountain
(Yu-Gi-Oh) - 🎴Mountain
(MTG)
* [Mountain]{.card source="yugioh"} (Yu-Gi-Oh)
* [Mountain]{.card source="mtg"} (MTG)Chat Bubbles
::: chat
@Alice[avatar-cool-user]: Hey, have you seen the new site redesign?
@Claude[avatar-chatbot]: Yeah! The widget system is really clean now.
@Alice[avatar-cool-user]: The markdown syntax is so much simpler.
:::Forum Posts
This is what a forum post looks like. It has a sidebar with author information and a main content area.
The styling is inspired by old.reddit.com’s clean layout.
This is a reply. It’s indented to show conversation threading.
::: {.forum-post name="RedditUser" avatar="snoo-3" title="Site Admin" posts="9001"}
This is what a forum post looks like. It has a sidebar with author information and a main content area.
The styling is inspired by old.reddit.com's clean layout.
:::
::: {.forum-reply name="AutoModerator" avatar="robot" title="Bot" posts="999999"}
This is a reply. It's indented to show conversation threading.
:::Other Widgets
Greentext
implying this is actually green
[implying this is actually green]{.greentext}Keyboard Keys
Press Ctrl+C to copy and Ctrl+V to paste.
Press <kbd>Ctrl</kbd>+<kbd>C</kbd> to copy and <kbd>Ctrl</kbd>+<kbd>V</kbd> to paste.Blockquotes
This is a blockquote with decorative quotation marks.
> This is a blockquote with decorative quotation marks.Figures
Caption in brackets becomes visible figcaption, alt in attributes is for screen readers.
{alt="A cute, slightly deep-fried illustration of a sleepy Dratini"}Side-by-Side Images
Put images in a row with equal widths. CSS grid under the hood. Add .thumbnail to constrain large images to a reasonable max-width/max-height.
::: comparison
{.thumbnail}
{.thumbnail}
:::Heading Anchors
All headings have a section symbol (§) that appears on hover. Click to copy the anchor link.
Code Blocks
Syntax highlighting via Pandoc at build time. Line numbers included.
main :: IO ()
main = putStrLn "Hello, World!"```haskell
main :: IO ()
main = putStrLn "Hello, World!"
```Custom starting line number:
int main(void) {
printf("Hello from line 42!\n");
return 0;
}```{.c startFrom="42"}
int main(void) {
printf("Hello from line 42!\n");
return 0;
}
```Build Pipeline
The site is built with make build. This orchestrates several steps:
config/fonts-src/*.woff2 --> font-subset.py --> static/fonts/
(scans content for used characters) |
|
config/{pokemon,yugioh,mtg}/ --> card-cache.py |
(builds index, copies images) | |
v |
card-cache.json |
| |
scss/main.scss --> sass --> css/main.css | |
| | |
v v v
content/*.md -----------------> site.hs (Hakyll) -> _site/
Running it
make watch for development. make build if you add new characters or cards (to regenerate fonts/cache).
Font Subsetting
Ships only glyphs used in content. Pool fonts in config/fonts-src/, subsetted output in static/fonts/. Emoji font built from scratch via nanoemoji (config/blobmoji/build-subset.py).
Card Cache
config/card-cache.py reads card JSON from config/{pokemon,yugioh,mtg}/, generates alt text, writes card-cache.json, copies images. Haskell transform reads the cache at build time.
Pandoc Transforms
Custom AST transforms in src/Transforms/*.hs. Order matters: forum/chat → admonitions → cards → anchors → Japanese text last.