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 ThrabenThalia, Guardian of Thraben {1}{W} - Legendary Creature — Human Soldier - First strike Noncreature spells cost {1} more to cast. - 2/1 🖚

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: 🎴CharizardCharizard - 120 HP - Fire - Energy Burn: As often as you like during your turn (before your attack), you may turn all Energy attached to Charizard into Fire Energy for the rest of the turn. This power can't be used if Charizard is Asleep, Confused, or Paralyzed. - Fire Spin [FireFireFireFire] 100: Discard 2 Energy cards attached to Charizard in order to use this attack. - Weakness: Water ×2 and 🎴MewtwoMewtwo - 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

[Charizard]{.card} and [Mewtwo]{.card}

Pokemon with set disambiguation: 🎴MewtwoMewtwo - 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

[Mewtwo]{.card set="basep"}

Yu-Gi-Oh: 🎴Blue-Eyes White DragonBlue-Eyes White Dragon - Normal Monster (Dragon) [LIGHT] - Level 8 - ATK 3000 / DEF 2500 - This legendary dragon is a powerful engine of destruction. Virtually invincible, very few have faced this awesome creature and lived to tell the tale. and 🎴Dark MagicianDark Magician - Normal Monster (Spellcaster) [DARK] - Level 7 - ATK 2500 / DEF 2100 - ''The ultimate wizard in terms of attack and defense.''

MTG: 🎴Lightning AxeLightning Axe {R} - Instant - As an additional cost to cast this spell, discard a card or pay {5}. Lightning Axe deals 5 damage to target creature. and 🎴Arclight PhoenixArclight Phoenix {3}{R} - Creature — Phoenix - Flying, haste At the beginning of combat on your turn, if you've cast three or more instant and sorcery spells this turn, return this card from your graveyard to the battlefield. - 3/2

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:

  • 🎴MountainMountain - Spell Card (Field) - All Dragon, Winged Beast, and Thunder monsters on the field gain 200 ATK/DEF. (Yu-Gi-Oh)
  • 🎴MountainMountain - Basic Land — Mountain - ({T}: Add {R}.) (MTG)
* [Mountain]{.card source="yugioh"} (Yu-Gi-Oh)
* [Mountain]{.card source="mtg"} (MTG)

Chat Bubbles

Alice
Alice

Hey, have you seen the new site redesign?

Claude
Claude

Yeah! The widget system is really clean now.

Alice
Alice

The markdown syntax is so much simpler.

::: 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.

A cute, slightly deep-fried illustration of a sleepy Dratini
A sleepy Dratini
![A sleepy Dratini](/images/dratini.png){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.

No adblock
Content highlighted
::: comparison
![No adblock](/images/fandom-post/full-nofilter.png){.thumbnail}

![Content highlighted](/images/fandom-post/full-nofilter-emphasis.png){.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.