Skip to main content
  1. Posts/

Building a Personalized Hugo Blog with Claude

Author
Your Name
Love coding, sharing technical insights
Table of Contents

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
Note: This article assumes you have a basic understanding of Hugo. If you’re new to Hugo, I recommend reading the Hugo Documentation first.

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/blowfish

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

  1. Theme configuration optimization: Adjusting colors, layouts, feature toggles
  2. Multilingual support: Configuring Chinese/English switching
  3. 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:

  1. Identify rotation layer: Determine axis and layer based on move (R, U, F, etc.)
  2. Collect cubies in layer: Filter cubies that need to rotate
  3. Execute rotation animation: Use quaternions for smooth rotation
  4. 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 controller

Bilingual 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 = 40

Chinese menu (config/_default/menus.zh-cn.toml):

[[main]]
  name = "博客"
  pageRef = "posts"
  weight = 10

[[main]]
  name = "导航"
  pageRef = "navlinks"
  weight = 15

[[main]]
  name = "关于"
  pageRef = "about"
  weight = 40

2. 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 }} &amp; {{ .Theme }} &amp; 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 directory

Summary
#

With Claude AI’s assistance, we successfully built a feature-rich Hugo blog:

  1. Rubik’s Cube Intro Page: 3D cube effect using Three.js with auto-solve and manual mode
  2. Bilingual Support: Complete Chinese/English switching including menus, UI text, and cube hints
  3. Navigation Bookmarks Page: Card-based layout with categories and dark mode support
  4. 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.

Related articles