From 164ad296288effe082f3eaaf8a61a02254fcc97c Mon Sep 17 00:00:00 2001 From: Tben Date: Thu, 20 Jul 2023 21:21:46 +0100 Subject: [PATCH] updates --- app/(components)/AboutSection.tsx | 125 ++------ app/(components)/ContactForm.tsx | 65 ++++ app/(components)/ContactMeSection.tsx | 24 ++ app/(components)/Hero.tsx | 168 ++++++---- app/(components)/HomepageComponent.tsx | 56 ++++ app/(components)/MySkillsSection.tsx | 91 ++++++ app/(components)/MyWorkCard.tsx | 36 +++ app/(components)/MyWorkSection.tsx | 43 +++ app/(utils)/homepage-timeline.ts | 206 ++++++++++++ app/(utils)/web-dev-stack.json | 4 - app/about/(components)/Hero.tsx | 19 +- app/about/(components)/MoreAboutMe.tsx | 5 - app/layout.tsx | 15 + app/page.tsx | 27 +- ...mitContactForm.js => submitContactForm.ts} | 8 +- layouts/general_layout/GeneralFooter.tsx | 6 + layouts/general_layout/GeneralHeader.tsx | 62 +++- layouts/general_layout/GeneralLayout.tsx | 8 +- layouts/general_layout/HeaderNav.tsx | 60 ++++ package-lock.json | 205 ++++++++++-- package.json | 4 +- pages/api/contactForm.ts | 2 +- pages/contact.jsx | 88 ------ pages/contact.tsx | 39 +++ public/images/external-link-dark.png | Bin 0 -> 983 bytes public/images/external-link.png | Bin 0 -> 785 bytes public/images/github-white.png | Bin 0 -> 1938 bytes public/images/linkedin-white.png | Bin 0 -> 919 bytes public/images/my-photo-2.png | Bin 0 -> 235667 bytes public/images/my-photo-3.png | Bin 0 -> 278457 bytes public/images/my-photo-stroked.png | Bin 0 -> 243131 bytes public/images/programming-laptop.png | Bin 0 -> 158039 bytes public/images/projects-section-image.png | Bin 0 -> 172214 bytes public/images/why-so-serious.png | Bin 0 -> 132249 bytes public/scripts/homepage-gsap.js | 169 ++++++++++ public/scripts/main.js | 21 ++ scripts/main.js | 1 - styles/main.css | 30 +- styles/tw_main.css | 296 +++++++++++++++--- tailwind.config.js | 3 +- 40 files changed, 1500 insertions(+), 386 deletions(-) create mode 100644 app/(components)/ContactForm.tsx create mode 100644 app/(components)/ContactMeSection.tsx create mode 100644 app/(components)/HomepageComponent.tsx create mode 100644 app/(components)/MySkillsSection.tsx create mode 100644 app/(components)/MyWorkCard.tsx create mode 100644 app/(components)/MyWorkSection.tsx create mode 100644 app/(utils)/homepage-timeline.ts rename functions/frontend/{submitContactForm.js => submitContactForm.ts} (72%) create mode 100644 layouts/general_layout/HeaderNav.tsx delete mode 100644 pages/contact.jsx create mode 100644 pages/contact.tsx create mode 100644 public/images/external-link-dark.png create mode 100644 public/images/external-link.png create mode 100644 public/images/github-white.png create mode 100644 public/images/linkedin-white.png create mode 100644 public/images/my-photo-2.png create mode 100644 public/images/my-photo-3.png create mode 100644 public/images/my-photo-stroked.png create mode 100644 public/images/programming-laptop.png create mode 100644 public/images/projects-section-image.png create mode 100644 public/images/why-so-serious.png create mode 100644 public/scripts/homepage-gsap.js diff --git a/app/(components)/AboutSection.tsx b/app/(components)/AboutSection.tsx index d55137f..06ad946 100644 --- a/app/(components)/AboutSection.tsx +++ b/app/(components)/AboutSection.tsx @@ -1,120 +1,51 @@ "use client"; import React from "react"; -// import TextShuffler from "../../components/actions/TextShuffler"; -import { about, genericScroll } from "../(utils)/animate"; export default function AboutSection() { - React.useEffect(about, []); - React.useEffect(genericScroll, []); - - const webDevStack = require("../(utils)/web-dev-stack.json"); - const uiStack = require("../(utils)/ui-ux-stack.json"); - - const [targetStack, setTargetStack] = React.useState("dev"); - return (
- {/* - About Me - */}
-
-

- About Me - {/* */} -

+
-
-
-
{ - setTargetStack("dev"); - }} +
+
+

About Me

+ + Quick learner, adaptable, problem solver, curious. I strive to know the system, rather than the status quo. I thrive in difficult situations and complex hurdles: problem solving is now second nature to me. + +
+ +

_ Code Ben

+ In the last two years I've developed from a complete designer to pro software engineer. After countless days of writing code, debugging, testing, building projects, server administration, deployment, CI/CD, etc, I've developed the most vital skill of all: adaptability. The ability to asimilate new knowledge at record pace: the tech industry moves really fast: you either keepup, or fall behind. + +
+ +

Graphic Design

+ + After spending about 5 years in the design industry, I've picked up a few vital concepts about UI/UX design. My design path still sips into my developer life: and I must say, it's the perfect harmony of modern tech. Some of my designs can be found on my{" "} + -
Web Dev Stack
-
-
{ - setTargetStack("design"); - }} - > -
UI/UX Stack
-
-
- - Quick learner, adaptable, problem solver, curious. I strive to know the system, rather than the status quo. My credo is: no problem too great, no knowledge too vast, no logic too complex. I thrive in difficult situations and complex hurdles: problem solving is now second nature to me: if you can think it, it can be done. - {/* */} + 99designs + {" "} + profile. +
+ Learn More About Me -
-
- -
-
-

- {targetStack?.match(/dev/i) ? "Web Dev Tech Stack" : "UI/UX tech stack"} - {/* */} -

-
-
    - {targetStack?.match(/dev/i) ? ( - - {webDevStack.map((item: { title: string; description: string }, index: number) => ( -
  • -

    - {item.title} - {/* */} -

    - - {item.description} - {/* */} - -
  • - ))} -
    - ) : ( - - {uiStack.map((item: { title: string; description: string }, index: number) => ( -
  • -

    - {item.title} - {/* */} -

    - - {item.description} - {/* */} - -
  • - ))} -
    - )} -
diff --git a/app/(components)/ContactForm.tsx b/app/(components)/ContactForm.tsx new file mode 100644 index 0000000..5266561 --- /dev/null +++ b/app/(components)/ContactForm.tsx @@ -0,0 +1,65 @@ +"use client"; + +import React from "react"; + +import submitContactForm from "../../functions/frontend/submitContactForm"; + +export default function ContactForm() { + let [success, setSuccess] = React.useState(null); + let [loading, setLoading] = React.useState(false); + + return ( +
{ + submitContactForm(e, setSuccess, setLoading); + }} + > + {loading &&
Sending Mail...
} + + + + + {success === "Success" && ( +
+ Success!!!{" "} + +
+ )} + {success === "Failed" && ( +
+ Failed{" "} + +
+ )} +
+ ); +} diff --git a/app/(components)/ContactMeSection.tsx b/app/(components)/ContactMeSection.tsx new file mode 100644 index 0000000..29e8b5b --- /dev/null +++ b/app/(components)/ContactMeSection.tsx @@ -0,0 +1,24 @@ +"use client"; + +import React from "react"; +import ContactForm from "./ContactForm"; + +export default function ContactMeSection() { + return ( +
+
+
+ +

Why So Serious?

+ +
+ Let's talk. +
+ +
+ +
+
+
+ ); +} diff --git a/app/(components)/Hero.tsx b/app/(components)/Hero.tsx index f0c9ed5..7bf19e7 100644 --- a/app/(components)/Hero.tsx +++ b/app/(components)/Hero.tsx @@ -2,73 +2,121 @@ import React from "react"; import Image from "next/image"; -// import TextShuffler from "../../components/actions/TextShuffler"; -import { hero } from "../(utils)/animate"; +import { gsap } from "gsap"; +import { TextPlugin } from "gsap/all"; +gsap.registerPlugin(TextPlugin); export default function Hero() { - React.useEffect(hero, []); + /** + * Handle animations on page load + */ + React.useEffect(() => { + const tl = gsap.timeline(); + gsap.fromTo( + "#hero-sub-text", + { + opacity: 0, + }, + { + opacity: 1, + delay: 0.5, + duration: 1, + } + ); + gsap.to("#hero-text", { + text: "I'm Benjamin Toby, a Software Engineer and UI/UX expert.", + delay: 1, + duration: 2, + ease: "none", + }); + gsap.fromTo( + ".hero-button-link", + { + opacity: 0, + }, + { + opacity: 1, + delay: 3, + duration: 1, + stagger: 0.5, + } + ); + }, []); + + /** + * Render component + */ return ( -
-
-
- Benjamin Toby Image -
+ diff --git a/app/(components)/HomepageComponent.tsx b/app/(components)/HomepageComponent.tsx new file mode 100644 index 0000000..3849712 --- /dev/null +++ b/app/(components)/HomepageComponent.tsx @@ -0,0 +1,56 @@ +"use client"; + +import React from "react"; +import Hero from "./Hero"; +import AboutSection from "./AboutSection"; +import { gsap } from "gsap"; +import MyWorkSection from "./MyWorkSection"; +import MySkillsSection from "./MySkillsSection"; +import homepageTimeline from "../(utils)/homepage-timeline"; +import { SiteContext } from "../../layouts/general_layout/GeneralLayout"; +import ContactMeSection from "./ContactMeSection"; + +export type Project = { + id: number; + title: string; + description: string; + image?: string | null; + url?: string | null; + full_description?: string; +}; + +type ChildProps = { + projects: Project[]; +}; + +export default function HomepageComponent({ projects }: ChildProps) { + const layoutContext: any = React.useContext(SiteContext); + + if (layoutContext) layoutContext.projects = projects; + + const comp = React.useRef(null); + + React.useLayoutEffect(() => { + let ctx = gsap.context(() => { + homepageTimeline({ componentRef: comp }); + }, comp); + + return () => ctx.revert(); + }, []); + + return ( + +
+ + + + + +
+
+ ); +} diff --git a/app/(components)/MySkillsSection.tsx b/app/(components)/MySkillsSection.tsx new file mode 100644 index 0000000..3030b3a --- /dev/null +++ b/app/(components)/MySkillsSection.tsx @@ -0,0 +1,91 @@ +"use client"; + +import React from "react"; + +export default function MySkillsSection() { + const webDevStack = require("../(utils)/web-dev-stack.json"); + const uiStack = require("../(utils)/ui-ux-stack.json"); + + const [targetStack, setTargetStack] = React.useState("dev"); + + return ( +
+
+ +
+

My Skills

+ + I am well-versed in the full spectrum of design and development. While I started out as a designer, I have focused more on development in the last few years. + +
+ +

{targetStack?.match(/dev/i) ? "Web Dev Tech Stack" : "UI/UX tech stack"}

+ +
+ {/*

My Tech Stack

*/} +
+
{ + setTargetStack("dev"); + }} + > +
Web Dev Stack
+
+
{ + setTargetStack("design"); + }} + > +
UI/UX Stack
+
+
+
+ +
    + {targetStack?.match(/dev/i) ? ( + + {webDevStack.map((item: { title: string; description: string }, index: number) => ( +
  • +

    + {item.title} + {/* */} +

    + + {item.description} + {/* */} + +
  • + ))} +
    + ) : ( + + {uiStack.map((item: { title: string; description: string }, index: number) => ( +
  • +

    + {item.title} + {/* */} +

    + + {item.description} + {/* */} + +
  • + ))} +
    + )} +
+
+
+ ); +} diff --git a/app/(components)/MyWorkCard.tsx b/app/(components)/MyWorkCard.tsx new file mode 100644 index 0000000..b609909 --- /dev/null +++ b/app/(components)/MyWorkCard.tsx @@ -0,0 +1,36 @@ +"use client"; + +import React from "react"; +import { Project } from "./HomepageComponent"; +import Image from "next/image"; + +export default function MyWorkCard({ project }: { project: Project }) { + return ( +
+ {project.title} +
+

{project.title}

+

{project.description}

+
+ + + External Link Icon + +
+ ); +} diff --git a/app/(components)/MyWorkSection.tsx b/app/(components)/MyWorkSection.tsx new file mode 100644 index 0000000..c00b35b --- /dev/null +++ b/app/(components)/MyWorkSection.tsx @@ -0,0 +1,43 @@ +"use client"; + +import React from "react"; +import { SiteContext } from "../../layouts/general_layout/GeneralLayout"; +import { Project } from "./HomepageComponent"; +import MyWorkCard from "./MyWorkCard"; + +export default function MyWorkSection() { + const layoutContext: { projects: Project[] } = React.useContext(SiteContext); + const projects = layoutContext.projects; + + return ( +
+
+
+
+ +

Some of my work

+ +
+ I've been creating and building awesome designs and applications since 2016. Here are a few picks: +
+ +
+ {projects && + projects.slice(0, 4).map((project, index) => ( + + ))} +
+ + + See More + +
+
+ ); +} diff --git a/app/(utils)/homepage-timeline.ts b/app/(utils)/homepage-timeline.ts new file mode 100644 index 0000000..2b5887e --- /dev/null +++ b/app/(utils)/homepage-timeline.ts @@ -0,0 +1,206 @@ +import { gsap } from "gsap"; +import { ScrollTrigger, Observer } from "gsap/all"; +gsap.registerPlugin(ScrollTrigger, Observer); + +export default function homepageTimeline({ componentRef }: { componentRef: { current: HTMLDivElement | null } }) { + const mm = gsap.matchMedia(); + + const compHeight = componentRef.current?.clientHeight; + + const isMobile = window.innerWidth <= 1200; + + // mm.add("(min-width: 1200px)", () => { + ScrollTrigger.create({ + trigger: "#homepage-content-wrapper", + start: "200px 200px", + end: "bottom 90%", + pin: isMobile ? null : "#main-image", + scrub: 1, + onUpdate: (self) => { + const scrollTop = compHeight ? self.progress * compHeight : 0; + + /** + * Origin + */ + if (scrollTop < 300) { + gsap.to("nav", { + opacity: 1, + pointerEvents: "visible", + }); + gsap.to("header", { + zIndex: 2000, + }); + } else { + gsap.to("nav", { + opacity: 0, + pointerEvents: "none", + }); + gsap.to("header", { + zIndex: 0, + }); + } + + /** + * Handle Mobile + */ + if (isMobile && scrollTop > 100) { + gsap.to("#main-image", { + opacity: 0.1, + duration: 1, + }); + } else { + gsap.to("#main-image", { + opacity: 1, + duration: 1, + }); + } + }, + onEnter: (self) => {}, + }); + + /** + * Position color map + */ + const positionColorMap: { + x: number; + imgBg: string; + bodyBg: string; + duration: number; + imgSrc?: string; + }[] = [ + { + x: 0, + imgBg: "#3e3f9c", + bodyBg: "#181515", + duration: 1, + imgSrc: "/images/my-photo-stroked.png", + }, + { + x: -50, + imgBg: "#FE6847", + bodyBg: "#292a6b", + duration: 1, + imgSrc: "/images/my-photo-3.png", + }, + { + x: 600, + imgBg: "#688e26", + bodyBg: "#181515", + duration: 1.2, + imgSrc: "/images/programming-laptop.png", + }, + { + x: -50, + imgBg: "#1B2CC1", + bodyBg: "#091540", + duration: 1.2, + imgSrc: "/images/projects-section-image.png", + }, + { + x: 600, + imgBg: "#ffd87d", + bodyBg: "#3e3f9c", + duration: 1.2, + imgSrc: "/images/why-so-serious.png", + }, + ]; + + /** + * Activate section based on scroll position + */ + const sections = document.querySelectorAll(".page-section"); + + function activateSection(index: number) { + const targetPositionMap = positionColorMap[index]; + + if (!targetPositionMap) { + return; + } + + gsap.to("#main-image", { + backgroundColor: targetPositionMap.imgBg, + x: isMobile ? 0 : targetPositionMap.x, + duration: targetPositionMap.duration, + }); + gsap.to("body", { + backgroundColor: targetPositionMap.bodyBg, + }); + + if (targetPositionMap?.imgSrc) { + changeMainImage(targetPositionMap.imgSrc); + } + + if (index >= 2) { + gsap.to("#main-image img", { + filter: "none", + duration: 1, + }); + } else { + gsap.to("#main-image img", { + filter: "grayscale(100%) contrast(140%)", + duration: 1, + }); + } + } + + sections.forEach((section, index) => { + ScrollTrigger.create({ + trigger: section, + start: "top 50%", + end: "bottom 50%", + onEnter: (self) => { + activateSection(index); + }, + onEnterBack: (self) => { + activateSection(index); + }, + }); + }); + + /** + * Entry animation timeline + */ + const entryTimeline = gsap.timeline(); + + entryTimeline + .from("#main-image", { + clipPath: "circle(0)", + }) + .to("#main-image", { + clipPath: "circle(100%)", + scale: 1, + duration: 1, + }); + + gsap.to("#main-image", { + y: 20, + repeat: -1, + duration: 2, + yoyo: true, + ease: "sine.inOut", + }); + // }); +} + +async function changeMainImage(src: string) { + const mainImage: HTMLImageElement | null = document.querySelector("#main-image img"); + + if (mainImage) { + await gsap.to(mainImage, { + opacity: 0, + duration: 0.5, + }); + + if (mainImage) { + mainImage.setAttribute("src", src); + + mainImage.onload = () => { + gsap.to(mainImage, { + opacity: 1, + duration: 0.5, + delay: 0.5, + }); + }; + } + } +} diff --git a/app/(utils)/web-dev-stack.json b/app/(utils)/web-dev-stack.json index be75009..be34ff4 100644 --- a/app/(utils)/web-dev-stack.json +++ b/app/(utils)/web-dev-stack.json @@ -1,8 +1,4 @@ [ - { - "title": "HTML, CSS, Javascript", - "description": "The basics, the bedrock of all websites." - }, { "title": "HTML, CSS, Javascript", "description": "The basics, the bedrock of all websites." diff --git a/app/about/(components)/Hero.tsx b/app/about/(components)/Hero.tsx index b791ca9..3638dea 100644 --- a/app/about/(components)/Hero.tsx +++ b/app/about/(components)/Hero.tsx @@ -1,37 +1,22 @@ "use client"; import React from "react"; -// import TextShuffler from "../../../components/actions/TextShuffler"; -import { appear, genericScroll } from "../../(utils)/animate"; export default function Hero() { - React.useEffect(genericScroll, []); - React.useEffect(appear, []); - return (
- - About Me - {/* */} - + About Me

Ben of All Trades, Master of All - {/* */}

- - Quick learner, adaptable, problem solver, curious. I strive to know the system, rather than the status quo. My credo is: no problem too great, no knowledge too vast, no logic too complex. I thrive in difficult situations and complex hurdles: problem solving is now second nature to me: if you can think it, it can be done. - {/* */} - + Quick learner, adaptable, problem solver, curious. I strive to know the system, rather than the status quo. My credo is: no problem too great, no knowledge too vast, no logic too complex. I thrive in difficult situations and complex hurdles: problem solving is now second nature to me: if you can think it, it can be done.
); diff --git a/app/about/(components)/MoreAboutMe.tsx b/app/about/(components)/MoreAboutMe.tsx index 3be63e6..a0dadef 100644 --- a/app/about/(components)/MoreAboutMe.tsx +++ b/app/about/(components)/MoreAboutMe.tsx @@ -1,13 +1,8 @@ "use client"; import React from "react"; -// import TextShuffler from "../../../components/actions/TextShuffler"; -import { appear, genericScroll } from "../../(utils)/animate"; export default function MoreAboutMe() { - React.useEffect(genericScroll, []); - React.useEffect(appear, []); - const webDevStack = require("../../(utils)/web-dev-stack.json"); const uiStack = require("../../(utils)/ui-ux-stack.json"); diff --git a/app/layout.tsx b/app/layout.tsx index ebe50bb..2facedc 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -18,6 +18,21 @@ export default function RootLayout({ children }: { children: React.ReactNode }) return ( + {/* + + */} + + +