A bilingual (English / Arabic) portfolio site, built as a single Next.js app where right-to-left is a first-class layout mode rather than a translation layer.
Live: arcode-portfolio.netlify.app · English · العربية
Most sites treat RTL as a checkbox. It isn't one — layout, typography, numerals, punctuation and even the direction a progress bar grows all change. This codebase handles it structurally rather than with per-component patches:
diris set once, server-side, from the route segment. Every component inherits it; there is not a singledir={isArabic ? …}in the tree.- Logical properties throughout —
ms-/me-,ps-/pe-,start-/end-,text-start/text-end. Physicalleft/rightutilities survive only where the geometry genuinely is not directional. rtl:/ltr:Tailwind variants registered intailwind.config.ts(Tailwind v3 does not ship them; they arrive in v4).- Arabic typography is not Latin typography. Letter-spacing is zeroed —
tracking breaks the joins of a connected script —
text-transformis disabled, leading opens from 0.88 to 1.25, and display sizes step down per breakpoint so an Arabic heading occupies the same block as its English counterpart. - Latin islands (email, coordinates, version strings, the UTC clock) opt back
out with
dir="ltr"and keep their Latin face and tracking. - Direction-aware motion — CSS has no logical
transform-origin, so progress and fill bars use anorigin-inline-starthelper, and the marquee reverses so it never scrolls against the reading direction.
Each language is a real, indexable URL (/en, /ar) with its own metadata,
canonical link and hreflang alternates. / redirects by cookie, then
Accept-Language, then English.
| Layer | Tooling |
|---|---|
| Framework | Next.js 14 (App Router) · React 18 · TypeScript 5 |
| Styling | TailwindCSS 3 |
| Motion | Framer Motion · GSAP |
| Smooth scroll | @studio-freight/lenis |
| 3D | Three.js (vanilla — no R3F, for bundle size) |
| Type | Space Grotesk · Inter · Space Mono · IBM Plex Sans Arabic |
| Contact form | Web3Forms (no backend) |
| # | Project | Year | Stack | Live |
|---|---|---|---|---|
| 01 | Why Did I Get It Wrong — AI math-mistake analyzer | 2024 | React · Groq API · RTL · Tailwind | wdigiw-v2.netlify.app |
| 02 | Bayt Halab — luxury restaurant site | 2026 | HTML/CSS · RTL · Responsive | bayt-halab.netlify.app |
| 03 | Wily's Iraq — fine-dining concept site | 2026 | Next.js · Reservations · Dark UI | wilysiq.netlify.app |
| 04 | TaskLock — Android focus app | 2026 | Flutter · Android | — |
npm install
npm run dev # http://localhost:3000 → redirects to /en or /arSubmissions are forwarded by Web3Forms. Without a key the endpoint returns 503 with a real error — it will not pretend a message was delivered.
- Register your address at web3forms.com and verify it.
- Create
.env.localat the project root (Next.js does not read.envfiles from insideapp/):WEB3FORMS_ACCESS_KEY=your-access-key-here - In production, set the same variable in your host's dashboard (Netlify → Site configuration → Environment variables) and redeploy.
All copy for both languages lives in one file:
app/lib/content.ts # portfolioContent: Record<Locale, PortfolioContent>
Add a locale by extending Locale, adding its block, and listing it in locales;
routing, metadata, sitemap and the language toggle all read from there.
app/
├── [locale]/
│ ├── layout.tsx # root layout — sets lang/dir, per-locale metadata
│ └── page.tsx
├── api/contact/route.ts # Web3Forms endpoint, bilingual errors
├── components/
│ ├── animations/ # Reveal, SplitHeading
│ ├── layout/ # Navbar, Footer, SmoothScroll, Theme/LanguageProvider
│ ├── sections/ # Hero, About, Skills, Projects, Services, Contact, Marquee
│ ├── three/ # WireSphere, KineticGrid
│ └── ui/ # CustomCursor, LoaderScreen, CommandMenu, …
├── lib/content.ts # ← all content, both locales
├── globals.css # tokens, RTL/Arabic typography, keyframes
└── sitemap.ts
middleware.ts # locale detection and redirect
| Trigger | Effect |
|---|---|
⌘K / Ctrl K |
Command palette |
EN / ع in the header |
Switch language (navigates to /en or /ar) |
| Theme button | Light ⇄ dark |
| Hover skill cards | Invert + expand tag list |
| Hover project images | Scale + "View" reveal → live demo |
Konami code, or type studio |
Easter egg |
- Semantic landmarks, visible
:focus-visiblerings,aria-labelon icon-only controls prefers-reduced-motionshortens every animation and skips the intro loader entirely- Intro loader is time-boxed to 800ms and shown once per session
- Custom cursor and the 3D scene are gated to fine-pointer devices
next/fontself-hosts all four families — no layout shift, no render-blocking request
Designed and built by Abdulrahman Mohammed — Baghdad, Iraq.
Lenis · Three.js · Framer Motion · Web3Forms