enhance footer mobile style & implement footer in each page

This commit is contained in:
Mohammed Al-yaseen
2025-08-02 15:36:11 +03:00
parent 7bcbea35e1
commit 5f050a7a60
8 changed files with 451 additions and 210 deletions

View File

@@ -14,6 +14,7 @@ import { Avatar, Card, Col, Row } from "antd";
import Paragraph from "antd/es/typography/Paragraph";
import Title from "antd/es/typography/Title";
import { motion } from "framer-motion";
import Footer from "../components/Footer/Footer";
import styles from "./page.module.css";
export default function AboutPage() {
@@ -391,6 +392,8 @@ export default function AboutPage() {
</Row>
</div>
</section>
<Footer />
</main>
);
}

View File

@@ -73,17 +73,30 @@
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
transition: all 0.3s ease;
padding: 8px 0;
}
.footerColumnTitle:hover {
color: #a855f7;
}
.footerColumnIcon {
color: #a855f7;
font-size: 1.2rem;
transition: transform 0.3s ease;
}
.footerColumnTitle:hover .footerColumnIcon {
transform: scale(1.1);
}
.footerLinks {
list-style: none;
padding: 0;
margin: 0;
transition: all 0.3s ease;
}
.footerLink {
@@ -98,12 +111,17 @@
display: inline-flex;
align-items: center;
gap: 8px;
padding: 4px 0;
padding: 8px 0;
min-height: 44px; /* Better touch target */
border-radius: 6px;
padding-left: 8px;
padding-right: 8px;
}
.footerLink a:hover {
color: #a855f7;
transform: translateX(6px);
background: rgba(168, 85, 247, 0.1);
}
.footerLinkIcon {
@@ -124,6 +142,7 @@
max-width: 600px;
margin-left: auto;
margin-right: auto;
line-height: 1.6;
}
.socialLinks {
@@ -131,6 +150,7 @@
justify-content: center;
gap: 20px;
margin-bottom: 30px;
flex-wrap: wrap;
}
.socialLink {
@@ -146,6 +166,8 @@
transition: all 0.3s ease;
backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.1);
min-width: 45px; /* Ensure minimum touch target */
min-height: 45px;
}
.socialLink:hover {
@@ -155,20 +177,77 @@
box-shadow: 0 8px 25px rgba(139, 92, 246, 0.3);
}
.socialLink:active {
transform: translateY(-1px);
box-shadow: 0 4px 15px rgba(139, 92, 246, 0.4);
}
.copyright {
color: #64748b;
font-size: 0.85rem;
line-height: 1.6;
display: flex;
flex-direction: column;
gap: 8px;
align-items: center;
}
.copyright a {
color: #94a3b8;
text-decoration: none;
transition: color 0.3s ease;
padding: 4px 8px;
border-radius: 4px;
min-height: 32px;
display: inline-flex;
align-items: center;
}
.copyright a:hover {
color: #a855f7;
background: rgba(168, 85, 247, 0.1);
}
/* Mobile-specific enhancements */
.mobileFooterColumn {
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
padding-bottom: 20px;
margin-bottom: 20px;
}
.mobileFooterColumn:last-child {
border-bottom: none;
margin-bottom: 0;
}
.mobileFooterTitle {
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
padding: 12px 0;
user-select: none;
}
.mobileFooterTitle::after {
content: '+';
font-size: 1.2rem;
color: #a855f7;
transition: transform 0.3s ease;
}
.mobileFooterTitle.expanded::after {
transform: rotate(45deg);
}
.mobileFooterLinks {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
.mobileFooterLinks.expanded {
max-height: 300px;
}
/* Responsive Design */
@@ -184,36 +263,99 @@
padding: 60px 0 30px;
}
.footerContainer {
padding: 0 16px;
}
.footerContent {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 30px;
grid-template-columns: 1fr;
gap: 0;
margin-bottom: 40px;
}
.footerColumn {
text-align: center;
text-align: left;
margin-bottom: 0;
}
.footerColumnTitle {
justify-content: center;
justify-content: space-between;
font-size: 1.05rem;
padding: 16px 0;
margin-bottom: 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.footerColumnTitle::after {
content: '+';
font-size: 1.3rem;
color: #a855f7;
transition: transform 0.3s ease;
}
.footerColumnTitle.expanded::after {
transform: rotate(45deg);
}
.footerLinks {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
padding-top: 0;
}
.footerLinks.expanded {
max-height: 300px;
padding-top: 16px;
}
.footerLink {
margin-bottom: 8px;
}
.footerLink a {
justify-content: center;
justify-content: flex-start;
font-size: 0.95rem;
padding: 12px 0;
min-height: 48px;
border-radius: 8px;
padding-left: 12px;
padding-right: 12px;
}
.footerLink a:hover {
transform: translateX(4px);
}
.socialLinks {
gap: 15px;
gap: 16px;
margin-bottom: 25px;
}
.socialLink {
width: 40px;
height: 40px;
width: 48px;
height: 48px;
min-width: 48px;
min-height: 48px;
}
.footerBottomText {
font-size: 0.9rem;
font-size: 0.95rem;
margin-bottom: 25px;
padding: 0 16px;
}
.copyright {
font-size: 0.8rem;
flex-direction: column;
gap: 12px;
padding: 0 16px;
}
.copyright a {
padding: 8px 12px;
min-height: 36px;
border-radius: 6px;
}
}
@@ -222,29 +364,131 @@
padding: 50px 0 25px;
}
.footerContainer {
padding: 0 12px;
}
.footerContent {
grid-template-columns: 1fr;
gap: 25px;
gap: 0;
margin-bottom: 30px;
}
.footerColumnTitle {
font-size: 1rem;
padding: 14px 0;
}
.footerColumnTitle::after {
font-size: 1.2rem;
}
.footerLinks.expanded {
padding-top: 12px;
}
.footerLink a {
font-size: 0.85rem;
font-size: 0.9rem;
padding: 10px 0;
min-height: 44px;
padding-left: 10px;
padding-right: 10px;
}
.socialLinks {
gap: 12px;
margin-bottom: 20px;
}
.socialLink {
width: 35px;
height: 35px;
width: 44px;
height: 44px;
min-width: 44px;
min-height: 44px;
}
.footerBottomText {
font-size: 0.9rem;
margin-bottom: 20px;
padding: 0 12px;
}
.copyright {
font-size: 0.8rem;
font-size: 0.75rem;
gap: 10px;
padding: 0 12px;
}
.copyright a {
padding: 6px 10px;
min-height: 32px;
font-size: 0.75rem;
}
}
@media (max-width: 360px) {
.footer {
padding: 40px 0 20px;
}
.footerContainer {
padding: 0 8px;
}
.footerColumnTitle {
font-size: 0.95rem;
padding: 12px 0;
}
.footerLink a {
font-size: 0.85rem;
padding: 8px 0;
min-height: 40px;
padding-left: 8px;
padding-right: 8px;
}
.socialLinks {
gap: 10px;
}
.socialLink {
width: 40px;
height: 40px;
min-width: 40px;
min-height: 40px;
}
.footerBottomText {
font-size: 0.85rem;
padding: 0 8px;
}
.copyright {
font-size: 0.7rem;
padding: 0 8px;
}
}
/* Touch device optimizations */
@media (hover: none) and (pointer: coarse) {
.footerLink a:hover {
transform: none;
background: rgba(168, 85, 247, 0.1);
}
.socialLink:hover {
transform: none;
}
.socialLink:active {
transform: scale(0.95);
}
.footerColumnTitle:hover {
color: white;
}
.footerColumnTitle:hover .footerColumnIcon {
transform: none;
}
}

View File

@@ -15,12 +15,26 @@ import {
} from '@ant-design/icons';
import { Typography } from 'antd';
import { motion } from 'framer-motion';
import React from 'react';
import React, { useEffect, useState } from 'react';
import styles from './Footer.module.css';
const { Paragraph } = Typography;
const Footer: React.FC = () => {
const [expandedSections, setExpandedSections] = useState<{ [key: string]: boolean }>({});
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth <= 768);
};
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}, []);
const containerVariants = {
hidden: { opacity: 0 },
visible: {
@@ -40,6 +54,70 @@ const Footer: React.FC = () => {
}
};
const toggleSection = (sectionKey: string) => {
if (isMobile) {
setExpandedSections(prev => ({
...prev,
[sectionKey]: !prev[sectionKey]
}));
}
};
const footerSections = [
{
key: 'company',
title: 'Company',
icon: <TeamOutlined className={styles.footerColumnIcon} />,
links: [
{ href: '/about', text: 'About Us' },
{ href: '/services', text: 'Our Services' },
{ href: '/projects', text: 'Portfolio' },
{ href: '/contact', text: 'Contact Us' }
]
},
{
key: 'services',
title: 'Services',
icon: <BulbOutlined className={styles.footerColumnIcon} />,
links: [
{ href: '/services#webdev', text: 'Web Development' },
{ href: '/services#mobile', text: 'Mobile Apps' },
{ href: '/services#ai', text: 'AI Solutions' },
{ href: '/services#cloud', text: 'Cloud Services' }
]
},
{
key: 'resources',
title: 'Resources',
icon: <TrophyOutlined className={styles.footerColumnIcon} />,
links: [
{ href: '/blog', text: 'Blog & Insights' },
{ href: '/case-studies', text: 'Case Studies' },
{ href: '/whitepapers', text: 'Whitepapers' },
{ href: '/webinars', text: 'Webinars' }
]
},
{
key: 'contact',
title: 'Contact Info',
icon: <MailOutlined className={styles.footerColumnIcon} />,
links: [
{ href: 'mailto:info@techmaster.com', text: 'info@techmaster.com' },
{ href: 'tel:+15551234567', text: '+1 (555) 123-4567' },
{ href: '#', text: 'San Francisco, CA' },
{ href: '/support', text: '24/7 Support' }
]
}
];
const getLinkIcon = (href: string) => {
if (href.startsWith('mailto:')) return <MailOutlined className={styles.footerLinkIcon} />;
if (href.startsWith('tel:')) return <PhoneOutlined className={styles.footerLinkIcon} />;
if (href.includes('support')) return <ArrowRightOutlined className={styles.footerLinkIcon} />;
if (href.includes('San Francisco')) return <EnvironmentOutlined className={styles.footerLinkIcon} />;
return <ArrowRightOutlined className={styles.footerLinkIcon} />;
};
return (
<footer className={styles.footer}>
<div className={styles.footerBackground}>
@@ -55,137 +133,49 @@ const Footer: React.FC = () => {
viewport={{ once: true }}
className={styles.footerContent}
>
<motion.div variants={itemVariants} className={styles.footerColumn}>
<div className={styles.footerColumnTitle}>
<TeamOutlined className={styles.footerColumnIcon} />
Company
</div>
<ul className={styles.footerLinks}>
<li className={styles.footerLink}>
<a href="/about">
<ArrowRightOutlined className={styles.footerLinkIcon} />
About Us
</a>
</li>
<li className={styles.footerLink}>
<a href="/services">
<ArrowRightOutlined className={styles.footerLinkIcon} />
Our Services
</a>
</li>
<li className={styles.footerLink}>
<a href="/projects">
<ArrowRightOutlined className={styles.footerLinkIcon} />
Portfolio
</a>
</li>
<li className={styles.footerLink}>
<a href="/contact">
<ArrowRightOutlined className={styles.footerLinkIcon} />
Contact Us
</a>
</li>
</ul>
</motion.div>
<motion.div variants={itemVariants} className={styles.footerColumn}>
<div className={styles.footerColumnTitle}>
<BulbOutlined className={styles.footerColumnIcon} />
Services
</div>
<ul className={styles.footerLinks}>
<li className={styles.footerLink}>
<a href="/services#webdev">
<ArrowRightOutlined className={styles.footerLinkIcon} />
Web Development
</a>
</li>
<li className={styles.footerLink}>
<a href="/services#mobile">
<ArrowRightOutlined className={styles.footerLinkIcon} />
Mobile Apps
</a>
</li>
<li className={styles.footerLink}>
<a href="/services#ai">
<ArrowRightOutlined className={styles.footerLinkIcon} />
AI Solutions
</a>
</li>
<li className={styles.footerLink}>
<a href="/services#cloud">
<ArrowRightOutlined className={styles.footerLinkIcon} />
Cloud Services
</a>
</li>
</ul>
</motion.div>
<motion.div variants={itemVariants} className={styles.footerColumn}>
<div className={styles.footerColumnTitle}>
<TrophyOutlined className={styles.footerColumnIcon} />
Resources
</div>
<ul className={styles.footerLinks}>
<li className={styles.footerLink}>
<a href="/blog">
<ArrowRightOutlined className={styles.footerLinkIcon} />
Blog & Insights
</a>
</li>
<li className={styles.footerLink}>
<a href="/case-studies">
<ArrowRightOutlined className={styles.footerLinkIcon} />
Case Studies
</a>
</li>
<li className={styles.footerLink}>
<a href="/whitepapers">
<ArrowRightOutlined className={styles.footerLinkIcon} />
Whitepapers
</a>
</li>
<li className={styles.footerLink}>
<a href="/webinars">
<ArrowRightOutlined className={styles.footerLinkIcon} />
Webinars
</a>
</li>
</ul>
</motion.div>
<motion.div variants={itemVariants} className={styles.footerColumn}>
<div className={styles.footerColumnTitle}>
<MailOutlined className={styles.footerColumnIcon} />
Contact Info
</div>
<ul className={styles.footerLinks}>
<li className={styles.footerLink}>
<a href="mailto:info@techmaster.com">
<MailOutlined className={styles.footerLinkIcon} />
info@techmaster.com
</a>
</li>
<li className={styles.footerLink}>
<a href="tel:+15551234567">
<PhoneOutlined className={styles.footerLinkIcon} />
+1 (555) 123-4567
</a>
</li>
<li className={styles.footerLink}>
<a href="#">
<EnvironmentOutlined className={styles.footerLinkIcon} />
San Francisco, CA
</a>
</li>
<li className={styles.footerLink}>
<a href="/support">
<ArrowRightOutlined className={styles.footerLinkIcon} />
24/7 Support
</a>
</li>
</ul>
</motion.div>
{footerSections.map((section) => (
<motion.div
key={section.key}
variants={itemVariants}
className={`${styles.footerColumn} ${isMobile ? styles.mobileFooterColumn : ''}`}
>
<div
className={`${styles.footerColumnTitle} ${isMobile && expandedSections[section.key] ? styles.expanded : ''}`}
onClick={() => toggleSection(section.key)}
role={isMobile ? 'button' : undefined}
tabIndex={isMobile ? 0 : undefined}
onKeyDown={(e) => {
if (isMobile && (e.key === 'Enter' || e.key === ' ')) {
e.preventDefault();
toggleSection(section.key);
}
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
{section.icon}
{section.title}
</div>
</div>
<ul className={`${styles.footerLinks} ${isMobile && expandedSections[section.key] ? styles.expanded : ''}`}>
{section.links.map((link, index) => (
<li key={index} className={styles.footerLink}>
<a
href={link.href}
onClick={(e) => {
// Prevent navigation if it's a placeholder link
if (link.href === '#') {
e.preventDefault();
}
}}
>
{getLinkIcon(link.href)}
{link.text}
</a>
</li>
))}
</ul>
</motion.div>
))}
</motion.div>
<motion.div
@@ -200,22 +190,46 @@ const Footer: React.FC = () => {
</Paragraph>
<div className={styles.socialLinks}>
<a href="#" className={styles.socialLink}>
<a
href="#"
className={styles.socialLink}
onClick={(e) => e.preventDefault()}
aria-label="Facebook"
>
<FacebookOutlined />
</a>
<a href="#" className={styles.socialLink}>
<a
href="#"
className={styles.socialLink}
onClick={(e) => e.preventDefault()}
aria-label="Twitter"
>
<TwitterOutlined />
</a>
<a href="#" className={styles.socialLink}>
<a
href="#"
className={styles.socialLink}
onClick={(e) => e.preventDefault()}
aria-label="LinkedIn"
>
<LinkedinOutlined />
</a>
<a href="#" className={styles.socialLink}>
<a
href="#"
className={styles.socialLink}
onClick={(e) => e.preventDefault()}
aria-label="Instagram"
>
<InstagramOutlined />
</a>
</div>
<div className={styles.copyright}>
© 2024 Tech Master. All rights reserved. | Privacy Policy | Terms of Service
<span>© 2024 Tech Master. All rights reserved.</span>
<div style={{ display: 'flex', gap: '16px', flexWrap: 'wrap', justifyContent: 'center' }}>
<a href="/privacy" onClick={(e) => e.preventDefault()}>Privacy Policy</a>
<a href="/terms" onClick={(e) => e.preventDefault()}>Terms of Service</a>
</div>
</div>
</motion.div>
</div>

View File

@@ -1,6 +1,6 @@
"use client";
import { BellOutlined, SearchOutlined, UserOutlined } from "@ant-design/icons";
import { BellOutlined, UserOutlined } from "@ant-design/icons";
import { Avatar, Button, Menu, MenuProps, Typography } from "antd";
import { motion } from "framer-motion";
import Link from "next/link";
@@ -81,22 +81,22 @@ const Header = () => {
className={styles.actionsSection}
>
<div className={styles.actionButtons}>
<Button
{/* <Button
type="text"
icon={<SearchOutlined />}
className={styles.actionButton}
/>
/> */}
<Button
type="text"
icon={<BellOutlined />}
className={styles.actionButton}
/>
<Button
{/* <Button
type="primary"
className={styles.ctaButton}
>
Get Started
</Button>
</Button> */}
<Avatar
icon={<UserOutlined />}
className={styles.userAvatar}

View File

@@ -1,23 +1,22 @@
"use client";
import {
CloseOutlined,
EyeOutlined,
GithubOutlined,
LeftOutlined,
LinkOutlined,
RightOutlined,
CloseOutlined,
EyeOutlined,
LeftOutlined,
LinkOutlined,
RightOutlined
} from "@ant-design/icons";
import {
Badge,
Button,
Card,
Carousel,
Col,
Modal,
Row,
Tag,
Typography,
Badge,
Button,
Card,
Carousel,
Col,
Modal,
Row,
Tag,
Typography,
} from "antd";
import { AnimatePresence, motion, useAnimation } from "framer-motion";
import Image from "next/image";
@@ -262,7 +261,7 @@ const ProjectsShowcaseClient: React.FC<ProjectsShowcaseClientProps> = ({
Live Demo
</Button>
)}
{selectedProject.githubUrl && (
{/* {selectedProject.githubUrl && (
<Button
icon={<GithubOutlined />}
href={selectedProject.githubUrl}
@@ -271,7 +270,7 @@ const ProjectsShowcaseClient: React.FC<ProjectsShowcaseClientProps> = ({
>
View Code
</Button>
)}
)} */}
</div>
</div>

View File

@@ -13,6 +13,7 @@ import {
import { Button, Form, Input, message, Typography } from "antd";
import { motion } from "framer-motion";
import { useState } from "react";
import Footer from "../components/Footer/Footer";
import styles from "./page.module.css";
const { Title, Paragraph, Text } = Typography;
@@ -405,6 +406,8 @@ export default function ContactPage() {
</div>
</div>
</section>
<Footer />
</main>
);
}

View File

@@ -3,10 +3,9 @@
import {
CloseOutlined,
EyeOutlined,
GithubOutlined,
LinkOutlined,
RocketOutlined,
UserOutlined,
UserOutlined
} from "@ant-design/icons";
import {
Badge,
@@ -561,7 +560,7 @@ export default function ProjectsPage() {
Live Demo
</Button>
)}
{selectedProject.githubUrl && (
{/* {selectedProject.githubUrl && (
<Button
icon={<GithubOutlined />}
href={selectedProject.githubUrl}
@@ -569,7 +568,7 @@ export default function ProjectsPage() {
>
View Code
</Button>
)}
)} */}
</div>
</div>

View File

@@ -9,11 +9,12 @@ import {
MobileOutlined,
RobotOutlined,
RocketOutlined,
ShoppingOutlined
ShoppingOutlined,
} from "@ant-design/icons";
import { Button, Typography } from "antd";
import { motion } from "framer-motion";
import React, { useEffect, useState } from "react";
import Footer from "../components/Footer/Footer";
import styles from "./page.module.css";
const { Title, Paragraph } = Typography;
@@ -281,7 +282,7 @@ export default function ServicesPage() {
variants={itemVariants}
whileHover={{
scale: 1.02,
transition: { duration: 0.2 }
transition: { duration: 0.2 },
}}
>
<div className={styles.serviceCard}>
@@ -292,7 +293,7 @@ export default function ServicesPage() {
{service.icon}
</div>
<div className={styles.serviceBadge}>
Service {String(index + 1).padStart(2, '0')}
Service {String(index + 1).padStart(2, "0")}
</div>
</div>
@@ -380,29 +381,7 @@ export default function ServicesPage() {
</div>
</section>
{/* CTA Section */}
<section className={styles.ctaSection}>
<div className={styles.ctaContainer}>
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
viewport={{ once: true }}
>
<Title level={2} className={styles.ctaTitle}>
Ready to Start Your{" "}
<span className={styles.gradientText}>Project</span>?
</Title>
<Paragraph className={styles.ctaSubtitle}>
Let&apos;s discuss how we can help bring your vision to life. Our team
is ready to create something amazing together.
</Paragraph>
<Button size="large" className={styles.ctaButton}>
Get Started Today <ArrowRightOutlined />
</Button>
</motion.div>
</div>
</section>
<Footer />
</main>
);
}