feat(footer): implement responsive 3D isometric SVG footer scene with theme tokens
Some checks failed
Test and deploy / Verify production build (push) Has been cancelled
Test and deploy / Deploy production (push) Has been cancelled

This commit is contained in:
2026-09-08 11:31:36 +03:30
parent 0ed36b3c32
commit 88adced09f
19 changed files with 1471 additions and 954 deletions

View File

@@ -0,0 +1,128 @@
'use client';
import React, { useState } from 'react';
import type { ContactItem, FooterContactId } from './IsoTypes';
import IsometricScene from './IsometricScene';
import {
MailIcon,
PhoneIcon,
LinkedInIcon,
GitHubIcon,
TelegramIcon,
} from './IsoElements';
import styles from './IsometricFooter.module.css';
export const contacts: ContactItem[] = [
{
id: 'phone',
name: 'Phone',
label: '09388263077',
href: 'tel:09388263077',
Icon: PhoneIcon,
},
{
id: 'linkedin',
name: 'LinkedIn',
label: '@amiirkhl',
href: 'https://linkedin.com/in/amiirkhl',
Icon: LinkedInIcon,
},
{
id: 'github',
name: 'GitHub',
label: 'git.amiirkhl.ir',
href: 'https://git.amiirkhl.ir',
Icon: GitHubIcon,
},
{
id: 'telegram',
name: 'Telegram',
label: '@amiirkhl',
href: 'https://t.me/amiirkhl',
Icon: TelegramIcon,
},
];
export default function IsometricFooter() {
const [activePost, setActivePost] = useState<FooterContactId | null>(null);
const handlePostClick = (id: FooterContactId) => {
const item = contacts.find((c) => c.id === id);
if (item && item.href) {
if (item.href.startsWith('tel:')) {
window.location.href = item.href;
} else {
window.open(item.href, '_blank', 'noopener,noreferrer');
}
}
};
return (
<div className={styles.footerContainer}>
{/* Top Header & Contact Section */}
<header className={styles.footerHeader}>
<h2 className={styles.headerTitle}>
Let&apos;s build something meaningful.
</h2>
<p className={styles.headerSubtitle}>
Open to opportunities and interesting projects.
</p>
{/* Email Pill Button */}
<div className={styles.emailWrapper}>
<a
href="mailto:amirhosseinkhalili901@gmail.com"
className={styles.emailPill}
aria-label="Send email to amirhosseinkhalili901@gmail.com"
>
<span className={styles.emailIcon}>
<MailIcon size={16} color="currentColor" />
</span>
<span className={styles.emailText}>amirhosseinkhalili901@gmail.com</span>
</a>
</div>
</header>
{/* 3D Isometric SVG Scene */}
<div className={styles.sceneContainer}>
<IsometricScene
activePost={activePost}
onPostHover={setActivePost}
onPostClick={handlePostClick}
/>
</div>
{/* Responsive Synchronized Contact Cards Section */}
<section className={styles.contactCardsSection} aria-label="Social and contact links">
<div className={styles.contactCardsGrid}>
{contacts.map((contact) => {
const IconComponent = contact.Icon;
const isHovered = activePost === contact.id;
return (
<a
key={contact.id}
href={contact.href}
target={contact.href.startsWith('tel:') ? undefined : '_blank'}
rel={contact.href.startsWith('tel:') ? undefined : 'noopener noreferrer'}
className={`${styles.contactCard} ${isHovered ? styles.active : ''}`}
onMouseEnter={() => setActivePost(contact.id)}
onMouseLeave={() => setActivePost(null)}
onClick={() => setActivePost(contact.id)}
aria-label={`${contact.name}: ${contact.label}`}
>
<div className={styles.contactCardIconWrap}>
<IconComponent size={18} color="currentColor" />
</div>
<div className={styles.contactCardInfo}>
<span className={styles.contactCardName}>{contact.name}</span>
<span className={styles.contactCardLabel}>{contact.label}</span>
</div>
</a>
);
})}
</div>
</section>
</div>
);
}