
Corporate Website

An ultra-premium corporate web and consultancy platform designed for Emine Kaya, a mechanical engineer with 35 years of HVAC and plumbing expertise. Built with an 'Industrial Luxury' design concept featuring dark, editorial aesthetics and high performance.
Bringing 35 years of engineering history onto the web is not hard because of the writing; it is hard because that history has to become scannable. Within the first thirty seconds a visitor is looking for the answer to one question: has this person done my kind of work? The second issue was contact itself. In mechanical installation consultancy, an incoming request only turns into work if the service type, scale and budget range are clear from the start; a single-box "your message" form does not bring that information, and even when it does it leaves the visitor facing an empty field. The third was language: content had to be published in both Turkish and English, and an English-speaking visitor must not land on a Turkish URL.
Two sides. The visitor: usually a business owner or project manager looking for a consultant ahead of an investment or a renovation; they look at reference projects, areas of expertise and chronology, mostly on mobile. The business owner: wants to see the incoming request in their inbox, in a form they can reply to directly β they do not want to open a panel or log into a system.
Sole developer. Information architecture, bilingual route design, design system, animation layer, contact flow and transactional email templates, SEO structure and Vercel deployment.
Next.js 16 App Router, no database. Pages are produced as Server Components, with interactive sections split into separate client components (AboutClient, ProjectsClient, ContactClient) β the animation library only enters those islands. The two languages run on localized paths through next-intl's pathnames table: the same file-system route is published as /hizmetler in Turkish and /services in English. The only write path is the contact form: the client POSTs to /api/contact, the route handler renders React Email templates to HTML on the server and sends two emails through Resend β one notification to the business, one confirmation to the sender. Undefined localized paths fall through a catch-all route to 404.
A Turkish visitor should see /hizmetler and an English visitor /services β both for shareability and for search visibility. That mapping is defined in one place, next-intl's pathnames table; there is a single route in the file system and the published URL changes with the language. The alternative, a separate folder per language, would have meant writing the same page twice.
Trade-offA new page now has to be declared in two places: the file-system route and the pathnames table. The heavier cost is in navigation β links must come from next-intl's own navigation wrapper; if someone reaches for a bare next/link out of habit, the link silently goes to the wrong language, and that mistake is not caught at build time. Renaming a published path also breaks the old URL; the redirect has to be written by hand.
For a consultancy request to turn into work, the service type, scale and budget range have to arrive with it. The form was split into three steps β identity, project context, message β with a fourth screen as submission confirmation. Advancing step by step lowers abandonment compared with showing the same fields as one long vertical list, and each step shows only its own question.
Trade-offThe user cannot see the whole form at a glance; they infer how many questions remain from the progress indicator. Going back a step to fix something costs two extra clicks. Because state is held entirely on the client, a page refresh loses everything entered β there is no draft persistence.
The language of the notification and confirmation emails must match the language the form was filled in. The route handler imports messages/tr.json and messages/en.json directly and passes dictionary text into the templates, so fixing a sentence does not mean looking in two separate places and the translation stays in one source.
Trade-offThe entire dictionary β interface strings included, 249 lines per language β enters the server bundle just to send an email, while the part the email needs is a small fraction of it. The language choice is also a hand-written condition inside the route handler: adding a third language requires one more import and one more branch, not just another dictionary file.
The backbone of the design is parallax layers and a timeline that advances with scroll. With the browser's discrete wheel steps those layers moved out of sync with each other; Lenis takes over scrolling and, with a low lerp value (0.05), makes it continuous so the layers stay on the same curve.
Trade-offScrolling is no longer under the browser's control: jumping to an in-page find result, going to the end of the page with the keyboard and CSS scroll-behavior are all at the library's mercy. The library enters every page through the root layout, so content-focused pages carry the cost too. Until JavaScript loads, scrolling behaves natively and is then taken over β a shift in behavior you can feel in the first second.
There was no single need that required a database: the content is fixed and the only write path is sending an email. So no persistence layer was ever built, and Resend was chosen for transactional email β the request lands in the business owner's already-open inbox, with no separate panel to learn. React Email lets the email templates be written in the same JSX language and against the same translation dictionary; that stays readable where hand-written HTML table layout would not. next-intl was chosen because it puts bilingual route names in the routing layer rather than in application code. React Compiler is enabled; rather than writing memoization by hand in animation-heavy client components, it was left to the compiler.
The write surface is a single endpoint: /api/contact. It performs server-side required-field checks (name, email, message), the Resend key and recipient address are kept in server-only environment variables, and error messages are generalized so nothing leaks to the client. Undefined localized paths return 404 through the catch-all route. The known gap should be stated plainly: this endpoint has no rate limiting, no bot verification and no schema-based input validation; fields pass through neither length limits nor a header-injection check. The TemCraftTech contact endpoint I wrote in the same period has all of these layers β the difference here is not a deliberate design choice but an open gap.
Pages are produced as Server Components; Framer Motion and Lenis enter only the client islands. React Compiler is enabled, images are served as AVIF/WebP through next/image, and the remote image source is restricted to a single domain. No stored, date-stamped Lighthouse output exists for this project, so no page speed score is written here β given the full-screen hero video and a scroll library in the root layout, claiming an unmeasured score would be misleading.
Today I would harden the contact endpoint from the start: Zod schema validation, per-IP rate limiting and a bot check should have been in the first release. At TemCraftTech I built the same endpoint with an Upstash-backed sliding-window limit, Turnstile, a honeypot and CRLF injection rejection; that arrangement could have been carried over here as-is, and it is not the kind of thing that is cheapest to add later. Second, I would not send both emails inside a single Promise.all: as it stands, if the confirmation email fails the request returns an error even though the notification to the business may already have gone out β and when the user resubmits, the same request arrives twice. Treating the notification as required and the confirmation as best-effort is the right split.
Tell me what you want to build; I'll tell you up front how long it takes and where to start.