# hexo-theme-fomalhaut splits site configuration away from theme code, and that is its main idea

> A Hexo blog theme built on a deep second-pass modification of Butterfly 4.3.1, aimed at cloning a card style site and then editing configuration rather than theme source. The changelog reads like a design diary, with regression scripts, accessibility contrast numbers and effect timing budgets documented release by release.

**fomalhaut1998/hexo-theme-fomalhaut** — 基于 Butterfly 二次开发的 Hexo 博客主题 / 站点模板，具备丰富的视觉设计元素、交互式组件、以及充足的自定义样式控件

- Repository: https://github.com/fomalhaut1998/hexo-theme-fomalhaut
- Website: https://www.fomal.cc
- Stars: 1,015 · Forks: 153
- Language: JavaScript
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/fomalhaut1998-hexo-theme-fomalhaut

## Configuration lives apart from theme code, on purpose

The theme describes itself as a clone and use Hexo card style blog source, and the architectural decision it leads with is the separation of site configuration from theme code. Changing configuration builds your site, and upgrading the theme does not wipe your own changes.

That separation is visible in the repository root. There is a `_config.yml` and a separate `_config.fomalhaut.yml`, alongside the `themes/` directory that holds the theme itself. The README describes the arrangement as thorough separation between site configuration and theme code, so that editing configuration is enough to set up a site and a theme upgrade does not overwrite personal modifications.

The upgrade mechanism is documented too. The archive feature in the 1.0.3 release added two lines to the `inject.head` section of `_config.fomalhaut.yml`, and the rollback is to delete those two lines. That is a very small and very concrete rollback, which is what you want from a design where the theme injects into a site it does not own.

The version scheme is deliberately split in two. The theme's own version lives in `package.json` and is mirrored in the startup banner at `themes/fomalhaut/scripts/events/welcome.js` and in the footer badge title, all moved to 1.0.4 in the latest release. The upstream Butterfly version is tracked separately by `scripts/butterfly-version.js` at 4.3.1, and the two are kept apart on purpose. A reader who wants to know which Butterfly changes are underneath has to look in a second place, which is a small cost for keeping a fork and its upstream distinguishable.

## The effects panel is a settings module with real resource teardown

The most distinctive feature is the appearance or effects panel, and the 1.0.4 release rebuilt it from a long slider strip into a previewable set of sections, adding five front end modules and eight regression scripts.

The panel is split into three blocks, for atmosphere effects, interactive widgets and layout, arranged as a two column grid that collapses to one column when the panel narrows. Opacity and background filter controls stay at the top. The cat widget and the obedient mouse each have their own switch, both on by default, while the click ripple only counts as enabled if it was explicitly turned on.

The detail that matters is the teardown. The changelog states that turning something off really stops it. Disabling the cat clears the clock timers and the animation frame loop and removes the pointer and rectangle calculations, and disabling the mouse runs a pause and disconnects the observer, with a refresh and observe when it is switched back on. A boot script restores the hidden state on the first frame so nothing flashes during load.

That level of care is unusual in a blog theme, and it is the difference between a settings switch and a switch that leaves a timer running. The regression scripts cover exactly this, checking whether the switch really stops the clock, along with colour validation and old key migration, preload token cancellation, effect tiers and pause conditions, navigation touch targets and the degradation conditions for the blur effect.

The scripts live in `tools/tests/`, and the number of them grew from five to thirteen. The design records for the round are under `docs/plans/`, nine of them added alongside.

## Accessibility and colour are handled with measured numbers

Two details in the changelog show the author measuring rather than eyeballing.

The first is the custom theme colour. The panel adds a native colour picker, and the value is stored as a strictly validated six digit hex in `themeCustomColor`, with an invalid value falling back to the default teal on the first frame. A new helper computes the on-theme ink colour from WCAG relative luminance, and buttons, selected items, the current table of contents entry, the selection highlight and the AI assistant panel all read it, so light theme colours no longer produce washed out white text.

The second is the frame rate status bar. The small text contrast was measured at 3.21 and raised to 4.91, and after that the palette was adjusted again by eye, with the amber and red colours pulled apart in Lab delta E from 43.2 to 63.5 so they are distinguishable rather than merely different.

The preset palette was also reorganised. Three old options were removed, matcha green was added, and the result is eleven colours. The old keys are migrated automatically, with purple moving to purepurple and gray and black both moving to darkblue, and the swatches wrap into a six plus five layout at 375 pixels wide.

None of this is required for a blog to work, and all of it is the kind of detail that a theme maintained by one person over many small releases accumulates. The measured contrast ratios are the more useful signal, because they tell you the author checks rather than guesses.

## Search indexing and the archive page are site level, not theme level

The 1.0.3 release rebuilt the archive page from a date and title list into a card wall, and the way it does that is instructive.

The implementation adds three files, `scripts/archive-index.js`, `source/css/archive-page.css` and `source/js/inject/archive-page.js`, and the changelog insists the implementation is site level and does not touch theme source. The layout runs from a yearly distribution bar chart at the top, with each bar labelled with the year and the article count, drawn fully when the year span is seven or fewer and otherwise only for years that have articles, through a summary line, a sticky year jump control that highlights the current year while scrolling, into year sections with a large year number, a count, a collapse and expand button whose state is stored in `localStorage` under `ar-collapsed-years`, and finally the card grid.

The cards carry a cover image, a category pill, the title, the date and an approximate reading time, and the whole card is clickable while the title and cover remain independent links that can be opened in a new window and crawled. A missing or failed cover falls back to the first character of the title with a theme coloured glow, and the lazy loading placeholder reads `data-lazy-src` directly rather than waiting for the scan timing of the lazy load library.

The zero extra request claim is the part worth checking. A build time filter registered as `after_render:html` inserts a single `<script type="application/json" id="ar-index">` block before the closing `</head>` of the archive page, including the paginated ones, containing the title, path, date, category, word count and reading minutes for every article on the site. Reading time reuses the counting convention of the `hexo-wordcount-fomal` package, with 300 characters per minute for Chinese and 160 words per minute for English.

The degradation story is as careful as the feature. If the page has no index, the cards still render and only the category and reading time are missing. The container must be one of `#archive`, `#category` or `#tag`, there must be exactly one `.article-sort`, and there must be at least one article, and only if all of that passes does the container get the `.ar-ready` class, with every CSS selector starting from it. If any step fails, the whole block turns off and the page stays as it was. The container is also marked with `data-ar-archive` to keep the operation idempotent, and it can be re-entered safely on first paint, on `pjax:complete` and on `pageshow(persisted)`.

## Effects that respect reduced motion, and the pjax bug class

The animation work in the 1.0.4 release follows a consistent rule, which is that a switch off means the work stops, not merely that the effect is hidden.

The fireflies effect is pure CSS breathing and slow drifting, with no canvas, no animation frame loop and no timer polling. There are fourteen on desktop and eight at 600 pixels or below, they sit in the lower 44 percent of the viewport height, they use a negative layer so they fall behind the content cards, and they are set to ignore pointer events. Pausing is done through a `data-paused` attribute.

The snow effect was rewritten to drop a canvas dot loop in favour of two pre-rendered snowflake images layered by size, opacity and speed, with the frame delta capped at 50 milliseconds and an epoch guard against stale callbacks. The height map is bounded and only raised when a flake genuinely reaches the bottom. Both effects pause in dark mode, in the background, on mobile and when the user has asked for reduced motion, and they are mutually exclusive, so only the last enabled one stays on.

The night mode switch was rewritten into three beats, sunset or sunrise at 450 milliseconds, twilight and the first stars at 450 milliseconds, a full starry sky at 600 milliseconds, and a circular reveal from the click point at 350 milliseconds, which cut the full screen occlusion from 3000 milliseconds to about 900. The overlay is now owned by the switch itself instead of being picked up by finding the first matching element, so a second click clears the overlay before switching instantly, and a system with reduced motion enabled switches instantly. The night wallpaper is warmed up with `requestIdleCallback` so the switch does not land on a blank screen.

The changelog also documents a class of bug that pjax brings with it. The table of contents click handler was upgraded to version 4 with a generation counter, so a stale scroll correction chain exits immediately, because the previous version started one chain of up to three rounds per click and the chains did not know about each other. The calendar heat map had a similar problem, where a file level counter was reset by the externally loaded script after the inline script had taken a number, and the fetched data was then judged stale.

## Stack, licence and what the repository actually contains

The theme runs on Hexo 6.3.0 with Node 18, 20 or 22, and the package manifest declares Node 18 or newer as the engine requirement. The dependency list is long and tells you what the feature set is: a markdown-it plus renderer, an abbrlink plugin for short links, Algolia search, a Baidu URL submit plugin, a blog encrypt plugin for password protected posts, several Butterfly originated plugins for a clock widget, an envelope effect, a swiper, tag plugin plus and a wow animation, a git calendar filter, a nofollow filter, and a set of generators for archives, categories, feeds, the index, search, sitemaps and tags. There are also renderers for EJS, markdown-it, Pug and Stylus, an aplayer tag, a map tag, a PDF post generator, a magnet link plugin and the `hexo-wordcount-fomal` word count package.

The npm scripts are the standard Hexo set, with `hexo generate` for build, `hexo clean`, `hexo deploy`, `hexo server`, and a `deploy:vercel` script that runs `tools/deploy-vercel.js`. The repository root also has a `vercel.json`, a `gulpfile.js`, `functions/` and `api/` directories for a serverless deployment shape, `scaffolds/`, `scripts/`, `source/`, `tools/`, `docs/` and `repoPic/`.

The licence is Apache-2.0, declared in the package manifest and shipped in a LICENSE file at the root, which is a permissive grant with an explicit patent clause and no requirement to open source a site built with the theme.

The release cadence is dense. v1.0.4 came on 2026-10-11, v1.0.3 on 2026-10-08 and v1.0.2 on 2026-10-06, and the last push to the repository was on 2026-10-18. A theme that ships three releases in six days is a project in active churn, which is a virtue for a template you are evaluating and a liability for one you have deployed and customised.

## Conclusion

Adopt hexo-theme-fomalhaut if you want a card style Hexo site with a large set of visual effects and a configuration file you can edit without touching theme source. Do not adopt it as a stable base for a long-lived site without reading the changelog, because the releases are dense and the theme is still version 1.0.4, and because the theme's own version and the upstream Butterfly version are tracked separately and diverge. Verify first that the `_config.fomalhaut.yml` and `_config.yml` split works the way you expect for your deploy target, that the search index and archive features are wired to your own data, and that the effect defaults, which are mostly off, are what you want to enable.

## FAQ

### What is hexo-theme-fomalhaut based on?

It is a Hexo blog theme and site template built on a deep second-pass modification of the Butterfly theme at version 4.3.1. The theme version is tracked separately from the upstream Butterfly version, which is read through scripts/butterfly-version.js.

### How does the theme separate site configuration from theme code?

Site settings live in _config.yml and _config.fomalhaut.yml at the repository root, while the theme itself lives in the themes directory. Features are added to a site through configuration entries, and a rollback is a matter of removing the lines that were added.

### What do the appearance panel switches do when they are turned off?

They actually stop the work. Disabling the cat widget clears its timers and animation frame loop, disabling the mouse runs a pause and disconnects its observer, and the boot script restores the hidden state on the first frame so nothing flashes during load.

### Which Node and Hexo versions does the theme need?

Node 18 or newer, with the badges listing 18, 20 and 22, and Hexo 6.3.0 declared in the package manifest with hexo itself at 6.2.0 or newer in the dependencies.

### What licence is hexo-theme-fomalhaut released under?

Apache-2.0, declared in the package manifest and shipped in the LICENSE file at the repository root.

## Sources

- [fomalhaut1998/hexo-theme-fomalhaut on GitHub](https://github.com/fomalhaut1998/hexo-theme-fomalhaut)
- [License: Apache-2.0](https://github.com/fomalhaut1998/hexo-theme-fomalhaut/blob/main/LICENSE)
- [Project website](https://www.fomal.cc)
- [README](https://github.com/fomalhaut1998/hexo-theme-fomalhaut/blob/main/README.md)
- [Releases](https://github.com/fomalhaut1998/hexo-theme-fomalhaut/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/fomalhaut1998-hexo-theme-fomalhaut
