updates
1
.gitignore
vendored
@ -2,3 +2,4 @@ node_modules
|
||||
.next
|
||||
node_shell
|
||||
.env
|
||||
/dump
|
115
app/(components)/AboutSection.tsx
Normal file
@ -0,0 +1,115 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import TextShuffler from "../../components/actions/TextShuffler";
|
||||
import { about } from "../(utils)/animate";
|
||||
|
||||
export default function AboutSection() {
|
||||
React.useEffect(about, []);
|
||||
|
||||
const webDevStack = require("../(utils)/web-dev-stack.json");
|
||||
const uiStack = require("../(utils)/ui-ux-stack.json");
|
||||
|
||||
const [targetStack, setTargetStack] = React.useState("dev");
|
||||
|
||||
return (
|
||||
<div
|
||||
className="max-w-6xl w-full flex flex-col items-center pb-40"
|
||||
id="about-section"
|
||||
>
|
||||
{/* <span
|
||||
className="text-[300px] uppercase absolute whitespace-nowrap opacity-5 -mt-64 z-[-1]"
|
||||
id="about-me-label"
|
||||
>
|
||||
About Me
|
||||
</span> */}
|
||||
<div className="h-44"></div>
|
||||
|
||||
<div className="flex flex-col xl:flex-row w-full gap-8">
|
||||
<div className="w-full xl:w-[40%]">
|
||||
<h2>
|
||||
<TextShuffler textInput="About Me" />
|
||||
</h2>
|
||||
|
||||
<div className="flex flex-col-reverse xl:flex-col items-start gap-4">
|
||||
<div className="flex flex-col w-full gap-2">
|
||||
<div
|
||||
className={"p-4 cursor-pointer hover:opacity-60" + (targetStack.match(/dev/i) ? " bg-[#343680] w-full xl:w-[120%]" : " w-full border border-solid border-white/10")}
|
||||
onClick={() => {
|
||||
setTargetStack("dev");
|
||||
}}
|
||||
>
|
||||
<div>Web Dev Stack</div>
|
||||
</div>
|
||||
<div
|
||||
className={"p-4 cursor-pointer hover:opacity-60" + (targetStack.match(/design/i) ? " bg-[#343680] w-full xl:w-[120%]" : " w-full border border-solid border-white/10")}
|
||||
onClick={() => {
|
||||
setTargetStack("design");
|
||||
}}
|
||||
>
|
||||
<div>UI/UX Stack</div>
|
||||
</div>
|
||||
</div>
|
||||
<span className="hero-sub-text">
|
||||
<TextShuffler
|
||||
textInput="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."
|
||||
delay={500}
|
||||
/>
|
||||
</span>
|
||||
|
||||
<a
|
||||
href="/about"
|
||||
className="button"
|
||||
>
|
||||
Learn More About Me
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full xl:w-[60%] bg-[#343680] px-4 md:px-6 py-2">
|
||||
<section>
|
||||
<h3>
|
||||
<TextShuffler textInput={targetStack?.match(/dev/i) ? "Web Dev Tech Stack" : "UI/UX tech stack"} />
|
||||
</h3>
|
||||
<hr />
|
||||
<ul style={{ maxWidth: "800px" }}>
|
||||
{targetStack?.match(/dev/i) ? (
|
||||
<React.Fragment>
|
||||
{webDevStack.map((item: { title: string; description: string }, index: number) => (
|
||||
<li
|
||||
key={index}
|
||||
className="mb-4"
|
||||
>
|
||||
<h4 className="m-0">
|
||||
<TextShuffler textInput={item.title} />
|
||||
</h4>
|
||||
<span className="opacity-80">
|
||||
<TextShuffler textInput={item.description} />
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
{uiStack.map((item: { title: string; description: string }, index: number) => (
|
||||
<li
|
||||
key={index}
|
||||
className="mb-4"
|
||||
>
|
||||
<h4 className="m-0">
|
||||
<TextShuffler textInput={item.title} />
|
||||
</h4>
|
||||
<span className="opacity-80">
|
||||
<TextShuffler textInput={item.description} />
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
76
app/(components)/Hero.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import TextShuffler from "../../components/actions/TextShuffler";
|
||||
import { hero } from "../(utils)/animate";
|
||||
|
||||
export default function Hero() {
|
||||
React.useEffect(hero, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col xl:flex-row items-center gap-0 justify-center -mt-[120px] xl:-mt-[160px] w-full relative pt-[450px] md:pt-0">
|
||||
<div
|
||||
className="rounded-full max-w-[450px] absolute md:relative top-0 flex items-center md:items-start justify-center mr-0 xl:-mr-14 bg-[#3e3f9c] overflow-hidden"
|
||||
id="main-image"
|
||||
>
|
||||
<Image
|
||||
src="/images/my-photo.png"
|
||||
// fill
|
||||
width={500}
|
||||
height={562}
|
||||
objectFit="contain"
|
||||
alt="Benjamin Toby Image"
|
||||
className="contrast-[140%] grayscale mt-20 flex items-center justify-center"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="max-w-2xl relative z-10 mt-10 w-full"
|
||||
id="hero-text-section"
|
||||
>
|
||||
<h1
|
||||
className="text-5xl leading-snug"
|
||||
id="hero-text"
|
||||
>
|
||||
{/* Hi */}
|
||||
{/* Fullstack developer, UI/UX designer, Software Engineer, welcome */}
|
||||
<TextShuffler
|
||||
textInput="I'm Benjamin Toby, a Software Engineer and UI/UX expert"
|
||||
delay={500}
|
||||
/>
|
||||
</h1>
|
||||
|
||||
<div className="gap-4 flex items-center">
|
||||
<a
|
||||
href="/documents/Resume-Benjamin-Toby-Linkedin.pdf"
|
||||
download={true}
|
||||
className="button"
|
||||
>
|
||||
See my resume
|
||||
</a>
|
||||
<a
|
||||
href="https://www.linkedin.com/in/benjamin-toby/"
|
||||
target="_blank"
|
||||
className="button"
|
||||
>
|
||||
Linkedin
|
||||
</a>
|
||||
<a
|
||||
href="/contact"
|
||||
style={{
|
||||
backgroundColor: "transparent",
|
||||
color: "white",
|
||||
border: "2px solid white",
|
||||
}}
|
||||
className="button"
|
||||
>
|
||||
Contact Me
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
6
app/(styles)/header.css
Normal file
@ -0,0 +1,6 @@
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
149
app/(utils)/animate.ts
Normal file
@ -0,0 +1,149 @@
|
||||
import { gsap } from "gsap";
|
||||
import ScrollTrigger from "gsap/ScrollTrigger";
|
||||
import TextPlugin from "gsap/TextPlugin";
|
||||
|
||||
/**
|
||||
* Register plugins
|
||||
*/
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
gsap.registerPlugin(TextPlugin);
|
||||
|
||||
/**
|
||||
* Animates the hero section
|
||||
*/
|
||||
export function hero() {
|
||||
/**
|
||||
* Animate hero section
|
||||
*/
|
||||
gsap.fromTo(
|
||||
"#main-image",
|
||||
{
|
||||
z: -100,
|
||||
scale: 1.2,
|
||||
},
|
||||
{
|
||||
z: 0,
|
||||
duration: 1.5,
|
||||
scale: 1,
|
||||
}
|
||||
);
|
||||
|
||||
gsap.fromTo(
|
||||
"#hero-text-section",
|
||||
{
|
||||
z: -100,
|
||||
scale: 1.2,
|
||||
},
|
||||
{
|
||||
z: 0,
|
||||
duration: 2.5,
|
||||
scale: 1,
|
||||
}
|
||||
);
|
||||
|
||||
gsap.fromTo(
|
||||
"#main-image",
|
||||
{
|
||||
y: 0,
|
||||
// scale: 1,
|
||||
},
|
||||
{
|
||||
scrollTrigger: {
|
||||
scrub: 1,
|
||||
},
|
||||
y: 150,
|
||||
// scale: 0.8,
|
||||
}
|
||||
);
|
||||
|
||||
gsap.fromTo(
|
||||
"#hero-text-section",
|
||||
{
|
||||
y: 0,
|
||||
// scale: 1,
|
||||
},
|
||||
{
|
||||
scrollTrigger: {
|
||||
scrub: 2,
|
||||
},
|
||||
y: 100,
|
||||
// scale: 0.8,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the about section
|
||||
*/
|
||||
export function about() {
|
||||
gsap.fromTo(
|
||||
"#about-section",
|
||||
{
|
||||
y: 40,
|
||||
scale: 0.8,
|
||||
},
|
||||
{
|
||||
scrollTrigger: {
|
||||
scrub: 4,
|
||||
},
|
||||
y: 0,
|
||||
scale: 1.1,
|
||||
}
|
||||
);
|
||||
|
||||
gsap.to("#about-me-label", {
|
||||
scrollTrigger: {
|
||||
scrub: 2,
|
||||
},
|
||||
y: 300,
|
||||
scale: 0.8,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the about section
|
||||
*/
|
||||
export function genericScroll() {
|
||||
gsap.fromTo(
|
||||
".generic-scroll",
|
||||
{
|
||||
y: 0,
|
||||
scale: 0.9,
|
||||
},
|
||||
{
|
||||
scrollTrigger: {
|
||||
scrub: 3,
|
||||
},
|
||||
y: 100,
|
||||
scale: 1,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the about section
|
||||
*/
|
||||
export function appear() {
|
||||
gsap.fromTo(
|
||||
".appear",
|
||||
{
|
||||
y: 40,
|
||||
scale: 0.8,
|
||||
opacity: 0,
|
||||
},
|
||||
{
|
||||
y: 0,
|
||||
scale: 1,
|
||||
opacity: 1,
|
||||
duration: 2,
|
||||
delay: (i, target) => {
|
||||
const datasetDelay = target?.dataset?.delay;
|
||||
|
||||
if (datasetDelay) {
|
||||
return datasetDelay;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
30
app/(utils)/ui-ux-stack.json
Normal file
@ -0,0 +1,30 @@
|
||||
[
|
||||
{
|
||||
"title": "Adobe Photoshop",
|
||||
"description": "Web design, image manipulation, image compositing, and more."
|
||||
},
|
||||
{
|
||||
"title": "Adobe Illustrator",
|
||||
"description": "Vector graphic of all types"
|
||||
},
|
||||
{
|
||||
"title": "Figma",
|
||||
"description": "Web, UI, UX design."
|
||||
},
|
||||
{
|
||||
"title": "Affinity Designer",
|
||||
"description": "Vector graphics."
|
||||
},
|
||||
{
|
||||
"title": "After Effects",
|
||||
"description": "Motion graphics and animation"
|
||||
},
|
||||
{
|
||||
"title": "Adobe XD",
|
||||
"description": "UI/UX design"
|
||||
},
|
||||
{
|
||||
"title": "Webflow",
|
||||
"description": "Visual Web coding"
|
||||
}
|
||||
]
|
42
app/(utils)/web-dev-stack.json
Normal file
@ -0,0 +1,42 @@
|
||||
[
|
||||
{
|
||||
"title": "HTML, CSS, Javascript",
|
||||
"description": "The basics, the bedrock of all websites."
|
||||
},
|
||||
{
|
||||
"title": "HTML, CSS, Javascript",
|
||||
"description": "The basics, the bedrock of all websites."
|
||||
},
|
||||
{
|
||||
"title": "React JS",
|
||||
"description": "JavaScript library for high-performance web applications"
|
||||
},
|
||||
{
|
||||
"title": "Next JS",
|
||||
"description": "High performance React and Node js web framework for building blazing flast and performant web applications"
|
||||
},
|
||||
{
|
||||
"title": "Tailwind CSS",
|
||||
"description": "Lighting fast mobile first styling"
|
||||
},
|
||||
{
|
||||
"title": "Node JS",
|
||||
"description": "JavaScript runtime for the server. For creating backend architectures and APIs"
|
||||
},
|
||||
{
|
||||
"title": "Ubuntu Linux",
|
||||
"description": "Secure server management with ubuntu and Linux"
|
||||
},
|
||||
{
|
||||
"title": "Nginx",
|
||||
"description": "Super secure web server, reverse proxy and load balancer"
|
||||
},
|
||||
{
|
||||
"title": "MySQL",
|
||||
"description": "Tried and tested data storage, querying, and management."
|
||||
},
|
||||
{
|
||||
"title": "Git and Github",
|
||||
"description": "Version control"
|
||||
}
|
||||
]
|
35
app/about/(components)/Hero.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
"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 (
|
||||
<div className="-mt-10 appear">
|
||||
<div
|
||||
className="flex flex-col items-start gap-0 max-w-6xl w-full relative generic-scroll"
|
||||
// id="hero-text-section"
|
||||
>
|
||||
<span className="text-primary-light text-lg">
|
||||
<TextShuffler textInput="About Me" />
|
||||
</span>
|
||||
<h1
|
||||
className="text-5xl leading-snug"
|
||||
id="hero-text"
|
||||
>
|
||||
<TextShuffler textInput="Ben of All Trade, Master of All" />
|
||||
</h1>
|
||||
<span className="hero-sub-text">
|
||||
<TextShuffler
|
||||
textInput="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."
|
||||
delay={1000}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
97
app/about/(components)/MoreAboutMe.tsx
Normal file
@ -0,0 +1,97 @@
|
||||
"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");
|
||||
|
||||
const [targetStack, setTargetStack] = React.useState("dev");
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl w-full flex flex-col items-center pb-40 generic-scroll">
|
||||
<div className="h-20"></div>
|
||||
|
||||
<div
|
||||
className="flex flex-col w-full gap-8 appear"
|
||||
data-delay={2}
|
||||
>
|
||||
<div className="w-full">
|
||||
<h2>
|
||||
<TextShuffler textInput="More About Me" />
|
||||
</h2>
|
||||
|
||||
<div className="flex flex-col items-start gap-4">
|
||||
<div className="flex w-full gap-4">
|
||||
<div
|
||||
className={"p-4 cursor-pointer hover:opacity-60 grow" + (targetStack.match(/dev/i) ? " bg-[#343680]" : " border border-solid border-white/10")}
|
||||
onClick={() => {
|
||||
setTargetStack("dev");
|
||||
}}
|
||||
>
|
||||
<div>Web Dev Stack</div>
|
||||
</div>
|
||||
<div
|
||||
className={"p-4 cursor-pointer hover:opacity-60 grow" + (targetStack.match(/design/i) ? " bg-[#343680]" : " border border-solid border-white/10")}
|
||||
onClick={() => {
|
||||
setTargetStack("design");
|
||||
}}
|
||||
>
|
||||
<div>UI/UX Stack</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full bg-[#343680] px-4 md:px-6 py-2">
|
||||
<section>
|
||||
<h3>
|
||||
<TextShuffler textInput={targetStack?.match(/dev/i) ? "Web Dev Tech Stack" : "UI/UX tech stack"} />
|
||||
</h3>
|
||||
<hr />
|
||||
<ul style={{ maxWidth: "800px" }}>
|
||||
{targetStack?.match(/dev/i) ? (
|
||||
<React.Fragment>
|
||||
{webDevStack.map((item: { title: string; description: string }, index: number) => (
|
||||
<li
|
||||
key={index}
|
||||
className="mb-4"
|
||||
>
|
||||
<h4 className="m-0">
|
||||
<TextShuffler textInput={item.title} />
|
||||
</h4>
|
||||
<span className="opacity-80">
|
||||
<TextShuffler textInput={item.description} />
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
{uiStack.map((item: { title: string; description: string }, index: number) => (
|
||||
<li
|
||||
key={index}
|
||||
className="mb-4"
|
||||
>
|
||||
<h4 className="m-0">
|
||||
<TextShuffler textInput={item.title} />
|
||||
</h4>
|
||||
<span className="opacity-80">
|
||||
<TextShuffler textInput={item.description} />
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
12
app/about/page.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import React from "react";
|
||||
import MoreAboutMe from "./(components)/MoreAboutMe";
|
||||
import Hero from "./(components)/Hero";
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Hero />
|
||||
<MoreAboutMe />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
31
app/layout.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
// General imports
|
||||
import { Metadata } from "next";
|
||||
import GeneralLayout from "../layouts/general_layout/GeneralLayout";
|
||||
|
||||
// Styles imports
|
||||
import "../styles/main.css";
|
||||
import "../styles/tw_main.css";
|
||||
|
||||
// Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: "Homepage",
|
||||
description: "Software engineer, UI/UX designer, Full Stack Web Developer, Web/graphic/motion designer, React Developer, Next JS developer, Node JS developer, Javascript Developer, Linux Ubuntu, DevOps, Nginx, MySQL developer, Freelancer",
|
||||
keywords: "UI/UX designer, Full Stack Web Developer, Web/graphic/motion designer, React Developer, NextJS developer, Node JS developer, Javascript Developer, Linux Ubuntu, DevOps, Nginx, MySQL developer, Freelancer",
|
||||
};
|
||||
|
||||
// Main Layout Component
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script
|
||||
src="/scripts/main.js"
|
||||
defer
|
||||
></script>
|
||||
</head>
|
||||
<body>
|
||||
<GeneralLayout>{children}</GeneralLayout>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
12
app/page.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import React from "react";
|
||||
import Hero from "./(components)/Hero";
|
||||
import AboutSection from "./(components)/AboutSection";
|
||||
|
||||
export default function Homepage() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Hero />
|
||||
<AboutSection />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
import React, { Fragment, useEffect, useState } from 'react';
|
||||
import { gsap } from "gsap";
|
||||
|
||||
let timer = 0;
|
||||
let keyNum = 0;
|
||||
let textTimeout;
|
||||
let textInterval;
|
||||
// let chars = ("abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+=-;.,/?><][}{:`~").split("");
|
||||
let interval = 200;
|
||||
|
||||
const TextShuffler = ({ textInput, delay }) => {
|
||||
|
||||
// let [text, setText] = useState(textInput);
|
||||
// let [refresh, setRefresh] = useState(0);
|
||||
let [readyState, setReadyState] = useState(false);
|
||||
|
||||
const spanRef = React.useRef();
|
||||
|
||||
useEffect(() => {
|
||||
const spanObserver = new IntersectionObserver((entries, observer) => {
|
||||
if (entries[0].isIntersecting) {
|
||||
if (delay) {
|
||||
setTimeout(() => {
|
||||
setReadyState(true);
|
||||
}, delay);
|
||||
} else {
|
||||
setReadyState(true);
|
||||
}
|
||||
|
||||
observer.unobserve(spanRef.current)
|
||||
}
|
||||
|
||||
}, {
|
||||
rootMargin: "0px 0px 0px 0px"
|
||||
});
|
||||
|
||||
spanObserver.observe(spanRef.current)
|
||||
}, [])
|
||||
|
||||
// useEffect(() => {
|
||||
// if (!readyState) return;
|
||||
|
||||
// requestAnimationFrame(() => {
|
||||
// textTimeout = setTimeout(() => {
|
||||
// if (timer === 7) {
|
||||
// setText(textInput);
|
||||
// return window.clearTimeout(textTimeout);
|
||||
// };
|
||||
|
||||
// setRefresh(prev => prev + 1);
|
||||
// timer++
|
||||
// }, 300);
|
||||
// })
|
||||
// }, [refresh, readyState])
|
||||
|
||||
useEffect(() => {
|
||||
if (!readyState) return;
|
||||
|
||||
|
||||
|
||||
let chars = textInput.split("");
|
||||
|
||||
|
||||
|
||||
let charsSpans = chars.map(char => `<span style="opacity:0">${char}</span>`)
|
||||
|
||||
spanRef.current.innerHTML = charsSpans.join("");
|
||||
|
||||
gsap.to(spanRef.current, {
|
||||
opacity: 1,
|
||||
duration: 1
|
||||
});
|
||||
|
||||
let textSpans = spanRef.current.querySelectorAll("span");
|
||||
|
||||
// textInterval = setInterval(() => {
|
||||
// if (timer >= 600) {
|
||||
// window.clearInterval(textInterval);
|
||||
|
||||
// requestAnimationFrame(() => {
|
||||
// textSpans.forEach(span => {
|
||||
// // gsap.killTweensOf(span, "opacity");
|
||||
|
||||
// gsap.to(span, {
|
||||
// opacity: 1,
|
||||
// duration: Math.random(),
|
||||
// delay: Math.random()
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
|
||||
// return;
|
||||
// }
|
||||
|
||||
// requestAnimationFrame(() => {
|
||||
// textSpans.forEach(span => {
|
||||
// gsap.to(span, {
|
||||
// opacity: 1,
|
||||
// duration: Math.random() * 2,
|
||||
// delay: Math.random() * 1.5
|
||||
// })
|
||||
// })
|
||||
|
||||
// // textSpans.forEach(span => {
|
||||
// // gsap.to(span, {
|
||||
// // opacity: 0,
|
||||
// // duration: Math.random() * 2,
|
||||
// // delay: Math.random() * 1.5
|
||||
// // })
|
||||
// // })
|
||||
|
||||
// // gsap.to(spanRef.current, {
|
||||
// // opacity: 0,
|
||||
// // duration: 2
|
||||
// // });
|
||||
|
||||
// timer += interval;
|
||||
// })
|
||||
|
||||
// }, interval)
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
// let charsSpans = chars.map(char => `<span>${char}</span>`)
|
||||
|
||||
// spanRef.current.innerHTML = charsSpans.join("");
|
||||
|
||||
// let textSpans = spanRef.current.querySelectorAll("span");
|
||||
|
||||
textSpans.forEach(span => {
|
||||
gsap.to(span, {
|
||||
opacity: 1,
|
||||
duration: Math.random() * 1.5,
|
||||
})
|
||||
})
|
||||
|
||||
textSpans.forEach(span => {
|
||||
gsap.killTweensOf(span, "opacity");
|
||||
gsap.to(span, {
|
||||
opacity: 0,
|
||||
duration: Math.random() * 1.5,
|
||||
delay: Math.random() * 0.5
|
||||
})
|
||||
})
|
||||
|
||||
textSpans.forEach(span => {
|
||||
gsap.killTweensOf(span, "opacity");
|
||||
|
||||
gsap.to(span, {
|
||||
opacity: 1,
|
||||
duration: Math.random() * 1.5,
|
||||
delay: Math.random()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
}, [readyState])
|
||||
|
||||
return (
|
||||
<span className="shuffled-text-span" ref={ spanRef } style={ { opacity: 0 } }>
|
||||
{/* { text.split("").map(char => <span key={ keyNum++ } style={ { animationDelay: Math.random() * 1.5 + "s", animationDuration: (Math.random() * 2) + "s" } }>{ char }</span>) } */ }
|
||||
{ textInput }
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default TextShuffler
|
98
components/actions/TextShuffler.tsx
Normal file
@ -0,0 +1,98 @@
|
||||
"use client";
|
||||
|
||||
import React, { FC, useEffect, useState, ReactElement } from "react";
|
||||
import { gsap } from "gsap";
|
||||
|
||||
type ChildProps = {
|
||||
textInput: string;
|
||||
delay?: number;
|
||||
};
|
||||
|
||||
const TextShuffler: FC<ChildProps> = ({ textInput, delay }): ReactElement => {
|
||||
let [readyState, setReadyState] = useState(false);
|
||||
|
||||
const spanRef = React.useRef<HTMLElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const spanObserver = new IntersectionObserver(
|
||||
(entries, observer) => {
|
||||
if (entries[0].isIntersecting) {
|
||||
if (delay) {
|
||||
setTimeout(() => {
|
||||
setReadyState(true);
|
||||
}, delay);
|
||||
} else {
|
||||
setReadyState(true);
|
||||
}
|
||||
if (spanRef.current) observer.unobserve(spanRef.current);
|
||||
}
|
||||
},
|
||||
{
|
||||
rootMargin: "0px 0px 0px 0px",
|
||||
}
|
||||
);
|
||||
|
||||
if (spanRef.current) spanObserver.observe(spanRef.current);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!readyState) return;
|
||||
|
||||
let chars = textInput.split("");
|
||||
|
||||
let charsSpans = chars.map((char) => `<span style="opacity:0">${char}</span>`);
|
||||
|
||||
if (spanRef?.current?.innerHTML) spanRef.current.innerHTML = charsSpans.join("");
|
||||
|
||||
if (spanRef.current) {
|
||||
gsap.to(spanRef.current, {
|
||||
opacity: 1,
|
||||
duration: 1,
|
||||
});
|
||||
}
|
||||
|
||||
let textSpans: NodeList | null = spanRef.current ? spanRef.current.querySelectorAll("span") : null;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (textSpans) {
|
||||
textSpans.forEach((span) => {
|
||||
gsap.to(span, {
|
||||
opacity: 1,
|
||||
duration: Math.random() * 1.5,
|
||||
});
|
||||
});
|
||||
|
||||
textSpans.forEach((span) => {
|
||||
gsap.killTweensOf(span, "opacity");
|
||||
gsap.to(span, {
|
||||
opacity: 0,
|
||||
duration: Math.random() * 1.5,
|
||||
delay: Math.random() * 0.5,
|
||||
});
|
||||
});
|
||||
|
||||
textSpans.forEach((span) => {
|
||||
gsap.killTweensOf(span, "opacity");
|
||||
|
||||
gsap.to(span, {
|
||||
opacity: 1,
|
||||
duration: Math.random() * 1.5,
|
||||
delay: Math.random(),
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [readyState]);
|
||||
|
||||
return (
|
||||
<span
|
||||
className="shuffled-text-span"
|
||||
ref={spanRef}
|
||||
style={{ opacity: 0 }}
|
||||
>
|
||||
{textInput}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default TextShuffler;
|
10
functions/backend/sanitizeHtmlOptions.ts
Normal file
@ -0,0 +1,10 @@
|
||||
const sanitizeHtmlOptions: object = {
|
||||
allowedTags: ["b", "i", "em", "strong", "a", "p", "span", "ul", "ol", "li", "h1", "h2", "h3", "h4", "h5", "h6", "img", "div", "button", "pre", "code", "br"],
|
||||
allowedAttributes: {
|
||||
a: ["href"],
|
||||
img: ["src", "alt", "width", "height", "class", "style"],
|
||||
"*": ["style", "class"],
|
||||
},
|
||||
};
|
||||
|
||||
export default sanitizeHtmlOptions;
|
@ -2,7 +2,6 @@ import * as THREE from "three";
|
||||
|
||||
export default function threeJsAnimations() {
|
||||
const animationWrapper = document.getElementById("homepage-animation-wrapper");
|
||||
console.log(THREE);
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
const camera = new THREE.PerspectiveCamera(75, animationWrapper.clientWidth / animationWrapper.clientHeight, 0.1, 1000);
|
||||
@ -16,13 +15,6 @@ export default function threeJsAnimations() {
|
||||
const sphereSize = 1;
|
||||
const pointLightHelper = new THREE.PointLightHelper(pointLight, sphereSize);
|
||||
|
||||
// const spotLight = new THREE.SpotLight(0xffffff);
|
||||
// spotLight.position.set(10, 10, 10);
|
||||
// scene.add(spotLight);
|
||||
|
||||
// const spotLightHelper = new THREE.SpotLightHelper(spotLight);
|
||||
// scene.add(spotLightHelper);
|
||||
|
||||
const renderer = new THREE.WebGLRenderer();
|
||||
renderer.setSize(animationWrapper.clientWidth, animationWrapper.clientHeight);
|
||||
animationWrapper.appendChild(renderer.domElement);
|
||||
@ -122,21 +114,10 @@ export default function threeJsAnimations() {
|
||||
|
||||
relMouseX < 0 ? (cube3.position.x += 0.0007) : (cube3.position.x -= 0.0007);
|
||||
relMouseY > 0 ? (cube3.position.y += 0.0007) : (cube3.position.y -= 0.0007);
|
||||
|
||||
// relMouseX < 0 ? scene.rotateX(5) : scene.rotateX(-5);
|
||||
// relMouseY > 0 ? scene.rotateX(5) : scene.rotateX(-5);
|
||||
// relMouseX < 0 ? (scene.rotation.z += 0.005) : (scene.rotation.x += 0.005);
|
||||
// relMouseY > 0 ? (scene.rotation.y += 0.005) : (scene.rotation.y += 0.005);
|
||||
// cube.rotation.x += relMouseX / e.x;
|
||||
// cube.rotation.y += relMouseY / e.y;
|
||||
|
||||
// scene.rotation.x;
|
||||
});
|
||||
|
||||
window.addEventListener("resize", (e) => {
|
||||
renderer.setSize(animationWrapper.clientWidth, window.innerHeight);
|
||||
// animationWrapper.querySelector("canvas").width = animationWrapper.clientWidth;
|
||||
// animationWrapper.querySelector("canvas").height = animationWrapper.clientHeight;
|
||||
});
|
||||
|
||||
window.addEventListener("scroll", (e) => {
|
||||
|
46
layouts/general_layout/BG.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { gsap } from "gsap";
|
||||
// import scrollTrigger from "gsap/ScrollTrigger";
|
||||
|
||||
const BG = () => {
|
||||
React.useEffect(() => {
|
||||
// gsap.registerPlugin(scrollTrigger);
|
||||
|
||||
gsap.to("#bg-blurred-image", {
|
||||
scrollTrigger: {
|
||||
scrub: 5,
|
||||
},
|
||||
y: 50,
|
||||
scale: 0.8,
|
||||
});
|
||||
|
||||
gsap.to("#bg-image", {
|
||||
scrollTrigger: {
|
||||
scrub: 2,
|
||||
},
|
||||
y: 50,
|
||||
opacity: 0,
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<img
|
||||
src="/images/rm378-07c-min.png"
|
||||
alt="bg-image"
|
||||
className="fixed top-0 left-0 w-full h-full object-cover z-[-2] p-[100px] opacity-20 blur-sm"
|
||||
id="bg-blurred-image"
|
||||
/>
|
||||
<img
|
||||
src="/images/rm378-07c-min.png"
|
||||
alt="bg-image"
|
||||
className="fixed top-0 left-0 w-full h-full object-cover z-[-1] p-[150px] opacity-5"
|
||||
id="bg-image"
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default BG;
|
@ -1,15 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
const GeneralFooter = () => {
|
||||
const date = new Date();
|
||||
return (
|
||||
<footer className='mt-10'>
|
||||
{/* <div className='flex-col flex'>
|
||||
<a href="/" className='text-xl font-bold mb-1'>Tben.me</a>
|
||||
</div> */}
|
||||
<span className='text-sm opacity-40'>Copyright © { date.getFullYear() } Tben.me. All Rights Reserved.</span>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
export default GeneralFooter
|
42
layouts/general_layout/GeneralFooter.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
const GeneralFooter = () => {
|
||||
const date = new Date();
|
||||
return (
|
||||
<footer
|
||||
className="mt-10 w-full flex flex-col items-center py-20"
|
||||
style={{
|
||||
borderTop: "1px solid rgba(255,255,255,0.2)",
|
||||
}}
|
||||
>
|
||||
<div className="max-w-6xl w-full flex flex-wrap items-stretch gap-10">
|
||||
<a
|
||||
href="/"
|
||||
data-href="/"
|
||||
className="logo-link-block -mb-4"
|
||||
>
|
||||
<Image
|
||||
src="/images/logo-white.svg"
|
||||
width={50}
|
||||
height={100}
|
||||
alt="Logo"
|
||||
/>
|
||||
</a>
|
||||
<div className="flex flex-col items-start justify-between">
|
||||
<div className="flex-col flex items-start gap-1">
|
||||
<span className="opacity-50">Contact Me</span>
|
||||
<div className="flex gap-4">
|
||||
<a href="/">LinkedIn</a>
|
||||
<a href="/">Mail</a>
|
||||
<a href="/">Phone</a>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-sm opacity-40">Copyright © {date.getFullYear()} Tben.me. All Rights Reserved.</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default GeneralFooter;
|
@ -1,45 +0,0 @@
|
||||
import React from 'react'
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const GeneralHeader = () => {
|
||||
const router = useRouter();
|
||||
|
||||
function pushRouter(e) {
|
||||
e.preventDefault();
|
||||
let url = e.target.dataset.href;
|
||||
router.push(url);
|
||||
}
|
||||
|
||||
return (
|
||||
<header>
|
||||
<a className="logo-link-block" onClick={ () => {
|
||||
pushRouter("/")
|
||||
} }><h1>Tben.me</h1></a>
|
||||
<nav>
|
||||
<a href='/' data-href="/" onClick={ (e) => {
|
||||
pushRouter(e)
|
||||
} }>Home</a>
|
||||
|
||||
<a href='/about' data-href="/about" onClick={ (e) => {
|
||||
pushRouter(e)
|
||||
} }>About Me</a>
|
||||
|
||||
<a href='/work' data-href="/work" onClick={ (e) => {
|
||||
pushRouter(e)
|
||||
} }>My Work</a>
|
||||
|
||||
<a href='/blog' data-href="/blog" onClick={ (e) => {
|
||||
pushRouter(e)
|
||||
} }>Blog</a>
|
||||
|
||||
<a data-href="#" href='/documents/Benjamin-Toby-CV-7-27-2022.pdf' target="_blank">My Resume</a>
|
||||
|
||||
<a href='/contact' data-href="/contact" onClick={ (e) => {
|
||||
pushRouter(e)
|
||||
} }>Contact Me</a>
|
||||
</nav>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
export default GeneralHeader
|
73
layouts/general_layout/GeneralHeader.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
import React from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Image from "next/image";
|
||||
|
||||
import { gsap } from "gsap";
|
||||
|
||||
/**
|
||||
* General Header for all pages
|
||||
*/
|
||||
const GeneralHeader = (): React.ReactElement => {
|
||||
const links: { title: string; url: string }[] = require("./links.json");
|
||||
|
||||
/**
|
||||
* Animate the header on mount
|
||||
*/
|
||||
React.useEffect(() => {
|
||||
gsap.fromTo(
|
||||
"#main-header",
|
||||
{
|
||||
y: -20,
|
||||
filter: "blur(100px)",
|
||||
webkitFilter: "blur(100px)",
|
||||
opacity: 0,
|
||||
},
|
||||
{
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
duration: 1,
|
||||
filter: "none",
|
||||
webkitFilter: "none",
|
||||
delay: 0.5,
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<header
|
||||
id="main-header"
|
||||
className="flex items-start justify-between gap-10 w-full py-4 md:py-10 relative z-10"
|
||||
>
|
||||
<a
|
||||
href="/"
|
||||
data-href="/"
|
||||
className="logo-link-block"
|
||||
>
|
||||
<Image
|
||||
src="/images/logo-white.svg"
|
||||
width={50}
|
||||
height={100}
|
||||
alt="Logo"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<nav>
|
||||
{links.map((link: { title: string; url: string; download?: boolean }, index) => {
|
||||
return (
|
||||
<a
|
||||
key={index}
|
||||
href={link.url}
|
||||
data-href={link.url}
|
||||
className="text-lg"
|
||||
download={link.download}
|
||||
>
|
||||
{link.title}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default GeneralHeader;
|
@ -1,94 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import GeneralHeader from './GeneralHeader';
|
||||
import Head from "next/head"
|
||||
import GeneralFooter from './GeneralFooter';
|
||||
import { gsap } from "gsap";
|
||||
import threeJsAnimations from '../../functions/frontend/threeJsAnimations';
|
||||
|
||||
export const SiteContext = React.createContext();
|
||||
|
||||
const GeneralLayout = ({ children, pageName }) => {
|
||||
const [readyState, setReadyState] = React.useState(false);
|
||||
|
||||
// React.useEffect(() => {
|
||||
// setReadyState(true);
|
||||
// }, [readyState]);
|
||||
|
||||
React.useEffect(() => {
|
||||
// barba.init({
|
||||
// transitions: [{
|
||||
// name: 'default-transition',
|
||||
// leave() {
|
||||
// // create your stunning leave animation here
|
||||
// return gsap.to("main", {
|
||||
// opacity: 1,
|
||||
// duration: 1,
|
||||
// delay: 2
|
||||
// })
|
||||
// },
|
||||
// enter() {
|
||||
// // create your amazing enter animation here
|
||||
// return gsap.from("main", {
|
||||
// opacity: 0,
|
||||
// duration: 1,
|
||||
// delay: 2
|
||||
// })
|
||||
// }
|
||||
// }]
|
||||
// });
|
||||
|
||||
// window.addEventListener("load", () => {
|
||||
// })
|
||||
// setReadyState(true);
|
||||
|
||||
document.querySelectorAll("nav a").forEach((link) => {
|
||||
let locationRegex = new RegExp(`${window.location.pathname}.*?`);
|
||||
if (link.dataset.href === window.location.pathname) {
|
||||
link.classList.add("active-page");
|
||||
}
|
||||
|
||||
if (window.location.pathname.match(new RegExp(`${link.dataset.href}\\/.*`))) {
|
||||
link.classList.add("active-page");
|
||||
}
|
||||
});
|
||||
|
||||
gsap.to("#main-content-wrapper", {
|
||||
opacity: 1,
|
||||
duration: 2,
|
||||
})
|
||||
|
||||
// gsap.to("#page-loader", {
|
||||
// opacity: 0,
|
||||
// duration: 0.5,
|
||||
// delay: 1,
|
||||
// pointerEvents: "none"
|
||||
// })
|
||||
|
||||
// document.getElementById("page-loader").style.opacity
|
||||
|
||||
threeJsAnimations();
|
||||
}, [])
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<SiteContext.Provider value={ { readyState, setReadyState } }>
|
||||
<Head>
|
||||
<meta name="keywords" content="UI/UX designer, Full Stack Web Developer, Web/graphic/motion designer, React Developer, NextJS developer, Node JS developer, Javascript Developer, Linux Ubuntu, DevOps, Nginx, MySQL developer, Freelancer" />
|
||||
</Head>
|
||||
|
||||
<div id='main-content-wrapper' style={ { opacity: 0 } }>
|
||||
<GeneralHeader />
|
||||
<main>
|
||||
{ children }
|
||||
</main>
|
||||
<GeneralFooter />
|
||||
<div className='fixed top-0 left-0 -z-10' id='homepage-animation-wrapper'></div>
|
||||
</div>
|
||||
|
||||
</SiteContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default GeneralLayout
|
62
layouts/general_layout/GeneralLayout.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
"use client";
|
||||
|
||||
import React, { ReactElement } from "react";
|
||||
import GeneralHeader from "./GeneralHeader";
|
||||
import GeneralFooter from "./GeneralFooter";
|
||||
import { gsap } from "gsap";
|
||||
import BG from "./BG";
|
||||
|
||||
export const SiteContext = React.createContext({});
|
||||
|
||||
type GeneralLayoutProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const GeneralLayout = ({ children }: GeneralLayoutProps): ReactElement => {
|
||||
const [readyState, setReadyState] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
const links: NodeListOf<HTMLAnchorElement> | null = document.querySelectorAll("nav a");
|
||||
|
||||
links?.forEach((link) => {
|
||||
if (link.dataset.href === window.location.pathname) {
|
||||
link.classList.add("active-page");
|
||||
}
|
||||
|
||||
if (window.location.pathname.match(new RegExp(`${link.dataset.href}\\/.*`))) {
|
||||
link.classList.add("active-page");
|
||||
}
|
||||
});
|
||||
|
||||
gsap.to("#main-content-wrapper", {
|
||||
opacity: 1,
|
||||
duration: 2,
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SiteContext.Provider value={{ readyState, setReadyState }}>
|
||||
<div
|
||||
id="main-content-wrapper"
|
||||
style={{
|
||||
opacity: 0,
|
||||
}}
|
||||
className="px-4 md:px-10"
|
||||
>
|
||||
<GeneralHeader />
|
||||
<main
|
||||
className="flex items-center flex-col w-full"
|
||||
style={{
|
||||
perspective: "800px",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</main>
|
||||
<GeneralFooter />
|
||||
</div>
|
||||
<BG />
|
||||
</SiteContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default GeneralLayout;
|
27
layouts/general_layout/links.json
Normal file
@ -0,0 +1,27 @@
|
||||
[
|
||||
{
|
||||
"title": "Home",
|
||||
"url": "/"
|
||||
},
|
||||
{
|
||||
"title": "About Me",
|
||||
"url": "/about"
|
||||
},
|
||||
{
|
||||
"title": "My Work",
|
||||
"url": "/work"
|
||||
},
|
||||
{
|
||||
"title": "My Resume",
|
||||
"url": "/documents/Resume-Benjamin-Toby-Linkedin.pdf",
|
||||
"download": true
|
||||
},
|
||||
{
|
||||
"title": "Blog",
|
||||
"url": "/blog"
|
||||
},
|
||||
{
|
||||
"title": " Contact Me",
|
||||
"url": "/contact"
|
||||
}
|
||||
]
|
6
next-env.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference types="next/navigation-types/compat/navigation" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
@ -3,4 +3,14 @@ module.exports = {
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "static.datasquirel.com",
|
||||
port: "",
|
||||
pathname: "/images/**",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
2038
package-lock.json
generated
13
package.json
@ -27,14 +27,19 @@
|
||||
"dependencies": {
|
||||
"@barba/core": "^2.9.7",
|
||||
"contentful": "^9.1.32",
|
||||
"datasquirel": "^1.1.82",
|
||||
"gsap": "^3.10.4",
|
||||
"next": "^12.0.4",
|
||||
"next": "^13.4.10",
|
||||
"nodemailer": "^6.7.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"sanitize-html": "^2.11.0",
|
||||
"three": "^0.138.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tailwindcss": "^3.0.23"
|
||||
"@types/node": "^20.4.2",
|
||||
"@types/react": "^18.2.15",
|
||||
"tailwindcss": "^3.0.23",
|
||||
"typescript": "^5.1.6"
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
import React from 'react'
|
||||
import TextShuffler from '../components/actions/TextShuffler'
|
||||
import { textSHuffle } from '../functions/frontend/textShuffle'
|
||||
import GeneralLayout from '../layouts/general_layout/GeneralLayout'
|
||||
|
||||
const index = () => {
|
||||
return (
|
||||
<div className='not-found-page-wrapper'>
|
||||
<h1><TextShuffler textInput="404" /></h1>
|
||||
<span className='hero-sub-text'>
|
||||
<TextShuffler textInput="Oops ... page not found" />
|
||||
</span>
|
||||
<div className="hero-ctas-section">
|
||||
<a href='/'>Go Back Home</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default index
|
@ -2,7 +2,9 @@ import "../styles/main.css";
|
||||
import "../styles/tw_main.css";
|
||||
import { Fragment } from "react";
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
import type { AppProps } from "next/app";
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<Fragment>
|
||||
<Component {...pageProps} />
|
@ -5,11 +5,12 @@ export default function Document() {
|
||||
return (
|
||||
<Html>
|
||||
<Head>
|
||||
{/* <script src="https://unpkg.com/@barba/core"></script> */}
|
||||
{/* <script src="/scripts/swup.js"></script> */}
|
||||
<script src="/scripts/main.js" defer></script>
|
||||
<script
|
||||
src="/scripts/main.js"
|
||||
defer
|
||||
></script>
|
||||
</Head>
|
||||
<body className="bg-black">
|
||||
<body className="w-full">
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
|
139
pages/about.jsx
@ -1,139 +0,0 @@
|
||||
import React from 'react'
|
||||
import Head from 'next/head'
|
||||
import TextShuffler from '../components/actions/TextShuffler'
|
||||
import GeneralLayout from '../layouts/general_layout/GeneralLayout'
|
||||
import threeJsAnimations from '../functions/frontend/threeJsAnimations'
|
||||
|
||||
const about = () => {
|
||||
|
||||
return (
|
||||
<GeneralLayout pageName="about">
|
||||
<Head>
|
||||
<title>About Me</title>
|
||||
<meta name="description" content="Ben of all trade, master of all! Learn more about me" />
|
||||
</Head>
|
||||
<h1><TextShuffler textInput="Ben of all trade, master of all!" /></h1>
|
||||
<span className='hero-sub-text'>
|
||||
<TextShuffler textInput="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." delay={ 500 } />
|
||||
</span>
|
||||
|
||||
<div className='w-full h-6'></div>
|
||||
|
||||
<section>
|
||||
<h2><TextShuffler textInput="Web Dev Tech Stack" /></h2>
|
||||
<ul style={ { maxWidth: "800px" } }>
|
||||
<li>
|
||||
<h3 className="tech-stack-header"><TextShuffler textInput="HTML, CSS, Javascript" /></h3>
|
||||
<span className='opacity-50'>
|
||||
<TextShuffler textInput="The basics, the bedrock of all websites." />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<h3 className="tech-stack-header"><TextShuffler textInput="React JS" /></h3>
|
||||
<span className='opacity-50'>
|
||||
<TextShuffler textInput=" JavaScript library for high-performance web applications" />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<h3 className="tech-stack-header"><TextShuffler textInput="Next JS" /></h3>
|
||||
<span className='opacity-50'>
|
||||
<TextShuffler textInput="High performance React and Node js web framework for building blazing flast and performant web applications" />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<h3 className="tech-stack-header"><TextShuffler textInput="Tailwind CSS" /></h3>
|
||||
<span className='opacity-50'>
|
||||
<TextShuffler textInput="Lighting fast mobile first styling" />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<h3 className="tech-stack-header"><TextShuffler textInput="Node JS" /></h3>
|
||||
<span className='opacity-50'>
|
||||
<TextShuffler textInput="JavaScript runtime for the server. For creating backend architectures and APIs" />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<h3 className="tech-stack-header"><TextShuffler textInput="Ubuntu Linux" /></h3>
|
||||
<span className='opacity-50'>
|
||||
<TextShuffler textInput="Secure server management with ubuntu and Linux" />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<h3 className="tech-stack-header"><TextShuffler textInput="Nginx" /></h3>
|
||||
<span className='opacity-50'>
|
||||
<TextShuffler textInput="Super secure web server, reverse proxy and load balancer" />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<h3 className="tech-stack-header"><TextShuffler textInput="MySQL" /></h3>
|
||||
<span className='opacity-50'>
|
||||
<TextShuffler textInput="Tried and tested data storage, querying, and management." />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<h3 className="tech-stack-header"><TextShuffler textInput="Git and Github" /></h3>
|
||||
<span className='opacity-50'>
|
||||
<TextShuffler textInput="Version control" />
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2><TextShuffler textInput="UI/UX tech Stack" /></h2>
|
||||
<ul style={ { maxWidth: "800px" } }>
|
||||
<li>
|
||||
<h3 className="tech-stack-header"><TextShuffler textInput="Adobe Photoshop" /></h3>
|
||||
<span className='opacity-50'>
|
||||
<TextShuffler textInput="Web design, image manipulation, image compositing, and more" />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<h3 className="tech-stack-header"><TextShuffler textInput="Adobe Illustrator" /></h3>
|
||||
<span className='opacity-50'>
|
||||
<TextShuffler textInput="Vector graphic of all types" />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<h3 className="tech-stack-header"><TextShuffler textInput="Figma" /></h3>
|
||||
<span className='opacity-50'>
|
||||
<TextShuffler textInput="Web, UI, UX design." />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<h3 className="tech-stack-header"><TextShuffler textInput="Affinity Designer" /></h3>
|
||||
<span className='opacity-50'>
|
||||
<TextShuffler textInput="Vector graphics." />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<h3 className="tech-stack-header"><TextShuffler textInput="After Effects" /></h3>
|
||||
<span className='opacity-50'>
|
||||
<TextShuffler textInput="Motion graphics and animation" />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<h3 className="tech-stack-header"><TextShuffler textInput="Adobe XD" /></h3>
|
||||
<span className='opacity-50'>
|
||||
<TextShuffler textInput="UI/UX design" />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<h3 className="tech-stack-header"><TextShuffler textInput="Webflow" /></h3>
|
||||
<span className='opacity-50'>
|
||||
<TextShuffler textInput="Visual Web coding" />
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
|
||||
<div className="hero-ctas-section">
|
||||
<a href='/documents/Benjamin_Toby_CV-updated.pdf' download={ true }>See my resume</a>
|
||||
<a href='https://www.linkedin.com/in/benjamin-toby/' target="_blank">Linkedin</a>
|
||||
</div>
|
||||
</GeneralLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default about
|
@ -1,57 +0,0 @@
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Imports
|
||||
* ------------------------------------------------------------------------------
|
||||
*
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const nodemailer = require("nodemailer");
|
||||
|
||||
/** ********************* Functions and Other API Imports */
|
||||
let transporter = nodemailer.createTransport({
|
||||
host: "smtp.gmail.com",
|
||||
port: 465,
|
||||
secure: true,
|
||||
auth: {
|
||||
user: "benoti.san@gmail.com",
|
||||
pass: "dkxxbvomzyqfyfuq",
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* API handler
|
||||
* ------------------------------------------------------------------------------
|
||||
* @param {Object} req - http incoming request
|
||||
* @param {Object} res - http response
|
||||
*
|
||||
*/
|
||||
export default async function handler(req, res) {
|
||||
/** ********************* Get Page Data */
|
||||
if (req.method === "POST") {
|
||||
let name = req.body.name;
|
||||
let email = req.body.email;
|
||||
let message = req.body.message;
|
||||
|
||||
console.log("Message Sending ...");
|
||||
|
||||
try {
|
||||
// send mail with defined transport object
|
||||
let info = await transporter.sendMail({
|
||||
from: email, // sender address
|
||||
to: "benoti.san@gmail.com, benoti.sanchez@gmail.com", // list of receivers
|
||||
subject: "Tben.me | Client Message", // Subject line
|
||||
text: "Hello from tben",
|
||||
html: `<h1>Message from ${name} | ${email}</h1><h4>Name:</h4><p>${name}</p><h4>Email:</h4><p>${email}</p><h4>Message:</h4><p>${message}</p>`, // html body
|
||||
});
|
||||
|
||||
console.log("Message sent: %s", info.messageId);
|
||||
res.json({ msg: "Success", info: info });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.json({ msg: "Failed" });
|
||||
}
|
||||
}
|
||||
}
|
49
pages/api/contactForm.ts
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Imports
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const sanitizeHtml = require("sanitize-html");
|
||||
const nodemailer = require("nodemailer");
|
||||
|
||||
import sanitizeHtmlOptions from "../../functions/backend/sanitizeHtmlOptions";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
let transporter = nodemailer.createTransport({
|
||||
host: process.env.OUTLOOK_HOST,
|
||||
port: 587,
|
||||
auth: {
|
||||
user: process.env.OUTLOOK_EMAIL,
|
||||
pass: process.env.OUTLOOK_EMAIL_PASSWORD,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* API handler
|
||||
*
|
||||
*/
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method === "POST") {
|
||||
let name = req.body.name;
|
||||
let email = req.body.email;
|
||||
let message = req.body.message;
|
||||
|
||||
const html = `<h1>Message from ${name} | ${email}</h1><h4>Name:</h4><p>${name}</p><h4>Email:</h4><p>${email}</p><h4>Message:</h4><p>${message}</p>`;
|
||||
const sanitizedHtml = sanitizeHtml(html, sanitizeHtmlOptions);
|
||||
|
||||
try {
|
||||
let info = await transporter.sendMail({
|
||||
from: email,
|
||||
to: "benoti.san@gmail.com, benoti.sanchez@gmail.com",
|
||||
subject: "Tben.me | Client Message",
|
||||
text: "Hello from tben",
|
||||
html: sanitizedHtml,
|
||||
});
|
||||
|
||||
console.log("Message sent: %s", info.messageId);
|
||||
res.json({ msg: "Success", info: info });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.json({ msg: "Failed" });
|
||||
}
|
||||
}
|
||||
}
|
@ -1,302 +0,0 @@
|
||||
/**
|
||||
* ==============================================================================
|
||||
* Imports
|
||||
* ==============================================================================
|
||||
*/
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
const https = require("https");
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
import GeneralLayout from "../../layouts/general_layout/GeneralLayout";
|
||||
import TextShuffler from "../../components/actions/TextShuffler";
|
||||
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
|
||||
/**
|
||||
* ==============================================================================
|
||||
* Main Component { Functional }
|
||||
* ==============================================================================
|
||||
* @param {Object} props - Server props
|
||||
*/
|
||||
export default function BlogIndex({ blogPost }) {
|
||||
// ## Get Contexts
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## Javascript Variables
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## React Hooks { useState, useEffect, useRef, etc ... }
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## Function Return
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Head>
|
||||
<title>{ blogPost.title } | Tben.me Blog</title>
|
||||
<meta name="description" content={ blogPost.excerpt } />
|
||||
</Head>
|
||||
<GeneralLayout>
|
||||
<div className="flex flex-col items-start gap-2 mb-8 max-w-3xl">
|
||||
<button
|
||||
className="bg-transparent text-white/50 border-2 border-solid border-white/20"
|
||||
onClick={ (e) => {
|
||||
window.history.back()
|
||||
} }
|
||||
>Back</button>
|
||||
<h1 className="m-0"><TextShuffler textInput={ blogPost.title } /></h1>
|
||||
<span className="text-lg">
|
||||
<TextShuffler textInput={ blogPost.excerpt } />
|
||||
</span>
|
||||
<span className="text-base opacity-50">
|
||||
<TextShuffler textInput={ blogPost.date_created.substring(0, 24) } />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span className="flex flex-col items-start max-w-3xl w-full gap-4 text-lg" dangerouslySetInnerHTML={ { __html: blogPost.body } }></span>
|
||||
</GeneralLayout>
|
||||
</React.Fragment>
|
||||
);
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
};
|
||||
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
|
||||
/**
|
||||
* ==============================================================================
|
||||
* Server Side Props or Static Props
|
||||
* ==============================================================================
|
||||
* @param {Object} req - http incoming request object
|
||||
* @param {Object} res - http response object
|
||||
* @param {Object} query - queries attached to the url
|
||||
*/
|
||||
export async function getStaticProps({ params }) {
|
||||
// ## Environment processes
|
||||
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## User Authentication
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## Page/Site Data Data Fetching
|
||||
const postsResponse = await new Promise((resolve, reject) => {
|
||||
https
|
||||
.get(
|
||||
/** ********************* Get Options object */
|
||||
{
|
||||
host: "datasquirel.com",
|
||||
path: `/api/query/get?db=tbenme&query=select+*+from+blog_posts+where+slug='${params.single}'`,
|
||||
headers: {
|
||||
Authorization: process.env.DATASQUIREL_API_KEY,
|
||||
},
|
||||
},
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
/** ********************* Callback function */
|
||||
(response) => {
|
||||
var str = "";
|
||||
|
||||
// ## another chunk of data has been received, so append it to `str`
|
||||
response.on("data", function (chunk) {
|
||||
str += chunk;
|
||||
});
|
||||
|
||||
// ## the whole response has been received, so we just print it out here
|
||||
response.on("end", function () {
|
||||
resolve(JSON.parse(str))
|
||||
});
|
||||
|
||||
response.on("error", (err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
)
|
||||
.end();
|
||||
});
|
||||
|
||||
// if (!postsResponse.success || !postsResponse.payload[0]) {
|
||||
// return {
|
||||
// redirect: {
|
||||
// destination: "/blog",
|
||||
// permanent: false
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
const post = postsResponse.payload[0];
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## Server Props Return
|
||||
return {
|
||||
props: {
|
||||
blogPost: post,
|
||||
},
|
||||
revalidate: 3600
|
||||
};
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
}
|
||||
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
|
||||
/**
|
||||
* ==============================================================================
|
||||
* Server Side Props or Static Props
|
||||
* ==============================================================================
|
||||
* @param {Object} req - http incoming request object
|
||||
* @param {Object} res - http response object
|
||||
* @param {Object} query - queries attached to the url
|
||||
*/
|
||||
export async function getStaticPaths() {
|
||||
/**
|
||||
* Data fetching
|
||||
*
|
||||
* @abstract fetch date from the server or externnal source
|
||||
*/
|
||||
const postsResponse = await new Promise((resolve, reject) => {
|
||||
https
|
||||
.get(
|
||||
/** ********************* Get Options object */
|
||||
{
|
||||
host: "datasquirel.com",
|
||||
path: `/api/query/get?db=tbenme&query=select+slug+from+blog_posts`,
|
||||
headers: {
|
||||
Authorization: process.env.DATASQUIREL_API_KEY,
|
||||
},
|
||||
},
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
/** ********************* Callback function */
|
||||
(response) => {
|
||||
var str = "";
|
||||
|
||||
// ## another chunk of data has been received, so append it to `str`
|
||||
response.on("data", function (chunk) {
|
||||
str += chunk;
|
||||
});
|
||||
|
||||
// ## the whole response has been received, so we just print it out here
|
||||
response.on("end", function () {
|
||||
resolve(JSON.parse(str))
|
||||
});
|
||||
|
||||
response.on("error", (err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
)
|
||||
.end();
|
||||
});
|
||||
|
||||
// if (!postsResponse.success) {
|
||||
// return {
|
||||
// redirect: {
|
||||
// destination: "/blog",
|
||||
// permanent: false
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
const posts = postsResponse.payload;
|
||||
|
||||
const paths = posts.map((entry) => {
|
||||
return {
|
||||
params: { single: entry.slug }
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
paths: paths,
|
||||
fallback: "blocking",
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy
|
||||
|
||||
// { blogPost.body.map((element) => {
|
||||
// reactKey++;
|
||||
|
||||
// if (element.tag.match(/img/i)) {
|
||||
// return <img
|
||||
// key={ reactKey }
|
||||
// src={ element.src }
|
||||
// width={ element.width }
|
||||
// height={ element.height }
|
||||
// className={ element.class }
|
||||
// alt={ element.alt }
|
||||
// style={ element.style }
|
||||
// />
|
||||
// }
|
||||
|
||||
// function construtElement(elementEntry) {
|
||||
// if (elementEntry.children) {
|
||||
// return (
|
||||
// <elementEntry.tag
|
||||
// key={ reactKey }
|
||||
// className={ elementEntry.class ? elementEntry.class : null }
|
||||
// href={ elementEntry.href }
|
||||
// style={ element.style }
|
||||
// >
|
||||
// { elementEntry.children.map(child => construtElement(child)) }
|
||||
// </elementEntry.tag>
|
||||
// )
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <elementEntry.tag
|
||||
// key={ reactKey }
|
||||
// className={ elementEntry.class ? elementEntry.class : null }
|
||||
// href={ elementEntry.href }
|
||||
// >
|
||||
// { elementEntry.content }
|
||||
// </elementEntry.tag>
|
||||
// )
|
||||
// }
|
||||
|
||||
// return construtElement(element);
|
||||
// }
|
||||
// ) }
|
178
pages/blog/[single].tsx
Normal file
@ -0,0 +1,178 @@
|
||||
/**
|
||||
* ==============================================================================
|
||||
* Imports
|
||||
* ==============================================================================
|
||||
*/
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import type { InferGetStaticPropsType, GetStaticProps, GetStaticPaths } from "next";
|
||||
const datasquirel = require("datasquirel");
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
import GeneralLayout from "../../layouts/general_layout/GeneralLayout";
|
||||
import TextShuffler from "../../components/actions/TextShuffler";
|
||||
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
|
||||
/**
|
||||
* ==============================================================================
|
||||
* Main Component { Functional }
|
||||
* ==============================================================================
|
||||
* @param {Object} props - Server props
|
||||
*/
|
||||
export default function BlogIndex({ blogPost }: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||
// ## Get Contexts
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## Javascript Variables
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## React Hooks { useState, useEffect, useRef, etc ... }
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## Function Return
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Head>
|
||||
<title>{blogPost.title} | Tben.me Blog</title>
|
||||
<meta
|
||||
name="description"
|
||||
content={blogPost.excerpt}
|
||||
/>
|
||||
</Head>
|
||||
<GeneralLayout>
|
||||
<div className="flex flex-col items-start gap-2 mb-8 max-w-6xl w-full">
|
||||
<button
|
||||
className="bg-transparent text-white/50 border-2 border-solid border-white/20"
|
||||
onClick={(e) => {
|
||||
window.history.back();
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
<h1 className="m-0">
|
||||
<TextShuffler textInput={blogPost.title} />
|
||||
</h1>
|
||||
<span className="text-lg">
|
||||
<TextShuffler textInput={blogPost.excerpt} />
|
||||
</span>
|
||||
<span className="text-base opacity-50">
|
||||
<TextShuffler textInput={blogPost.date_created.substring(0, 24)} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span
|
||||
className="flex flex-col items-start max-w-6xl w-full gap-4 text-xl"
|
||||
dangerouslySetInnerHTML={{ __html: blogPost.body }}
|
||||
></span>
|
||||
</GeneralLayout>
|
||||
</React.Fragment>
|
||||
);
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
}
|
||||
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
|
||||
/**
|
||||
* Server Side Props or Static Props
|
||||
* ==============================================================================
|
||||
*/
|
||||
export const getStaticProps: GetStaticProps = async ({ params }) => {
|
||||
// ## Environment processes
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## User Authentication
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## Page/Site Data Data Fetching
|
||||
const postsResponse = await datasquirel.get({
|
||||
key: process.env.DATASQUIREL_API_KEY,
|
||||
db: "tbenme",
|
||||
query: `select * from blog_posts WHERE slug='${params?.single}'`,
|
||||
});
|
||||
|
||||
const post = postsResponse.payload[0];
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## Server Props Return
|
||||
return {
|
||||
props: {
|
||||
blogPost: post,
|
||||
},
|
||||
revalidate: 3600,
|
||||
};
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
};
|
||||
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
|
||||
/**
|
||||
* Server Side Props or Static Props
|
||||
* ==============================================================================
|
||||
*/
|
||||
export const getStaticPaths: GetStaticPaths = async () => {
|
||||
/**
|
||||
* Data fetching
|
||||
*
|
||||
* @abstract fetch date from the server or externnal source
|
||||
*/
|
||||
const postsResponse = await datasquirel.get({
|
||||
key: process.env.DATASQUIREL_API_KEY,
|
||||
db: "tbenme",
|
||||
query: `select slug from blog_posts`,
|
||||
});
|
||||
|
||||
const posts: { slug: string }[] | null = postsResponse.payload;
|
||||
|
||||
const paths = posts?.map((entry) => {
|
||||
return {
|
||||
params: { single: entry.slug },
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
paths: paths ? paths : [],
|
||||
fallback: "blocking",
|
||||
};
|
||||
};
|
@ -6,9 +6,8 @@
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
|
||||
|
||||
// const contentful = require('contentful');
|
||||
const https = require("https")
|
||||
import type { InferGetStaticPropsType, GetStaticProps, GetStaticPropsContext } from "next";
|
||||
const datasquirel = require("datasquirel");
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
@ -31,7 +30,7 @@ import TextShuffler from "../../components/actions/TextShuffler";
|
||||
* ==============================================================================
|
||||
* @param {Object} props - Server props
|
||||
*/
|
||||
export default function BlogIndex(props) {
|
||||
export default function BlogIndex(props: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||
/**
|
||||
* Get Contexts
|
||||
*
|
||||
@ -65,29 +64,42 @@ export default function BlogIndex(props) {
|
||||
<React.Fragment>
|
||||
<Head>
|
||||
<title>Blog | Tben.me</title>
|
||||
<meta name="description" content="Tech talks" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Tech talks"
|
||||
/>
|
||||
</Head>
|
||||
<GeneralLayout>
|
||||
<h1 className="mb-8"><TextShuffler textInput="My Blog" /></h1>
|
||||
<div className="flex flex-col items-start max-w-6xl w-full">
|
||||
<h1 className="mb-8">
|
||||
<TextShuffler textInput="My Blog" />
|
||||
</h1>
|
||||
<div className="flex flex-col items-start w-full gap-4">
|
||||
{ props.blogPosts.map(post =>
|
||||
{props.blogPosts.map((post: { slug: string; title: string; excerpt: string; date_created: string }, index: number) => (
|
||||
<a
|
||||
key={index}
|
||||
href={`/blog/${post.slug}`}
|
||||
className="flex flex-col items-start gap-2 w-full hover:bg-blue-600 border border-solid border-white/20 p-8 transition-all"
|
||||
key={ post.slug }
|
||||
className="flex flex-col items-start gap-2 w-full hover:bg-blue-600 border border-solid border-white/20 p-8 transition-all bg-primary/10"
|
||||
>
|
||||
<h2 className="m-0"><TextShuffler textInput={ post.title } /></h2>
|
||||
<span className="opacity-80"><TextShuffler textInput={ post.excerpt } /></span>
|
||||
<span className="text-sm opacity-50"><TextShuffler textInput={ post.date_created.substring(0, 24) } /></span>
|
||||
<h2 className="m-0">
|
||||
<TextShuffler textInput={post.title} />
|
||||
</h2>
|
||||
<span className="opacity-80">
|
||||
<TextShuffler textInput={post.excerpt} />
|
||||
</span>
|
||||
<span className="text-sm opacity-50">
|
||||
<TextShuffler textInput={post.date_created.substring(0, 24)} />
|
||||
</span>
|
||||
</a>
|
||||
) }
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</GeneralLayout>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
/** ********************************************** */
|
||||
};
|
||||
}
|
||||
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
@ -97,27 +109,15 @@ export default function BlogIndex(props) {
|
||||
/** ****************************************************************************** */
|
||||
|
||||
/**
|
||||
* ==============================================================================
|
||||
* Server Side Props or Static Props
|
||||
* ==============================================================================
|
||||
* @param {Object} req - http incoming request object
|
||||
* @param {Object} res - http response object
|
||||
* @param {Object} query - queries attached to the url
|
||||
*/
|
||||
export async function getStaticProps({ req, res, query }) {
|
||||
export const getStaticProps: GetStaticProps = async ({ params }: GetStaticPropsContext) => {
|
||||
/**
|
||||
* User Auth
|
||||
*
|
||||
* @abstract grab user
|
||||
*/
|
||||
// const contentfulClient = contentful.createClient({
|
||||
// space: process.env.CONTENTFUL_SPACE_ID,
|
||||
// accessToken: process.env.CONTENTFUL_API_KEY,
|
||||
// });
|
||||
|
||||
// const posts = await contentfulClient.getEntries()
|
||||
|
||||
// console.log(posts);
|
||||
|
||||
/** ********************************************** */
|
||||
|
||||
@ -126,59 +126,19 @@ export async function getStaticProps({ req, res, query }) {
|
||||
*
|
||||
* @abstract fetch date from the server or externnal source
|
||||
*/
|
||||
// let blogPosts = await httpFetch({
|
||||
// options: {
|
||||
// method:"DELETE",
|
||||
// path: "/api/tben-blogs"
|
||||
// },
|
||||
// paradigm:"https"
|
||||
// })
|
||||
|
||||
// console.log(blogPosts.data);
|
||||
const postsResponse = await new Promise((resolve, reject) => {
|
||||
https
|
||||
.get(
|
||||
/** ********************* Get Options object */
|
||||
{
|
||||
host: "datasquirel.com",
|
||||
path: `/api/query/get?db=tbenme&query=select+title,slug,excerpt,date_created+from+blog_posts+limit+10`,
|
||||
headers: {
|
||||
Authorization: process.env.DATASQUIREL_API_KEY,
|
||||
},
|
||||
},
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
/** ********************* Callback function */
|
||||
(response) => {
|
||||
var str = "";
|
||||
|
||||
// ## another chunk of data has been received, so append it to `str`
|
||||
response.on("data", function (chunk) {
|
||||
str += chunk;
|
||||
});
|
||||
|
||||
// ## the whole response has been received, so we just print it out here
|
||||
response.on("end", function () {
|
||||
resolve(JSON.parse(str))
|
||||
});
|
||||
|
||||
response.on("error", (err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
)
|
||||
.end();
|
||||
const postsResponse = await datasquirel.get({
|
||||
key: process.env.DATASQUIREL_API_KEY,
|
||||
db: "tbenme",
|
||||
query: "select title,slug,excerpt,date_created from blog_posts limit 10",
|
||||
});
|
||||
|
||||
if (!postsResponse.success) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: "/",
|
||||
permanent: false
|
||||
}
|
||||
}
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const posts = postsResponse.payload;
|
||||
@ -192,10 +152,10 @@ export async function getStaticProps({ req, res, query }) {
|
||||
props: {
|
||||
blogPosts: posts,
|
||||
},
|
||||
revalidate: 3600
|
||||
revalidate: 3600,
|
||||
};
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
}
|
||||
};
|
@ -1,36 +1,88 @@
|
||||
import React from 'react'
|
||||
import Head from 'next/head'
|
||||
import TextShuffler from '../components/actions/TextShuffler'
|
||||
import submitContactForm from '../functions/frontend/submitContactForm'
|
||||
import GeneralLayout from '../layouts/general_layout/GeneralLayout'
|
||||
import threeJsAnimations from '../functions/frontend/threeJsAnimations'
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import TextShuffler from "../components/actions/TextShuffler";
|
||||
import submitContactForm from "../functions/frontend/submitContactForm";
|
||||
import GeneralLayout from "../layouts/general_layout/GeneralLayout";
|
||||
import threeJsAnimations from "../functions/frontend/threeJsAnimations";
|
||||
|
||||
const contact = () => {
|
||||
|
||||
let [success, setSuccess] = React.useState(false);
|
||||
|
||||
return (
|
||||
<GeneralLayout>
|
||||
<Head>
|
||||
<title>Contact me</title>
|
||||
<meta name="description" content="Get in touch" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Get in touch"
|
||||
/>
|
||||
</Head>
|
||||
|
||||
<h1><TextShuffler textInput="Great things await ..." /></h1>
|
||||
<span className='hero-sub-text'>
|
||||
<TextShuffler textInput="Let's talk" delay={ 500 } />
|
||||
<div className="flex flex-col items-start max-w-6xl w-full">
|
||||
<h1>
|
||||
<TextShuffler textInput="Get in touch" />
|
||||
</h1>
|
||||
<span className="hero-sub-text">
|
||||
<TextShuffler
|
||||
textInput="Have a question? Want to work together? Send me a message!"
|
||||
delay={500}
|
||||
/>
|
||||
</span>
|
||||
|
||||
<form autoComplete='on' onSubmit={ (e) => { submitContactForm(e, setSuccess) } }>
|
||||
<input type="text" placeholder='Your Name' autoComplete='name' required />
|
||||
<input type="email" placeholder='Your Email Address' autoComplete='email' required />
|
||||
<textarea name="message" id="contact-form-message" cols="30" rows="10" placeholder='Message'></textarea>
|
||||
<form
|
||||
autoComplete="on"
|
||||
onSubmit={(e) => {
|
||||
submitContactForm(e, setSuccess);
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Your Name"
|
||||
autoComplete="name"
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Your Email Address"
|
||||
autoComplete="email"
|
||||
required
|
||||
/>
|
||||
<textarea
|
||||
name="message"
|
||||
id="contact-form-message"
|
||||
cols="30"
|
||||
rows="10"
|
||||
placeholder="Message"
|
||||
></textarea>
|
||||
<button type="submit">Submit</button>
|
||||
{ success === "Success" && <div className='message-response'>Success!!! <button onClick={ () => { window.location.reload() } }>Reload</button></div> }
|
||||
{ success === "Failed" && <div className='message-response failed'>Failed <button onClick={ () => { window.location.reload() } }>Reload</button></div> }
|
||||
{success === "Success" && (
|
||||
<div className="message-response">
|
||||
Success!!!{" "}
|
||||
<button
|
||||
onClick={() => {
|
||||
window.location.reload();
|
||||
}}
|
||||
>
|
||||
Reload
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{success === "Failed" && (
|
||||
<div className="message-response failed">
|
||||
Failed{" "}
|
||||
<button
|
||||
onClick={() => {
|
||||
window.location.reload();
|
||||
}}
|
||||
>
|
||||
Reload
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
</GeneralLayout>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default contact
|
||||
export default contact;
|
||||
|
@ -1,35 +0,0 @@
|
||||
import Head from 'next/head'
|
||||
import React from 'react'
|
||||
import TextShuffler from '../components/actions/TextShuffler'
|
||||
import threeJsAnimations from '../functions/frontend/threeJsAnimations'
|
||||
import GeneralLayout from '../layouts/general_layout/GeneralLayout'
|
||||
|
||||
const index = () => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Head>
|
||||
<title>Benjamin Toby | Fullstack developer, UI UX designer</title>
|
||||
<meta name="description" content="UI/UX designer, Full Stack Web Developer, Web/graphic/motion designer, React Developer, Next JS developer, Node JS developer, Javascript Developer, Linux Ubuntu, DevOps, Nginx, MySQL developer, Freelancer" />
|
||||
</Head>
|
||||
<GeneralLayout pageName="home">
|
||||
|
||||
<h1><TextShuffler textInput="UI/UX designer, Full Stack Web Developer, Web/graphic/motion designer" /></h1>
|
||||
<span className='hero-sub-text'>
|
||||
<TextShuffler textInput="Hi, I'm Benjamin Toby, a fullstack web developer and UI/UX expert." delay={ 500 } />
|
||||
</span>
|
||||
<div className="hero-ctas-section">
|
||||
<a href='/documents/Benjamin_Toby_CV-updated.pdf' download={ true }>See my resume</a>
|
||||
<a href='https://www.linkedin.com/in/benjamin-toby/' target="_blank">Linkedin</a>
|
||||
<a href='/contact' style={ {
|
||||
backgroundColor: "transparent",
|
||||
color: "white",
|
||||
border: "2px solid white"
|
||||
} }>Contact Me</a>
|
||||
</div>
|
||||
</GeneralLayout>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default index
|
@ -1,8 +1,8 @@
|
||||
import React from 'react'
|
||||
import Head from 'next/head'
|
||||
import TextShuffler from '../components/actions/TextShuffler'
|
||||
import GeneralLayout from '../layouts/general_layout/GeneralLayout'
|
||||
import PortfolioEntry from '../components/PortfolioEntry'
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import TextShuffler from "../components/actions/TextShuffler";
|
||||
import GeneralLayout from "../layouts/general_layout/GeneralLayout";
|
||||
import PortfolioEntry from "../components/PortfolioEntry";
|
||||
|
||||
const myWork = () => {
|
||||
const portfolioEntries = require("../components/portfolioEntries.json");
|
||||
@ -11,21 +11,40 @@ const myWork = () => {
|
||||
<GeneralLayout>
|
||||
<Head>
|
||||
<title>My Work | Tben</title>
|
||||
<meta name="description" content="Some of my Work" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Some of my Work"
|
||||
/>
|
||||
</Head>
|
||||
<h1><TextShuffler textInput="My Work" /></h1>
|
||||
<span className='hero-sub-text'>
|
||||
<TextShuffler textInput="Some of my work ..." delay={ 500 } />
|
||||
|
||||
<div className="flex flex-col items-start w-full max-w-6xl">
|
||||
<h1>
|
||||
<TextShuffler textInput="My Work" />
|
||||
</h1>
|
||||
<span className="hero-sub-text mb-2">
|
||||
<TextShuffler
|
||||
textInput="Some of the projects I've worked on"
|
||||
delay={500}
|
||||
/>
|
||||
</span>
|
||||
|
||||
<div className='portfolio-entries-block mt-4'>
|
||||
{ portfolioEntries.map(entry => <PortfolioEntry key={ entry.title } title={ entry.title } description={ entry.description } url={ entry.url } image={ entry.image } />) }
|
||||
<div className="portfolio-entries-block mt-4">
|
||||
{portfolioEntries.map((entry) => (
|
||||
<PortfolioEntry
|
||||
key={entry.title}
|
||||
title={entry.title}
|
||||
description={entry.description}
|
||||
url={entry.url}
|
||||
image={entry.image}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</GeneralLayout>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default myWork
|
||||
export default myWork;
|
||||
|
||||
// {
|
||||
// "title": "Stirrmedia Social webapp",
|
||||
|
BIN
public/documents/Resume-Benjamin-Toby-Linkedin.pdf
Normal file
Before Width: | Height: | Size: 755 B After Width: | Height: | Size: 320 B |
BIN
public/favicon.png
Normal file
After Width: | Height: | Size: 320 B |
BIN
public/icons/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
public/icons/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 984 B |
BIN
public/icons/icon-192x192.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
public/icons/icon-256x256.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
public/icons/icon-384x384.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
public/icons/icon-512x512.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
public/icons/touch-icon-ipad-retina.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
public/icons/touch-icon-ipad.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
public/icons/touch-icon-iphone-retina.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
public/icons/touch-icon-iphone.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
15
public/images/logo-icon.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 611 611" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<rect id="Artboard1" x="0" y="0" width="610.63" height="610.63" style="fill:none;"/>
|
||||
<clipPath id="_clip1">
|
||||
<rect id="Artboard11" serif:id="Artboard1" x="0" y="0" width="610.63" height="610.63"/>
|
||||
</clipPath>
|
||||
<g clip-path="url(#_clip1)">
|
||||
<g id="Layer_2">
|
||||
<g id="Layer_1-2">
|
||||
<path d="M610.63,515.37L610.63,619.37L590.72,607.63L461.72,531.55L306.83,617L151.94,702.39L138.33,709.89L0,786.14L0,0L362,0C434.173,0 490.98,15.707 532.42,47.12C534.38,48.613 536.297,50.137 538.17,51.69C575.177,82.423 593.98,125.773 594.58,181.74L594.58,184.49C594.58,225.95 583.717,260.877 561.99,289.27C540.263,317.663 511.353,336.547 475.26,345.92C518.027,355.92 551.283,376.477 575.03,407.59C598.777,438.703 610.643,474.63 610.63,515.37ZM368.49,519.89C378.163,512.21 383,499.673 383,482.28C383,448.193 363.28,431.15 323.84,431.15L222.6,431.15L222.6,531.42L323.87,531.42C343.923,531.42 358.797,527.577 368.49,519.89ZM368,227.61C368,210.23 363.153,197.36 353.46,189C343.767,180.64 328.893,176.463 308.84,176.47L222.6,176.47L222.6,276.74L308.83,276.74C328.877,276.74 343.75,272.74 353.45,264.74C363.15,256.74 368,244.363 368,227.61Z" style="fill:rgb(99,105,176);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
1
public/images/logo-v3.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 610.63 1042.32"><defs><style>.cls-1{fill:#565e9a;}.cls-2{fill:#6369b0;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="610.63 784.67 610.63 952.95 461.71 1042.32 461.71 874.03 464.52 872.35 610.63 784.67"/><polygon class="cls-1" points="610.63 619.33 610.63 784.67 461.71 702.53 468.56 698.7 563.9 645.44 610.63 619.33"/><path class="cls-2" d="M610.63,515.37v104l-19.91-11.74-129-76.08L306.83,617,151.94,702.39l-13.61,7.5L0,786.14V0H362Q470.26,0,532.42,47.12q2.94,2.24,5.75,4.57,55.51,46.1,56.41,130.05c0,.92,0,1.83,0,2.75q0,62.19-32.59,104.78t-86.73,56.65q64.15,15,99.77,61.67T610.63,515.37Zm-242.14,4.52Q383,508.37,383,482.28q0-51.13-59.16-51.13H222.6V531.42H323.87Q353.95,531.42,368.49,519.89ZM368,227.61q0-26.07-14.54-38.61t-44.62-12.53H222.6V276.74h86.23q30.07,0,44.62-12T368,227.61Z"/><polygon class="cls-2" points="461.71 874.03 461.71 1042.32 306.83 957.3 306.83 789.02 461.71 874.03"/><polygon class="cls-1" points="306.83 789.02 306.83 957.3 151.94 1042.32 151.94 875.51 156.15 873.19 306.83 789.02"/><polygon class="cls-2" points="151.94 875.51 151.94 1042.32 0 958.98 0 786.14 151.94 875.51"/></g></g></svg>
|
After Width: | Height: | Size: 1.2 KiB |
14
public/images/logo-white.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 611 1043" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g id="Layer_2">
|
||||
<g id="Layer_1-2">
|
||||
<path d="M610.63,784.67L610.63,952.95L461.71,1042.32L461.71,874.03L464.52,872.35L610.63,784.67Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M610.63,619.33L610.63,784.67L461.71,702.53L468.56,698.7L563.9,645.44L610.63,619.33Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M610.63,515.37L610.63,619.37L590.72,607.63L461.72,531.55L306.83,617L151.94,702.39L138.33,709.89L0,786.14L0,0L362,0C434.173,0 490.98,15.707 532.42,47.12C534.38,48.613 536.297,50.137 538.17,51.69C575.177,82.423 593.98,125.773 594.58,181.74L594.58,184.49C594.58,225.95 583.717,260.877 561.99,289.27C540.263,317.663 511.353,336.547 475.26,345.92C518.027,355.92 551.283,376.477 575.03,407.59C598.777,438.703 610.643,474.63 610.63,515.37ZM368.49,519.89C378.163,512.21 383,499.673 383,482.28C383,448.193 363.28,431.15 323.84,431.15L222.6,431.15L222.6,531.42L323.87,531.42C343.923,531.42 358.797,527.577 368.49,519.89ZM368,227.61C368,210.23 363.153,197.36 353.46,189C343.767,180.64 328.893,176.463 308.84,176.47L222.6,176.47L222.6,276.74L308.83,276.74C328.877,276.74 343.75,272.74 353.45,264.74C363.15,256.74 368,244.363 368,227.61Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M461.71,874.03L461.71,1042.32L306.83,957.3L306.83,789.02L461.71,874.03Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M306.83,789.02L306.83,957.3L151.94,1042.32L151.94,875.51L156.15,873.19L306.83,789.02Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M151.94,875.51L151.94,1042.32L0,958.98L0,786.14L151.94,875.51Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
BIN
public/images/my-photo.png
Normal file
After Width: | Height: | Size: 402 KiB |
BIN
public/images/rm378-07c-min.png
Normal file
After Width: | Height: | Size: 180 KiB |
441
public/styles/main.css
Normal file
@ -0,0 +1,441 @@
|
||||
@import url("https://fonts.googleapis.com/css?family=Source+Code+Pro&display=swap");
|
||||
|
||||
html {
|
||||
width: 100%;
|
||||
/* overflow-x: hidden; */
|
||||
scroll-behavior: smooth;
|
||||
font-family: "Source Code Pro", Helvetica;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: #222;
|
||||
letter-spacing: -0.8px;
|
||||
}
|
||||
|
||||
* {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--main-color: #1668e4;
|
||||
--main-color-lighter: #5698fc;
|
||||
--dark-color: #201e1e;
|
||||
--sec-color-3: #688e26;
|
||||
--sec-color-4: #adb2d3;
|
||||
--sec-color-5: #c2a878;
|
||||
--light-color-1: rgb(64, 37, 216);
|
||||
--test-color: rgb(113, 116, 255);
|
||||
--transparent-white: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
body {
|
||||
width: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
top: 0;
|
||||
justify-content: center;
|
||||
background-color: var(--dark-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--sec-color-2);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 25px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: inherit;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
hr {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: var(--dark-color);
|
||||
/* background-color: #c52532; */
|
||||
color: white;
|
||||
border-color: var(--transparent-white);
|
||||
}
|
||||
|
||||
form * {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
header {
|
||||
z-index: 1000000;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 52px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* * */
|
||||
/* * */
|
||||
/* * */
|
||||
/* * */
|
||||
/* * */
|
||||
/* * */
|
||||
/* * */
|
||||
|
||||
p a,
|
||||
span a {
|
||||
color: var(--main-color-lighter);
|
||||
/* border-bottom: 1px solid var(--main-color-lighter); */
|
||||
}
|
||||
|
||||
p a:hover,
|
||||
span a:hover {
|
||||
color: var(--main-color);
|
||||
}
|
||||
|
||||
/* ################################################# -- Sliders */
|
||||
aside,
|
||||
.side-nav-block {
|
||||
scrollbar-width: none;
|
||||
}
|
||||
/* width */
|
||||
aside::-webkit-scrollbar,
|
||||
.side-nav-block::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
/* Track */
|
||||
aside::-webkit-scrollbar-track,
|
||||
.side-nav-block::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
aside::-webkit-scrollbar-thumb,
|
||||
.side-nav-block::-webkit-scrollbar-thumb {
|
||||
background: #dbe1eb;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* Handle on hover */
|
||||
aside::-webkit-scrollbar-thumb:hover,
|
||||
.side-nav-block::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
/*############################################# -- Common Actions */
|
||||
|
||||
.visible {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fixed {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.absolute {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.no-pointer-events {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.pointer-events {
|
||||
pointer-events: visible;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
/*############################################# -- Header */
|
||||
|
||||
header {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.logo-link-block h1 {
|
||||
font-size: 28px;
|
||||
margin: 0;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
nav a {
|
||||
opacity: 0.5;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
nav a:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.active-page {
|
||||
opacity: 1;
|
||||
border-bottom: 2px solid white;
|
||||
}
|
||||
|
||||
/*############################################# -- Shuffled Text */
|
||||
|
||||
#__next {
|
||||
max-width: 1200px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
animation-name: shuffle;
|
||||
animation-timing-function: ease-out;
|
||||
animation-duration: 1s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
/* .shuffled-text-span span {
|
||||
animation-name: shuffle;
|
||||
animation-timing-function: ease-out;
|
||||
animation-delay: 0.5s;
|
||||
} */
|
||||
|
||||
@keyframes shuffle {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*############################################# -- Hero Section */
|
||||
.hero-sub-text {
|
||||
font-size: 24px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.hero-ctas-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 40px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.hero-ctas-section a {
|
||||
padding: 10px 25px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: inherit;
|
||||
color: var(--dark-color);
|
||||
background-color: white;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.hero-ctas-section a:hover {
|
||||
background-color: var(--dark-color);
|
||||
color: white;
|
||||
border-color: var(--transparent-white);
|
||||
}
|
||||
|
||||
/*############################################# -- 404 page */
|
||||
.not-found-page-wrapper {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/*############################################# -- Portfolio page */
|
||||
.portfolio-entries-block {
|
||||
display: grid;
|
||||
gap: 40px;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.portfolio-entry-block {
|
||||
max-width: 700px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
padding: 20px;
|
||||
}
|
||||
/*############################################# -- Contact Forms */
|
||||
form {
|
||||
margin-top: 40px;
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
padding: 15px 20px;
|
||||
background-color: transparent;
|
||||
color: white;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
width: 100%;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.message-response {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: var(--dark-color);
|
||||
border: 2px solid #688e26;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.message-response.failed {
|
||||
border: 2px solid #d42222;
|
||||
}
|
||||
|
||||
#homepage-animation-wrapper {
|
||||
width: 150%;
|
||||
height: 100%;
|
||||
background-color: var(--dark-color);
|
||||
}
|
||||
|
||||
.tech-stack-header {
|
||||
color: rgb(113, 116, 255);
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.portfolio-image-wrapper {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
margin-bottom: 15px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.portfolio-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
object-position: left top;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* * */
|
||||
/* * */
|
||||
/* * */
|
||||
/* * */
|
||||
/* * */
|
||||
/* * */
|
||||
/* * */
|
||||
/* * */
|
||||
|
||||
span img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* * */
|
||||
/* * */
|
||||
/* * */
|
||||
/* * */
|
||||
/* * */
|
||||
/* * */
|
||||
/* * */
|
||||
/* * */
|
||||
|
||||
/* .transition-fade {
|
||||
transition: 0.4s;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
html.is-animating .transition-fade {
|
||||
opacity: 0;
|
||||
} */
|
||||
|
||||
/* ###############################################################################################
|
||||
##################################################################################################
|
||||
##################################################################################################
|
||||
##################################################################################################
|
||||
##################################### -- Mobile Styles -- ########################################
|
||||
##################################################################################################
|
||||
##################################################################################################
|
||||
##################################################################################################
|
||||
############################################################################################### */
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
}
|
||||
|
||||
@media (max-width: 990px) {
|
||||
.portfolio-entries-block {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
nav {
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 20px;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
.hero-ctas-section {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.portfolio-image-wrapper {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.portfolio-image {
|
||||
object-fit: contain;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 450px) {
|
||||
}
|
||||
|
||||
@media (max-width: 350px) {
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
@import url("https://fonts.googleapis.com/css?family=Source+Code+Pro&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css?family=Cutive+Mono&display=swap");
|
||||
|
||||
html {
|
||||
width: 100%;
|
||||
/* overflow-x: hidden; */
|
||||
scroll-behavior: smooth;
|
||||
font-family: "Source Code Pro", Helvetica;
|
||||
font-family: "Cutive Mono", Helvetica;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: #222;
|
||||
@ -18,9 +17,9 @@ html {
|
||||
}
|
||||
|
||||
:root {
|
||||
--main-color: #1668e4;
|
||||
--main-color-lighter: #5698fc;
|
||||
--dark-color: #201e1e;
|
||||
--main-color: #7174ff;
|
||||
--main-color-lighter: #9d9eff;
|
||||
--dark-color: #181515;
|
||||
--sec-color-3: #688e26;
|
||||
--sec-color-4: #adb2d3;
|
||||
--sec-color-5: #c2a878;
|
||||
@ -32,7 +31,6 @@ html {
|
||||
body {
|
||||
width: 100%;
|
||||
margin: 0px;
|
||||
padding: 40px;
|
||||
top: 0;
|
||||
justify-content: center;
|
||||
background-color: var(--dark-color);
|
||||
@ -49,19 +47,25 @@ a:hover {
|
||||
color: var(--sec-color-2);
|
||||
}
|
||||
|
||||
button {
|
||||
button,
|
||||
.button {
|
||||
padding: 10px 25px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: inherit;
|
||||
border: 1px solid transparent;
|
||||
color: var(--dark-color);
|
||||
background-color: white;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
hr {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
button:hover,
|
||||
.button:hover {
|
||||
background-color: var(--dark-color);
|
||||
/* background-color: #c52532; */
|
||||
color: white;
|
||||
@ -75,7 +79,6 @@ form * {
|
||||
|
||||
header {
|
||||
z-index: 1000000;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@ -84,6 +87,18 @@ h1 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 38px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
/* * */
|
||||
/* * */
|
||||
/* * */
|
||||
@ -204,9 +219,7 @@ nav a:hover {
|
||||
/*############################################# -- Shuffled Text */
|
||||
|
||||
#__next {
|
||||
max-width: 1200px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
animation-name: shuffle;
|
||||
animation-timing-function: ease-out;
|
||||
@ -329,7 +342,7 @@ textarea {
|
||||
}
|
||||
|
||||
.tech-stack-header {
|
||||
color: rgb(113, 116, 255);
|
||||
color: #7174ff;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
@ -412,11 +425,6 @@ html.is-animating .transition-fade {
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 20px;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
.hero-ctas-section {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
*, ::before, ::after {
|
||||
--tw-border-spacing-x: 0;
|
||||
--tw-border-spacing-y: 0;
|
||||
--tw-translate-x: 0;
|
||||
--tw-translate-y: 0;
|
||||
--tw-rotate: 0;
|
||||
@ -10,6 +12,59 @@
|
||||
--tw-pan-y: ;
|
||||
--tw-pinch-zoom: ;
|
||||
--tw-scroll-snap-strictness: proximity;
|
||||
--tw-gradient-from-position: ;
|
||||
--tw-gradient-via-position: ;
|
||||
--tw-gradient-to-position: ;
|
||||
--tw-ordinal: ;
|
||||
--tw-slashed-zero: ;
|
||||
--tw-numeric-figure: ;
|
||||
--tw-numeric-spacing: ;
|
||||
--tw-numeric-fraction: ;
|
||||
--tw-ring-inset: ;
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
--tw-blur: ;
|
||||
--tw-brightness: ;
|
||||
--tw-contrast: ;
|
||||
--tw-grayscale: ;
|
||||
--tw-hue-rotate: ;
|
||||
--tw-invert: ;
|
||||
--tw-saturate: ;
|
||||
--tw-sepia: ;
|
||||
--tw-drop-shadow: ;
|
||||
--tw-backdrop-blur: ;
|
||||
--tw-backdrop-brightness: ;
|
||||
--tw-backdrop-contrast: ;
|
||||
--tw-backdrop-grayscale: ;
|
||||
--tw-backdrop-hue-rotate: ;
|
||||
--tw-backdrop-invert: ;
|
||||
--tw-backdrop-opacity: ;
|
||||
--tw-backdrop-saturate: ;
|
||||
--tw-backdrop-sepia:
|
||||
}
|
||||
|
||||
::backdrop {
|
||||
--tw-border-spacing-x: 0;
|
||||
--tw-border-spacing-y: 0;
|
||||
--tw-translate-x: 0;
|
||||
--tw-translate-y: 0;
|
||||
--tw-rotate: 0;
|
||||
--tw-skew-x: 0;
|
||||
--tw-skew-y: 0;
|
||||
--tw-scale-x: 1;
|
||||
--tw-scale-y: 1;
|
||||
--tw-pan-x: ;
|
||||
--tw-pan-y: ;
|
||||
--tw-pinch-zoom: ;
|
||||
--tw-scroll-snap-strictness: proximity;
|
||||
--tw-gradient-from-position: ;
|
||||
--tw-gradient-via-position: ;
|
||||
--tw-gradient-to-position: ;
|
||||
--tw-ordinal: ;
|
||||
--tw-slashed-zero: ;
|
||||
--tw-numeric-figure: ;
|
||||
@ -47,32 +102,56 @@
|
||||
position: fixed
|
||||
}
|
||||
|
||||
.top-0 {
|
||||
top: 0px
|
||||
.absolute {
|
||||
position: absolute
|
||||
}
|
||||
|
||||
.relative {
|
||||
position: relative
|
||||
}
|
||||
|
||||
.left-0 {
|
||||
left: 0px
|
||||
}
|
||||
|
||||
.-z-10 {
|
||||
z-index: -10
|
||||
.top-0 {
|
||||
top: 0px
|
||||
}
|
||||
|
||||
.z-10 {
|
||||
z-index: 10
|
||||
}
|
||||
|
||||
.z-\[-1\] {
|
||||
z-index: -1
|
||||
}
|
||||
|
||||
.z-\[-2\] {
|
||||
z-index: -2
|
||||
}
|
||||
|
||||
.m-0 {
|
||||
margin: 0px
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 1rem
|
||||
.-mb-4 {
|
||||
margin-bottom: -1rem
|
||||
}
|
||||
|
||||
.mt-10 {
|
||||
margin-top: 2.5rem
|
||||
.-mt-10 {
|
||||
margin-top: -2.5rem
|
||||
}
|
||||
|
||||
.mb-1 {
|
||||
margin-bottom: 0.25rem
|
||||
.-mt-64 {
|
||||
margin-top: -16rem
|
||||
}
|
||||
|
||||
.-mt-\[120px\] {
|
||||
margin-top: -120px
|
||||
}
|
||||
|
||||
.mb-2 {
|
||||
margin-bottom: 0.5rem
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
@ -83,34 +162,42 @@
|
||||
margin-bottom: 2rem
|
||||
}
|
||||
|
||||
.mb-0 {
|
||||
margin-bottom: 0px
|
||||
.mr-0 {
|
||||
margin-right: 0px
|
||||
}
|
||||
|
||||
.mb-3 {
|
||||
margin-bottom: 0.75rem
|
||||
.mt-10 {
|
||||
margin-top: 2.5rem
|
||||
}
|
||||
|
||||
.mb-2 {
|
||||
margin-bottom: 0.5rem
|
||||
.mt-20 {
|
||||
margin-top: 5rem
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 1rem
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex
|
||||
}
|
||||
|
||||
.h-6 {
|
||||
height: 1.5rem
|
||||
.h-20 {
|
||||
height: 5rem
|
||||
}
|
||||
|
||||
.h-44 {
|
||||
height: 11rem
|
||||
}
|
||||
|
||||
.h-full {
|
||||
height: 100%
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.max-w-xl {
|
||||
max-width: 36rem
|
||||
}
|
||||
|
||||
.max-w-2xl {
|
||||
max-width: 42rem
|
||||
}
|
||||
@ -119,18 +206,66 @@
|
||||
max-width: 48rem
|
||||
}
|
||||
|
||||
.max-w-4xl {
|
||||
max-width: 56rem
|
||||
.max-w-6xl {
|
||||
max-width: 72rem
|
||||
}
|
||||
|
||||
.max-w-\[450px\] {
|
||||
max-width: 450px
|
||||
}
|
||||
|
||||
.grow {
|
||||
flex-grow: 1
|
||||
}
|
||||
|
||||
.cursor-pointer {
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
flex-direction: column
|
||||
}
|
||||
|
||||
.flex-col-reverse {
|
||||
flex-direction: column-reverse
|
||||
}
|
||||
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap
|
||||
}
|
||||
|
||||
.items-start {
|
||||
align-items: flex-start
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center
|
||||
}
|
||||
|
||||
.items-stretch {
|
||||
align-items: stretch
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
justify-content: center
|
||||
}
|
||||
|
||||
.justify-between {
|
||||
justify-content: space-between
|
||||
}
|
||||
|
||||
.gap-0 {
|
||||
gap: 0px
|
||||
}
|
||||
|
||||
.gap-1 {
|
||||
gap: 0.25rem
|
||||
}
|
||||
|
||||
.gap-10 {
|
||||
gap: 2.5rem
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 0.5rem
|
||||
}
|
||||
@ -139,6 +274,22 @@
|
||||
gap: 1rem
|
||||
}
|
||||
|
||||
.gap-8 {
|
||||
gap: 2rem
|
||||
}
|
||||
|
||||
.overflow-hidden {
|
||||
overflow: hidden
|
||||
}
|
||||
|
||||
.whitespace-nowrap {
|
||||
white-space: nowrap
|
||||
}
|
||||
|
||||
.rounded-full {
|
||||
border-radius: 9999px
|
||||
}
|
||||
|
||||
.border {
|
||||
border-width: 1px
|
||||
}
|
||||
@ -151,30 +302,23 @@
|
||||
border-style: solid
|
||||
}
|
||||
|
||||
.border-blue-500 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(59 130 246 / var(--tw-border-opacity))
|
||||
}
|
||||
|
||||
.border-white\/40 {
|
||||
border-color: rgb(255 255 255 / 0.4)
|
||||
}
|
||||
|
||||
.border-white\/20 {
|
||||
border-color: rgb(255 255 255 / 0.2)
|
||||
}
|
||||
|
||||
.border-white\/50 {
|
||||
border-color: rgb(255 255 255 / 0.5)
|
||||
}
|
||||
|
||||
.bg-blue-600 {
|
||||
.bg-\[\#343680\] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(37 99 235 / var(--tw-bg-opacity))
|
||||
background-color: rgb(52 54 128 / var(--tw-bg-opacity))
|
||||
}
|
||||
|
||||
.bg-transparent {
|
||||
background-color: transparent
|
||||
.bg-\[\#3e3f9c\] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(62 63 156 / var(--tw-bg-opacity))
|
||||
}
|
||||
|
||||
.bg-primary\/10 {
|
||||
background-color: rgb(113 116 255 / 0.1)
|
||||
}
|
||||
|
||||
.object-cover {
|
||||
-o-object-fit: cover;
|
||||
object-fit: cover
|
||||
}
|
||||
|
||||
.p-4 {
|
||||
@ -185,8 +329,58 @@
|
||||
padding: 2rem
|
||||
}
|
||||
|
||||
.text-xl {
|
||||
font-size: 1.25rem;
|
||||
.p-\[100px\] {
|
||||
padding: 100px
|
||||
}
|
||||
|
||||
.p-\[150px\] {
|
||||
padding: 150px
|
||||
}
|
||||
|
||||
.px-4 {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem
|
||||
}
|
||||
|
||||
.py-2 {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem
|
||||
}
|
||||
|
||||
.py-20 {
|
||||
padding-top: 5rem;
|
||||
padding-bottom: 5rem
|
||||
}
|
||||
|
||||
.py-4 {
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem
|
||||
}
|
||||
|
||||
.pb-40 {
|
||||
padding-bottom: 10rem
|
||||
}
|
||||
|
||||
.pt-\[450px\] {
|
||||
padding-top: 450px
|
||||
}
|
||||
|
||||
.text-5xl {
|
||||
font-size: 3rem;
|
||||
line-height: 1
|
||||
}
|
||||
|
||||
.text-\[300px\] {
|
||||
font-size: 300px
|
||||
}
|
||||
|
||||
.text-base {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem
|
||||
}
|
||||
|
||||
.text-lg {
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.75rem
|
||||
}
|
||||
|
||||
@ -195,63 +389,66 @@
|
||||
line-height: 1.25rem
|
||||
}
|
||||
|
||||
.text-lg {
|
||||
font-size: 1.125rem;
|
||||
.text-\[20px\] {
|
||||
font-size: 20px
|
||||
}
|
||||
|
||||
.text-xl {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.75rem
|
||||
}
|
||||
|
||||
.text-base {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem
|
||||
.uppercase {
|
||||
text-transform: uppercase
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 700
|
||||
.leading-snug {
|
||||
line-height: 1.375
|
||||
}
|
||||
|
||||
.text-white {
|
||||
.text-primary-light {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity))
|
||||
}
|
||||
|
||||
.text-white\/50 {
|
||||
color: rgb(255 255 255 / 0.5)
|
||||
}
|
||||
|
||||
.opacity-50 {
|
||||
opacity: 0.5
|
||||
}
|
||||
|
||||
.opacity-40 {
|
||||
opacity: 0.4
|
||||
}
|
||||
|
||||
.opacity-90 {
|
||||
opacity: 0.9
|
||||
}
|
||||
|
||||
.opacity-60 {
|
||||
opacity: 0.6
|
||||
color: rgb(157 158 255 / var(--tw-text-opacity))
|
||||
}
|
||||
|
||||
.opacity-20 {
|
||||
opacity: 0.2
|
||||
}
|
||||
|
||||
.opacity-30 {
|
||||
opacity: 0.3
|
||||
.opacity-40 {
|
||||
opacity: 0.4
|
||||
}
|
||||
|
||||
.opacity-70 {
|
||||
opacity: 0.7
|
||||
.opacity-5 {
|
||||
opacity: 0.05
|
||||
}
|
||||
|
||||
.opacity-50 {
|
||||
opacity: 0.5
|
||||
}
|
||||
|
||||
.opacity-80 {
|
||||
opacity: 0.8
|
||||
}
|
||||
|
||||
.outline {
|
||||
outline-style: solid
|
||||
.blur {
|
||||
--tw-blur: blur(8px);
|
||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)
|
||||
}
|
||||
|
||||
.blur-sm {
|
||||
--tw-blur: blur(4px);
|
||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)
|
||||
}
|
||||
|
||||
.contrast-\[140\%\] {
|
||||
--tw-contrast: contrast(140%);
|
||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)
|
||||
}
|
||||
|
||||
.grayscale {
|
||||
--tw-grayscale: grayscale(100%);
|
||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)
|
||||
}
|
||||
|
||||
.filter {
|
||||
@ -264,7 +461,65 @@
|
||||
transition-duration: 150ms
|
||||
}
|
||||
|
||||
.hover\:bg-blue-600:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(37 99 235 / var(--tw-bg-opacity))
|
||||
.hover\:opacity-60:hover {
|
||||
opacity: 0.6
|
||||
}
|
||||
|
||||
@media (min-width: 600px) {
|
||||
.md\:relative {
|
||||
position: relative
|
||||
}
|
||||
|
||||
.md\:items-start {
|
||||
align-items: flex-start
|
||||
}
|
||||
|
||||
.md\:px-10 {
|
||||
padding-left: 2.5rem;
|
||||
padding-right: 2.5rem
|
||||
}
|
||||
|
||||
.md\:px-6 {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem
|
||||
}
|
||||
|
||||
.md\:py-10 {
|
||||
padding-top: 2.5rem;
|
||||
padding-bottom: 2.5rem
|
||||
}
|
||||
|
||||
.md\:pt-0 {
|
||||
padding-top: 0px
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.xl\:-mr-14 {
|
||||
margin-right: -3.5rem
|
||||
}
|
||||
|
||||
.xl\:-mt-\[160px\] {
|
||||
margin-top: -160px
|
||||
}
|
||||
|
||||
.xl\:w-\[120\%\] {
|
||||
width: 120%
|
||||
}
|
||||
|
||||
.xl\:w-\[40\%\] {
|
||||
width: 40%
|
||||
}
|
||||
|
||||
.xl\:w-\[60\%\] {
|
||||
width: 60%
|
||||
}
|
||||
|
||||
.xl\:flex-row {
|
||||
flex-direction: row
|
||||
}
|
||||
|
||||
.xl\:flex-col {
|
||||
flex-direction: column
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
content: ["./components/**/*.{html,js,jsx}", "./pages/**/*.jsx", , "./layouts/**/*.jsx"],
|
||||
content: ["./components/**/*.{html,js,jsx}", "./pages/**/*.{jsx,tsx}", "./app/**/*.{html,js,jsx,tsx}", "./layouts/**/*.{jsx,tsx}"],
|
||||
theme: {
|
||||
screens: {
|
||||
xs: "350px",
|
||||
@ -9,7 +9,10 @@ module.exports = {
|
||||
lg: "976px",
|
||||
xl: "1200px",
|
||||
},
|
||||
// colors: {},
|
||||
colors: {
|
||||
primary: "#7174ff",
|
||||
"primary-light": "#9d9eff",
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ["Graphik", "sans-serif"],
|
||||
serif: ["Merriweather", "serif"],
|
||||
|
111
tsconfig.json
Normal file
@ -0,0 +1,111 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
/* Language and Environment */
|
||||
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
/* Modules */
|
||||
"module": "commonjs" /* Specify what module code is generated. */, // "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */ /* Type Checking */,
|
||||
"strict": true /* Enable all strict type-checking options. */, // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"noEmit": true,
|
||||
"incremental": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"moduleResolution": "node",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "pages/_app.js", ".next/types/**/*.ts", "dump/index.jsx", "pages/_document.js"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|