11"use client" ;
2- import { motion } from "framer-motion" ;
2+ import { useState } from "react" ;
3+ import { motion , AnimatePresence } from "framer-motion" ;
4+ import { FiCopy , FiCheck , FiDownload } from "react-icons/fi" ;
35import { profile , socials } from "@/data/portfolio" ;
46import { getIcon } from "./icons" ;
57import Tilt from "./Tilt" ;
68
79export default function Contact ( ) {
10+ const [ copied , setCopied ] = useState ( false ) ;
11+
12+ const copyEmail = async ( ) => {
13+ try {
14+ await navigator . clipboard . writeText ( profile . email ) ;
15+ } catch {
16+ const t = document . createElement ( "textarea" ) ;
17+ t . value = profile . email ;
18+ document . body . appendChild ( t ) ;
19+ t . select ( ) ;
20+ document . execCommand ( "copy" ) ;
21+ document . body . removeChild ( t ) ;
22+ }
23+ setCopied ( true ) ;
24+ setTimeout ( ( ) => setCopied ( false ) , 1800 ) ;
25+ } ;
26+
827 return (
9- < section
10- id = "contact"
11- className = "py-24 border-t border-line relative overflow-hidden"
12- >
28+ < section id = "contact" className = "py-24 border-t border-line relative overflow-hidden" >
1329 < div className = "pointer-events-none absolute inset-0 -z-10" >
1430 < div className = "absolute bottom-0 left-1/2 -translate-x-1/2 w-96 h-96 rounded-full bg-accent2/15 blur-3xl" />
1531 </ div >
@@ -26,56 +42,96 @@ export default function Contact() {
2642 Let's build something.
2743 </ motion . h2 >
2844 < p className = "mt-4 text-muted max-w-md mx-auto" >
29- Have a project or role in mind? Reach me on any of these.
45+ Have a project, role or training in mind? Reach me on any of these.
3046 </ p >
3147 </ div >
3248
33- < div
34- className = "mt-12 grid grid-cols-2 sm: grid-cols-3 gap-4 max-w-3xl mx-auto"
35- style = { { perspective : "1000px" } }
36- >
37- { socials . map ( ( s , i ) => {
38- const Icon = getIcon ( s . icon ) ;
39- return (
40- < motion . a
41- key = { s . name }
42- href = { s . url }
43- target = "_blank"
44- rel = "noopener noreferrer"
45- data-hover
46- initial = { { opacity : 0 , y : 20 } }
47- whileInView = { { opacity : 1 , y : 0 } }
48- viewport = { { once : true } }
49- transition = { { duration : 0.5 , delay : i * 0.06 } }
50- >
51- < Tilt className = "glass rounded-2xl p-5 h-full hover:border-accent/60 transition-colors" >
52- < div style = { { transform : "translateZ(35px )" } } >
53- < span className = "w-11 h-11 rounded-xl bg-gradient-to-br from-accent to-accent2 flex items-center justify-center text-bg mb-3 " >
54- < Icon size = { 20 } />
49+ < div className = "mt-14 max-w-4xl mx-auto grid md:grid-cols-[1fr_260px] gap-6 items-stretch" >
50+ < div className = "grid grid-cols-3 grid-rows-2 gap-4" style = { { perspective : "1000px" } } >
51+ { socials . map ( ( s , i ) => {
52+ const Icon = getIcon ( s . icon ) ;
53+ return (
54+ < motion . a
55+ key = { s . name }
56+ href = { s . url }
57+ target = "_blank"
58+ rel = "noopener noreferrer"
59+ data-hover
60+ initial = { { opacity : 0 , y : 16 } }
61+ whileInView = { { opacity : 1 , y : 0 } }
62+ viewport = { { once : true } }
63+ transition = { { duration : 0.4 , delay : i * 0.05 } }
64+ title = { s . handle }
65+ className = "block"
66+ >
67+ < Tilt max = { 12 } className = "glass rounded-2xl h-full flex flex-col items-center justify-center text-center gap-2 p-4 hover:border-accent/60 transition-colors" >
68+ < span style = { { transform : "translateZ(28px )" } }
69+ className = "w-11 h-11 rounded-xl bg-gradient-to-br from-accent to-accent2 flex items-center justify-center text-bg" >
70+ < Icon size = { 18 } />
5571 </ span >
56- < div className = "font-semibold text-sm" > { s . name } </ div >
57- < div className = "text-muted text-xs mt-0.5 truncate" >
58- { s . handle }
59- </ div >
60- </ div >
61- </ Tilt >
62- </ motion . a >
63- ) ;
64- } ) }
65- </ div >
72+ < span style = { { transform : "translateZ(18px)" } } className = "text-sm font-medium" >
73+ { s . name }
74+ </ span >
75+ </ Tilt >
76+ </ motion . a >
77+ ) ;
78+ } ) }
79+ </ div >
6680
67- < div className = "text-center mt-12" >
68- < a
69- href = { `mailto:${ profile . email } ` }
70- className = "text-lg text-gradient font-semibold hover:opacity-80 transition"
81+ < motion . div
82+ initial = { { opacity : 0 , scale : 0.95 } }
83+ whileInView = { { opacity : 1 , scale : 1 } }
84+ viewport = { { once : true } }
85+ transition = { { duration : 0.5 } }
86+ className = "glass rounded-2xl p-5 flex flex-col items-center justify-center gap-3"
7187 >
72- { profile . email }
73- </ a >
88+ { /* eslint-disable-next-line @next/next/no-img-element */ }
89+ < img src = "/qr.png" alt = "Scan to save my contact"
90+ className = "w-40 h-40 rounded-lg bg-white p-2 object-contain" />
91+ < p className = "text-xs text-muted text-center" > Scan to save my contact 📇</ p >
92+ </ motion . div >
93+ </ div >
94+
95+ < div className = "mt-8 flex flex-wrap items-center justify-center gap-4" >
96+ < div className = "relative" >
97+ < button
98+ data-hover
99+ onClick = { copyEmail }
100+ className = "flex items-center gap-2 px-5 py-3 rounded-full border border-line hover:border-accent hover:text-accent transition-colors text-sm font-medium"
101+ >
102+ { copied ? < FiCheck size = { 16 } /> : < FiCopy size = { 16 } /> }
103+ { profile . email }
104+ </ button >
105+ < AnimatePresence >
106+ { copied && (
107+ < motion . span
108+ initial = { { opacity : 0 , y : 6 , scale : 0.9 } }
109+ animate = { { opacity : 1 , y : 0 , scale : 1 } }
110+ exit = { { opacity : 0 , y : - 6 , scale : 0.9 } }
111+ className = "absolute -top-9 left-1/2 -translate-x-1/2 text-xs font-medium px-3 py-1.5 rounded-full bg-accent text-bg whitespace-nowrap"
112+ >
113+ Copied! ✓
114+ </ motion . span >
115+ ) }
116+ </ AnimatePresence >
117+ </ div >
118+
119+ { profile . resumeUrl && (
120+ < a
121+ href = { profile . resumeUrl }
122+ target = "_blank"
123+ rel = "noopener noreferrer"
124+ download = "Krishna_Andhe_CV.pdf"
125+ data-hover
126+ className = "flex items-center gap-2 px-5 py-3 rounded-full bg-accent text-bg font-semibold hover:opacity-90 transition"
127+ >
128+ < FiDownload size = { 16 } /> Download Résumé
129+ </ a >
130+ ) }
74131 </ div >
75132
76- < footer className = "mt-16 pt-8 border-t border-line text-sm text-muted text-center" >
77- © { new Date ( ) . getFullYear ( ) } { profile . name } · Built with Next.js &
78- Tailwind CSS
133+ < footer className = "mt-20 pt-8 border-t border-line text-sm text-muted text-center" >
134+ © { new Date ( ) . getFullYear ( ) } { profile . name } · Built with Next.js & Tailwind CSS
79135 </ footer >
80136 </ div >
81137 </ section >
0 commit comments