Personal portfolio of Mehmet Mutlu, Senior Frontend Developer. A statically generated, bilingual (EN/TR) site with a real-time 3D glass hero, an iOS-inspired glass UI, light and dark themes and a full SEO setup. Deployed to GitHub Pages.
Live: https://mhmtmutlu.github.io/
- Refraction hero — a
MeshTransmissionMaterialtorus knot rendered with React Three Fiber refracts a theme-aware aurora shader and particle field; it follows the pointer and shrinks into a corner seal on scroll. - Glass design system — layered
backdrop-filtersurfaces with sheen, grain and an optional SVG displacement “lens” filter where supported, defined as Tailwind utilities and CSS custom properties. - Light / dark theme — system preference with manual override, no flash on load, persisted across view transitions; the 3D scene re-colours itself on theme change.
- Bilingual — English at
/, Turkish at/tr/, withhreflangalternates, a localized sitemap and per-locale Open Graph images. - Command palette —
⌘K/Ctrl Knavigation, theme and language switching, built oncmdk. - Motion — Lenis smooth scrolling synchronised with GSAP ScrollTrigger reveals; everything degrades gracefully under
prefers-reduced-motionand without WebGL. - SEO — canonical URLs, Open Graph and Twitter cards, build-time OG images (satori + resvg), JSON-LD (
Person,WebSite,CreativeWork,BreadcrumbList), sitemap and robots.
| Area | Choice |
|---|---|
| Framework | Astro 7 — static output, React islands, Content Layer collections, i18n routing, View Transitions |
| UI | React 19 (islands only), TypeScript (strict) |
| Styling | Tailwind CSS v4 with design tokens in @theme, custom glass utilities |
| 3D | three.js, @react-three/fiber, @react-three/drei |
| Motion | GSAP + ScrollTrigger, Lenis |
| Command palette | cmdk |
| OG images | satori + @resvg/resvg-js |
| Fonts | Geist, Geist Mono, Instrument Serif (self-hosted via Fontsource) |
| Tooling | @astrojs/check, GitHub Actions |
src/
├── components/
│ ├── Home.astro # Home page composition
│ ├── ProjectPage.astro # Project detail template
│ ├── SEO.astro # Head tags, hreflang, JSON-LD
│ ├── sections/ # Hero, About, Experience, Skills, Projects, Education, Contact, Footer
│ ├── three/ # HeroScene (lazy loader) + GlassScene (R3F scene)
│ └── ui/ # Dock, CommandPalette, SectionHeader, LensFilter
├── content/projects/{en,tr}/ # Project case studies (Markdown + frontmatter)
├── data/ # profile, social, experience, skills, education
├── i18n/ # UI strings and locale helpers
├── layouts/Base.astro # Document shell, theme bootstrap, dock, palette
├── lib/ # theme, scroll and OG rendering helpers
├── pages/ # Routes (EN at root, TR under /tr)
├── scripts/motion.ts # Lenis + GSAP orchestration
└── styles/ # global.css (tokens, glass system), cmdk.css
scripts/og.mjs # Pre-build Open Graph image generation
- Zero JS by default. Content pages ship as plain HTML; only the hero scene (
client:only) and the command palette (client:idle) hydrate. - Deferred 3D.
HeroScene.tsxis a 3 KB loader that waits for an idle frame, then lazy-importsGlassScene.tsx(three.js + R3F). A CSS glass placeholder covers the hero until the first WebGL frame and remains as the static hero when WebGL is unavailable or reduced motion is requested. The environment map is generated withPMREMGeneratorfrom emissive panels, so no HDR loaders ship. - Single source of content. Experience, skills and profile live in typed data modules; project case studies are a content collection keyed by
key+localeso EN and TR entries stay paired. - One animation clock. Lenis drives GSAP's ticker, and the R3F scene reads scroll position independently, so scroll, DOM reveals and WebGL stay in sync.
- Theme without flash. An inline script sets
data-themebefore paint and re-applies it after view-transition swaps; components listen to athemechangeevent. - OG generation is a separate process.
scripts/og.mjsruns beforeastro buildbecause the resvg native module cannot share a process with Tailwind's native engine.
Requires Node 22.12 or newer.
npm install
npm run dev # http://localhost:4321/
npm run build # generates OG images, then builds to dist/
npm run preview
npm run check # Astro + TypeScript diagnostics| Edit | File |
|---|---|
| Name, role, links | src/data/profile.ts, src/data/social.ts |
| Experience | src/data/experience.ts |
| Skills | src/data/skills.ts |
| Education | src/data/education.ts |
| UI copy (EN/TR) | src/i18n/ui.ts |
| Projects | src/content/projects/en/*.md and src/content/projects/tr/*.md |
To add a project, create a Markdown file with the same key in both locale folders. Frontmatter fields: title, summary, key, locale, year, role, stack, order, optional url, repo, accent.
Pushes to main trigger .github/workflows/deploy.yml, which builds the site and publishes it with GitHub Pages (set Settings → Pages → Source to GitHub Actions).
Files in public/ are copied to the site root as is. public/app-ads.txt (AdMob authorized sellers for Zirve) is served at /app-ads.txt and is not linked from any page.
The build reads two environment variables:
| Variable | Default | Purpose |
|---|---|---|
SITE_URL |
https://mhmtmutlu.github.io |
Absolute origin used for canonical URLs, sitemap and OG images |
BASE_PATH |
/ |
Sub-path the site is served from |
- Add
public/CNAMEcontaining the domain. - Set
SITE_URLtohttps://<domain>andBASE_PATHto/in the workflow. - Point DNS at GitHub Pages (
Arecords185.199.108.153,.109.153,.110.153,.111.153;www→CNAME mhmtmutlu.github.io). - Enable Enforce HTTPS in the repository's Pages settings.
Evergreen browsers. The SVG displacement “lens” is a progressive enhancement for Chromium; other engines get the frosted-glass fallback. WebGL-less environments and users with reduced-motion preferences receive a static hero.
Source code is released under the MIT License. Personal content (texts, project descriptions, images) is © Mehmet Mutlu and not covered by the license.