← Portfolio Blog · Post
May 12, 2026 · meta · astro · motion

Hello from the new dawidesign.de

A fresh redesign, a new stack, and a quick look at the cursor trail that streaks behind your mouse.

This site has been rebuilt from the ground up. New look, new stack, same idea — a quiet home for the work and a notebook for the build. Welcome.

What changed

The previous site was a Nuxt 2 app from 2021: Vue 2, Tailwind 2, a sprinkling of plugins, and content lived inside components. It served well, but the framework had reached end-of-life and the content layer was hard to extend.

The new site keeps the visual character — dense typography, generous whitespace, a strong accent — and rewrites everything underneath:

  • Content moves out of components into typed collections (src/content/) with Zod schemas for home copy, life chapters, legal pages, and the blog you’re reading now. Posts are MDX.
  • Pages render static at build time and ship as plain HTML. Interactive bits hydrate on idle, not on load.
  • i18n is route-based (/de/...) instead of plugin-based, which removes a layer of magic from URLs and SEO.
  • One toolchain for lint, format, and check (Biome) replaces the old ESLint + Prettier pair.

The stack

  • Astro 6 — content-first, mostly-static, islands for the few pieces that need JavaScript.
  • React 19 + Motion — the only interactive bits (cursor effects, mobile menu, marquee) are React islands animated with Motion.
  • MDX — long-form content with the option to drop in components when a post needs them.
  • Tailwind 4 alongside a small set of hand-written CSS custom properties for type and color tokens.
  • Biome for lint + format, Netlify for hosting, Resend for the contact form.

A look inside: the cursor trail

The thing most people notice first is the streak that follows the cursor. It’s a React island that listens for pointermove, measures velocity between samples, and spawns short-lived blobs that stretch along the direction of motion — so a slow drag leaves soft dots, while a fast flick leaves a comet.

The core of it is small. Stripped of throttling and the React state plumbing, the geometry is just this:

const onMove = (e: PointerEvent) => {
  const now = performance.now();
  const dx = e.clientX - lastX;
  const dy = e.clientY - lastY;
  const dt = Math.max(now - lastTime, 1);

  // Speed → how much the blob stretches along its travel direction.
  const speed = Math.sqrt(dx * dx + dy * dy) / dt;
  const stretch = 1 + Math.min(speed / FAST_SPEED, 1) * (MAX_STRETCH - 1);
  const angle = (Math.atan2(dy, dx) * 180) / Math.PI;

  spawn({ x: e.clientX, y: e.clientY, angle, stretch });
  lastX = e.clientX;
  lastY = e.clientY;
  lastTime = now;
};

Each spawned mark is a blurred radial gradient inside an AnimatePresence — it fades out within ~700ms and removes itself. The full source, including the throttle (MIN_DIST, MIN_INTERVAL), the reduced-motion bail-out, and the coarse-pointer guard, lives in src/components/motion/CursorTrail.tsx.

If you have Reduce Motion enabled at the OS level, the effect doesn’t render — the entire component returns null. Same for touch devices, where it would be both pointless and expensive.

What’s next

More posts will land here as small tools, workflow notes, and the occasional deeper write-up on a piece of the site. Nothing on a schedule — only when there’s something worth keeping.

Thanks for visiting.

Back to the notebook ←

All posts