Compare commits
No commits in common. "fb9d9dc1931a5e553a4ac9a51a1723b0d1019cb9" and "44bd7f49d827e952aa9dd989add01140666c6200" have entirely different histories.
fb9d9dc193
...
44bd7f49d8
18 changed files with 774 additions and 251 deletions
19
LICENSE
19
LICENSE
|
|
@ -1,19 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
81
README.md
81
README.md
|
|
@ -1,57 +1,9 @@
|
||||||

|
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
# hangerthem.com
|
## Getting Started
|
||||||
|
|
||||||
Welcome to my personal portfolio repository, **hangerthem.com**. This webpage is a showcase of my skills and projects, providing a glimpse into who I am and what I do.
|
First, run the development server:
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
**hangerthem.com** is a fully custom-designed and coded personal portfolio website. It highlights my skills, projects, and personal information in a clean, responsive layout. The site is built with Next.js and styled-components, and it includes a reCaptcha implementation for secure email communication.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Fully custom design and code
|
|
||||||
- Built with Next.js and styled-components
|
|
||||||
- reCaptcha implemented for secure email sending
|
|
||||||
- Responsive design for all device sizes
|
|
||||||
- Easy navigation with a fixed navbar
|
|
||||||
|
|
||||||
## Installation Instructions
|
|
||||||
|
|
||||||
To run this project locally, you will need Node.js (or Bun) installed on your machine. Follow these steps:
|
|
||||||
|
|
||||||
1. **Clone the repository:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/HangerThem/hangerthem.com.git
|
|
||||||
cd hangerthem.com
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Install dependencies:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
# or
|
|
||||||
yarn install
|
|
||||||
# or
|
|
||||||
pnpm install
|
|
||||||
# or
|
|
||||||
bun install
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Set up environment variables:**
|
|
||||||
Create a `.env` file in the root directory and add the required environment variables. (Contact me if you need specific details about these variables.)
|
|
||||||
|
|
||||||
4. **Run the development server:**
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
npm run dev
|
||||||
# or
|
# or
|
||||||
|
|
@ -62,28 +14,23 @@ To run this project locally, you will need Node.js (or Bun) installed on your ma
|
||||||
bun dev
|
bun dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
## Usage
|
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||||
|
|
||||||
The portfolio is a single-page application with a navbar for easy navigation between sections. It is fully responsive and works well on all device sizes.
|
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||||
|
|
||||||
### Lighthouse Report
|
## Learn More
|
||||||
|
|
||||||

|
To learn more about Next.js, take a look at the following resources:
|
||||||
|
|
||||||
## Contributing
|
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||||
|
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||||
|
|
||||||
This is a personal portfolio, so contributions are not expected. However, if you find any issues or have suggestions, feel free to open an issue or contact me directly.
|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||||
|
|
||||||
## License
|
## Deploy on Vercel
|
||||||
|
|
||||||
This project is open source and available under the [MIT License](LICENSE).
|
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||||
|
|
||||||
## Contact Information
|
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||||
|
|
||||||
You can contact me through the contact form on the website or directly via email at [f.borisjuk@hangerthem.com](mailto:f.borisjuk@hangerthem.com).
|
|
||||||
|
|
||||||
## Additional Information
|
|
||||||
|
|
||||||
For any other information or inquiries, please feel free to reach out through the provided contact methods.
|
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,31 @@ import {
|
||||||
successResponse,
|
successResponse,
|
||||||
optionsResponse,
|
optionsResponse,
|
||||||
badRequestResponse,
|
badRequestResponse,
|
||||||
|
tooManyRequestsResponse,
|
||||||
internalServerErrorResponse,
|
internalServerErrorResponse,
|
||||||
} from "@/helpers/apiHelper"
|
} from "@/helpers/apiHelper"
|
||||||
import { NextRequest, NextResponse } from "next/server"
|
import { NextRequest, NextResponse } from "next/server"
|
||||||
import { sendEmail } from "@/utils/emailUtils"
|
import { sendEmail } from "@/utils/emailUtils"
|
||||||
|
import rateLimit from "express-rate-limit"
|
||||||
|
|
||||||
|
const limiter = rateLimit({
|
||||||
|
windowMs: 15 * 60 * 1000,
|
||||||
|
max: 100,
|
||||||
|
message: "Too many requests, please try again later.",
|
||||||
|
})
|
||||||
|
|
||||||
export async function POST(req: NextRequest, res: NextResponse) {
|
export async function POST(req: NextRequest, res: NextResponse) {
|
||||||
|
if (process.env.NODE_ENV !== "development") {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
limiter(req, res, (result: any) => {
|
||||||
|
if (result instanceof Error) {
|
||||||
|
return tooManyRequestsResponse()
|
||||||
|
}
|
||||||
|
resolve(result)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { email, name, message, recaptchaToken } = await req.json()
|
const { email, name, message, recaptchaToken } = await req.json()
|
||||||
|
|
||||||
|
|
@ -37,7 +56,7 @@ export async function POST(req: NextRequest, res: NextResponse) {
|
||||||
text: message,
|
text: message,
|
||||||
})
|
})
|
||||||
|
|
||||||
return successResponse({ message: "Email sent successfully" })
|
return successResponse("Email sent successfully")
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
return internalServerErrorResponse()
|
return internalServerErrorResponse()
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,12 @@ export const metadata: Metadata = {
|
||||||
alternateLocale: ["cs_CZ"],
|
alternateLocale: ["cs_CZ"],
|
||||||
countryName: "Czechia",
|
countryName: "Czechia",
|
||||||
emails: ["f.borisjuk@hangerthem.com"],
|
emails: ["f.borisjuk@hangerthem.com"],
|
||||||
images: ["https://hangerthem.com/hangerthem.png"],
|
|
||||||
url: "https://hangerthem.com",
|
url: "https://hangerthem.com",
|
||||||
determiner: "auto",
|
determiner: "auto",
|
||||||
ttl: 604800,
|
ttl: 604800,
|
||||||
audio: [],
|
audio: [],
|
||||||
faxNumbers: [],
|
faxNumbers: [],
|
||||||
|
images: [],
|
||||||
phoneNumbers: [],
|
phoneNumbers: [],
|
||||||
videos: [],
|
videos: [],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,6 @@ export default function About() {
|
||||||
I've built{" "}
|
I've built{" "}
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/HangerThem/linkboard"
|
href="https://github.com/HangerThem/linkboard"
|
||||||
aria-label="Linkboard a cool alternative to Linktree"
|
|
||||||
referrerPolicy="no-referrer"
|
referrerPolicy="no-referrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
|
|
@ -99,7 +98,6 @@ export default function About() {
|
||||||
, a cool alternative to Linktree, and I'm currently developing{" "}
|
, a cool alternative to Linktree, and I'm currently developing{" "}
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/HangerThem/ephemr"
|
href="https://github.com/HangerThem/ephemr"
|
||||||
aria-label="Ephemr a simple social network"
|
|
||||||
referrerPolicy="no-referrer"
|
referrerPolicy="no-referrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,6 @@ export default function Contact() {
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!data.name || !data.email || !data.message || loading}
|
disabled={!data.name || !data.email || !data.message || loading}
|
||||||
aria-label="Send email"
|
|
||||||
>
|
>
|
||||||
{loading ? "Sending..." : "Send"}
|
{loading ? "Sending..." : "Send"}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -124,7 +123,6 @@ export default function Contact() {
|
||||||
<SocialMedia>
|
<SocialMedia>
|
||||||
<Link
|
<Link
|
||||||
href="https://www.instagram.com/ferda_borisjuk/"
|
href="https://www.instagram.com/ferda_borisjuk/"
|
||||||
arial-label="Contact Frank Borisjuk on Instagram"
|
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
|
|
@ -132,7 +130,6 @@ export default function Contact() {
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/HangerThem"
|
href="https://github.com/HangerThem"
|
||||||
arial-label="Contact Frank Borisjuk on GitHub"
|
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
|
|
@ -140,7 +137,6 @@ export default function Contact() {
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="https://discord.com/users/495134242825699328"
|
href="https://discord.com/users/495134242825699328"
|
||||||
arial-label="Contact Frank Borisjuk on Discord"
|
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
|
|
@ -148,7 +144,6 @@ export default function Contact() {
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="https://www.linkedin.com/in/franti%C5%A1ek-borisjuk-022686225/"
|
href="https://www.linkedin.com/in/franti%C5%A1ek-borisjuk-022686225/"
|
||||||
arial-label="Contact Frank Borisjuk on LinkedIn"
|
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -41,20 +41,12 @@ export default function Footer() {
|
||||||
<FooterContent>
|
<FooterContent>
|
||||||
<p>
|
<p>
|
||||||
Made with ❤️ by{" "}
|
Made with ❤️ by{" "}
|
||||||
<Link
|
<Link href="https://github.com/HangerThem">Frank Borisjuk</Link>
|
||||||
href="https://github.com/HangerThem"
|
|
||||||
aria-label="Frank Borisjuk's GitHub"
|
|
||||||
>
|
|
||||||
Frank Borisjuk
|
|
||||||
</Link>
|
|
||||||
</p>
|
</p>
|
||||||
<p>© 2024 - All rights reserved</p>
|
<p>© 2024 - All rights reserved</p>
|
||||||
<p>
|
<p>
|
||||||
Sources available on{" "}
|
Sources available on{" "}
|
||||||
<Link
|
<Link href="https://github.com/HangerThem/hangerthem.com">
|
||||||
href="https://github.com/HangerThem/hangerthem.com"
|
|
||||||
aria-label="This site's repository on GitHub"
|
|
||||||
>
|
|
||||||
GitHub
|
GitHub
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -348,9 +348,7 @@ const Navbar = () => {
|
||||||
<NavbarContainer>
|
<NavbarContainer>
|
||||||
<Topbar>
|
<Topbar>
|
||||||
<Title>
|
<Title>
|
||||||
<Link href="/" aria-label="HangerThem's Home Page">
|
<Link href="/">HangerThem</Link>
|
||||||
HangerThem
|
|
||||||
</Link>
|
|
||||||
</Title>
|
</Title>
|
||||||
<HamburgerWrapper
|
<HamburgerWrapper
|
||||||
ref={hanburgerRef}
|
ref={hanburgerRef}
|
||||||
|
|
@ -366,7 +364,6 @@ const Navbar = () => {
|
||||||
<li key={link.id}>
|
<li key={link.id}>
|
||||||
<Link
|
<Link
|
||||||
href={link.href}
|
href={link.href}
|
||||||
arial-label={link.text}
|
|
||||||
className={navState.activeTag === link.id ? "active" : ""}
|
className={navState.activeTag === link.id ? "active" : ""}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setNavState((prev) => ({ ...prev, activeTag: link.id }))
|
setNavState((prev) => ({ ...prev, activeTag: link.id }))
|
||||||
|
|
@ -379,11 +376,7 @@ const Navbar = () => {
|
||||||
</LinksContainer>
|
</LinksContainer>
|
||||||
<SocialLinksContainer>
|
<SocialLinksContainer>
|
||||||
{socialLinks.map((socialLink) => (
|
{socialLinks.map((socialLink) => (
|
||||||
<Link
|
<Link key={socialLink.id} href={socialLink.href}>
|
||||||
key={socialLink.id}
|
|
||||||
href={socialLink.href}
|
|
||||||
aria-label={socialLink.text}
|
|
||||||
>
|
|
||||||
{socialLink.icon}
|
{socialLink.icon}
|
||||||
{socialLink.text}
|
{socialLink.text}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,10 @@ import { Container, Content } from "@/styles/pageStyles"
|
||||||
const CarouselWrapper = styled.div`
|
const CarouselWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
transition: transform 0.5s ease-in-out;
|
transition: transform 0.5s ease-in-out;
|
||||||
flex-direction: column;
|
gap: 2rem;
|
||||||
width: 100%;
|
|
||||||
gap: 1rem;
|
|
||||||
|
|
||||||
@media (min-width: 1250px) {
|
@media (max-width: 1200px) {
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
@ -27,9 +25,9 @@ const CarouselContainer = styled.div`
|
||||||
`
|
`
|
||||||
|
|
||||||
const ProjectCard = styled.div`
|
const ProjectCard = styled.div`
|
||||||
width: 100%;
|
padding: 2rem;
|
||||||
height: auto;
|
width: 400px;
|
||||||
padding: 1rem;
|
height: 600px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
|
@ -42,7 +40,7 @@ const ProjectCard = styled.div`
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
||||||
@media (min-width: 1250px) {
|
@media (max-width: 1200px) {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -51,18 +49,20 @@ const ProjectCard = styled.div`
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|
||||||
@media (min-width: 1250px) {
|
@media (max-width: 1200px) {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: 200px;
|
||||||
min-height: 150px;
|
object-fit: cover;
|
||||||
max-height: 200px;
|
|
||||||
object-fit: contain;
|
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.active) {
|
&:not(.active) {
|
||||||
|
|
@ -70,16 +70,15 @@ const ProjectCard = styled.div`
|
||||||
transform: scale(0.8);
|
transform: scale(0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1250px) {
|
@media (max-width: 1200px) {
|
||||||
padding: 2rem;
|
width: 100%;
|
||||||
width: 400px;
|
height: auto;
|
||||||
height: 550px;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const NavigationButton = styled.button`
|
const NavigationButton = styled.button`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: none;
|
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -94,17 +93,17 @@ const NavigationButton = styled.button`
|
||||||
color: rgb(var(--white));
|
color: rgb(var(--white));
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1250px) {
|
@media (max-width: 1200px) {
|
||||||
display: block;
|
display: none;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const PrevButton = styled(NavigationButton)`
|
const PrevButton = styled(NavigationButton)`
|
||||||
left: -0.25rem;
|
left: 0;
|
||||||
`
|
`
|
||||||
|
|
||||||
const NextButton = styled(NavigationButton)`
|
const NextButton = styled(NavigationButton)`
|
||||||
right: -0.25rem;
|
right: 0;
|
||||||
`
|
`
|
||||||
|
|
||||||
const CarouselIndicator = styled.div`
|
const CarouselIndicator = styled.div`
|
||||||
|
|
@ -113,7 +112,7 @@ const CarouselIndicator = styled.div`
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
|
||||||
@media (min-width: 1250px) {
|
@media (max-width: 1200px) {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
@ -129,7 +128,7 @@ const IndicatorDot = styled.div`
|
||||||
background-color: rgb(var(--white));
|
background-color: rgb(var(--white));
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1250px) {
|
@media (max-width: 1200px) {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
}
|
}
|
||||||
|
|
@ -147,7 +146,7 @@ const TechStack = styled.div`
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
|
|
||||||
@media (min-width: 1250px) {
|
@media (max-width: 1200px) {
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
padding: 0.2rem 0.4rem;
|
padding: 0.2rem 0.4rem;
|
||||||
}
|
}
|
||||||
|
|
@ -174,10 +173,9 @@ const ViewLink = styled(Link)`
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgb(var(--primary));
|
background-color: rgb(var(--primary));
|
||||||
color: rgb(var(--white));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1250px) {
|
@media (max-width: 1200px) {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
padding: 0.4rem 0.8rem;
|
padding: 0.4rem 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
@ -193,7 +191,7 @@ const SourceLink = styled(Link)`
|
||||||
color: rgb(var(--white));
|
color: rgb(var(--white));
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1250px) {
|
@media (max-width: 1200px) {
|
||||||
svg {
|
svg {
|
||||||
width: 25px;
|
width: 25px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
|
|
@ -271,7 +269,6 @@ const Projects = () => {
|
||||||
href={project.href}
|
href={project.href}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
title="View Project"
|
title="View Project"
|
||||||
arial-label="View Project"
|
|
||||||
>
|
>
|
||||||
View Project
|
View Project
|
||||||
</ViewLink>
|
</ViewLink>
|
||||||
|
|
@ -280,7 +277,6 @@ const Projects = () => {
|
||||||
href={project.source}
|
href={project.source}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
title="View Source"
|
title="View Source"
|
||||||
arial-label="View Source"
|
|
||||||
>
|
>
|
||||||
<Git size={30} />
|
<Git size={30} />
|
||||||
</SourceLink>
|
</SourceLink>
|
||||||
|
|
@ -288,10 +284,10 @@ const Projects = () => {
|
||||||
</ProjectCard>
|
</ProjectCard>
|
||||||
))}
|
))}
|
||||||
</CarouselWrapper>
|
</CarouselWrapper>
|
||||||
<PrevButton onClick={handlePrev} aria-label="Previous Project">
|
<PrevButton onClick={handlePrev}>
|
||||||
<CaretLeftFill size={50} />
|
<CaretLeftFill size={50} />
|
||||||
</PrevButton>
|
</PrevButton>
|
||||||
<NextButton onClick={handleNext} aria-label="Next Project">
|
<NextButton onClick={handleNext}>
|
||||||
<CaretRightFill size={50} />
|
<CaretRightFill size={50} />
|
||||||
</NextButton>
|
</NextButton>
|
||||||
<CarouselIndicator>
|
<CarouselIndicator>
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ export default function Skills() {
|
||||||
<Categories>
|
<Categories>
|
||||||
{skills.languages && (
|
{skills.languages && (
|
||||||
<SkillCategory>
|
<SkillCategory>
|
||||||
<h3>Languages</h3>
|
<h4>Languages</h4>
|
||||||
<ul>
|
<ul>
|
||||||
{skills.languages.map((language, index) => (
|
{skills.languages.map((language, index) => (
|
||||||
<li key={index}>
|
<li key={index}>
|
||||||
|
|
@ -115,7 +115,7 @@ export default function Skills() {
|
||||||
)}
|
)}
|
||||||
{skills.programmingLanguages && (
|
{skills.programmingLanguages && (
|
||||||
<SkillCategory>
|
<SkillCategory>
|
||||||
<h3>Programming Languages</h3>
|
<h4>Programming Languages</h4>
|
||||||
<ul>
|
<ul>
|
||||||
{skills.programmingLanguages.map(
|
{skills.programmingLanguages.map(
|
||||||
(programmingLanguage, index) => (
|
(programmingLanguage, index) => (
|
||||||
|
|
@ -132,7 +132,7 @@ export default function Skills() {
|
||||||
)}
|
)}
|
||||||
{skills.technologies && (
|
{skills.technologies && (
|
||||||
<SkillCategory>
|
<SkillCategory>
|
||||||
<h3>Technologies</h3>
|
<h4>Technologies</h4>
|
||||||
<ul>
|
<ul>
|
||||||
{skills.technologies.map((technology, index) => (
|
{skills.technologies.map((technology, index) => (
|
||||||
<li key={index}>
|
<li key={index}>
|
||||||
|
|
@ -147,7 +147,7 @@ export default function Skills() {
|
||||||
)}
|
)}
|
||||||
{skills.tools && (
|
{skills.tools && (
|
||||||
<SkillCategory>
|
<SkillCategory>
|
||||||
<h3>Tools</h3>
|
<h4>Tools</h4>
|
||||||
<ul>
|
<ul>
|
||||||
{skills.tools.map((tool, index) => (
|
{skills.tools.map((tool, index) => (
|
||||||
<li key={index}>
|
<li key={index}>
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ const projects: Project[] = [
|
||||||
{
|
{
|
||||||
title: "HangerThem Portfolio",
|
title: "HangerThem Portfolio",
|
||||||
description: "This very website and my personal portfolio.",
|
description: "This very website and my personal portfolio.",
|
||||||
image: "/hangerthem.png",
|
// image: "/hangerthem.png",
|
||||||
techStack: ["Next.js", "TypeScript", "Styled Components"],
|
techStack: ["Next.js", "TypeScript", "Styled Components"],
|
||||||
href: "https://hangerthem.com",
|
href: "https://hangerthem.com",
|
||||||
source: "https://github.com/HangerThem/hangerthem.com",
|
source: "https://github.com/HangerThem/hangerthem.com",
|
||||||
|
|
@ -58,16 +58,6 @@ const projects: Project[] = [
|
||||||
source: "https://github.com/HangerThem/ephemeris",
|
source: "https://github.com/HangerThem/ephemeris",
|
||||||
state: "done",
|
state: "done",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "Lighthouse SVG report",
|
|
||||||
description: "A simple SVG report generator for Lighthouse reports.",
|
|
||||||
image:
|
|
||||||
"https://lighthouse-report-svg.vercel.app/?perf=100&acc=92&best=100&seo=100&pwa=0",
|
|
||||||
techStack: ["TypeScript", "Node.js", "Express"],
|
|
||||||
href: "https://lighthouse-report-svg.vercel.app",
|
|
||||||
source: "https://github.com/HangerThem/lighthouse-report-svg",
|
|
||||||
state: "done",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "Evolution algorithm Flappy Bird",
|
title: "Evolution algorithm Flappy Bird",
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,10 @@ export function badRequestResponse(errorMessage?: string | {}) {
|
||||||
return jsonResponse(400, { error: errorMessage ?? "Bad request" })
|
return jsonResponse(400, { error: errorMessage ?? "Bad request" })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function tooManyRequestsResponse() {
|
||||||
|
return jsonResponse(429, { error: "Too many requests" })
|
||||||
|
}
|
||||||
|
|
||||||
export function internalServerErrorResponse() {
|
export function internalServerErrorResponse() {
|
||||||
return jsonResponse(500, { error: "Internal server error" })
|
return jsonResponse(500, { error: "Internal server error" })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
/** @type {import('next-sitemap').IConfig} */
|
|
||||||
module.exports = {
|
|
||||||
siteUrl: process.env.SITE_URL || "https://example.com",
|
|
||||||
generateRobotsTxt: true,
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +1,4 @@
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {};
|
||||||
images: {
|
|
||||||
remotePatterns: [
|
|
||||||
{
|
|
||||||
hostname: "lighthouse-report-svg.vercel.app",
|
|
||||||
protocol: "https",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
dangerouslyAllowSVG: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default nextConfig
|
export default nextConfig;
|
||||||
|
|
|
||||||
738
package-lock.json
generated
738
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -5,16 +5,15 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"postbuild": "next-sitemap",
|
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vercel/analytics": "^1.3.1",
|
"@vercel/analytics": "^1.3.1",
|
||||||
"canvas-confetti": "^1.9.3",
|
"canvas-confetti": "^1.9.3",
|
||||||
|
"express-rate-limit": "^7.4.0",
|
||||||
"next": "14.1.0",
|
"next": "14.1.0",
|
||||||
"next-recaptcha-v3": "^1.4.1",
|
"next-recaptcha-v3": "^1.4.1",
|
||||||
"next-sitemap": "^4.2.3",
|
|
||||||
"nodemailer": "^6.9.14",
|
"nodemailer": "^6.9.14",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-bootstrap-icons": "^1.11.4",
|
"react-bootstrap-icons": "^1.11.4",
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 529 KiB |
|
|
@ -4,9 +4,9 @@ import { createGlobalStyle } from "styled-components"
|
||||||
|
|
||||||
export const GlobalStyle = createGlobalStyle`
|
export const GlobalStyle = createGlobalStyle`
|
||||||
:root {
|
:root {
|
||||||
--primary: 239, 31, 31;
|
--primary: 201, 14, 14;
|
||||||
--white: 255, 255, 255;
|
--white: 255, 255, 255;
|
||||||
--black: 5, 5, 5;
|
--black: 0, 0, 0;
|
||||||
--background: 217, 217, 217;
|
--background: 217, 217, 217;
|
||||||
--accent: 104, 104, 104;
|
--accent: 104, 104, 104;
|
||||||
}
|
}
|
||||||
|
|
@ -56,8 +56,4 @@ export const GlobalStyle = createGlobalStyle`
|
||||||
color: rgb(var(--primary), 0.8);
|
color: rgb(var(--primary), 0.8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.grecaptcha-badge {
|
|
||||||
visibility: hidden !important;
|
|
||||||
}
|
|
||||||
`
|
`
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue