mirror of
https://github.com/liyunze-coding/RythonDev.git
synced 2026-09-22 07:44:26 +00:00
updated contact form
This commit is contained in:
parent
238e5dfdf7
commit
a4140819d6
13 changed files with 294 additions and 193 deletions
|
|
@ -1,4 +1,9 @@
|
|||
{
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-astro","prettier-plugin-tailwindcss"],
|
||||
"tabWidth": 4
|
||||
"plugins": [
|
||||
"prettier-plugin-svelte",
|
||||
"prettier-plugin-astro",
|
||||
"prettier-plugin-tailwindcss"
|
||||
],
|
||||
"tabWidth": 4,
|
||||
"useTabs": true
|
||||
}
|
||||
|
|
|
|||
BIN
public/fonts/Antonio-Bold.ttf
Normal file
BIN
public/fonts/Antonio-Bold.ttf
Normal file
Binary file not shown.
59
src/components/contact/ContactComponent.astro
Normal file
59
src/components/contact/ContactComponent.astro
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
---
|
||||
import { Image } from "astro:assets";
|
||||
import contactPhoto from "@/components/images/contact_photo.webp";
|
||||
import { AnimatedTooltip } from "@/components/ui/AnimatedToolTip";
|
||||
---
|
||||
|
||||
<div
|
||||
class="relative flex h-[400px] w-full flex-col-reverse items-start justify-center gap-10 lg:flex-row"
|
||||
>
|
||||
<div class="relative flex w-1/2 items-end justify-end">
|
||||
<Image
|
||||
src={contactPhoto}
|
||||
alt="Ryan"
|
||||
width={300}
|
||||
class="absolute top-0 right-0 aspect-[3/4] -translate-x-7 translate-y-3 rounded-3xl object-cover"
|
||||
/>
|
||||
<div
|
||||
class="absolute top-0 right-0 -z-10 aspect-[3/4] w-[300px] -translate-x-10 translate-y-5 rounded-3xl bg-[#c98703]"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex h-full w-1/2 flex-col items-start justify-center text-left"
|
||||
>
|
||||
<h1
|
||||
class="[font-family:'Antonio',sans-serif] text-5xl leading-14 font-bold uppercase"
|
||||
>
|
||||
have an idea? <br /> let's chat!
|
||||
</h1>
|
||||
|
||||
<div class="mt-10 flex w-fit items-center justify-center">
|
||||
<AnimatedTooltip
|
||||
href="mailto:rythondev@gmail.com"
|
||||
text="Email me!"
|
||||
client:load
|
||||
className="text-xl font-bold underline transition-colors duration-75 hover:text-blue-500"
|
||||
>rythondev@gmail.com</AnimatedTooltip
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#title {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
#85e6c5 0%,
|
||||
#a084e8 45%,
|
||||
#33bbc5 100%
|
||||
);
|
||||
|
||||
background-size: 300%;
|
||||
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
<script lang="ts">
|
||||
import Socials from "../socials/Socials.svelte";
|
||||
</script>
|
||||
|
||||
<div class="relative flex h-fit w-full flex-col items-center justify-center">
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<div
|
||||
id="title"
|
||||
class="animate-gradient mb-3 text-center text-3xl font-bold"
|
||||
>
|
||||
Get in Touch with me!
|
||||
</div>
|
||||
<div class="mb-10 px-5 text-center text-lg">
|
||||
I'm always open to new opportunities and collaborations!
|
||||
</div>
|
||||
|
||||
<a
|
||||
class="flex flex-row items-center justify-center rounded-xl border-2 border-blue-300 px-5 py-5 text-center text-blue-300 transition-colors duration-300 ease-in-out hover:bg-blue-300 hover:text-black"
|
||||
href="mailto:rythondev@gmail.com"
|
||||
><span class="mr-2 text-3xl">✉</span> Send me an email</a
|
||||
>
|
||||
|
||||
<div class="flex flex-row py-10 text-white">
|
||||
<Socials />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#title {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
#85e6c5 0%,
|
||||
#a084e8 45%,
|
||||
#33bbc5 100%
|
||||
);
|
||||
|
||||
background-size: 300%;
|
||||
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<div class="flex w-full items-center justify-center pb-16">
|
||||
<div class="text-center">
|
||||
Copyright © 2023-{new Date().getFullYear()} RythonDev. All Rights Reserved.
|
||||
</div>
|
||||
<div class="flex w-full items-center justify-center py-16">
|
||||
<div class="text-center">
|
||||
Copyright © 2023-{new Date().getFullYear()} RythonDev. All Rights Reserved.
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
import { HoverNavigation } from "@/components/ui/HoverNavigation";
|
||||
import { Image } from "astro:assets";
|
||||
import pfp from "@/components/images/pfp_new.webp";
|
||||
import pfp from "@/components/images/contact_photo_small.webp";
|
||||
|
||||
const navLinks = [
|
||||
{
|
||||
|
|
@ -28,7 +28,7 @@ const navLinks = [
|
|||
---
|
||||
|
||||
<div
|
||||
class="bg-secondary fixed top-5 left-1/2 z-30 box-border flex -translate-x-1/2 items-center justify-center overflow-auto rounded-full border-2 border-solid border-gray-700 px-5 py-3"
|
||||
class="bg-secondary/90 fixed top-5 left-1/2 z-30 box-border flex -translate-x-1/2 items-center justify-center overflow-auto rounded-full border-2 border-solid border-gray-700 px-5 py-3"
|
||||
>
|
||||
<Image src={pfp} alt="Ryan" width={50} class="mr-3 rounded-full" />
|
||||
<HoverNavigation items={navLinks} client:load />
|
||||
|
|
|
|||
BIN
src/components/images/contact_photo.webp
Normal file
BIN
src/components/images/contact_photo.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
src/components/images/contact_photo_small.webp
Normal file
BIN
src/components/images/contact_photo_small.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
94
src/components/ui/AnimatedToolTip.tsx
Normal file
94
src/components/ui/AnimatedToolTip.tsx
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useRef } from "react";
|
||||
import {
|
||||
motion,
|
||||
useTransform,
|
||||
AnimatePresence,
|
||||
useMotionValue,
|
||||
useSpring,
|
||||
} from "motion/react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export const AnimatedTooltip = ({
|
||||
className,
|
||||
children,
|
||||
text,
|
||||
href,
|
||||
}: {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
text: string;
|
||||
href?: string;
|
||||
}) => {
|
||||
const [hovered, setHovered] = useState<boolean>(false);
|
||||
const animationFrameRef = useRef<number | null>(null);
|
||||
|
||||
const handleMouseMove = (event: any) => {
|
||||
if (animationFrameRef.current) {
|
||||
cancelAnimationFrame(animationFrameRef.current);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="group relative -mr-4"
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
<AnimatePresence>
|
||||
{hovered && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20, scale: 0.6 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
y: 10,
|
||||
scale: 1,
|
||||
transition: {
|
||||
type: "spring",
|
||||
stiffness: 260,
|
||||
damping: 10,
|
||||
},
|
||||
}}
|
||||
exit={{ opacity: 0, y: 20, scale: 0.6 }}
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
className="absolute -top-16 left-1/2 z-50 flex -translate-x-1/2 flex-col items-center justify-center rounded-md bg-black px-4 py-2 text-xs shadow-xl"
|
||||
>
|
||||
<div className="absolute inset-x-10 -bottom-px z-30 h-px w-[20%] bg-gradient-to-r from-transparent via-emerald-500 to-transparent" />
|
||||
<div className="absolute -bottom-px left-10 z-30 h-px w-[40%] bg-gradient-to-r from-transparent via-sky-500 to-transparent" />
|
||||
<div className="relative z-30 text-base font-bold text-white">
|
||||
{text}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
{!href && (
|
||||
<div
|
||||
onMouseMove={handleMouseMove}
|
||||
className={cn(
|
||||
"relative h-14 w-14 transition duration-500 group-hover:z-30",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
{href && (
|
||||
<a
|
||||
href={href}
|
||||
onMouseMove={handleMouseMove}
|
||||
className={cn(
|
||||
"relative h-14 w-14 transition duration-500 group-hover:z-30",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,136 +1,136 @@
|
|||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
AnimatePresence,
|
||||
motion,
|
||||
useScroll,
|
||||
useMotionValueEvent,
|
||||
AnimatePresence,
|
||||
motion,
|
||||
useScroll,
|
||||
useMotionValueEvent,
|
||||
} from "motion/react";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
export const HoverNavigation = ({
|
||||
items,
|
||||
className,
|
||||
items,
|
||||
className,
|
||||
}: {
|
||||
items: {
|
||||
label: string;
|
||||
link: string;
|
||||
}[];
|
||||
className?: string;
|
||||
items: {
|
||||
label: string;
|
||||
link: string;
|
||||
}[];
|
||||
className?: string;
|
||||
}) => {
|
||||
let [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
||||
const { scrollYProgress } = useScroll();
|
||||
const [visible, setVisible] = useState(true);
|
||||
let [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
||||
const { scrollYProgress } = useScroll();
|
||||
const [visible, setVisible] = useState(true);
|
||||
|
||||
useMotionValueEvent(scrollYProgress, "change", (current) => {
|
||||
// Check if current is not undefined and is a number
|
||||
if (typeof current === "number") {
|
||||
let direction = current! - scrollYProgress.getPrevious()!;
|
||||
useMotionValueEvent(scrollYProgress, "change", (current) => {
|
||||
// Check if current is not undefined and is a number
|
||||
if (typeof current === "number") {
|
||||
let direction = current! - scrollYProgress.getPrevious()!;
|
||||
|
||||
if (scrollYProgress.get() < 0.05) {
|
||||
setVisible(true);
|
||||
} else {
|
||||
if (direction > 0) {
|
||||
setVisible(false);
|
||||
} else {
|
||||
setVisible(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (scrollYProgress.get() < 0.05 && current !== 1) {
|
||||
setVisible(true);
|
||||
} else {
|
||||
if (direction > 0) {
|
||||
setVisible(false);
|
||||
} else {
|
||||
setVisible(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<motion.div
|
||||
animate={{
|
||||
width: visible ? "0px" : "auto",
|
||||
opacity: visible ? "0%" : "100%",
|
||||
}}
|
||||
transition={{
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
className={cn(
|
||||
"flex w-0 flex-row items-center justify-center whitespace-nowrap",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<a
|
||||
href="/contact"
|
||||
className={`overflow-hidden whitespace-nowrap transition-colors duration-150 hover:text-green-500 ${!visible ? "" : "inline-block"}`}
|
||||
>
|
||||
Available for work
|
||||
</a>
|
||||
<div
|
||||
className={`relative z-50 ml-3 w-5 ${visible ? "hidden" : "inline-block"}`}
|
||||
>
|
||||
<div className="absolute top-1/2 left-1/2 aspect-square w-[6px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-green-500"></div>
|
||||
<div className="animate-glow absolute top-1/2 left-1/2 aspect-square w-[6px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-green-500"></div>
|
||||
</div>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
animate={{
|
||||
width: visible ? "auto" : "0px",
|
||||
opacity: visible ? "100%" : "0%",
|
||||
}}
|
||||
transition={{
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
className={cn(
|
||||
"flex flex-row items-center justify-center overflow-hidden",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{items.map((item, idx) => (
|
||||
<a
|
||||
href={item?.link}
|
||||
key={item?.link}
|
||||
className="group relative block h-full px-5 py-2"
|
||||
onMouseEnter={() => setHoveredIndex(idx)}
|
||||
onMouseLeave={() => setHoveredIndex(null)}
|
||||
>
|
||||
<AnimatePresence>
|
||||
{hoveredIndex === idx && (
|
||||
<motion.span
|
||||
className="absolute inset-0 block h-full w-full rounded-3xl bg-neutral-200"
|
||||
layoutId="hoverBackground"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
transition: { duration: 0.15 },
|
||||
}}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
transition: {
|
||||
duration: 0.15,
|
||||
delay: 0.2,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<NavBarLink>{item.label}</NavBarLink>
|
||||
</a>
|
||||
))}
|
||||
</motion.div>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<motion.div
|
||||
animate={{
|
||||
width: visible ? "0px" : "auto",
|
||||
opacity: visible ? "0%" : "100%",
|
||||
}}
|
||||
transition={{
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
className={cn(
|
||||
"flex w-0 flex-row items-center justify-center whitespace-nowrap",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<a
|
||||
href="/contact"
|
||||
className={`overflow-hidden whitespace-nowrap transition-colors duration-150 hover:text-green-500 ${!visible ? "" : "inline-block"}`}
|
||||
>
|
||||
Available for work
|
||||
</a>
|
||||
<div
|
||||
className={`relative z-50 ml-3 ${visible ? "hidden" : "inline-block"}`}
|
||||
>
|
||||
<div className="absolute top-1/2 left-1/2 aspect-square w-[6px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-green-500"></div>
|
||||
<div className="animate-glow absolute top-1/2 left-1/2 aspect-square w-[6px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-green-500"></div>
|
||||
</div>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
animate={{
|
||||
width: visible ? "auto" : "0px",
|
||||
opacity: visible ? "100%" : "0%",
|
||||
}}
|
||||
transition={{
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
className={cn(
|
||||
"flex flex-row items-center justify-center overflow-hidden",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{items.map((item, idx) => (
|
||||
<a
|
||||
href={item?.link}
|
||||
key={item?.link}
|
||||
className="group relative block h-full px-5 py-2"
|
||||
onMouseEnter={() => setHoveredIndex(idx)}
|
||||
onMouseLeave={() => setHoveredIndex(null)}
|
||||
>
|
||||
<AnimatePresence>
|
||||
{hoveredIndex === idx && (
|
||||
<motion.span
|
||||
className="absolute inset-0 block h-full w-full rounded-3xl bg-neutral-200"
|
||||
layoutId="hoverBackground"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
transition: { duration: 0.15 },
|
||||
}}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
transition: {
|
||||
duration: 0.15,
|
||||
delay: 0.2,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<NavBarLink>{item.label}</NavBarLink>
|
||||
</a>
|
||||
))}
|
||||
</motion.div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const NavBarLink = ({
|
||||
className,
|
||||
children,
|
||||
className,
|
||||
children,
|
||||
}: {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative z-20 h-full w-full overflow-hidden rounded-2xl text-center transition-all ease-out group-hover:text-black",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative z-20 h-full w-full overflow-hidden rounded-2xl text-center transition-all ease-out group-hover:text-black",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -51,6 +51,13 @@ import { ClientRouter } from "astro:transitions";
|
|||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Antonio";
|
||||
src: url("/fonts/Antonio-Bold.ttf") format("truetype");
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Montserrat Fonts */
|
||||
@font-face {
|
||||
font-family: "Montserrat";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
import NavBar from "../components/global/NavBar.astro";
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
import ContactComponent from "../components/contact/ContactComponent.svelte";
|
||||
import ContactComponent from "../components/contact/ContactComponent.astro";
|
||||
import Card from "../components/global/Card.astro";
|
||||
import Footer from "../components/global/FooterComponent.astro";
|
||||
---
|
||||
|
|
@ -13,26 +13,7 @@ import Footer from "../components/global/FooterComponent.astro";
|
|||
<NavBar />
|
||||
<div id="contact-component" class="mt-10 pt-20">
|
||||
<h1 class="text-center text-5xl">Contact</h1>
|
||||
<div class="mt-10 flex items-center justify-center">
|
||||
<div class="w-[90%] md:w-[600px]">
|
||||
<Card primaryColor="#40A2D8" className="animate-fadeIn">
|
||||
<div class="px-8 py-8">
|
||||
<div class="justify-left mb-3 flex w-full flex-col">
|
||||
<h2
|
||||
class="text-center text-xl font-bold text-balance"
|
||||
>
|
||||
Looking for a young developer with fresh ideas?
|
||||
</h2>
|
||||
</div>
|
||||
<hr class="my-3 w-full" />
|
||||
<div class="text-center text-balance">
|
||||
Whether it's sponsorships or collaboration
|
||||
opportunities, hit me up!
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-10">
|
||||
<ContactComponent />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import Intro from "../components/home/Intro.astro";
|
|||
import Projects from "../components/projects/ProjectsComponent.astro";
|
||||
import Experience from "../components/experience/ExperienceComponent.astro";
|
||||
import Skills from "../components/skills/SkillsComponent.astro";
|
||||
import Contact from "../components/contact/ContactComponent.svelte";
|
||||
import Contact from "../components/contact/ContactComponent.astro";
|
||||
import Footer from "../components/global/FooterComponent.astro";
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue