feat(footer): implement responsive 3D isometric SVG footer scene with theme tokens
This commit is contained in:
128
src/components/IsometricFooter/IsometricFooter.tsx
Normal file
128
src/components/IsometricFooter/IsometricFooter.tsx
Normal 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'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user