byminseok.com

From Bear Blog to Jekyll. Migrating 100 Posts and Redesigning Everything

Translated from Korean

Today, I migrated all posts from Bear Blog to my Jekyll blog (theorakim.github.io) and significantly restructured the site. Work log with Claude Code.

1. Migration of Technology Philosophy Posts (16 articles)

I started by extracting technology philosophy-related posts from byminseokcom/post_export.csv. The CSV is the entire 889KB Bear Blog export file. Since the all tags column contained tags as a JSON array, I filtered posts with the tags technology philosophy, STS, Art-n-Tech, and human-ai.

Seventeen matched, but one had publish: False, so I converted sixteen to Jekyll posts. I automated front matter generation and body text extraction using a Python script.

Angle Bracket Issue

The Algorithm Rebellion Club #1 post was broken. Text like <Meeting Leader Self-Introduction>, <Presentation>, <Discussion> was interpreted as HTML tags and disappeared entirely. Solution:

  • Section separators <Presentation>, <Discussion>**Presentation**, **Discussion** (bold formatting)
  • Work title <Preparing to Meet the Future>⟨Preparing to Meet the Future⟩ (Unicode angle brackets)

Subsequently, implemented a script to automatically convert all posts using the same pattern.

2. Uploaded All Remaining Posts (81 pieces)

Uploaded the remaining 81 pieces at once, excluding those with publish:false and technical philosophy posts. Additional work here:

  • Tag normalization: Unified essay에세이
  • 11 untagged posts: Manually assigned appropriate tags based on content (e.g., essay, reading, observation)
  • Excluded year tags: Hidden year tags like year-2024 in the UI

Tag Filter UI

Added a client-side tag filter to _layouts/archive.html. Structure: Liquid collects tag lists at build time, JS filters posts on click. Year group headings automatically hide empty years.

3. Page Separation

Separated writings, techphil, and devlog pages to avoid overlapping posts:

  • writings (exclude_tags): Excludes posts tagged with tech philosophy or development logs
  • techphil (filter_tags): Only includes tech philosophy, STS, Art-n-Tech, human-ai
  • devlog (filter_tags): Development logs only

Implemented two filter modes in the archive.html layout: filter_tags (include) and exclude_tags (exclude). This allows each page to function simply by declaring its settings in the front matter.

Resolving devlog confusion

There was an issue where tech philosophy posts were appearing mixed in with devlog entries. Two causes:

  1. Claude.AI was included in devlog's filter_tags, matching tech philosophy posts → Removed
  2. Three existing devlog posts had no 개발기록 tag at all → Added

4. From Modal to Page Navigation

Originally, clicking clicking (1) Write, (2) Tech Philosophy, or (3) Code Log would open a modal overlay displaying the post list. Changed this to directly navigate to each page (/writings/, /techphil/, /devlog/).

  • home.html: Replaced data-modal link → Actual URL link
  • Utilized the modal <section>- Deleted modal.js, removed script tags from default.html`
  • Cleaned up related CSS in _layout.scss: .modal, .modal-close, .list-item, etc.

5. Design Adjustments

  • Left alignment: Changed margin: 0 automargin: 0. Left-aligned instead of centered.
  • Footer: contact : summerinloving@gmail.com + © Kim Minseok · Font: Goun Dotum. (includes link)
  • Intro text: Bold applied then removed — ultimately kept original style

6. Local Build Environment

Spent considerable time troubleshooting Jekyll local build failures.

  • System Ruby 2.6: Too outdated, bundler version mismatch
  • Homebrew Ruby 4.0.1: github-pages gem's Liquid 4.0.3 calls tainted? method, removed in Ruby 3.2+
  • Tried Jekyll 4.x: google-protobuf native extension build failed
  • Final Solution: brew install ruby@3.3github-pages gem worked fine with Ruby 3.3.10 + Bundler 2.7.1

7. Book Spread Layout

Today's highlight. Implemented a "book spread" two-column layout on the archive page.

Structure

  • Left Panel (420px): Page title + Tag filter + Post list. Fixed with position: sticky during scroll, internally overflow scroll.
  • Divider: border-right: 1px solid
  • Right Panel (flex: 1, max-width 640px): Body of the selected post

How It Works

On desktop (960px+), clicking a post list link fetches the post's HTML via fetch(). It parses the HTML with DOMParser, extracts only the .post article element, and inserts it into the right panel. A cache object prevents re-fetching the same article, and history.replaceState updates the URL.

On mobile (under 960px), the default link behavior takes effect without JS intervention, simply navigating the page.

CSS Encoding Struggles

Adding Korean comments (// 책 날개) and Unicode characters (content: &quot;●&quot;) to SCSS files caused Jekyll's sass-converter 1.5.2 to throw an Invalid US-ASCII character error. Fixed by using English comments and CSS escapes (\25CF) for special characters.

Modified Files

  • _layouts/archive.html — spread wrapper + reader JS
  • _sass/_post.scss — spread layout CSS
  • _sass/_layout.scssbody:has(.spread) max-width extension

8. Portfolio Page

Filled the previously empty /portfolio/ page. Organized into categories: self-made projects, work achievements, community activities, and writing.

Categories and Items

  • Projects: Creative Morning Calendar (Discourse integration), Pangyo Weather Bot(Korea Meteorological Administration API + KakaoTalk)
  • Work: Retrospective Archive (tfi-retro-library), Tech for Impact GitBook
  • Community: Netfliq Yeonga Meetup
  • Writing: Publy Articles

Each item consists of a title + 2-3 line description + technical tags. The entire block is a single <a> block, so clicking anywhere on the card navigates to it. I pre-embedded the data-id attribute to allow adding a spread layout later.

I also tried a 3-column grid card layout, but it clashed with the blog's minimal tone, so I finalized the vertical list.

Modified Files

  • portfolio.html — Category-specific HTML structure
  • _sass/_portfolio.scss — New file, BEM structure styles
  • assets/css/main.scss — Added @import &quot;portfolio&quot;

9. Refining the Homepage

Emoji Replacement

Replaced numbers in the intro text (1) Write (2) Reflect on technology (3) Record code with emojis: ✍️ Write 💬 Reflect on technology 💻 Record code. Applied to both home.html and nav.html.

Header Letter Spacing

Added letter-spacing: 0.05em to the "Kim Minseok" header displayed on all pages. Tried adding bold but removed it — bold was too thick with the Goun Dotum font.

Expanding the Home Page Width

In default.html, <body id=""> functionality to add body_id: home to the home.html layout. In CSS, body#home { max-width: 740px } was applied to widen only the home section. Expanded from the default 640px by 100px. This was to naturally adjust the line break position of the intro text, fine-tuned by reducing the width from 800px → 760px → 750px → 740px.

However, placing body_id in the layout front matter caused issues where page.body_id wouldn't be captured. Since Jekyll requires accessing layout variables via layout.body_id, I modified default.html to page.body_id | default: layout.body_id.

Modified Files

  • _layouts/home.html — Emoji + body_id: home
  • _layouts/default.html — Added layout.body_id fallback
  • _includes/nav.html — Emoji
  • _sass/_layout.scss — Header letter spacing + body#home width

Final Results

  • 100 posts migrated (16 tech philosophy + 81 general + 3 existing devlog)
  • 3 sections cleanly separated (writings / techphil / devlog)
  • Tag filter UI functional
  • Book-style layout — Side-by-side reading of list and body on PC
  • Local build functioning normally (Ruby 3.3)
  • Portfolio page — 4 categories: Projects·Work·Community·Articles
  • Home page — Emoji, spacing, width adjustments

10. Terracotta Design Implementation

Changed the blog's background color from pure white (#ffffff) to Pampas (#F4F3EE) from the Terracotta palette. This matches the tone of my VSCode custom theme (Terracotta Light). Dark mode retains the original #1a1a1a.

Also tweaked the footer. Removed the separator (border-top), reduced margins, and consolidated content into a single line: © minseok | summerinloving@gmail.com. Reduced body padding and header margins so the footer is visible on a single screen without scrolling.

  • Body padding: --space-xl(6rem) → --space-lg(4rem)
  • Bottom header margin: --space-lg(4rem) → --space-md(2rem)
  • Top footer margin: --space-xl(6rem) → --space-md(2rem)

11. Homepage Spread Layout

Applied the book spread pattern used on the archive page to the homepage. Structure features an introduction on the left and content unfolding on the right.

Behavior

  • Click "My Work" → Fetches and displays /portfolio/ content in the right panel
  • Click "Newsletter" → Displays /newsletter/ content
  • Mobile: Page navigation remains unchanged

Default State: 7 Recent Posts

To avoid an empty right panel, it displays 7 recent posts with date+tag when nothing is selected. Generated at build time using Liquid.

Initially used circles like the archive, but since the home page only has 2 links, shading worked better. Tried a black background but it stood out too much, so changed to terracotta Crail (#C15F3C) background + Pampas (#F4F3EE) text.

Left Panel Width Adjustment

I iterated through widths: 420px → 760px → 600px → 580px. 760px was too wide, making the right side cramped, while 580px balanced the introduction text wrapping and the right content width. Since the same value applies to the archive page, the entire spread layout changed consistently.

Modified Files

  • _layouts/home.html — spread structure + fetch JS
  • _sass/_layout.scssbody#home:has(.spread) max-width, active link styles, recent posts styles
  • _sass/_post.scss.spread__left width 580px, .spread__right &gt; :first-child margin removal

12. Newsletter Page

Created a new /newsletter/ page. Initially added a Substack embed iframe, but it clashed with the blog design. Removed the iframe and replaced it with an introduction text + terracotta-colored subscribe button.

  • newsletter.html — Intro text + Substack link button
  • _sass/_layout.scss.newsletter__btn style (Crail background, Crail Dark on hover)

13. Obsidian Blogging Workflow

Set up a system for comfortable writing and publishing.

Structure

The _claudecode-notes folder in the Obsidian vault is linked via a symbolic link to Jekyll's _posts/ directory. This means posts written in Obsidian become Jekyll posts directly.

옵시디언 vault/_claudecode-notes/
  → workspace/_claudecode-notes/ (심볼릭 링크)
    → theorakim.github.io/_posts/ (심볼릭 링크)

Template

I created a _templates/blog-post.md template in the Obsidian vault. When creating a new post, Cmd+P → Insert Template automatically fills in the front matter.

Publishing Workflow

  1. Write the post in Obsidian's _claudecode-notes (filename: YYYY-MM-DD-slug.md)
  2. In Claude Code, say "Publish this" → Check syntax + Verify build + Git commit & push

Drafts or notes can stay in other Obsidian folders; only move posts ready for publishing to _claudecode-notes. Git push is intentionally a manual step to prevent accidental publishing.

Changed the "Recent Posts" list on the homepage: clicking now links to an archive spread page showing both the list and the full post body, instead of the standalone post page.

Each post's tags are checked via Liquid to store the appropriate archive page in the data-archive attribute. On desktop, clicking takes you to that page with the ?read= query parameter. archive.html detects this parameter on load and automatically fetches the post. On mobile, it still goes to the single post page as before.

  • Tech Philosophy/STS/Art-n-Tech/human-ai → /techphil/
  • Development Log → /devlog/
  • Everything else → /writings/

15. Rebranding: byminseok.com

Changed the header's "Kim Minseok" to "byminseok.com" and adjusted it to font-weight: 600 (semi-bold). This structure makes the domain name the site's identity.

Updated the site description to I write, reflect on technology, and document code. Removed the redundant `

From Bear Blog to Jekyll. Migrating 100 Posts and Redesigning Everything | byminseok.com

` tag and deleted three `</body></a></section>