cd /news/developer-tools/redesigning-my-personal-website-with… · home topics developer-tools article
[ARTICLE · art-2211] src=andlukyane.com ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

Redesigning My Personal Website with Claude Code

The author redesigned their personal website in two waves, first adding infrastructure improvements like full-text search, dark mode support, and structured data, then overhauling content and layout using Claude Code. The site, originally built with Jekyll and GitHub Pages, had become outdated with missing features and poorly organized pages. Key fixes included client-side search via a JSON index, expanded dark mode coverage, and fixing a bug where comments failed to load during AJAX page transitions.

read11 min views19 publishedMar 23, 2026

Redesigning My Personal Website with Claude Code #

Three years ago, I wrote about creating this website using Jekyll, GitHub Pages, and a custom domain. That post is still one of the most-read things on my blog. The site served me well since then — it helped during job interviews, got me a few consulting projects, and became a home for 190+ ML paper reviews.

But I felt that by early 2026, the site was in need of a refresh:

  • Some content was outdated (About and Career pages weren’t updated for years)
  • Some pages weren’t structured well (Actitivies page was a flat chronological list, Projects page had exactly two entries, Tags page was a wall of unsorted tags)
  • And the site itself lacked many basic features, like search or dark mode support

I did the redesign in two waves. I started with infrastructure improvements (search, dark mode, structured data, footer, sharing). Then I did a full content and layout overhaul using Claude Code.

Wave 1: infrastructure improvements

Before touching any content, I wanted to fix the technical foundation. It took several sessions to make them.

Full-text search

The site had no search functionality at all. With 190+ posts, finding something specific meant scrolling through the blog listing or using the browser’s Ctrl+F on the tags page. I added a client-side search powered by a JSON index file that Jekyll generates at build time. The search modal opens with Ctrl + K

(or Cmd + K

on Mac) and supports multi-term filtering across titles, descriptions, tags, and content.

The search index is generated by a search.json

file with Liquid:

[{% for post in site.posts %}
  {
    "title": {{ post.title | jsonify }},
    "url": "{{ post.url }}",
    "date": "{{ post.date | date: '%b %d, %Y' }}",
    "description": {{ post.description | jsonify }},
    "tags": {{ post.tags | jsonify }},
    "content": {{ post.content | strip_html | truncatewords: 50 | jsonify }}
  }{% unless forloop.last %},{% endunless %}
{% endfor %}]

The JavaScript fetches this JSON once on the first search, then filters locally. It is not as powerful as Algolia or Lunr, but it works well enough for a static site with a few hundred posts and requires no external service.

Dark mode

The site already had a basic dark mode toggle from an earlier refactor, but it was incomplete — many elements (code blocks, tables, form inputs, the career timeline) still used hardcoded light colors. I expanded the dark-mode.css

file from about 100 lines to over 250, covering every component on the site. The dark mode toggle also needed to sync with the Utterances comment theme, so switching to dark mode now sends a postMessage

to the Utterances iframe:

const updateUtterancesTheme = (theme) => {
  const utterancesFrame = document.querySelector('.utterances-frame');
  if (utterancesFrame) {
    utterancesFrame.contentWindow.postMessage(
      { type: 'set-theme', theme: theme },
      'https://utteranc.es'
    );
  }
};

Speaking of Utterances — the comments had an embarrassing bug. The comment section did not appear at all until you manually refreshed the page. The cause was that the site uses AJAX navigation (via History.js in personal.js

), so navigating to a post did not trigger a full page load. The Utterances <script>

tag was embedded inline in the post layout, and inline scripts only execute on initial load — AJAX page transitions skip them entirely. The fix was to replace the inline script with a placeholder <div id="utterances-container">

and move the Utterances initialization into personal.js

, where it runs on every page transition.

This also handles the initial dark mode state — the theme is read from localStorage

at injection time, so comments load with the correct theme from the start. The postMessage

approach described above is still needed when the user toggles dark mode while comments are already visible.

Structured data and OpenGraph

I added JSON-LD structured data for both the site (WebSite schema) and individual blog posts (BlogPosting schema). The blog post schema includes headline, description, author, publication date, and keywords from tags:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "Redesigning My Personal Website with Claude Code",
  "description": "A practical walkthrough of redesigning a Jekyll personal website — adding search, dark mode, structured data, and a full card-based redesign using Claude Code. What changed, what broke, and what it is like to iterate on a site with an AI coding assistant.",
  "datePublished": "2026-03-23T00:00:00+00:00",
  "author": {
    "@type": "Person",
    "name": "Andrey Lukyanenko",
    "url": "https://andlukyane.com/"
  },
  "keywords": ["blogpost","career"]
}
</script>

I also added missing OpenGraph tags (og:url

, og:type

) and a Twitter Card twitter:site

tag. These are small changes, but they improve how links appear when shared on LinkedIn, Twitter, and other platforms. The og:type

dynamically switches between article

for blog posts and website

for other pages.

Floating share bar and footer redesign

Blog posts got a floating share bar on desktop — four social buttons (Twitter, LinkedIn, Facebook, Reddit) that appear when you start reading and disappear when you scroll past the article. The visibility is controlled by checking the article’s bounding rectangle on the scroll:

function check() {
  var rect = article.getBoundingClientRect();
  var show = rect.top < window.innerHeight * 0.3
          && rect.bottom > window.innerHeight * 0.5;
  bar.classList.toggle('visible', show);
}
window.addEventListener('scroll', check, { passive: true });

The footer was redesigned from a single-column layout to a three-column grid (about, quick links, social icons) using CSS Grid. I also added proper focus-visible styles for accessibility across all interactive elements.

Wave 2: content and layout overhaul with Claude Code

With the infrastructure in place, the site still had the same content problems: outdated positioning, buried content, and flat hierarchies. For this part, I used Claude Code to do the whole thing in a single session (not one-shot).

Gathering ideas

First, I prepared a list of what I wanted to change myself, then asked three AI assistants (ChatGPT, Claude, Gemini) to analyze the live site and suggest improvements. All three converged on the same core problems: the strongest professional signals were buried or absent, good content was difficult to find, and some content was years out of date. I saved their suggestions into Markdown files, compared the overlapping recommendations, and wrote a combined improvement plan.

About page

The old page was a chronological autobiography. The new page is structured as a hub: a short intro, a row of credential badges, a horizontally scrolling “Latest Paper Reviews” section, curated “Featured Work” and “Beyond Work” card grids, and a collapsible section for fun facts at the bottom.

The “Latest Paper Reviews” section autopopulates using a Liquid include:

{% assign reviews = site.posts | where_exp: "post",
    "post.tags contains 'paperreview'" %}
{% for post in reviews limit:3 %}
<a href="{{ post.url }}" class="card">
  <div class="card__meta">{{ post.date | date: "%b %d, %Y" }}</div>
  <div class="card__title">{{ post.title }}</div>
  <div class="card__description">
    {{ post.description | truncate: 140 }}
  </div>
</a>
{% endfor %}

The horizontal scroll is done with pure CSS using flexbox and overflow-x: auto

:

.card-scroll {
  display: flex;
  gap: 20px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
}

.card-scroll .card {
  min-width: 280px;
  max-width: 340px;
  flex-shrink: 0;
  scroll-snap-align: start;
}

The main technical challenge was that Jekyll refused to process Liquid tags in the root index.html

. After debugging, the cause turned out to be a quirk of how Jekyll handles root-level files — possibly a conflict with the paginator plugin. The fix was moving the homepage to _pages/home.html

with permalink: /

. Not obvious, and the kind of thing that wastes hours.

Card component system

Most visual changes are built on a reusable CSS card system (css/cards.css

, 237 lines). The core card is a simple flexbox column with a hover effect:

.card {
  background: var(--background-alt-color, #f4f5f6);
  border-radius: 8px;
  padding: 24px;
  transition: box-shadow 0.3s ease, transform 0.2s ease;
  display: flex;
  flex-direction: column;
}

.card:hover {
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
  transform: translateY(-2px);
}

Cards are placed in responsive grids that go from one column on mobile to two or three on desktop:

.card-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
}

@media (min-width: 768px) {
  .card-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 1024px) {
  .card-grid--3 { grid-template-columns: repeat(3, 1fr); }
}

Tag chips are small pill-shaped links that appear on cards and in the blog listing:

.tag-chip {
  display: inline-block;
  font-size: 12px;
  padding: 4px 10px;
  border-radius: 12px;
  background: var(--background-color, #ffffff);
  color: var(--text-light-color, #6B7B8D);
  border: 1px solid var(--border-color, #dddddd);
  transition: background 0.2s ease, color 0.2s ease;
}

a.tag-chip:hover {
  background: var(--accent-color, #3498db);
  color: #ffffff;
  border-color: var(--accent-color, #3498db);
}

Everything uses CSS custom properties, so dark mode works automatically.

Blog listing tag chips

Every post in the blog listing now shows up to four clickable tag chips. The first attempt had an interesting bug: the entire blog post card was clickable via a jQuery handler in personal.js

, so clicking a tag chip navigated to the post instead of the tag page. The fix was a guard in the click handler:

$(document).on('click', '.post', function (e) {
  if ($(e.target).closest('.tag-chip').length) {
    return;  // let the tag link handle its own navigation
  }
  // ... existing post navigation code
});

Other pages

I restructured the Activities page from flat collapsible lists into cards for publications and talks, data tables for Kaggle notebooks and competitions, and compact inline archives for the full talk history. The Projects page went from two entries to eight, organized into Featured Projects (with images), Production ML, and Tools and Writing. The Tags page now groups 120+ tags into 12 thematic categories displayed as a two-column card grid, and individual tag pages show posts as cards instead of plain lists.

Working with Claude Code

Most of the changes were done in a single Claude Code session over a couple of days. The most useful pattern was version comparison: for each major page redesign, I asked it to create three versions at temporary URLs (/about-v1

, /about-v2

, /about-v3

), previewed them all in the browser, and described which elements to combine. It is faster to compare three concrete implementations than to describe an abstract preference in words. One version could have a better visual appeal, another one - better structure, another one - a new useful feature, and so on.

Not everything worked well on the first attempt; I had to spend considerable time on debugging and testing. For the Liquid processing issue, Claude Code tried several approaches — adding layout: page

to the front matter, moving Liquid into an include file, and finally identifying that the _pages

collection was the correct fix. It diagnosed problems by inspecting the built _site/index.html

output and checking whether Liquid tags had been resolved. For the tag chip click issue, it found the jQuery handler, understood the event propagation, and added the guard.

All content decisions were mine — which posts to feature, what texts to write, what to include or exclude. Some CSS required several iterations because I could see the rendered result, but Claude Code could only verify by grepping the HTML output. When I sent screenshots of broken layouts, it could usually identify the issue, though not always on the first try. It also occasionally generated text that was too promotional, which I had to tone down.

Summary of changes

Metric Before After
Search None Full-text with Ctrl+K
Dark mode coverage Partial Complete (250+ CSS rules)
Structured data None WebSite + BlogPosting JSON-LD
OpenGraph tags Partial Complete (og:url, og:type, twitter:site)
Share functionality Bottom of post only Floating sidebar + bottom
About page Chronological bio 6 sections with cards and badges
Projects shown 2 8
Activities structure Flat lists Cards, tables, compact archives
Tag organization Flat list 12 thematic card groups
Blog tag chips None Up to 4 per post

Conclusions

The site now reflects where I am in 2026 rather than where I was in 2021 or 2023. The design is still built on a purchased Jekyll theme with incremental modifications, but it is much better at helping people discover useful content. For me, this is good enough.

Additionally, this was an interesting exercise in using AI to improve a personal website. I remember that it took me almost a month to set up the initial version, then days or weeks to make specific changes because I didn’t know web development well enough. This redesign took less than a week in total, and I learnt a lot about using AI and web development.

── more in #developer-tools 4 stories · sorted by recency
── more on @claude code 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/redesigning-my-perso…] indexed:0 read:11min 2026-03-23 ·