# How I Built an Interactive Terminal + TIL System for My Dev Blog

**Author:** kelexine  
**Date:** 2024-12-08  
**Category:** Tutorial  
**Tags:** React, WebDev, UI/UX, Developer Tools  
**URL:** https://kelexine.is-a.dev/blog/building-interactive-terminal-til-system

---

# How I Built an Interactive Terminal + TIL System for My Dev Blog

When I set out to rebuild my developer blog, I had one rule: **make it memorable**. Not just another dark-themed portfolio with a hero section and blog cards. I wanted something that would make visitors think, "Wait, that's different."

Two features emerged from that goal:
1. **A hidden terminal interface** accessible via keyboard shortcut
2. **A TIL (Today I Learned) micro-blog** for bite-sized insights

This is the story of how I built both features while maintaining perfect 100/100/100/100 Lighthouse scores.

## The Problem with Developer Blogs

Most developer blogs look identical. Same layouts, same sections, same "About Me" pages. Nothing *wrong* with them, but nothing *memorable* either.

I wanted features that:
- Showcased technical skills organically
- Enhanced user experience (not just aesthetic)
- Actually provided value
- Fit my brand (systems programming, security, low-level work)

The terminal interface and TIL system checked all boxes.

## Part 1: The Terminal Easter Egg

### The Concept

Press `Ctrl+~` (or `Cmd+~` on Mac) anywhere on the site, and a terminal slides down from the top. Full keyboard navigation, working commands, and actual functionality—not just a visual trick.

### Why a Terminal?

Because my background is in:
- Android ROM development
- System-level programming
- Linux/CLI tools
- Cybersecurity

A terminal interface is authentic to who I am as a developer. It's not trying to be clever—it's genuinely how I interact with computers daily.

### Technical Architecture

**Stack:**
- React 18 (with hooks)
- Framer Motion (animations)
- Context API (global terminal state)
- Custom keyboard hook

**Key Components:**

```jsx
// Terminal context for global state
export function TerminalProvider({ children }) {
  const [isOpen, setIsOpen] = useState(false);
  // ... state management
}

// Custom keyboard hook
export function useKeyboardShortcut(key, callback, modifiers) {
  useEffect(() => {
    const handleKeyDown = (event) => {
      // Match key + modifiers (ctrl, meta, etc.)
      if (event.key === key && isModifierMatch) {
        event.preventDefault();
        callback(event);
      }
    };
    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, [key, callback, modifiers]);
}
```

### The Command System

I built a modular command registry that maps command strings to execution functions:

```javascript
export async function executeCommand(command) {
  const [cmd, ...args] = command.split(' ');

  switch (cmd) {
    case 'help':
      return showHelpMessage();
    
    case 'whoami':
      return showAbout();
    
    case 'blog':
      const posts = await getAllPosts();
      return formatBlogList(posts.slice(0, 5));
    
    case 'projects':
      return formatProjectList(projects);
    
    // ... 15+ commands total
  }
}
```

Commands include:
- `help` - Show available commands
- `about` / `whoami` - About me
- `blog` - Recent blog posts
- `til` - TIL feed
- `projects` - Project list
- `skills` - Tech stack
- `ls` - Site structure (tree view)
- `clear` - Clear terminal
- `exit` - Close terminal

Plus some easter eggs:
- `sudo rm -rf /` - Cheeky message about trying
- `matrix` - Reference to The Void theme

### Performance Considerations

**Challenge:** How do you add a feature-rich terminal without hurting load times?

**Solution:**
1. **Lazy loading** - Terminal component only loads when triggered
2. **CSS animations** - Use GPU-accelerated transforms
3. **Minimal JavaScript** - No heavy dependencies
4. **Tree shaking** - Only bundle commands that are used

**Result:** Terminal adds ~8KB (gzipped) to the bundle. Lighthouse stayed at 100/100.

### The ASCII Art Banner

Every good terminal needs a banner. I generated ASCII art of "KELEXINE" and styled it in lemon-green:

```
██╗  ██╗███████╗██╗     ███████╗██╗  ██╗██╗███╗   ██╗███████╗
██║ ██╔╝██╔════╝██║     ██╔════╝╚██╗██╔╝██║████╗  ██║██╔════╝
█████╔╝ █████╗  ██║     █████╗   ╚███╔╝ ██║██╔██╗ ██║█████╗  
██╔═██╗ ██╔══╝  ██║     ██╔══╝   ██╔██╗ ██║██║╚██╗██║██╔══╝  
██║  ██╗███████╗███████╗███████╗██╔╝ ██╗██║██║ ╚████║███████╗
╚═╝  ╚═╝╚══════╝╚══════╝╚══════╝╚═╝  ╚═╝╚═╝╚═╝  ╚═══╝╚══════╝

Welcome to The Void. Type 'help' for available commands.
```

Rendered in monospace (Fira Code) with proper spacing. It's bold, it's memorable, it sets the tone.

### Animations That Don't Suck

I see a lot of over-animated sites. Transitions everywhere. Bounce effects on everything. It's exhausting.

For the terminal, I kept it minimal:
- Slide down from top (0.3s ease-out)
- Backdrop blur fade-in (0.2s)
- Cursor blink (530ms interval, classic)
- No bounce, no spring physics, no unnecessary flourishes

**Rule:** Animation should guide attention, not demand it.

### Accessibility Wins

Terminal is fully keyboard navigable:
- No mouse required to use it
- ESC to close
- Arrow keys for command history
- Screen reader compatible
- Focus indicators visible
- ARIA labels on interactive elements

You shouldn't need to see the screen to navigate your site. Period.

## Part 2: The TIL (Today I Learned) System

### The Micro-Blog Problem

Writing full blog posts is time-consuming. Research, outline, write, edit, polish—easily 4-8 hours per post. That's fine for deep-dives, but what about quick insights?

Twitter threads? They disappear in the noise.

Personal notes? Hidden on my local machine.

I needed something between tweets and blog posts. Enter: **TIL**.

### What's a TIL?

**TIL = Today I Learned**

Short-form posts (100-500 characters) documenting:
- Quick wins
- Code snippets that saved time
- "Aha!" moments
- CLI tricks
- Library features I didn't know existed

Lower friction = more content = better SEO = more value to readers.

### Data Structure

I went with simple JSON files in the repo:

```json
{
  "id": "react-useid-ssr",
  "date": "2024-12-07",
  "content": "TIL: React's useId() hook generates stable unique IDs on both server and client. Perfect for SSR forms without hydration mismatches!",
  "tags": ["React", "SSR", "WebDev"],
  "code": {
    "language": "jsx",
    "snippet": "function Form() {\n  const id = useId();\n  return <input id={id} />;\n}"
  },
  "link": "https://react.dev/reference/react/useId"
}
```

**Why not a database?**
- Version controlled (Git history)
- No server needed
- Works with static hosting (Cloudflare Pages)
- Easy to batch-write content
- Can search with `grep` if needed

**Build script** generates an index file at build time:

```javascript
// scripts/generate-til-index.js
const files = fs.readdirSync(TIL_DIR);
const tils = files.map(file => {
  return JSON.parse(fs.readFileSync(file));
});
fs.writeFileSync('public/data/til-index.json', JSON.stringify(tils));
```

### UI/UX Design

**Requirements:**
- Scannable (date + content visible at a glance)
- Code syntax highlighting (when present)
- Tag filtering
- Copy link functionality
- Mobile responsive

**Card Layout:**
```
┌─────────────────────────────────────┐
│ 📅 Dec 7, 2024                      │
│                                     │
│ TIL: React's useId() hook generates │
│ stable IDs on server and client...  │
│                                     │
│ [code block if present]             │
│                                     │
│ #React #SSR #WebDev    🔗 📋      │
└─────────────────────────────────────┘
```

**Tag Filter Bar:**
26 unique tags extracted from all TILs, rendered as clickable pills:

```jsx
<div className="filter-bar">
  <button onClick={() => setTag(null)}>All</button>
  {tags.map(tag => (
    <button 
      key={tag}
      onClick={() => setTag(tag)}
      className={selectedTag === tag ? 'active' : ''}
    >
      #{tag}
    </button>
  ))}
</div>
```

Clicking a tag filters TILs instantly (client-side, no API calls).

### Content Strategy

I frontloaded 15 TILs covering my full stack:
- React (useId, useCallback, lazy loading)
- Android (ADB over WiFi, SELinux, vendor blobs)
- Rust (mem::take)
- Docker (build cache optimization)
- Git (bisect automation)
- Linux CLI (fuser, dd progress, curl timing)
- Web APIs (Promise.allSettled)
- CSS (:has selector)
- Tailwind (arbitrary values)

**Goal:** 2-3 TILs per week going forward. Way more sustainable than full blog posts.

### SEO Benefits

Each TIL is effectively a micro landing page:
- 26 unique tag pages (implicit)
- Long-tail keyword targets (e.g., "React useId SSR hydration")
- Fresh content signals
- Internal linking opportunities

Google loves frequent updates. TILs provide that without burning out.

## Integration: Terminal + TIL

The features complement each other:

**In terminal:**
```bash
kelexine@void:~$ til

Today I Learned (Recent):

[Dec 7] React's useId() hook generates stable IDs...
[Dec 6] Debug Android devices over WiFi without USB...
[Dec 5] Rust's std::mem::take() swaps out values...

View all TILs at: https://kelexine.is-a.dev/til
```

**On homepage:**
- Terminal hint in footer: "Pro tip: Press Ctrl+~ for a surprise"
- Recent TILs section showcasing 3 latest

**In nav:**
- TIL link in main navigation
- Accessible from anywhere

They work independently but feel cohesive. Both are low-friction, high-value features that fit the brand.

## Lessons Learned

### 1. Features Should Feel Inevitable

The terminal wasn't added because "terminals are cool." It was added because **I'm a systems programmer**. TILs weren't added because "micro-blogs are trendy." They were added because **I learn things daily that are worth sharing**.

If a feature feels forced, it probably is.

### 2. Constraints Breed Creativity

Perfect Lighthouse scores weren't a constraint—they were a forcing function. Every decision had to balance features with performance. That led to:
- Lazy loading
- Minimal dependencies
- CSS over JavaScript animations
- Static generation over dynamic APIs

The site is better for it.

### 3. Accessibility Isn't Optional

Keyboard navigation, screen reader support, semantic HTML—these aren't nice-to-haves. They're table stakes.

My terminal works without a mouse. My TIL cards are navigable with Tab. Color contrast meets WCAG AA.

**Your site should work for everyone.**

### 4. Content Strategy Matters

Building a TIL system is pointless without TILs. I frontloaded 15 posts before launch. That sets the tone and proves the format works.

Don't launch with "Coming soon" placeholders. Launch with substance.

### 5. Easter Eggs Are Underrated

The terminal is hidden. You have to know the keyboard shortcut. That's intentional.

When someone discovers it, they feel clever. They screenshot it. They share it.

**Delight lives in the details.**

## The Numbers

**Development time:** ~5 days (40 hours)
- Terminal: 2 days
- TIL system: 2 days
- Integration + polish: 1 day

**Bundle size impact:**
- Before: ~150KB (gzipped)
- After: ~183KB (gzipped)
- Increase: +33KB (22%)

**Lighthouse scores:**
- Performance: 100 (desktop), 97 (mobile)
- Accessibility: 100
- Best Practices: 100
- SEO: 100

**Content velocity:**
- Blog posts: 1-2 per month
- TILs: 8-12 per month (projected)
- 4x more content without 4x the effort

## What's Next

The foundation is solid. From here:

1. **More terminal commands** - `matrix` animation, `coffee` ASCII art
2. **TIL analytics** - Track popular tags, engagement
3. **RSS feed** - Let people subscribe to TIL updates
4. **Advanced filtering** - Multi-tag selection, date ranges
5. **Content backfilling** - Document past learnings

But honestly? The system works. Now it's about **using it consistently**.

## Try It Yourself

Visit [kelexine.is-a.dev](https://kelexine.is-a.dev) and press `Ctrl+~` (or `Cmd+~`).

Or check out the [TIL feed](/til) to see the micro-blog in action.

Both features are open source. Want to build something similar? The architecture is battle-tested and production-ready.

---

## Tech Stack

**Frontend:**
- React 18
- Vite
- Tailwind CSS
- Framer Motion

**Content:**
- MDX (blog posts)
- JSON (TIL posts)
- Static generation

**Hosting:**
- Cloudflare Pages
- Automatic deployments from Git

**Performance:**
- Lazy loading
- Code splitting
- Image optimization
- CDN caching

---

## Closing Thoughts

Building memorable developer experiences isn't about flashy animations or the latest framework. It's about understanding your audience, playing to your strengths, and shipping features that provide real value.

The terminal interface works because **it's authentically me**. The TIL system works because **it solves a real problem**.

Your features should do the same.

Now go build something people remember.

---

**Questions? Feedback?** Find me on [GitHub](https://github.com/kelexine) or subscribe to [The Void](/subscribe) for more posts like this.

*Create. Secure. Deploy.*

---

*This content is available at [kelexine.is-a.dev/blog/building-interactive-terminal-til-system](https://kelexine.is-a.dev/blog/building-interactive-terminal-til-system)*
