Introduction #
As a programmer, having a personal blog is an important way to showcase your work and share knowledge. This article will detail how to use Claude AI to build a feature-rich Hugo blog, including:
- Personalized configuration based on the Blowfish theme
- Rubik’s cube intro page implemented with Three.js
- Complete bilingual support (Chinese/English)
- Navigation bookmarks page feature
Project Initialization #
1. Creating a Hugo Project #
First, we need to create a Hugo project and install the Blowfish theme:
# Create new site
hugo new site my-blog
# Enter project directory
cd my-blog
# Initialize git
git init
# Add Blowfish theme
git submodule add -b main https://github.com/nunocoracao/blowfish.git themes/blowfish2. Theme Configuration #
Blowfish uses modular configuration with files located in the config/_default/ directory. Main configuration files include:
| File | Purpose |
|---|---|
params.toml |
Theme parameters |
menus.en.toml |
English menu configuration |
menus.zh-cn.toml |
Chinese menu configuration |
languages.en.toml |
English language settings |
languages.zh-cn.toml |
Chinese language settings |
3. Using Claude for Development #
During the initialization phase, Claude helped with:
- Theme configuration optimization: Adjusting colors, layouts, feature toggles
- Multilingual support: Configuring Chinese/English switching
- Custom feature development: Rubik’s cube intro page, navigation bookmarks page
Rubik’s Cube Intro Page Implementation #
This is the most distinctive feature of this project — users must solve the Rubik’s cube to enter the blog homepage.
Technology Choices #
- Three.js: For 3D rendering
- OrbitControls: For drag-to-rotate interaction
- Tailwind CSS: UI styling (built into Blowfish theme)
Core Implementation Concepts #
1. Cube Structure #
The cube consists of 27 small cubes (Cubies), each with 6 faces. Colors are determined by position:
// Cube colors (standard scheme)
const FACE_COLORS = {
R: 0xC41E3A, // Red - Right face
L: 0xFF5800, // Orange - Left face
U: 0xFFFFFF, // White - Up face
D: 0xFFD500, // Yellow - Down face
F: 0x009E60, // Green - Front face
B: 0x0051BA, // Blue - Back face
INNER: 0x1a1a1a // Inner color
};2. Rotation Logic #
Rotation is the core challenge, requiring:
- Identify rotation layer: Determine axis and layer based on move (R, U, F, etc.)
- Collect cubies in layer: Filter cubies that need to rotate
- Execute rotation animation: Use quaternions for smooth rotation
- Update state: Update cubie positions and color states after rotation
function rotateLayer(move, instant = false) {
return new Promise((resolve) => {
const info = getAxisAndLayer(move);
const { axis, layer, dir } = info;
const angle = (Math.PI / 2) * dir;
const layerCubies = getCubiesInLayer(axis, layer);
// Create temporary rotation group
const pivot = new THREE.Group();
scene.add(pivot);
layerCubies.forEach(c => pivot.add(c.mesh));
// Execute rotation animation...
});
}3. Scramble and Solve #
When scrambling, record the sequence. For solving, execute the inverse:
// Scramble: generate random sequence
function generateScramble(length = 20) {
const MOVES = ['R', 'L', 'U', 'D', 'F', 'B', "R'", "L'", "U'", "D'", "F'", "B'"];
// Ensure adjacent moves are not on the same face...
}
// Solve: execute inverse of scramble sequence
const solveSequence = [...scrambleSequence].reverse().map(m => getInverseMove(m));4. Manual Mode State Tracking #
When users make manual moves, auto-solve needs to account for them:
// Record manual moves
manualMoves.push(move);
// Calculate complete sequence for auto-solve
if (manualMoves.length > 0) {
const undoManual = [...manualMoves].reverse().map(m => getInverseMove(m));
const originalSolve = [...scrambleSequence].reverse().map(m => getInverseMove(m));
solveSequence = [...undoManual, ...originalSolve];
}5. Particle Explosion Effect #
When the cube is solved, trigger a particle explosion animation:
function createParticleExplosion() {
const particles = [];
const colors = [FACE_COLORS.R, FACE_COLORS.L, ...];
for (let i = 0; i < PARTICLE_COUNT; i++) {
// Create colorful particles
const p = new THREE.Mesh(geo, mat);
p.userData.vel = new THREE.Vector3(
(Math.random() - 0.5) * 0.35,
(Math.random() - 0.5) * 0.35,
(Math.random() - 0.5) * 0.35
);
particles.push(p);
scene.add(p);
}
// Animation loop
function animateParticles() {
particles.forEach(p => {
p.position.add(p.userData.vel);
p.userData.vel.y -= 0.003; // Gravity effect
p.scale.multiplyScalar(0.97); // Gradually shrink
});
// ...
}
}File Structure #
Files related to the cube feature:
layouts/
├── index.html # Homepage template
└── partials/
└── home/
└── cube.html # Cube component
static/
└── js/
├── three.module.js # Three.js core library
└── OrbitControls.js # Orbit controllerBilingual Support #
Blowfish natively supports multiple languages. We made deep customizations.
1. Menu Configuration #
English menu (config/_default/menus.en.toml):
[[main]]
name = "Blog"
pageRef = "posts"
weight = 10
[[main]]
name = "Nav"
pageRef = "navlinks"
weight = 15
[[main]]
name = "About"
pageRef = "about"
weight = 40Chinese menu (config/_default/menus.zh-cn.toml):
[[main]]
name = "博客"
pageRef = "posts"
weight = 10
[[main]]
name = "导航"
pageRef = "navlinks"
weight = 15
[[main]]
name = "关于"
pageRef = "about"
weight = 402. Internationalization Text #
Create custom translation files to override theme defaults:
i18n/en.yaml:
global:
language: "English"
article:
reading_time:
one: "{{ .Count }} min"
other: "{{ .Count }} mins"
table_of_contents: "Table of Contents"
footer:
powered_by: "Powered by {{ .Hugo }} & {{ .Theme }} & Claude"
navlinks:
no_links: "No navigation links yet. Please add links in data/navlinks.yaml"i18n/zh-CN.yaml:
global:
language: "简体中文"
navlinks:
no_links: "暂无导航链接,请在 data/navlinks.yaml 中添加"3. Cube Interface Internationalization #
Implement dynamic language switching in the cube component:
const i18n = {
'zh-cn': {
title: '解开魔方,进入博客',
autoSolve: '自动还原',
manualMode: '手动模式',
skip: '跳过',
// ...
},
'en': {
title: 'Solve the Cube to Enter',
autoSolve: 'Auto Solve',
manualMode: 'Manual Mode',
skip: 'Skip',
// ...
}
};
function getCurrentLang() {
const htmlLang = document.documentElement.lang || 'en';
if (htmlLang.startsWith('zh')) return 'zh-cn';
return 'en';
}
function updateI18nText() {
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
el.textContent = i18n[getCurrentLang()][key];
});
}Use data-i18n attribute in HTML to mark elements needing translation:
<h1 data-i18n="title">Solve the Cube to Enter</h1>
<button data-i18n="autoSolve">Auto Solve</button>Navigation Bookmarks Page #
Implement a navigation-style page for bookmarking useful links.
1. Data Structure #
Create data/navlinks.yaml to store link data:
categories:
- name: "Development Tools"
icon: "code"
links:
- name: "GitHub"
url: "https://github.com"
icon: "github"
description: "World's largest code hosting platform"
- name: "VS Code"
url: "https://code.visualstudio.com"
icon: "code"
description: "Microsoft's open-source code editor"
- name: "AI Tools"
icon: "cpu"
links:
- name: "Claude"
url: "https://claude.ai"
icon: "bot"
description: "AI assistant by Anthropic"2. Page Template #
Create layouts/navlinks/list.html:
{{ define "main" }}
<div class="container mx-auto px-4 py-8">
{{ range .Site.Data.navlinks.categories }}
<section class="mb-8">
<h2 class="text-2xl font-bold mb-4 text-neutral-800 dark:text-white">
{{ .name }}
</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{{ range .links }}
<a href="{{ .url }}" target="_blank" rel="noopener noreferrer"
class="block p-4 rounded-lg bg-neutral-100 dark:bg-neutral-800
hover:bg-neutral-200 dark:hover:bg-neutral-700
border border-neutral-200 dark:border-neutral-700
transition-all duration-200 hover:shadow-md">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-primary-100 dark:bg-primary-900
flex items-center justify-center">
<!-- Icon -->
</div>
<div>
<h3 class="font-semibold text-neutral-800 dark:text-white">
{{ .name }}
</h3>
<p class="text-sm text-neutral-500 dark:text-neutral-400">
{{ .description }}
</p>
</div>
</div>
</a>
{{ end }}
</div>
</section>
{{ end }}
</div>
{{ end }}3. Dark Mode Support #
Use Tailwind CSS dark: prefix for dark mode:
<!-- Background color -->
class="bg-neutral-100 dark:bg-neutral-800"
<!-- Text color -->
class="text-neutral-800 dark:text-white"
<!-- Border color -->
class="border-neutral-200 dark:border-neutral-700"
<!-- Hover state -->
class="hover:bg-neutral-200 dark:hover:bg-neutral-700"Navigation Lock Feature #
Disable page navigation when the cube is unsolved:
// Lock navigation
function lockPageNavigation() {
document.body.classList.add('cube-locked');
}
// Unlock navigation
function unlockPageNavigation() {
document.body.classList.remove('cube-locked');
}CSS styles:
body.cube-locked header,
body.cube-locked footer,
body.cube-locked nav,
body.cube-locked [class*="menu"],
body.cube-locked [class*="nav"] {
pointer-events: none !important;
}Project Structure Overview #
my-blog/
├── config/
│ └── _default/
│ ├── params.toml # Theme parameters
│ ├── menus.en.toml # English menu
│ ├── menus.zh-cn.toml # Chinese menu
│ ├── languages.en.toml # English config
│ └── languages.zh-cn.toml # Chinese config
├── content/
│ ├── posts/ # Blog posts
│ ├── navlinks/ # Navigation page
│ └── about/ # About page
├── data/
│ └── navlinks.yaml # Navigation links data
├── i18n/
│ ├── en.yaml # English translations
│ └── zh-CN.yaml # Chinese translations
├── layouts/
│ ├── index.html # Homepage template
│ ├── navlinks/
│ │ └── list.html # Navigation page template
│ └── partials/
│ └── home/
│ └── cube.html # Cube component
├── static/
│ └── js/
│ ├── three.module.js
│ └── OrbitControls.js
└── themes/
└── blowfish/ # Theme directorySummary #
With Claude AI’s assistance, we successfully built a feature-rich Hugo blog:
- Rubik’s Cube Intro Page: 3D cube effect using Three.js with auto-solve and manual mode
- Bilingual Support: Complete Chinese/English switching including menus, UI text, and cube hints
- Navigation Bookmarks Page: Card-based layout with categories and dark mode support
- Navigation Lock: Disable page navigation when cube is unsolved
Key Takeaways #
- Leverage AI Assistance: Claude can help quickly implement complex features, but requires clear requirement descriptions
- Modular Development: Split features into independent components for easier maintenance and reuse
- State Management: Complex interactions require careful state tracking (e.g., cube manual move recording)
- Style Consistency: Use Tailwind CSS
dark:prefix to ensure dark mode compatibility
Resources #
I hope this article helps you! If you have any questions, feel free to leave a comment.