Skip to content

About

A card-style Hexo theme with memos timeline, gradient visuals, and built-in Atom feed & OG images. Forked from hexo-theme-butterfly (Apache-2.0).

Topics

Resources

Stars

20 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

23 Commits

Folders and files

Repository files navigation

δΈ­ζ–‡ | English

Panda Logo

hexo-theme-panda

License npm npm downloads Hexo Node.js Demo

A card-style Hexo theme: memos timeline, blog series, gradient visuals, built-in Atom feed & OG image generation

Forked and extended from hexo-theme-butterfly 5.7.0 (Apache-2.0)

Demo: SpeechlessPanda's Blog Β· npm: hexo-theme-panda


✨ What Panda adds on top of Butterfly

Feature Description Default
Memos enhancements Per-memo Giscus comment iframes, auto-expand commented memos, standalone deep-link pages, local-search injection on
Home-as-about Home renders about/index.md; the post stream moves to /blog/ on (optional)
Latest memo card The newest memo pinned on top of the post stream on
Blog series A _posts/<name>/ folder with index.md becomes one card on the post stream; chapters stay off the stream on
Atom feed Custom generator mixing posts + memos, with update-notification entries on
OG share images Per-post 1200Γ—630 gradient images (requires @resvg/resvg-js) off
Gradient look Blue→purple→orange gradient header/footer/background (configurable, yields to your images) on
Nav background band With an image background, the fixed nav paints that image's top band, viewport-locked and aligned with #web_bg on
Link target blank Article/memo links open in a new tab on
Extra highlighting fish & typst grammars registered out of the box built-in
{% hideToggle %} collapse Expanded blocks get a bottom collapse bar that scrolls back to the header on

Everything Butterfly offers (PJAX, dark mode, comment systems, search, word count, etc.) is kept intact and configured exactly the Butterfly way.

πŸ“¦ Installation

npm (recommended)

Hexo β‰₯ 5.3.0. Run this in the Hexo site root (not inside the theme). Package: hexo-theme-panda.

npm install hexo-theme-panda

The renderers Panda needs (hexo-renderer-pug, hexo-renderer-stylus, hexo-util, moment-timezone) are its own dependencies, so npm/pnpm/yarn installs them automatically. Hexo 5+ loads hexo-theme-panda from node_modules.

themes/ wins over node_modules. Hexo looks for themes/<theme> first and only falls back to node_modules/hexo-theme-<theme>. If you previously cloned the theme, delete themes/panda/ β€” otherwise the stale copy keeps loading and npm update hexo-theme-panda silently does nothing. The build prints a warning when this happens.

Upgrading an existing install: see πŸ”„ Upgrading.

Git clone

Only if you want to hack on the theme itself.

git clone https://github.com/SpeechlessPanda/hexo-theme-panda.git themes/panda

Enable the theme in the site _config.yml:

theme: panda

New site from scratch:

npm install -g hexo-cli
hexo init my-blog && cd my-blog
npm install hexo-theme-panda

Then set theme: panda as above.

πŸ”„ Upgrading

Two commands, and both of them matter:

npm update hexo-theme-panda
hexo clean && hexo generate
  • npm update only swaps files in node_modules. It rebuilds nothing, so a public/ written by the previous theme keeps rendering exactly what it had β€” including the old version in the footer.
  • hexo clean before hexo generate. An upgrade can drop or rename files under source/; hexo clean deletes public/ and db.json so nothing from the old theme survives. Running hexo generate alone after a theme upgrade is the usual cause of a site that looks half-updated.

Swap in pnpm update / yarn upgrade if that is your package manager.

If npm update says Already up to date while a newer release exists on npm, you are resolving through a registry mirror that has not caught up yet. Pin the version to get past it: npm install hexo-theme-panda@latest (pnpm add / yarn up behave the same way).

If a CI or a host builds your site

GitHub Actions, Vercel, Netlify, Cloudflare Pages and friends build from the committed lockfile, not from your laptop. After the two commands above, commit the result and push:

git add package.json package-lock.json   # or pnpm-lock.yaml / yarn.lock
git commit -m "chore(deps): bump Panda theme"
git push

Skipping this is the single most common "I upgraded but the footer still shows the old version" report: your local public/ runs the new theme, while the deployed site keeps rendering whatever the lockfile in the repository still pins. The build now prints the version it used, so the CI log tells you which theme produced the deployment.

Verify

Where What you should see
hexo g output [panda] theme X.Y.Z Β· Hexo A.B.C Β· N files generated (plus the banner at startup)
CI log the same [panda] theme X.Y.Z line on the run that deployed
Any page footer Panda X.Y.Z next to the Hexo version
Page source theme assets carrying ?v=X.Y.Z

If the [panda] line, the footer and the ?v= disagree, the page you are reading was not built by the theme you have installed β€” regenerate, then redeploy.

πŸ–₯️ Daily commands

All of these run in the site root. Full CLI: Hexo commands.

Command What it does
hexo new "My post" New article β†’ source/_posts/My-post.md
hexo new series "Intro to X" New series β†’ source/_posts/Intro to X/index.md (title is the folder name, not slugized)
hexo new --series "Intro to X" "Chapter 1" New chapter β†’ source/_posts/Intro to X/Chapter 1.md (creates index.md if missing)
hexo new page about New page β†’ source/about/index.md
hexo server / hexo s Local preview at http://localhost:4000/
hexo s --draft Preview including drafts
hexo generate / hexo g Build static files into public/
hexo deploy / hexo d Publish public/ with your deployer
hexo g -d Generate, then deploy
hexo clean Delete db.json and public/
hexo clean && hexo g -d Full republish
hexo clean && hexo s Stale theme/config? wipe cache, then preview

Memos (璎璎忡) are not created with hexo new. Edit source/_data/shuoshuo.yml, then hexo s or hexo g.

Deploy (publish the blog)

hexo deploy needs a deployer plugin. GitHub Pages example:

npm install hexo-deployer-git
# site _config.yml
deploy:
  type: git
  repo: git@github.com:<user>/<user>.github.io.git
  branch: main
hexo clean && hexo g -d

Panda writes public/atom.xml itself. Do not also enable hexo-generator-feed (both would claim the same path).

βš™οΈ Configuration

Same convention as Butterfly: don't edit the packaged _config.yml (node_modules/hexo-theme-panda/_config.yml or themes/panda/_config.yml). Create _config.panda.yml in your site root and put your overrides there (Hexo deep-merges, overlay wins).

The fully-commented default config lives in _config.yml.

flowchart LR
  overlay["_config.panda.yml"] --> hexo[Hexo]
  theme["_config.yml"] --> init["scripts/events/init.js"]
  init --> hexo
  hexo --> layouts["layout/*.pug"]
  hexo --> memos["scripts/panda/memos.js"]
  hexo --> feed["scripts/panda/feed.js"]
  hexo --> series["scripts/panda/blog-series.js"]
  hexo --> og["scripts/panda/og-image.js"]
Loading

βœ… Requirements

  • Hexo β‰₯ 5.3.0 (8.x works; this is what the demo runs)
  • Node.js β‰₯ 18
  • Renderers: hexo-renderer-pug and hexo-renderer-stylus (install them if your site does not already have them)

Optional extras, only if you turn the matching feature on:

Feature Extra install
OG share images (og_image.enable) npm install @resvg/resvg-js
Local search injection Butterfly-style search.use: local_search plus a search data file
Memo comment counts GH_DISCUSSION_TOKEN in the build environment

🐼 Panda features

Home-as-about

# _config.panda.yml
home_about:
  enable: true            # falls back to the post list when the source page is missing
  source: about/index.md

Move the post stream to /blog/ and bind the home page:

# site _config.yml
index_generator:
  path: /blog
---
title: About
layout: home
---

(source/index.md)

/ keeps the full-screen hero (title + typewriter). /blog/ uses the same short header as About / Links (#page-site-info, i18n page.articles), not the homepage banner.

Memos (shuoshuo)

  1. Create source/memos/index.md:
---
title: Memos
type: shuoshuo
---
  1. Write entries in source/_data/shuoshuo.yml:
- date: 2026-09-10 12:00   # site timezone (_config.yml `timezone`)
  content: Supports **markdown** and [links](https://example.com)
  tags: [life]              # optional
  key: my-custom-key        # optional; derived from date otherwise
# _config.panda.yml
memos:
  enable: true
  path: /memos/            # page path (shared by card link, standalone pages, feed)
  data_key: shuoshuo       # source/_data/<data_key>.yml
  latest_card: true        # "latest memo" card on the post stream
  standalone_pages: true   # one deep-linkable page per memo
  search_injection: true   # inject into local search (needs search.use: local_search)
  comment_count: true      # build-time Giscus counts -> auto-expand commented memos

While the data file is missing or empty, the theme shows one built-in sample memo ("Hello, it's my blog!") on the timeline, the latest-memo card and the feed, so a fresh site demonstrates the format end to end. It disappears everywhere the moment you write your first real entry, and it is never persisted into your db.json.

Comments use Giscus (comments.use: Giscus + giscus.*, same as Butterfly). Each memo gets its own discussion via the theme's multi-iframe giscus /widget integration.

comment_count needs GH_DISCUSSION_TOKEN (a GitHub token with discussions read access) in the build environment. Without it, the step is skipped silently and comment areas stay collapsed.

Blog series

A series is a one-level folder under source/_posts, not Butterfly's {% series %} tag (series.enable stays off).

Path Role
source/_posts/hello.md Independent post, shown on the post stream
source/_posts/Intro to X/index.md Series metadata only (title + description keys required; description may be empty). Not a post, not in the feed
source/_posts/Intro to X/Chapter 1.md Chapter: listed on the series page, hidden from the post stream, still an RSS entry

index.md example:

---
title: Intro to X
description: From zero to a working setup
cover: /img/series-intro.jpg
---

cover is the series thumbnail (same idea as a post cover). It appears on the /blog/ series card and as the series page header. It does not inherit from chapters.

cover value Effect
/img/foo.jpg, https://…/foo.png Image. Local files go in source/ so the URL matches (source/img/foo.jpg β†’ /img/foo.jpg). Also gif / svg / webp / avif
linear-gradient(135deg, #6ec6ff, #ffd2a8) or #6ec6ff CSS background, not an <img>
empty / omit / false No card image; series page header falls back to default_top_img

The post stream mixes independent posts and series cards, newest first. A series card's date is its latest chapter. Empty series (index only) are omitted. Drag existing posts into the folder β€” no extra front-matter β€” and they leave the stream on the next hexo g / hexo s. CLI: hexo new series "Intro to X" and hexo new --series "Intro to X" "Chapter 1" (see Daily commands).

# _config.panda.yml
blog_series:
  enable: true
  path: /series/    # landing page URL prefix

Landing pages: /series/<folder>/. Chapter permalinks stay whatever your site permalink already is. In-series prev/next only walks sibling chapters. Comments on the series page follow the site comment system; set comments: false on index.md to turn them off.

Deeper than one folder (_posts/a/b/c.md) is ignored.

Atom feed

# _config.panda.yml
feed:
  enable: true           # on by default; do not run hexo-generator-feed together
  path: atom.xml
  post_limit: 20
  excerpt_limit: 140
  update_notify_hours: 24  # re-push posts with explicit front-matter `updated:` >24h after `date:`
  include_memos: true

Entry identity lives entirely in the URL path (never query/fragment), which every reader preserves; memo entries link to their standalone pages and update entries to auto-generated redirect stubs. Entry ids derive from file content, never from file mtime (which is just checkout time on CI): rebuilding after any unrelated change β€” e.g. adding a memo β€” never re-pushes old posts. New posts scaffold with updated: false and sticky: false; to re-push a revised post, flip updated to a real date (updated: 2026-09-28 21:00:00) more than update_notify_hours after date: β€” false/null/empty all mean "never revised" and are ignored. The stub id is a content hash, so every further edit notifies exactly once more, and ids stop moving the moment you stop editing. Series chapters are ordinary posts: they scaffold the same keys and re-push the same way. Memo dates accept YYYY-MM-DD HH:mm (also ISO T or date-only); a memo with an unparseable date is skipped with a build warning. Series index.md is not a post and never appears in the feed. A series card on the post stream is dated by its latest chapter, so a new chapter also moves the card.

OG share images

npm install @resvg/resvg-js   # once, in your site
# _config.panda.yml
og_image:
  enable: true
  width: 1200
  height: 630

The LXGW WenKai font (SIL OFL 1.1) is downloaded once into fonts/ on first build. Each post gets og-images/<slug>.png wired into og:image.

Gradient look

On by default. Setting Butterfly's background, default_top_img or footer_img makes the matching gradient area yield to your image automatically. To recolor:

# _config.panda.yml
gradient:
  header_light: linear-gradient(135deg, ...)
  header_dark: linear-gradient(135deg, ...)
  web_bg_light: linear-gradient(180deg, ...)
  web_bg_dark: linear-gradient(180deg, ...)
  # plus header_mask_* / nav_* β€” see the default config comments

Disable entirely: gradient.enable: false.

Website background & nav band

Butterfly's background paints the page background (#web_bg). Panda adds one thing on top: when that value is an image, the fixed nav bar paints the top band of the same image, locked to the viewport. It no longer scrolls away, and it no longer falls back to a flat gradient bar.

# _config.panda.yml
background: /img/bg.webp        # an array works too β€” a random pick on each load

Why it lines up: #web_bg is a viewport-sized box with background-size: cover + background-position: center. Painting that same cover onto the 60px #nav box zooms into the photo's middle. The scrolled bar instead uses a viewport-sized #nav::before (100vwΓ—100vh, cover, center, clipped to 60px) so it shows the top strip of the rendered page background β€” same pixels, no seam, no scroll.

Legibility: the band carries a scrim (gradient.nav_band_mask_light/dark) under white / #eaf4ff nav text. Measured against a mid-tone photo: 5.0:1 in light mode, 5.2:1 in dark mode. Unusually light or dark image? Retune:

# _config.panda.yml
nav:
  background_band: true      # false keeps the gradient nav bar
  band_text_light: '#ffffff'
  band_text_dark: '#eaf4ff'
gradient:
  nav_band_mask_light: 'linear-gradient(180deg, rgba(12, 24, 44, 0.6) 0%, rgba(12, 24, 44, 0.42) 100%)'
  nav_band_mask_dark: 'linear-gradient(180deg, rgba(0, 0, 0, 0.58) 0%, rgba(0, 0, 0, 0.42) 100%)'

Edge cases:

  • A color or a raw CSS gradient in background has no "top band", so the nav keeps the normal gradient bar.
  • A random-array background keeps the nav on the image #web_bg actually picked (synced at runtime, PJAX included).
  • iOS Safari still mishandles a viewport-locked image in the 60px bar. There the theme drops the image layer and keeps the scrim + blur.

Misc

# _config.panda.yml
link_target_blank: true   # article/memo links open in a new tab (default on)

fish / typst code highlighting works out of the box.

{% hideToggle Title %}…{% endhideToggle %} is Butterfly's tag. Panda adds a bottom collapse bar (i18n: en Collapse / zh-CN ζ”Άθ΅· / zh-TW ζ”Άεˆ / ja ι–‰γ˜γ‚‹ / ko μ ‘κΈ°). Clicking it closes the block and scrolls back to the header under the fixed nav. Asset URLs carry ?v=<theme version> so a patch like 1.1.1 busts cached main.js / index.css.

πŸ”„ Migrating from Butterfly

  1. Replace themes/butterfly with this theme (or swap the npm package) and set theme: panda
  2. Rename _config.butterfly.yml to _config.panda.yml
  3. Butterfly's legacy _data/butterfly.yml override is not supported (Panda warns on startup)
  4. Every other config key is compatible

πŸ“„ License

Apache-2.0. Panda is derived from hexo-theme-butterfly by Jerry (Apache-2.0); see NOTICE for attribution and the list of changes. Modified files carry a notice in their header comments. The OG font LXGW WenKai is SIL OFL 1.1.

Releases: CHANGELOG.md.

About

A card-style Hexo theme with memos timeline, gradient visuals, and built-in Atom feed & OG images. Forked from hexo-theme-butterfly (Apache-2.0).

Topics

Resources

Stars

20 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages