Selected work
Projects.
A collection of products, experiments, and systems I've helped bring to life.
Selected work
A collection of products, experiments, and systems I've helped bring to life.
A cozy website for my plushie collection, where each plushie has its own page with a name, birthday, favorite things and a photo gallery. Visitors can sign in to like plushies and leave comments, and a dashboard lets editors and admins look after the collection and the community. **Stack:** Next.js 16 (App Router, server actions), React 19, TypeScript, Tailwind CSS 4, shadcn/ui, Prisma on PostgreSQL, Better Auth, UploadThing, Resend, Bun ## What it does - **Plushie pages** with a thumbnail, a photo gallery and details such as species, pronouns and a birthday that can be as vague as just a year. - **Likes and threaded comments.** Replies nest as deep as people go. Comments can be edited and deleted, and they have a length limit, a rate limit and a filter that blocks links. - **Accounts** with email and password, Discord, or passkeys. Password sign-ins can use two-step codes from an authenticator app or by email, with backup codes. - **Three roles.** Users look, like and comment. Editors also manage plushies and moderate comments. Admins also manage people. - **A dashboard** with an overview, lists you can search, sort and filter, a photolibrary, a comment moderation page, upcoming birthdays and photo storage usage. - **An activity log** that records every change to plushies, accounts and moderated comments, and lets you revert most of them with one click. - **Admin tools:** changing roles, time-limited bans with reasons, signing someone out everywhere, sending them a password reset, and viewing the site as them. - **Light and dark themes** in pink and purple, with link previews and icons generated from the same colors. ## Things I'm proud of **Reverts that can't destroy newer work.** Each activity entry stores the relevant fields from before and after the change. A revert is only offered while the current state still matches the entry's "after". If anything has changed since, the button goes away instead of silently overwriting that change. **Security at every step.** Deleting an account or changing a verified email needs a link from the current inbox, so a stolen session isn't enough. Discord tokens are encrypted at rest. Sign-in codes are stored only as hashes and have their own rate limit. Password reset requests take the same time whether or not the email exists. Viewing the site as someone else is strictly read-only, enforced on the server. The page that tells a banned person why they were banned only opens through a signed, short-lived cookie, so nobody else can look up someone's ban. **Guardrails for admins.** The last admin can't delete their account and leave the site without one. Admins can't be banned. The auth library's own admin endpoints are narrowed to only what the site actually offers. **Tests against a real database.** 75 tests cover plushies and photos, comments, likes, user management, bans, reverts, two-step sign-in and account settings. Each run starts a throwaway PostgreSQL container, applies the real migrations and removes the container afterwards. The test setup refuses to run against any database other than a local one, emails are recorded instead of sent, and file uploads go to a pretend store. **Polish.** The loading placeholders match the shape of the real pages. Empty states are designed, not blank. Forms keep what you typed when saving fails. Text in menus is trimmed to cap height so it sits centered. ## What I learned - Designing permissions as data (roles mapped to allowed actions) and checking them on the server in every action, not just hiding buttons. - Modeling threaded comments in Postgres so a page can load whole threads in one query, and keeping deleted comments as "[deleted]" while they still have replies. - Writing database migrations for a live schema that kept growing, from plushies to auth, bans, comments, two-step sign-in and passkeys. - Building an audit log that outlives what it describes, by storing names and snapshots instead of relying on relations.
The classic board game "Mensch ärgere Dich nicht", in your browser. Create a room, send your friends the link, and play together in real time. You can also play against bots, and there's no account and nothing to install. ## Features - **Multiplayer in real time:** two to four players share a room and see every move as it happens. - **Rooms:** share a six-character code or a link, or make a room public so anyone can find it on the discovery page. - **The whole game:** captures, home column, move timer, automatic moves when there's only one option, an optional "must spawn on six" rule, and a fair dice option so nobody waits forever for a six. - **Bots:** the host can fill empty seats with computer players, so a game with just one human works too. - **Host tools:** change room settings, remove players who have gone quiet, and pass the host role on automatically. After a game, everyone can vote for a rematch. - **Animation and sound:** pieces walk around the board, captured pieces fly back to their yard, and the dice roll. All sound effects are generated in the browser, so there are no audio files. - **A live demo on the home page:** four bots play each other using the same rules as the real game. - **English and German**, light, dark and system themes, and a layout that works on phones. - **No tracking:** no accounts, no ads, no analytics. ## Tech Stack - **Frontend:** Next.js (App Router, React 19), Tailwind CSS v4, Radix UI / shadcn/ui, i18next, Lucide icons - **Backend:** TypeScript on Bun, using its built-in WebSocket support - **Shared code:** a `@ludo/shared` package with the game model, the event types and the bot logic, used by both the server and the website - **Tooling:** Bun workspaces, `bun test`, Prettier, Docker Compose ## Technical Highlights - **The server decides everything.** Clients only send actions like "join room" or "move this piece". The server rolls the dice, checks that every move is legal, resolves captures, and sends the new state to everyone in the room. Nothing the browser does can change the outcome of a game. - **Animations run on top of the game state, not inside it.** When a state update arrives, the board plays it out as a sequence: a piece walks to its target, and a captured piece stays where it was until the attacker arrives, then flies home. This means animations never hold up the game. - **Reloading doesn't kick you out.** A player who reloads the tab or loses their connection can take their seat back for up to five minutes. If the host stays away for 30 seconds, the host role moves to another player and the game carries on. - **One package for the rules.** Because the server and the website share the game types and bot logic, they can't drift apart, and the demo on the home page plays by exactly the same rules as a real game. - **Fair dice.** Numbers are balanced over the course of a game so a six turns up regularly, while the order stays random.
Meet Orbit! A full-stack real-time communication platform designed to bridge the gap between traditional social media feeds and synchronous chat. This project was a massive excuse to dive deep into WebSockets, peer-to-peer signaling, and complex client-side state to build a seamless, sub-second social experience. ## The Stack * **Framework:** Next.js (App Router) for the core architecture. * **Language:** TypeScript, keeping the entire full-stack type-safe. * **Styling:** Tailwind CSS paired with Shadcn UI for clean, accessible, and consistent components. * **Database:** SQL, queried and managed through Drizzle ORM. * **Package Manager:** Bun, for fast installs and script execution. * **Real-Time:** Pusher for instant text messaging, combined with WebRTC for peer-to-peer audio and video calls. ## Features & Social Graph Orbit isn't just a chat app; it's a space for serendipitous discovery and building connections. I built out a full social ecosystem from scratch: * Instant messaging featuring sub-second delivery, read receipts, message replies, and typing indicators. * Peer-to-peer WebRTC audio and video calls, with custom signaling and stream management. * A random matchmaking system that pairs users up for spontaneous conversations. * A robust social feed complete with hashtag parsing, mentions, and a responsive timeline. * A friendship system to send invites, manage requests, and build a private network. * Full internationalization (i18n) supporting both English and German locales. ## Trust & Safety Because a social platform is only as good as its moderation, I couldn't just skip the admin tools. I built a comprehensive moderation dashboard with automated reporting features and a reliable banning system to ensure a safe environment for users. ## The Challenge Building a real-time application meant wrestling with complex client-side state. Every incoming event needed to update the UI instantly without blocking it. Implementing WebRTC from scratch—managing `RTCPeerConnection` states, handling local/remote media streams, and negotiating ICE servers—was a massive learning curve, but incredibly rewarding to build out by hand rather than relying on a heavy third-party video SDK.
A modern, highly personalized profile and showcase website designed to act as my own soft, welcoming corner of the internet. This project was a perfect opportunity to move away from rigid, third-party link-in-bio services and build a fully custom space that reflects my personality, interests, and real-time presence. ## The Stack * **Framework:** Next.js utilizing the React Compiler for optimal frontend performance. * **Language:** TypeScript, keeping the entire project strictly type-safe. * **Styling:** Tailwind CSS paired with Radix UI for clean, accessible, and consistent components. * **Package Manager:** Bun, for fast dependency resolution and script execution. * **Real-Time:** Lanyard API for live Discord presence. ## Features & Live Presence This isn't just a static page; it's a living snapshot of who I am and what I'm up to. I built out dedicated sections to warmly introduce my world: * A real-time status integration that dynamically displays my current Discord presence and what I'm currently listening. * A beautiful photo gallery to showcase personal albums—like my plush family, cozy outfits, and comfort items, perfectly captured. * An eye-catching hero section and an "about me" space to share my interests, favorite media, and the things I love. * A clean, centralized social grid linking out to all my active platforms, from GitHub and Discord to Twitch and Bluesky. ## Theming & Aesthetics Because this space is deeply personal, the aesthetic had to be exactly right. I implemented a seamless theme toggle managed via Next Themes, ensuring user preferences are saved persistently while maintaining the site's signature soft, pastel-heavy look in both light and dark modes. The entire layout is highly responsive, adapting flawlessly from mobile to desktop without losing its charm. ## The Challenge Building this website meant balancing a comforting, cute aesthetic with robust, modern frontend performance. Working with the Lanyard API to fetch and render real-time presence data without causing layout shifts—and optimizing the gallery images for fast loading—provided a great opportunity to blend creative UI/UX design with strict technical standards.
Instead of relying on a dozen different fragmented plugins to run a Minecraft server, I decided to build my own all-in-one core plugin from scratch. This project handles everything from essential player commands to custom chat formatting and even hosts its own resource packs. ## The Stack * **Language:** Java, utilizing a strict, object-oriented architecture. * **Build Tool:** Maven, managed through a central configuration. * **Data Management:** A custom Database Manager to safely handle player data and server states. * **Web Integration:** A built-in HTTP server designed specifically to distribute custom server resource packs. * **Configuration:** YAML-based config files for easy server-side adjustments. ## Features & Player Mechanics This plugin replaces heavily bloated third-party tools with clean, handwritten implementations of core server mechanics: * **Teleportation System:** A fully interactive TPA system handling `/tpa`, `/tpaccept`, `/tpdeny`, and `/tphere` requests. * **Moderation Tools:** Essential oversight features including `/vanish`, `/fly`, `/invsee`, and more. * **World Navigation:** Comprehensive Home and Warp Managers for setting and managing player locations. * **Quality of Life:** Portable utility commands granting players instant access to `/enderchest`, `/workbench`, and `/trash`. ## Theming, UI & Customization A server should feel like a cohesive experience, not a patchwork of random tools. I built out dedicated managers and listeners to control the visual flow: * **Chat & Assets:** Custom chat formatting, message prefixes, and autocomplete features. * **Live Server Branding:** A fully customized `TablistManager` and dynamic MOTD handling. * **Event Tracking:** Dedicated listeners for player actions, including custom join and leave messages. ## Why Build It Myself? Writing a Minecraft plugin of this scale is a massive exercise in handling edge cases and server events. How do you ensure a player in `/vanish` stays completely invisible to the server's `TablistManager` and `JoinListener`? How do you efficiently serve custom assets without relying on a third-party web host? Building features like the `ResourcePackServer` and wrestling with complex inventory states in the `InvseeListener` made this project a fantastic deep dive into backend Java development and real-time event handling.
A full-stack profile discovery platform designed around thoughtful introductions rather than endless swiping. Users can create personalized profiles, upload images, discover compatible profiles, and manage their account securely. The platform also includes a moderation workflow where moderators can review submitted profiles, approve or reject them, and provide field-specific feedback for corrections. ## Technical Highlights - Built with Next.js App Router and React - PostgreSQL database managed with Prisma ORM - Schema validation using Zod - Secure password hashing with `scrypt` - Token-based password recovery with hashed reset tokens - Role-based access control for users, moderators, and administrators - Image uploads powered by UploadThing - Email delivery handled through Resend - Responsive UI built with Tailwind CSS and Radix UI - Server actions for authenticated profile and moderation operations ## Features - User registration and secure authentication - Profile creation and editing - Avatar and banner image uploads - Profile discovery with search, pagination, and randomized results - Profile verification and moderation workflows - Rejection feedback for specific profile fields - Password reset via email - Session management and protected routes - Responsive interface with animated transitions - Support for metric and imperial height units ## What I Learned This project strengthened my experience building production-style full-stack applications, particularly around authentication, database relationships, role-based permissions, validation, file uploads, moderation systems, and secure account recovery workflows.
You're looking at it right now! This portfolio is a full-stack site built completely from scratch - no CMS, no template, just a space to showcase my work and experiment with the stack I actually enjoy using. ## The Stack * **Framework:** Next.js with the React Compiler enabled for automatic memoization. * **Language:** TypeScript, strict mode throughout. * **Styling:** Tailwind CSS paired with Shadcn UI and Radix primitives for accessible, consistent components. * **Database:** PostgreSQL, queried through Prisma ORM. * **Package Manager:** Bun, for fast installs and script execution. * **Email:** Resend + React Email for the contact form. * **Animations:** Framer Motion for subtle scroll-reveal and page transitions. ## Auth & Admin Authentication runs on a custom Discord OAuth2 implementation - no third-party auth library, just handled callbacks, token refresh, and session management written by hand. Logged-in admins and writers get access to a protected dashboard where they can: * Write and publish blog posts (Markdown-powered, with syntax highlighting and nested comments) * Manage projects - like this one! * Manage user roles and permissions ## Theming Light, dark, system, and two Catppuccin variants (Latte and Macchiato), because a portfolio should feel like home. ## Why Build It Myself? Every "boring" feature - auth, comments, theming, forms - became a small excuse to dig into an edge case I hadn't hit before. This site is as much a learning log as it is a portfolio.