Where I Built the Foundation
+{copy.title}
Where I've Made an Impact
+{copy.title}
- Hi, I'm
- Amirhossein Khalili
+
+ {copy.greeting}
+ {copy.name}
-
- Product engineer building useful, resilient software. -
+{copy.subtitle}
{/* Action Buttons */} diff --git a/src/components/IsometricFooter/IsoTypes.ts b/src/components/IsometricFooter/IsoTypes.ts index 3e1b12b..23c5c58 100644 --- a/src/components/IsometricFooter/IsoTypes.ts +++ b/src/components/IsometricFooter/IsoTypes.ts @@ -1,4 +1,4 @@ -import React from 'react'; +import type React from 'react'; export type FooterContactId = 'phone' | 'linkedin' | 'github' | 'telegram'; @@ -72,6 +72,7 @@ export interface IsoSignpostProps { export interface IsometricSceneProps { activePost: FooterContactId | null; + ariaLabel: string; onPostHover?: (id: FooterContactId | null) => void; onPostClick?: (id: FooterContactId) => void; } diff --git a/src/components/IsometricFooter/IsometricFooter.module.css b/src/components/IsometricFooter/IsometricFooter.module.css index bf28185..d2cb8db 100644 --- a/src/components/IsometricFooter/IsometricFooter.module.css +++ b/src/components/IsometricFooter/IsometricFooter.module.css @@ -39,6 +39,11 @@ animation: footerFadeIn 0.7s ease-out forwards; } +:global(html[lang='fa']) .headerTitle { + font-family: 'Peyda', 'IRANYekanXVF', sans-serif; + font-weight: 900; +} + .headerSubtitle { font-size: clamp(0.85rem, 2.5vw, 1.05rem); color: var(--text-secondary); @@ -91,6 +96,8 @@ } .emailText { + direction: ltr; + unicode-bidi: isolate; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -245,7 +252,7 @@ display: flex; flex-direction: column; min-width: 0; - text-align: left; + text-align: start; } .contactCardName { @@ -258,6 +265,8 @@ } .contactCardLabel { + direction: ltr; + unicode-bidi: isolate; font-size: 0.88rem; font-weight: 600; color: var(--text-primary); diff --git a/src/components/IsometricFooter/IsometricFooter.tsx b/src/components/IsometricFooter/IsometricFooter.tsx index 01dcab9..4e0a44d 100644 --- a/src/components/IsometricFooter/IsometricFooter.tsx +++ b/src/components/IsometricFooter/IsometricFooter.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useState } from 'react'; +import { useState } from 'react'; import type { ContactItem, FooterContactId } from './IsoTypes'; import IsometricScene from './IsometricScene'; import { @@ -11,6 +11,7 @@ import { TelegramIcon, } from './IsoElements'; import styles from './IsometricFooter.module.css'; +import { messagesByLocale, type Locale } from '@/i18n/content'; export const contacts: ContactItem[] = [ { @@ -43,7 +44,12 @@ export const contacts: ContactItem[] = [ }, ]; -export default function IsometricFooter() { +export default function IsometricFooter({ locale }: { locale: Locale }) { + const copy = messagesByLocale[locale].footer; + const localizedContacts = contacts.map((contact) => ({ + ...contact, + name: copy[contact.id], + })); const [activePost, setActivePost] = useState- Let's build something meaningful. + {copy.title}
- Open to opportunities and interesting projects. + {copy.subtitle}
{/* Email Pill Button */} @@ -74,12 +80,14 @@ export default function IsometricFooter() {Milestones Along the Way
+{copy.title}
- Products I've Brought to Life + {copy.title}
{project.title}
- {project.link && ( + {project.link && isRtl && ( + + )} ++ {project.title} +
+ {project.link && !isRtl && (From Pixel to Product
+ {copy.eyebrow} ++ {copy.title} +
-- I began as a digital artist, designing visual stories. Curiosity turned - into code, and code into systems that solve real problems. Today, I - build products end-to-end using agentic workflows that blend creativity, - engineering, and product thinking. -
+{copy.description}
-
- {steps.map((step) => {
- const StepIcon = step.icon;
+ {copy.steps.map((step, index) => {
+ const StepIcon = stepIcons[index];
return (
-
- +
-
diff --git a/src/components/ScenicHeroJourney.module.css b/src/components/ScenicHeroJourney.module.css
index c6a4b3a..3bec7f8 100644
--- a/src/components/ScenicHeroJourney.module.css
+++ b/src/components/ScenicHeroJourney.module.css
@@ -100,6 +100,11 @@
animation: landscapeFloatLeft 8s var(--motion-ease-in-out) infinite alternate;
}
+:global(html[dir='rtl']) .heroBackdrop {
+ transform: scaleX(-1);
+ transform-origin: center;
+}
+
.backdropRight {
top: clamp(80px, 9vw, 110px);
right: clamp(-240px, calc(22% - 29vw), -40px);
diff --git a/src/components/ScenicHeroJourney.tsx b/src/components/ScenicHeroJourney.tsx
index 32254cb..2c41772 100644
--- a/src/components/ScenicHeroJourney.tsx
+++ b/src/components/ScenicHeroJourney.tsx
@@ -1,6 +1,5 @@
'use client';
-import React from 'react';
import { HeroSection } from './HeroSection';
import { RiverJourney } from './RiverJourney';
import {
@@ -10,6 +9,7 @@ import {
HeroIllustrationMoon,
} from './HeroIllustrations';
import styles from './ScenicHeroJourney.module.css';
+import { messagesByLocale, type Locale } from '@/i18n/content';
function HeroBackdrop() {
return (
@@ -22,15 +22,17 @@ function HeroBackdrop() {
);
}
-export const ScenicHeroJourney: React.FC = () => {
+export const ScenicHeroJourney = ({ locale }: { locale: Locale }) => {
+ const copy = messagesByLocale[locale];
+
return (
-); }; diff --git a/src/components/SiteLoader.module.css b/src/components/SiteLoader.module.css index 4b7b167..b3b7c8d 100644 --- a/src/components/SiteLoader.module.css +++ b/src/components/SiteLoader.module.css @@ -169,11 +169,15 @@ transition: transform 180ms var(--motion-ease-out); } +:global(html[dir='rtl']) .progressFill { + transform-origin: right center; +} + .progressValue { color: var(--text-secondary); font-size: 0.8125rem; font-variant-numeric: tabular-nums; - text-align: right; + text-align: end; } @keyframes riverArrive { diff --git a/src/components/SiteLoader.tsx b/src/components/SiteLoader.tsx index ef0f960..743d639 100644 --- a/src/components/SiteLoader.tsx +++ b/src/components/SiteLoader.tsx @@ -6,6 +6,7 @@ import { experiences } from '@/data/experience'; import { milestones } from '@/data/milestones'; import { projects } from '@/data/projects'; import { skillsByDiscipline } from '@/data/skills'; +import { messagesByLocale, type Locale } from '@/i18n/content'; import styles from './SiteLoader.module.css'; const DARK_PROJECT_IDS = new Set([ @@ -78,7 +79,8 @@ const preloadImage = (src: string, onSettled: () => void) => type LoaderPhase = 'loading' | 'leaving' | 'hidden'; -export function SiteLoader() { +export function SiteLoader({ locale }: { locale: Locale }) { + const copy = messagesByLocale[locale].loader; const [progress, setProgress] = useState(0); const [phase, setPhase] = useState
+ -- + + ('loading'); @@ -149,7 +151,7 @@ export function SiteLoader() { role="status" aria-live="polite" aria-busy={phase === 'loading'} - aria-label={`Loading portfolio: ${progress}%`} + aria-label={copy.ariaLabel(progress)} > @@ -163,15 +165,15 @@ export function SiteLoader() {
- Amirhossein Khalili + {copy.signature}
-Assembling the experience
+{copy.message}