Blog

Things I've learned, patterns I've discovered, and honest lessons from building real projects.

What Building a Pokémon Battle Simulator Taught Me About APIs

When I started the Pokémon KO Battle project I thought the hard part would be the battle logic. I was wrong. The hard part was the API — specifically, learning to handle it gracefully when it was slow, failed, or returned data I didn't expect.

The Problem With My First Version

My first attempt fetched Pokémon data and immediately tried to use it. No loading state. No error handling. If the PokéAPI took 3 seconds (which it sometimes does on cold starts), the UI just sat there blank. If the fetch failed, the whole app broke silently. Not great.

What I Actually Learned

The fix was adding three things I had skipped in my rush to get to the fun stuff:

  1. A loading skeleton — placeholder shapes that appear instantly while real data loads. Users know something is happening.
  2. try/catch around every fetch — so a failed request shows a friendly error, not a broken screen.
  3. Data validation — PokéAPI returns deeply nested objects. I now always check that a property exists before I use it, rather than assuming the shape is always correct.
// Before (fragile)
const data = await fetch(url).then(r => r.json());
updateUI(data.sprites.front_default);

// After (resilient)
try {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`API error: ${res.status}`);
  const data = await res.json();
  const sprite = data?.sprites?.front_default ?? '/images/fallback.png';
  updateUI(sprite);
} catch (err) {
  showErrorState('Could not load Pokémon. Try again.');
  console.error(err);
}

The Bigger Lesson

Happy-path coding is easy. Real frontend engineering is about what happens when things go wrong. Every API call is a promise that might not be kept — and your UI should handle that gracefully.

I now apply this thinking to every project: what does the user see while waiting? What do they see if something breaks? Those two questions make the difference between a project that feels polished and one that just works in demos.

You Don't Need a Library for Most CSS Animations

Reach-for-a-library is a real trap in frontend development. I fell into it early — Animate.css, GSAP, Framer Motion. All excellent tools. But I wasn't using them well because I didn't understand what they were doing.

The Scroll Reveal Pattern

The most common animation on portfolio sites — elements fading in as you scroll — needs zero JavaScript libraries. Here's the entire thing:

/* CSS */
.reveal {
  opacity: 0;
  transform: translateY(40px);
  transition: opacity 0.7s ease, transform 0.7s ease;
}
.reveal.active {
  opacity: 1;
  transform: translateY(0);
}

/* JS — that's it */
const observer = new IntersectionObserver(entries => {
  entries.forEach(e => {
    if (e.isIntersecting) e.target.classList.add('active');
  });
}, { threshold: 0.15 });

document.querySelectorAll('.reveal').forEach(el => observer.observe(el));

When You Actually Need a Library

Scroll-driven parallax with multiple layers, physics-based spring animations, and complex SVG path morphing — these are genuinely hard to build well in raw CSS. That's when GSAP or Framer Motion earn their bundle size. But a card hover lift, a fade-in on scroll, a shimmer skeleton? Pure CSS. Every time.

About Me

I focus on building responsive interfaces with strong JavaScript logic, clean structure, and scalable UI behaviour. Currently improving my understanding of state management patterns, API integrations and backend fundamentals. Actively seeking opportunities where I can work on real production systems and grow as a developer.

More posts coming soon!