Happy Birthday
Romanian-language web experience for generating and sharing a personalized wish, with 5 color themes, confetti animations, a stable random message, audio control, a shareable link, and export of the card as an image.

Core stack
The challenge
Turning an open-source greeting-card demo into a coherent, easily shareable Romanian experience, keeping the interaction simple on mobile and desktop, and resolving the inconsistencies between live rendering, audio, and the image export.
The solution
The Next.js/React base was adapted with full localization, Romanian messages, custom routes, five CSS themes, manual audio control, a PWA manifest + icons and screenshots, plus a stabilized export flow: the random message is fixed in state for the current session, and PNG generation is triggered in a controlled way via state/effect and html-to-image.
Notable complexity
Happy Birthday is a small web experience built in Next.js 11 and React 17 to quickly create a personalized birthday card. The user picks one of five color themes, enters the name of the person being celebrated, and is directed to a shareable route that encodes the chosen name and theme. The resulting screen combines a title animated letter by letter, canvas confetti, a randomly chosen wish, and controls for music, going back, and, in the creator flow, copying the link and downloading the card as a PNG.
The repository is not a from-scratch implementation. The CONTRIBUTING file and license explicitly point to the open-source project gouravkhunger/nextjs-birthday-wish as the base. Comparing the current code with the upstream shows that the main Next.js structure, the theme hook, the Button/CopyLinkButton components, the title animation, the confetti, and the export mechanism via html-to-image + file-saver come from that base. The verifiable customization consists of full localization into Romanian, replacing and rewriting the messages, adapting the "La Mulți Ani" (Happy Birthday) title, removing the upstream branding from the interface, PWA integration, custom assets and screenshots, changing the audio flow, and a few behavior fixes on the card page.
The main flow is fully client-side. The "/" page keeps the name and selected theme locally; validation blocks an empty value or a name starting with a space, then Next Router navigates to "/{name}" for the default blue theme or "/{name}/{themeId}" for the other themes. The catch-all route "[...name].js" extracts the name and color ID from the URL, applies the theme via a custom hook, and sets the color in a global CSS variable. There is no API, controller, backend service, database, accounts, or server-persisted data.
The wish message is chosen randomly only once when the component mounts and kept in state. This is a notable difference from upstream, where the message was recomputed on every render and could become inconsistent between the screen and the exported image. In the analyzed version, the same message is used both in the visible experience and in the temporary template rendered for export. The download function was also moved into a React.useCallback and triggered via an effect when the downloading state is activated, removing the side-effecting call directly from render that was present upstream.
The title is built character by character to apply a wavy CSS animation. The Romanian prefix "La Mulți Ani, " and the name are separated dynamically based on the prefix length, rather than the hardcoded threshold used by the English upstream version. The name received from the URL is rendered as React text, without dangerouslySetInnerHTML, so React escapes the user-supplied content. The theme is chosen only from the fixed list of five variants: blue, green, violet, yellow, and red.
The audio was changed from upstream's autoplay to an explicit "Start music" / "Stop music" control, wired to an audio element via useRef. Stopping pauses playback and resets currentTime to zero. The audio asset is served locally from public/media. The creation page also writes a "playMusic" flag to localStorage before navigating, but the current snapshot doesn't read it anywhere, so the flag is residual code with no effect on actual behavior.
Sharing uses the Clipboard API to copy the current URL and provides temporary "Link copied!" feedback. The copy and export buttons are shown only when the internal history shows the user arrived at the card from the homepage; opening the link directly gives the recipient experience, with a button to create a new wish and audio control. This separation is a UX rule based on local state, not an authorization mechanism, and is lost on refresh.
The visual export uses html-to-image to turn a dedicated container into a PNG and file-saver to save the "birthday-wish.png" file. In export mode, the animation is removed from the title and the background uses the static confetti asset, so the captured result is a clean card rather than the animated canvas from the interactive experience.
The app is configured as a PWA via next-pwa, a web manifest, and a full icon set between 48×48 and 512×512. The manifest includes wide and mobile screenshots of the Romanian UI and runs in standalone display mode. The next-pwa configuration uses register and skipWaiting. The repository also contains a generated service worker/Workbox; the committed snapshot mainly demonstrates PWA configuration rather than a mature offline strategy, since the saved service worker uses NetworkFirst only for the start URL and NetworkOnly for the other routes in that artifact.
The frontend is responsive and minimalist: white background, system/Roboto typography, pill-shaped input and buttons, and an accent controlled via a CSS custom property. On desktop the interface is very airy and centered; on mobile the title wraps onto two lines, the theme selector remains visible as five colored dots, and the form reflows vertically. The versioned screenshots confirm this responsive adaptation.
From a security standpoint, the repository contains no secrets or versioned .env files; .gitignore excludes local environment files and PEM certificates. There is no authentication, access to sensitive data, or server endpoints that would introduce IDOR, SQL injection, mass assignment, or webhook forgery risks. The remaining risk surface is that of a browser-only app: the Clipboard API can fail in insecure or unpermitted contexts, URLs can become very long since the name has no explicit limit, and clipboard/export/audio errors have no fallback UI.
The repository contains no automated tests, GitHub Actions, Docker, deployment configuration, analytics, or error monitoring. The available scripts are dev, build, export, start, and lint. Next.js is started with openssl-legacy-provider for compatibility with the older toolchain, and start runs on port 3001. The available Git history contains a single backup commit, so the evolution of the changes cannot be demonstrated feature-by-feature; the contributions above are attributed through direct comparison between the snapshot and upstream, not through commit count.
Key results
- Fully client-side flow for creating a wish: theme selection → name entry → custom route
- Five color themes persisted in the URL via the theme ID
- Card experience with canvas confetti and a character-by-character animated title
- Set of wishes localized into Romanian, with a stable random message for the duration of the visit
- Manual audio control with play, stop, and reset to the start of the track
- Copying the current link to the clipboard with temporary visual feedback
- Exporting the card as a PNG image via html-to-image and file-saver
- UX separation between creator and recipient based on local navigation history
- PWA setup with manifest, multiple icons, standalone display, and a service worker generated via next-pwa
- Versioned screenshots for desktop and mobile layout
- Verifiable localization and customization on top of the open-source upstream project, without claiming the base as original work
Want something similar?
Start with the estimate wizard — answer a few questions and get a realistic price range and timeline.