Compare commits
90 Commits
bc51e625b4
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| f15a1fb167 | |||
| bab35fbfce | |||
| 84cc6d64db | |||
| 63245d9afd | |||
| 9b8b59d4e6 | |||
|
|
dc7e82b143 | ||
|
|
703b611dfd | ||
|
|
dd4f848071 | ||
|
|
46097b8390 | ||
| e78bc12c49 | |||
| aa3bb66d5e | |||
| c9f5dfcba7 | |||
| 4902006130 | |||
| 71f679c3ca | |||
| f7c6d3ae16 | |||
| f884ac1d1d | |||
| 3e5bb19970 | |||
| 22755e66e5 | |||
| ff8536f8c6 | |||
| ad99601407 | |||
| 2d88365a69 | |||
| 3140f9b666 | |||
| 9d98b3ac69 | |||
| 9a48d20a16 | |||
|
|
50358c43b9 | ||
|
|
839defdd06 | ||
|
|
b4e6631f07 | ||
|
|
b0a87b5592 | ||
| 957ff33555 | |||
| 8ce6560482 | |||
| c5018b3bb9 | |||
| f76edaec72 | |||
| 4d9092ae3d | |||
| d397503c66 | |||
| e545a596ec | |||
| 446c4a4241 | |||
| ba2edb6c38 | |||
| 437a79877e | |||
| 94ca95821d | |||
| ceee45409d | |||
| 7eed2ee59a | |||
| 9a7e9048f2 | |||
| de224373ed | |||
| a1a0e15f6d | |||
| b200528007 | |||
| 083fb3fd48 | |||
| 75e24a6a08 | |||
| 5a9e65396c | |||
| 406183536e | |||
| 4be09b8100 | |||
| d71f599f0d | |||
| be1b6268e7 | |||
| d229d639fd | |||
| 74aa0ab925 | |||
| 281e0f4170 | |||
| e37f2f0212 | |||
| 144020b6d1 | |||
| 1c165a68cb | |||
| bbe50c4790 | |||
| 4c4e762a4a | |||
| d19966baba | |||
| c3f3a4914a | |||
| c714281330 | |||
| bf0e8e2c02 | |||
| a83e68ea73 | |||
| 49fc655de9 | |||
| 2a6198aa1f | |||
| e1e9a41d59 | |||
| 2abb8a1bfc | |||
| da4c565cb3 | |||
| 31c7e8ea27 | |||
| 25cd9c75ab | |||
| 2913688e63 | |||
| 77ae0a746b | |||
| 9ee61716d4 | |||
| 2ff40d856a | |||
| f1f84aec3d | |||
| a609afcefc | |||
| c77371b0a4 | |||
| 5f2853083f | |||
| 2cfcc3c892 | |||
| 611fe5f12b | |||
| 8f6b36eca1 | |||
| a833d2666a | |||
| 0e18750370 | |||
| d97e329f4c | |||
| e46339c7a7 | |||
| 2c88713ab6 | |||
| 6c993511bf | |||
| e3d98b4e32 |
48
.drone.yml
48
.drone.yml
@@ -1,15 +1,41 @@
|
||||
---
|
||||
kind: pipeline
|
||||
name: default
|
||||
name: prod-pipe
|
||||
|
||||
steps:
|
||||
- name: now
|
||||
image: one000mph/drone-now # Use one000mph's version as lucaperret's is outdated
|
||||
environment:
|
||||
NOW_TOKEN:
|
||||
from_secret: now_token # Refers to a secret in your drone repo titled "NOW_TOKEN"
|
||||
settings:
|
||||
secret: [now_token] # Refers to the above environment variable
|
||||
deploy_name: peroxy-dev # The name of your vercel project
|
||||
prod: true # Leave this if you want to deploy to production, remove to disable production
|
||||
# directory: public # Only if you've pre-rendered the page. Normally you let vercel handle this
|
||||
- name: prod
|
||||
image: docker/compose
|
||||
volumes:
|
||||
- name: docker_socket
|
||||
path: /var/run/docker.sock
|
||||
commands:
|
||||
- docker-compose -f docker-compose.yml up --build --force-recreate -d
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
|
||||
volumes:
|
||||
- name: docker_socket
|
||||
host:
|
||||
path: /var/run/docker.sock
|
||||
---
|
||||
kind: pipeline
|
||||
name: dev-pipe
|
||||
|
||||
steps:
|
||||
- name: dev
|
||||
image: docker/compose
|
||||
volumes:
|
||||
- name: docker_socket
|
||||
path: /var/run/docker.sock
|
||||
commands:
|
||||
- docker-compose -f docker-compose.dev.yml up --build --force-recreate -d
|
||||
trigger:
|
||||
branch:
|
||||
- dev
|
||||
|
||||
volumes:
|
||||
- name: docker_socket
|
||||
host:
|
||||
path: /var/run/docker.sock
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,6 +7,7 @@
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
/content/projects/markdown-test.md
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
|
||||
2
.vscode/settings.json
vendored
Normal file
2
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
||||
31
Dockerfile
Normal file
31
Dockerfile
Normal file
@@ -0,0 +1,31 @@
|
||||
#Creates a layer from node:alpine image.
|
||||
FROM node:alpine
|
||||
|
||||
#Creates directories
|
||||
RUN mkdir -p /usr/src/app
|
||||
|
||||
#Sets an environment variable
|
||||
ENV PORT 3000
|
||||
|
||||
#Sets the working directory for any RUN, CMD, ENTRYPOINT, COPY, and ADD commands
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
#Copy new files or directories into the filesystem of the container
|
||||
COPY package.json /usr/src/app
|
||||
#COPY package-lock.json /usr/src/app
|
||||
|
||||
#Execute commands in a new layer on top of the current image and commit the results
|
||||
RUN npm install
|
||||
|
||||
##Copy new files or directories into the filesystem of the container
|
||||
COPY . /usr/src/app
|
||||
|
||||
#Execute commands in a new layer on top of the current image and commit the results
|
||||
RUN npm run build
|
||||
|
||||
#Informs container runtime that the container listens on the specified network ports at runtime
|
||||
EXPOSE 3000
|
||||
|
||||
#Allows you to configure a container that will run as an executable
|
||||
ENTRYPOINT ["npm", "run"]
|
||||
|
||||
41
components/ImageSlide.tsx
Normal file
41
components/ImageSlide.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import React, { useState } from "react";
|
||||
import { AiFillLeftCircle } from "react-icons/ai";
|
||||
|
||||
export const ImageSlide = ({ children }: any) => {
|
||||
const [current, setCurrent] = useState(0);
|
||||
const length = children.length;
|
||||
|
||||
const nextSlide = () => {
|
||||
setCurrent(current === length - 1 ? 0 : current + 2);
|
||||
};
|
||||
|
||||
const prevSlide = () => {
|
||||
setCurrent(current === 0 ? length - 1 : current - 2);
|
||||
};
|
||||
if (children.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="slider">
|
||||
<AiFillLeftCircle className="left-arrow" onClick={prevSlide} />
|
||||
<AiFillLeftCircle className="right-arrow" onClick={nextSlide} />
|
||||
{children.map((slide: any, index: any) => {
|
||||
return (
|
||||
<div
|
||||
className={index === current ? "slide active" : "slide"}
|
||||
key={index}
|
||||
>
|
||||
{index === current && (
|
||||
<img
|
||||
src={slide.props.children[0]}
|
||||
alt="travel image"
|
||||
className="image"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
102
components/Logo.tsx
Normal file
102
components/Logo.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
export const Logo = () => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg166"
|
||||
version="1.1"
|
||||
className="fill-action group-hover:fill-primary dark:group-hover:fill-secondary transition-all ease-in duration-100 flex-no-shrink xl:w-12 xl:h-12 w-8 h-8"
|
||||
viewBox="0 0 100 100"
|
||||
>
|
||||
<g id="167217677994600400">
|
||||
<path
|
||||
id="path119"
|
||||
d="M108.79 15a1 1 0 0 0 .59 1.29c9.72 3.6 15.07 8.12 15.07 12.72a1 1 0 0 0 2 0c0-5.57-5.66-10.62-16.37-14.6a1 1 0 0 0-1.29.59Z"
|
||||
/>
|
||||
<path
|
||||
id="path121"
|
||||
d="M120.92 35.51a1 1 0 0 0-1.13 1.65c3.79 2.59 5.71 5.32 5.71 8.09a1 1 0 0 0 2 0c0-3.47-2.21-6.75-6.58-9.74z"
|
||||
/>
|
||||
<path
|
||||
id="path123"
|
||||
d="M112.9 48.45a1 1 0 1 0-.75 1.85c8.61 3.49 13.35 7.77 13.35 12.06a1 1 0 1 0 2 0c0-5.23-5.05-10.04-14.6-13.91z"
|
||||
/>
|
||||
<path
|
||||
id="path125"
|
||||
d="M125.45 80.52a1 1 0 0 0 1-1c0-6.42-7.62-12.2-21.46-16.28a1 1 0 1 0-.56 1.91c12.72 3.76 20 9 20 14.37a1 1 0 0 0 1.02 1z"
|
||||
/>
|
||||
<path
|
||||
id="path127"
|
||||
d="M32.89 98.39h.19a1 1 0 0 0 .37-.08h.08a.83.83 0 0 0 .22-.14l.09-.1a.76.76 0 0 0 .12-.19.84.84 0 0 0 .06-.14v-.12a.27.27 0 0 0 0-.09V62.47c3.92-.71 8.06-1.27 12.35-1.68v11.07a1 1 0 0 0 .22.63l8.2 10.09v10a93.19 93.19 0 0 0-23.09 11.2 101.78 101.78 0 0 0-18.31 15.17 1 1 0 0 0 .72 1.69 1 1 0 0 0 .73-.31 99.86 99.86 0 0 1 18-14.88 88.68 88.68 0 0 1 32-13.37 1.0179 1.0179 0 0 0-.38-2c-2.67.53-5.19 1.16-7.58 1.86v-9.71a1 1 0 0 0-.22-.63l-8.2-10.1V59.64a.83.83 0 0 0 0-.15.74.74 0 0 0 0-.14v-.08a.76.76 0 0 0-.08-.11l-.27-.16a.27.27 0 0 1-.08-.07l-2.93-2 3.27-7.05a.58.58 0 0 0 0-.19.71.71 0 0 0 0-.2V47a16.75 16.75 0 1 1 33.5 0v46.3a1 1 0 1 0 2 0v-3.65c11.28-.13 19.27 2.68 23.73 8.35a20 20 0 0 1 3.87 9.31v11.22H30.38a1 1 0 1 0 0 2h82.15a1 1 0 0 0 1-1v-12.29a.78.78 0 0 0 0-.16 21.86 21.86 0 0 0-4.28-10.32c-4.86-6.18-13.38-9.24-25.3-9.11v-26.5a136.18 136.18 0 0 1 15.95 2.78h.23a1 1 0 0 0 1-.76 1 1 0 0 0-.74-1.21c-1.84-.45-3.78-.85-5.74-1.23V45.44a106.09 106.09 0 0 1 11.71 2.81 1 1 0 0 0 .29 0 1 1 0 0 0 .3-1.95 108 108 0 0 0-12.3-2.94V28.33l1.73.32a80 80 0 0 1 19.57 6.24.93.93 0 0 0 .45.11 1 1 0 0 0 .89-.55 1 1 0 0 0-.44-1.34 81.26 81.26 0 0 0-20.08-6.42l-2.12-.4v-14c2.23.44 4.42.91 6.51 1.45a1.0017 1.0017 0 1 0 .5-1.94 157.17 157.17 0 0 0-38.19-4.33C28.16 7.47.5 16.92.5 29v50.52c0 8.03 12.11 15.08 32.39 18.87zM63.47 9.47a167.36 167.36 0 0 1 29.14 2.41v48.51c-2.79-.49-5.69-.91-8.68-1.25V46.83a18.7502 18.7502 0 0 0-37.5.17v2.14l-3.53 7.63a1 1 0 0 0 .34 1.23l1.38 1c-3.64.38-7.17.88-10.54 1.48V46.77a1.16 1.16 0 0 0 0-.18 1.08 1.08 0 0 0-.08-.19 1.16 1.16 0 0 0-.25-.29l-.19-.11a1.89 1.89 0 0 0-.21-.09h-.09C14.57 42.41 2.5 35.77 2.5 29c0-10.59 27.92-19.53 60.97-19.53Zm-61 25c4.14 5.57 14.55 10.29 29.58 13.2V96.2c-18-3.55-29.58-10-29.58-16.68Z"
|
||||
/>
|
||||
<path
|
||||
id="path129"
|
||||
d="M56.59 63.16a1.0098 1.0098 0 0 0 .28 2 1 1 0 0 0 .29 0 8.51 8.51 0 0 0 3.92-2.43c2.29-2.55 3.39-6.87 1.16-9.41a6.25 6.25 0 0 0-4.9-1.82 1 1 0 0 0 0 2 4.36 4.36 0 0 1 3.35 1.14c1.43 1.64.54 4.87-1.15 6.75a6.55 6.55 0 0 1-2.95 1.77z"
|
||||
/>
|
||||
<path
|
||||
id="path131"
|
||||
d="M40.93 30a1 1 0 0 0 .75-.33 1 1 0 0 0-.08-1.41L38 25.07a1 1 0 1 0-1.33 1.49l3.55 3.18a1 1 0 0 0 .71.26Z"
|
||||
/>
|
||||
<path
|
||||
id="path133"
|
||||
d="M43.86 30.27a1 1 0 1 0-1.33 1.49L45 33.92a1 1 0 0 0 .67.26 1 1 0 0 0 .75-.34 1 1 0 0 0-.08-1.41z"
|
||||
/>
|
||||
<path
|
||||
id="path135"
|
||||
d="M16.82 25.61 27 31a1 1 0 0 0 .46.12 1 1 0 0 0 .89-.53 1 1 0 0 0-.42-1.36l-8.13-4.28a98.46 98.46 0 0 1 11.66-3l3.15 2.8a1 1 0 0 0 1.33-1.49l-2-1.79c3.2-.58 6.54-1.07 10-1.45.92-.1 1.87-.18 2.8-.27l1.2 3.83a1 1 0 0 0 .95.7 1 1 0 0 0 .3 0 1 1 0 0 0 .66-1.26l-1.08-3.42c2.37-.2 4.79-.33 7.22-.42l.16 1.81a1 1 0 0 0 1 .91h.09a1 1 0 0 0 .9-1.09L58 19c1.82-.06 3.66-.09 5.5-.09a1 1 0 0 0 0-2c-2.22 0-4.42.05-6.61.13h-.12c-4.44.16-8.81.46-13 .93a115.63 115.63 0 0 0-26.76 5.84 1 1 0 0 0-.65.87 1 1 0 0 0 .46.93z"
|
||||
/>
|
||||
<path
|
||||
id="path137"
|
||||
d="M33.79 34.56a1.12 1.12 0 0 0 .47.11 1 1 0 0 0 .88-.53 1 1 0 0 0-.41-1.35l-3.66-1.93a1.002 1.002 0 0 0-.94 1.77z"
|
||||
/>
|
||||
<path
|
||||
id="path139"
|
||||
d="m44.28 37.82-6-3.18a1 1 0 1 0-.93 1.77l6 3.18a.92.92 0 0 0 .46.11 1 1 0 0 0 .47-1.88z"
|
||||
/>
|
||||
<path
|
||||
id="path141"
|
||||
d="m9 32.13 4.68 2a1.09 1.09 0 0 0 .39.08 1 1 0 0 0 .39-1.92l-4.68-2A1 1 0 0 0 9 32.13Z"
|
||||
/>
|
||||
<path
|
||||
id="path143"
|
||||
d="M43.08 44.33 20 34.6a1 1 0 1 0-.77 1.84l23.07 9.74a1 1 0 0 0 .38.08 1 1 0 0 0 .39-1.93z"
|
||||
/>
|
||||
<path
|
||||
id="path145"
|
||||
d="M51 26.68a1.001 1.001 0 0 0-1.91.6l.72 2.28a1 1 0 0 0 1 .71 1 1 0 0 0 .3-.05 1 1 0 0 0 .62-1.22z"
|
||||
/>
|
||||
<path
|
||||
id="path147"
|
||||
d="M57.32 22.93a1 1 0 0 0-.9 1.09l.28 3.05a1 1 0 0 0 1 .91h.09a1 1 0 0 0 .9-1.09l-.28-3.06a1 1 0 0 0-1.09-.9z"
|
||||
/>
|
||||
<path
|
||||
id="path149"
|
||||
d="M82.93 105.09a1 1 0 0 0 1-1l.13-4.31a1.0004 1.0004 0 0 0-2-.06l-.13 4.31a1 1 0 0 0 1 1z"
|
||||
/>
|
||||
<path
|
||||
id="path151"
|
||||
d="M25.12 55c0 .07.06.12.09.18l.13.15a1.21 1.21 0 0 0 .32.22 1 1 0 0 0 .39.07 1 1 0 0 0 .7-.29l.13-.15c0-.06.06-.11.09-.18l.06-.18a1.36 1.36 0 0 0 0-.2 1 1 0 0 0-.3-.7.87.87 0 0 0-.32-.22 1 1 0 0 0-.77 0 .87.87 0 0 0-.32.22 1 1 0 0 0-.29.7.68.68 0 0 0 0 .2 1.21 1.21 0 0 0 .09.18z"
|
||||
/>
|
||||
<path
|
||||
id="path153"
|
||||
d="M25.66 60.29a1 1 0 0 0 .39.07 1 1 0 1 0-.39-1.92 1 1 0 0 0-.32.21 1 1 0 0 0 0 1.42 1.21 1.21 0 0 0 .32.22z"
|
||||
/>
|
||||
<path
|
||||
id="path155"
|
||||
d="M25.66 65.06a1.09 1.09 0 0 0 .39.08 1 1 0 0 0 1-1 1 1 0 0 0-.08-.38.91.91 0 0 0-.22-.33 1 1 0 0 0-1.09-.21 1 1 0 0 0-.32.21 1.06 1.06 0 0 0-.22.33 1 1 0 0 0-.07.38 1 1 0 0 0 .29.71 1 1 0 0 0 .32.21z"
|
||||
/>
|
||||
<path
|
||||
id="path157"
|
||||
d="M78.41 53.24a1.51 1.51 0 0 0 .18.09.64.64 0 0 0 .18.06h.2a1 1 0 0 0 .71-.3 1 1 0 0 0 .29-.7 1 1 0 0 0-.29-.71 1.19 1.19 0 0 0-.33-.22 1 1 0 0 0-.58 0h-.18l-.18.1a.79.79 0 0 0-.15.12 1 1 0 0 0-.29.71 1 1 0 0 0 .29.7z"
|
||||
/>
|
||||
<path
|
||||
id="path159"
|
||||
d="m78.26 57.89.15.13.18.09.18.06h.2a1 1 0 0 0 .71-.3 1 1 0 0 0 .29-.7 1 1 0 0 0-.29-.71 1.19 1.19 0 0 0-.33-.22 1 1 0 0 0-.58 0h-.18l-.18.09-.15.13a1 1 0 0 0-.29.71 1 1 0 0 0 .29.72z"
|
||||
/>
|
||||
<path
|
||||
id="path161"
|
||||
d="M78 62.16a.7.7 0 0 0 .06.19.56.56 0 0 0 .09.17.88.88 0 0 0 .12.15l.15.13.18.09.18.06H79a1 1 0 0 0 .38-.07 1.19 1.19 0 0 0 .33-.22l.12-.15a.56.56 0 0 0 .09-.17.7.7 0 0 0 .06-.19A1.23 1.23 0 0 0 80 62a1 1 0 0 0-.08-.39 1 1 0 0 0-.21-.32 1.19 1.19 0 0 0-.33-.22 1 1 0 0 0-1.09.22 1 1 0 0 0-.21.32.88.88 0 0 0-.08.39 1.23 1.23 0 0 0 0 .16z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -2,53 +2,59 @@ import Link from "next/link";
|
||||
import { IoMenu as MenuIcon, IoClose as CloseIcon } from "react-icons/io5";
|
||||
import { useState } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { Logo } from "./Logo";
|
||||
|
||||
const links = [
|
||||
{ name: "blog", href: "/blog" },
|
||||
{ name: "projects", href: "/projects" },
|
||||
{ name: "github", href: "https://github.com/kookroach" },
|
||||
{ name: "Blog", href: "/blog" },
|
||||
{ name: "Projects", href: "/projects" },
|
||||
{ name: "Gitea", href: "https://git.peroxy.dev/kookroach" },
|
||||
];
|
||||
export const Navbar = () => {
|
||||
const [showMenu, setShowMenu] = useState(false);
|
||||
|
||||
return (
|
||||
<nav className="lg:p-4 p-2 lg:py-10 mx-6 flex flex-row justify-between">
|
||||
<nav className="py-4 xl:px-10 px-4 flex flex-row justify-between sticky top-0 z-50 bg-gradient-to-b from-black/50 to-black/30">
|
||||
<Link
|
||||
href="/"
|
||||
id="logo"
|
||||
className="flex flex-row gap-2 items-end transition-all ease-in duration-100 text-action hover:text-secondary"
|
||||
className="flex flex-row gap-2 items-center group"
|
||||
>
|
||||
<div className="font-bold text-4xl font-mono">peroxy</div>
|
||||
<Logo />
|
||||
<div className="font-bold xl:text-4xl text-xl font-mono transition-all ease-in duration-100 text-action group-hover:text-primary dark:group-hover:text-secondary">
|
||||
peroxy
|
||||
<span className="xl:text-sm text-xs">.dev</span>
|
||||
</div>
|
||||
</Link>
|
||||
<div className="hidden lg:flex flex-row gap-10 items-center font-medium font-mono">
|
||||
<div className="hidden xl:flex flex-row gap-10 items-center font-medium font-mono">
|
||||
{links.map(({ name, href }) => (
|
||||
<Link href={href} key={name}>
|
||||
<button className="px-10 py-1 rounded-sm bg-primary hover:bg-primary/50 transition-all duration-150 ease-out">
|
||||
<button className="px-10 py-1 rounded-sm bg-gradient-to-r from-indigo-500/30 to-fuchsia-500/20 hover:bg-primary/50 transition-all duration-150 ease-out text-white">
|
||||
{name}
|
||||
</button>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div className="lg:hidden flex">
|
||||
<div className="xl:hidden flex">
|
||||
<button onClick={() => setShowMenu(true)} className="text-2xl">
|
||||
<MenuIcon />
|
||||
</button>
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{showMenu && (
|
||||
<>
|
||||
<div className="fixed h-screen w-screen top-0 left-0">
|
||||
<motion.div
|
||||
className="absolute left-0 top-0 h-screen w-screen bg-black/70 z-10"
|
||||
className="absolute left-0 top-0 h-full w-full bg-black/70 z-10"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
/>
|
||||
<motion.div
|
||||
className={`z-40 absolute w-1/2 bg-blue-900
|
||||
right-0 top-0 px-10 py-6 h-screen`}
|
||||
initial={{ x: 100 }}
|
||||
className={`z-40 absolute w-1/2 bg-blue-900 text-primary-text
|
||||
right-0 top-0 px-10 py-6 h-full`}
|
||||
initial={{ x: "100%" }}
|
||||
animate={{ x: 0 }}
|
||||
exit={{ x: 100 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
exit={{ x: "100%" }}
|
||||
transition={{ ease: "easeInOut", duration: 0.4 }}
|
||||
>
|
||||
<button onClick={() => setShowMenu(false)} className="text-2xl">
|
||||
<CloseIcon />
|
||||
@@ -61,10 +67,9 @@ export const Navbar = () => {
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
51
components/PostHeader.tsx
Normal file
51
components/PostHeader.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import Link from "next/link";
|
||||
import { FrontMatter } from "../types/types";
|
||||
|
||||
export interface BasicArticleProps extends FrontMatter {
|
||||
title: string;
|
||||
description: string;
|
||||
date: string;
|
||||
author: string;
|
||||
authorLink: string;
|
||||
thumbnail: string;
|
||||
}
|
||||
export const PostHeader = ({
|
||||
frontMatter,
|
||||
estTime,
|
||||
}: {
|
||||
frontMatter: FrontMatter;
|
||||
estTime: number;
|
||||
}) => {
|
||||
const { title, author, date, description, authorLink } =
|
||||
frontMatter as BasicArticleProps;
|
||||
|
||||
return (
|
||||
<div className="flex items-start gap-2 flex-col">
|
||||
<div className="lg:text-5xl text-3xl font-bold mt-2 text-gray-50">
|
||||
{title}
|
||||
</div>
|
||||
<div className="mt-2 text-gray-600dark:text-gray-400 text-sm">{description}</div>
|
||||
<div className="mt-2 mb-10 flex lg:flex-row flex-col gap-2 items-start">
|
||||
{author && (
|
||||
<div className="font-medium ">
|
||||
By{" "}
|
||||
{authorLink && (
|
||||
<Link href={authorLink} className="dark:text-action text-primary">
|
||||
@{author}
|
||||
</Link>
|
||||
)}
|
||||
{!authorLink && (
|
||||
<span className="dark:text-action text-primary">@{author}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-sm font-medium text-gray-500 lg:before:content-['•'] lg:after:content-['•'] lg:before:pr-2 lg:after:pl-2">
|
||||
{date}
|
||||
</div>
|
||||
<div className="text-sm font-medium text-gray-500">
|
||||
{estTime} min read
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
66
components/R3Background.tsx
Normal file
66
components/R3Background.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { useRef, useMemo } from "react";
|
||||
import { Canvas, useFrame } from "@react-three/fiber";
|
||||
import { BufferGeometry, Material, MathUtils, Mesh } from "three";
|
||||
import { vertexShader } from "./Shaders/Background/vertex";
|
||||
import { fragmentShader } from "./Shaders/Background/fragment";
|
||||
|
||||
const Fragment = () => {
|
||||
// This reference will give us direct access to the mesh
|
||||
const meshRef = useRef<Mesh<BufferGeometry, Material | Material[]>>(null);
|
||||
|
||||
const uniforms = useMemo(
|
||||
() => ({
|
||||
u_intensity: {
|
||||
value: 1.0,
|
||||
},
|
||||
u_time: {
|
||||
value: 0.0,
|
||||
},
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
useFrame((state) => {
|
||||
if (!meshRef.current) {
|
||||
return;
|
||||
}
|
||||
const { clock } = state;
|
||||
//@ts-ignore
|
||||
meshRef.current.material.uniforms.u_time.value =
|
||||
(0.4 * clock.getElapsedTime()) / 5;
|
||||
//@ts-ignore
|
||||
meshRef.current.material.uniforms.u_intensity.value = MathUtils.lerp(
|
||||
//@ts-ignore
|
||||
meshRef.current.material.uniforms.u_intensity.value,
|
||||
1.0,
|
||||
0.02
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<mesh
|
||||
ref={meshRef}
|
||||
position={[0, 0, 0]}
|
||||
rotation={[-Math.PI / 17, Math.PI / 20, 0]}
|
||||
scale={1.5}
|
||||
>
|
||||
<planeGeometry args={[30, 30, 200, 200]} />
|
||||
<shaderMaterial
|
||||
fragmentShader={fragmentShader}
|
||||
vertexShader={vertexShader}
|
||||
uniforms={uniforms}
|
||||
wireframe={false}
|
||||
/>
|
||||
</mesh>
|
||||
);
|
||||
};
|
||||
|
||||
export const R3Gradient = () => {
|
||||
return (
|
||||
<div className="-z-40 h-screen w-screen fixed bg-black opacity-60 top-0 left-0 blur">
|
||||
<Canvas camera={{ position: [0.0, 0.0, 5.0] }}>
|
||||
<Fragment />
|
||||
</Canvas>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
75
components/Shaders/Background/fragment.tsx
Normal file
75
components/Shaders/Background/fragment.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
|
||||
export const fragmentShader = `
|
||||
uniform float u_intensity;
|
||||
uniform float u_time;
|
||||
uniform int width;
|
||||
uniform int height;
|
||||
|
||||
varying vec2 vUv;
|
||||
varying float vDisplacement;
|
||||
|
||||
//from Hash Functions for GPU Rendering (Jarzynski et al.)
|
||||
//http://www.jcgt.org/published/0009/03/02/
|
||||
vec3 pcg3d(uvec3 v)
|
||||
{
|
||||
v = v * 1664525u + 1013904223u;
|
||||
v.x += v.y*v.z; v.y += v.z*v.x; v.z += v.x*v.y;
|
||||
v ^= v >> 16u;
|
||||
v.x += v.y*v.z; v.y += v.z*v.x; v.z += v.x*v.y;
|
||||
return vec3(v) * (1.0/float(0xffffffffu));
|
||||
}
|
||||
|
||||
// convert texture coordinates to pixel
|
||||
float t2p(float t, int noOfPixels){
|
||||
return t * float(noOfPixels) - 0.5;
|
||||
}
|
||||
|
||||
#define M_PI 3.1415926535897932384626433832795
|
||||
vec2 randomGradient(uvec3 p){
|
||||
vec3 _uv = pcg3d(p);
|
||||
float r = sqrt(_uv[0]);
|
||||
float phi = 2.0 * M_PI * _uv[1];
|
||||
return vec2(r* cos(phi), r * sin(phi));
|
||||
}
|
||||
|
||||
|
||||
vec3 gradientNoise(vec2 pos, float gridSize){
|
||||
vec2 gridPos = pos * gridSize;
|
||||
uvec2 i = uvec2(gridPos);
|
||||
vec2 f = fract(gridPos);
|
||||
|
||||
vec2 g11 = randomGradient(uvec3(i.x,i.y,1));
|
||||
vec2 g12 = randomGradient(uvec3(i.x + 1u,i.y,1));
|
||||
vec2 g21 = randomGradient(uvec3(i.x,i.y + 1u,1));
|
||||
vec2 g22 = randomGradient(uvec3(i.x + 1u,i.y +1u,1));
|
||||
|
||||
float d11 = dot(g11, f);
|
||||
float d12 = dot(g12, f - vec2(1.0, 0.0));
|
||||
float d21 = dot(g21, f - vec2(0.0, 1.0));
|
||||
float d22 = dot(g22, f - vec2(1.0, 1.0));
|
||||
|
||||
vec3 f11 = pcg3d(uvec3(i.x,i.y,0)) * (d11 + 1.0);
|
||||
vec3 f12 = pcg3d(uvec3(i.x + 1u,i.y,0))* (d12 + 1.0);
|
||||
vec3 f21 = pcg3d(uvec3(i.x,i.y + 1u,0))* (d21 + 1.0);
|
||||
vec3 f22 = pcg3d(uvec3(i.x + 1u,i.y +1u,0))* (d22 + 1.0);
|
||||
|
||||
f = smoothstep(0.0, 1.0, f);
|
||||
|
||||
vec3 q1 = mix(f11, f12, vec3(f.x));
|
||||
vec3 q2 = mix(f21, f22, vec3(f.x));
|
||||
vec3 p = mix(q1, q2, vec3(f.y));
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
float distort = 2.0 * vDisplacement + u_intensity;
|
||||
vec2 pos = vec2(u_time) /5.0 + vUv;
|
||||
vec3 texColor = gradientNoise(pos, 4.0);
|
||||
texColor.y = 0.0;
|
||||
gl_FragColor = vec4(texColor, 1.0);
|
||||
}
|
||||
|
||||
`
|
||||
|
||||
108
components/Shaders/Background/vertex.tsx
Normal file
108
components/Shaders/Background/vertex.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
export const vertexShader = `
|
||||
uniform float u_intensity;
|
||||
uniform float u_time;
|
||||
|
||||
varying vec2 vUv;
|
||||
varying float vDisplacement;
|
||||
|
||||
|
||||
// Classic Perlin 3D Noise
|
||||
// by Stefan Gustavson
|
||||
//
|
||||
vec4 permute(vec4 x) {
|
||||
return mod(((x*34.0)+1.0)*x, 289.0);
|
||||
}
|
||||
|
||||
vec4 taylorInvSqrt(vec4 r) {
|
||||
return 1.79284291400159 - 0.85373472095314 * r;
|
||||
}
|
||||
|
||||
vec3 fade(vec3 t) {
|
||||
return t*t*t*(t*(t*6.0-15.0)+10.0);
|
||||
}
|
||||
|
||||
float cnoise(vec3 P) {
|
||||
vec3 Pi0 = floor(P); // Integer part for indexing
|
||||
vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1
|
||||
Pi0 = mod(Pi0, 289.0);
|
||||
Pi1 = mod(Pi1, 289.0);
|
||||
vec3 Pf0 = fract(P); // Fractional part for interpolation
|
||||
vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0
|
||||
vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
|
||||
vec4 iy = vec4(Pi0.yy, Pi1.yy);
|
||||
vec4 iz0 = Pi0.zzzz;
|
||||
vec4 iz1 = Pi1.zzzz;
|
||||
|
||||
vec4 ixy = permute(permute(ix) + iy);
|
||||
vec4 ixy0 = permute(ixy + iz0);
|
||||
vec4 ixy1 = permute(ixy + iz1);
|
||||
|
||||
vec4 gx0 = ixy0 / 7.0;
|
||||
vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5;
|
||||
gx0 = fract(gx0);
|
||||
vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
|
||||
vec4 sz0 = step(gz0, vec4(0.0));
|
||||
gx0 -= sz0 * (step(0.0, gx0) - 0.5);
|
||||
gy0 -= sz0 * (step(0.0, gy0) - 0.5);
|
||||
|
||||
vec4 gx1 = ixy1 / 7.0;
|
||||
vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5;
|
||||
gx1 = fract(gx1);
|
||||
vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
|
||||
vec4 sz1 = step(gz1, vec4(0.0));
|
||||
gx1 -= sz1 * (step(0.0, gx1) - 0.5);
|
||||
gy1 -= sz1 * (step(0.0, gy1) - 0.5);
|
||||
|
||||
vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);
|
||||
vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);
|
||||
vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);
|
||||
vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);
|
||||
vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);
|
||||
vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);
|
||||
vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);
|
||||
vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);
|
||||
|
||||
vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));
|
||||
g000 *= norm0.x;
|
||||
g010 *= norm0.y;
|
||||
g100 *= norm0.z;
|
||||
g110 *= norm0.w;
|
||||
vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));
|
||||
g001 *= norm1.x;
|
||||
g011 *= norm1.y;
|
||||
g101 *= norm1.z;
|
||||
g111 *= norm1.w;
|
||||
|
||||
float n000 = dot(g000, Pf0);
|
||||
float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));
|
||||
float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));
|
||||
float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));
|
||||
float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));
|
||||
float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));
|
||||
float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));
|
||||
float n111 = dot(g111, Pf1);
|
||||
|
||||
vec3 fade_xyz = fade(Pf0);
|
||||
vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z);
|
||||
vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);
|
||||
float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);
|
||||
return 2.2 * n_xyz;
|
||||
}
|
||||
|
||||
// End of Perlin Noise Code
|
||||
|
||||
void main() {
|
||||
vUv = uv;
|
||||
|
||||
vDisplacement = cnoise(position + vec3(2.0 * u_time));
|
||||
|
||||
vec3 newPosition = position + normal * (u_intensity * vDisplacement);
|
||||
|
||||
vec4 modelPosition = modelMatrix * vec4(newPosition, 1.0);
|
||||
vec4 viewPosition = viewMatrix * modelPosition;
|
||||
vec4 projectedPosition = projectionMatrix * viewPosition;
|
||||
|
||||
gl_Position = projectedPosition;
|
||||
}
|
||||
|
||||
`
|
||||
216
components/StyledMarkdown.tsx
Normal file
216
components/StyledMarkdown.tsx
Normal file
@@ -0,0 +1,216 @@
|
||||
import rangeParser from "parse-numeric-range";
|
||||
import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import tsx from "react-syntax-highlighter/dist/cjs/languages/prism/tsx";
|
||||
import typescript from "react-syntax-highlighter/dist/cjs/languages/prism/typescript";
|
||||
import scss from "react-syntax-highlighter/dist/cjs/languages/prism/scss";
|
||||
import bash from "react-syntax-highlighter/dist/cjs/languages/prism/bash";
|
||||
import markdown from "react-syntax-highlighter/dist/cjs/languages/prism/markdown";
|
||||
import json from "react-syntax-highlighter/dist/cjs/languages/prism/json";
|
||||
import { coldarkDark as defaulttheme } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||
import { BiCopy } from "react-icons/bi";
|
||||
import { Components } from "react-markdown";
|
||||
import { generateSlug } from "../utils/general";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkTypograf from "@mavrin/remark-typograf";
|
||||
import smartypants from "remark-smartypants";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
import emoji from "remark-emoji";
|
||||
import oembed from "@agentofuser/remark-oembed";
|
||||
import remarkDirective from "remark-directive";
|
||||
import remarkDirectiveRehype from "remark-directive-rehype";
|
||||
import { YouTubeVideo } from "./Youtube";
|
||||
import { ImageSlide } from "./ImageSlide";
|
||||
//import rehypeSanitize from "rehype-sanitize";
|
||||
|
||||
SyntaxHighlighter.registerLanguage("tsx", tsx);
|
||||
SyntaxHighlighter.registerLanguage("typescript", typescript);
|
||||
SyntaxHighlighter.registerLanguage("scss", scss);
|
||||
SyntaxHighlighter.registerLanguage("bash", bash);
|
||||
SyntaxHighlighter.registerLanguage("markdown", markdown);
|
||||
SyntaxHighlighter.registerLanguage("json", json);
|
||||
|
||||
export const StyledMarkdown = ({ html }: { html: string }) => {
|
||||
const MarkdownComponents: Components = {
|
||||
h1: (props: any) => {
|
||||
const arr = props.children;
|
||||
let heading = "";
|
||||
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i]?.type !== undefined) {
|
||||
for (let j = 0; j < arr[i].props.children.length; j++) {
|
||||
heading += arr[i]?.props?.children[0];
|
||||
}
|
||||
} else heading += arr[i];
|
||||
}
|
||||
|
||||
const slug = generateSlug(heading);
|
||||
return (
|
||||
<h1 id={slug}>
|
||||
<a href={`#${slug}`} {...props}></a>
|
||||
</h1>
|
||||
);
|
||||
},
|
||||
h2: (props: any) => {
|
||||
const arr = props.children;
|
||||
let heading = "";
|
||||
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i]?.type !== undefined) {
|
||||
for (let j = 0; j < arr[i].props.children.length; j++) {
|
||||
heading += arr[i]?.props?.children[0];
|
||||
}
|
||||
} else heading += arr[i];
|
||||
}
|
||||
|
||||
const slug = generateSlug(heading);
|
||||
return (
|
||||
<h2 id={slug}>
|
||||
<a href={`#${slug}`} {...props}></a>
|
||||
</h2>
|
||||
);
|
||||
},
|
||||
h3: (props: any) => {
|
||||
const arr = props.children;
|
||||
let heading = "";
|
||||
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i]?.type !== undefined) {
|
||||
for (let j = 0; j < arr[i].props.children.length; j++) {
|
||||
heading += arr[i]?.props?.children[0];
|
||||
}
|
||||
} else heading += arr[i];
|
||||
}
|
||||
|
||||
const slug = generateSlug(heading);
|
||||
return (
|
||||
<h3 id={slug} className="text-red-500">
|
||||
<a href={`#${slug}`} {...props}></a>
|
||||
</h3>
|
||||
);
|
||||
},
|
||||
h4: (props: any) => {
|
||||
const arr = props.children;
|
||||
let heading = "";
|
||||
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i]?.type !== undefined) {
|
||||
for (let j = 0; j < arr[i].props.children.length; j++) {
|
||||
heading += arr[i]?.props?.children[0];
|
||||
}
|
||||
} else heading += arr[i];
|
||||
}
|
||||
|
||||
const slug = generateSlug(heading);
|
||||
return (
|
||||
<h4 id={slug}>
|
||||
<a href={`#${slug}`} {...props}></a>
|
||||
</h4>
|
||||
);
|
||||
},
|
||||
code({ node, inline, className, ...props }: any) {
|
||||
const match = /language-(\w+)/.exec(className || "");
|
||||
const hasMeta = node?.data?.meta;
|
||||
|
||||
const applyHighlights: object = (applyHighlights: number) => {
|
||||
if (hasMeta) {
|
||||
const RE = /{([\d,-]+)}/;
|
||||
const metadata = node.data.meta?.replace(/\s/g, "");
|
||||
const strlineNumbers = RE?.test(metadata)
|
||||
? // @ts-ignore
|
||||
RE?.exec(metadata)[1]
|
||||
: "0";
|
||||
|
||||
const highlightLines = rangeParser(strlineNumbers);
|
||||
const highlight = highlightLines;
|
||||
const data = highlight.includes(applyHighlights) ? "highlight" : null;
|
||||
return { data };
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const applyStartingNumber = () => {
|
||||
if (hasMeta) {
|
||||
const metadata = node.data.meta?.replace(/\s/g, "");
|
||||
const RE = /line=(\d+)/;
|
||||
const start = RE?.test(metadata) && RE.exec(metadata);
|
||||
return start ? +start[1] : 1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
const getTitle = () => {
|
||||
if (hasMeta) {
|
||||
const metadata = node.data.meta;
|
||||
const RE = /title=\"(.*)\"/;
|
||||
const title = RE?.test(metadata) && RE.exec(metadata);
|
||||
return title ? title[1] : "Code";
|
||||
}
|
||||
return "Code";
|
||||
};
|
||||
|
||||
return match ? (
|
||||
<div>
|
||||
<div className="w-full flex flex-row items-center gap-4">
|
||||
<button
|
||||
className="w-fit ml-2 important"
|
||||
onClick={() => navigator.clipboard.writeText(props.children)}
|
||||
>
|
||||
<BiCopy />
|
||||
</button>
|
||||
{<div>{getTitle()}</div>}
|
||||
</div>
|
||||
<SyntaxHighlighter
|
||||
style={defaulttheme}
|
||||
language={match[1]}
|
||||
PreTag="div"
|
||||
className="codeStyle"
|
||||
showLineNumbers={true}
|
||||
wrapLines={hasMeta ? true : false}
|
||||
useInlineStyles={true}
|
||||
lineProps={applyHighlights}
|
||||
startingLineNumber={applyStartingNumber()}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<code className={className} {...props} />
|
||||
);
|
||||
},
|
||||
//custom directives
|
||||
//@ts-ignore
|
||||
yt: YouTubeVideo,
|
||||
"img-slide": ImageSlide,
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="
|
||||
prose
|
||||
prose-code:font-mono prose-code:text-gray-100 prose-code:bg-black/20 prose-code:p-1 prose-code:m-1 prose-code:rounded-md
|
||||
prose-headings:text-gray-800 dark:prose-headings:text-gray-100 prose-p:text-gray-600 dark:prose-p:text-gray-300
|
||||
prose-img:w-full prose-img:h-auto prose-img:object-cover
|
||||
prose-li:text-gray-600 dark:prose-li:text-gray-200 prose-td:text-gray-600 dark:prose-td:text-gray-300
|
||||
prose-a:text-primary prose-strong:text-gray-900 dark:prose-strong:text-gray-50
|
||||
dark:prose-hr:bg-gray-200 prose-hr:bg-gray-400
|
||||
"
|
||||
>
|
||||
<ReactMarkdown
|
||||
components={MarkdownComponents}
|
||||
rehypePlugins={[rehypeRaw]}
|
||||
remarkPlugins={[
|
||||
remarkDirective,
|
||||
remarkDirectiveRehype,
|
||||
oembed,
|
||||
remarkGfm,
|
||||
smartypants,
|
||||
emoji,
|
||||
remarkTypograf,
|
||||
]}
|
||||
children={html}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
5
components/Youtube.tsx
Normal file
5
components/Youtube.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
export const YouTubeVideo = ({ id, children }: any) => (
|
||||
<iframe src={'https://www.youtube.com/embed/' + id} className="w-full xl:h-96 md:h-80 h-64">
|
||||
{children}
|
||||
</iframe>
|
||||
)
|
||||
@@ -2,9 +2,20 @@
|
||||
title: "My First Blog"
|
||||
date: 27.12.2022
|
||||
author: kookroach
|
||||
authorLink: https://git.peroxy.dev/kookroach
|
||||
description: Short description
|
||||
---
|
||||
|
||||
# First blog
|
||||
|
||||
Insane
|
||||
|
||||
test change
|
||||
|
||||
# Header 1 test
|
||||
|
||||
## Header 2 test
|
||||
|
||||
### Header 3 test
|
||||
|
||||
#### Header 4 test
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "My Third Blog"
|
||||
title: "My Third Blog (TEST)"
|
||||
date: 27.12.2022
|
||||
author: kookroach
|
||||
description: Short description
|
||||
|
||||
27
content/projects/ConceptArt and Game Design.md
Normal file
27
content/projects/ConceptArt and Game Design.md
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "Concept Art and Game Design"
|
||||
date: 17.01.2022
|
||||
author: kookroach
|
||||
authorLink: https://git.peroxy.dev/kookroach
|
||||
description: "Creative basics for professional game development. A Workshop by Dr.-Ing. Wolfgang Höhl."
|
||||
thumbnail: https://docs.peroxy.dev/uploads/f001b7e2-4e85-4335-8302-40cbda9c3490.png
|
||||
---
|
||||
|
||||
From concept drawing to modeling to game engine. A neo-noir Game Concept inspired from Games like Max Payne and Luke Cage Noir comics.
|
||||
|
||||
### Sketches
|
||||
|
||||
#### Character Concept
|
||||
::img-slide[https://wiki.tum.de/download/attachments/1030785260/Character%20Design%201%20rework.png?version=1&modificationDate=1642430318357&api=v2 https://wiki.tum.de/download/attachments/1030785260/Character%20Design%202.png?version=1&modificationDate=1642430320190&api=v2]{}
|
||||
|
||||
#### Environment Concept
|
||||
::img-slide[https://wiki.tum.de/download/attachments/1030785260/Env-Design%20Skizze.png?version=1&modificationDate=1642430334223&api=v2 https://wiki.tum.de/download/attachments/1030785260/Env-Design%20Final.png?version=1&modificationDate=1642430333137&api=v2]{}
|
||||
|
||||
### Models
|
||||
::img-slide[https://docs.peroxy.dev/uploads/251bda2e-05e2-4fe4-828c-e3a747e5c9e8.png https://docs.peroxy.dev/uploads/7a9e94b3-cb00-43bb-85ad-7fdf25e047fd.png https://docs.peroxy.dev/uploads/b8c07d01-11d6-4771-af21-4f208e443216.png https://docs.peroxy.dev/uploads/99cec6be-6239-46f3-a350-9ef9bfb60932.png https://docs.peroxy.dev/uploads/a3f69410-3e32-41a0-9f9a-c87f35d4cbfa.png https://docs.peroxy.dev/uploads/8b4cf730-48e4-422c-8cf5-590efd3e79b7.png]{}
|
||||
|
||||
### Final Render
|
||||

|
||||
|
||||
### Final Result in Unity Engine
|
||||

|
||||
8
content/projects/MadTrax.md
Normal file
8
content/projects/MadTrax.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: "MadTrax"
|
||||
date: 11.12.2022
|
||||
author: kookroach
|
||||
authorLink: https://git.peroxy.dev/kookroach
|
||||
description: "A game jam game made entirely from scratch in just 42 hours using the Godot 4 game engine."
|
||||
thumbnail: https://docs.peroxy.dev/uploads/b968afc4-059c-41fd-b948-67c9b32c2cc2.png
|
||||
---
|
||||
8
content/projects/RE-Chess.md
Normal file
8
content/projects/RE-Chess.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: "RE:Chess"
|
||||
date: 19.06.2022
|
||||
author: kookroach
|
||||
authorLink: https://git.peroxy.dev/kookroach
|
||||
description: "A game jam game made in just 42 hours using the Unity engine. Redefine the rules of chess to solve the re:chess puzzles in this unique chess game."
|
||||
thumbnail: https://docs.peroxy.dev/uploads/4806dc24-3fe7-4349-8121-f230e7a02359.png
|
||||
---
|
||||
45
content/projects/SpaceMadness.md
Normal file
45
content/projects/SpaceMadness.md
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: "Space Madness"
|
||||
date: 06.06.2020
|
||||
author: kookroach
|
||||
authorLink: https://git.peroxy.dev/kookroach
|
||||
description: "Navigate through an endless battlefield and destroy turrets while avoiding being killed. See if you can beat the current high score of 7471."
|
||||
thumbnail: https://docs.peroxy.dev/uploads/cd22a289-ebe7-4d82-98e3-b639d1df2ea6.png
|
||||
---
|
||||
|
||||
```
|
||||
_____ ___ ___ _
|
||||
/ ___| | \/ | | |
|
||||
\ `--. _ __ __ _ ___ ___ | . . | __ _ __| | _ __ ___ ___ ___
|
||||
`--. \| '_ \ / _` | / __| / _ \ | |\/| | / _` | / _` || '_ \ / _ \/ __|/ __|
|
||||
/\__/ /| |_) || (_| || (__ | __/ | | | || (_| || (_| || | | || __/\__ \\__ \
|
||||
\____/ | .__/ \__,_| \___| \___| \_| |_/ \__,_| \__,_||_| |_| \___||___/|___/
|
||||
| |
|
||||
|_|
|
||||
```
|
||||
|
||||
> :warning: **Assets and Sounds of this game are not self made**
|
||||
|
||||
### About this Project
|
||||
|
||||
My introduction to game development began with the completion of my first project, "Space Madness", which I created using the Unity Engine. This experience sparked my passion for the field and led me to pursue a degree in Informatics: Games Engineering at the Technical University of Munich.
|
||||
|
||||
A Challenging mobile endless space shooter game.
|
||||
|
||||
### Technical Features
|
||||
|
||||
- Procedual Terrain generation
|
||||
- Chunk rendering with LOD
|
||||
- Endless Level generation
|
||||
- Basic Boss Fight Logic
|
||||
- Touch input support
|
||||
|
||||
|
||||
### Gameplay Video
|
||||
::yt[Video of a cat in a box]{#1qquNEJrA10}
|
||||
|
||||
## Links
|
||||
|
||||
Repository [here](https://gitlab.com/moungoslukas/stonks)
|
||||
|
||||
Download Windows Build [here](https://cloud.peroxy.dev/s/c5PzG37xtqyD8gZ) (playable by using the Mouse and Left Mouse Button)
|
||||
253
content/projects/markdown-test.md
Normal file
253
content/projects/markdown-test.md
Normal file
@@ -0,0 +1,253 @@
|
||||
---
|
||||
title: "Markdown test"
|
||||
date: 29.12.2022
|
||||
author: kookroach
|
||||
authorLink: https://git.peroxy.dev/kookroach
|
||||
description: Short description
|
||||
---
|
||||
|
||||
# USE ONLY H3 TAGS FOR HEADER HYPERLINKS
|
||||
### Test heading
|
||||
|
||||
|
||||
|
||||
## Horizontal Rules
|
||||
|
||||
___
|
||||
|
||||
---
|
||||
|
||||
***
|
||||
|
||||
|
||||
## Typographic replacements
|
||||
|
||||
Enable typographer option to see result.
|
||||
|
||||
(c) (C) (r) (R) (tm) (TM) (p) (P) +-
|
||||
|
||||
|
||||
test.. test... test.... test..... test?..... test!....
|
||||
|
||||
|
||||
!!!!!! ???? ,, -- ---
|
||||
|
||||
"Smartypants, double quotes" and 'single quotes'
|
||||
|
||||
|
||||
## Emphasis
|
||||
|
||||
**This is bold text**
|
||||
|
||||
__This is bold text__
|
||||
|
||||
*This is italic text*
|
||||
|
||||
_This is italic text_
|
||||
|
||||
~~Strikethrough~~
|
||||
|
||||
|
||||
## Blockquotes
|
||||
|
||||
|
||||
> Blockquotes can also be nested...
|
||||
>> ...by using additional greater-than signs right next to each other...
|
||||
> > > ...or with spaces between arrows.
|
||||
|
||||
|
||||
## Lists
|
||||
|
||||
Unordered
|
||||
|
||||
+ Create a list by starting a line with `+`, `-`, or `*`
|
||||
+ Sub-lists are made by indenting 2 spaces:
|
||||
- Marker character change forces new list start:
|
||||
* Ac tristique libero volutpat at
|
||||
+ Facilisis in pretium nisl aliquet
|
||||
- Nulla volutpat aliquam velit
|
||||
+ Very easy!
|
||||
|
||||
Ordered
|
||||
|
||||
1. Lorem ipsum dolor sit amet
|
||||
2. Consectetur adipiscing elit
|
||||
3. Integer molestie lorem at massa
|
||||
|
||||
|
||||
1. You can use sequential numbers...
|
||||
1. ...or keep all the numbers as `1.`
|
||||
|
||||
Start numbering with offset:
|
||||
|
||||
57. foo
|
||||
1. bar
|
||||
|
||||
|
||||
https://www.youtube.com/watch?v=lrBRV3WK2x4
|
||||
|
||||
|
||||
## Code
|
||||
|
||||
Inline `code`
|
||||
|
||||
Indented code
|
||||
|
||||
// Some comments
|
||||
line 1 of code
|
||||
line 2 of code
|
||||
line 3 of code
|
||||
|
||||
|
||||
Block code "fences"
|
||||
|
||||
```
|
||||
Sample text here...
|
||||
```
|
||||
|
||||
Syntax highlighting
|
||||
|
||||
```tsx
|
||||
var foo = function (bar) {
|
||||
return bar++;
|
||||
};
|
||||
|
||||
console.log(foo(5));
|
||||
```
|
||||
|
||||
```tsx line=200, title="pidhsome je"
|
||||
var foo = function (bar) {
|
||||
return bar++;
|
||||
};
|
||||
|
||||
console.log(foo(5));
|
||||
```
|
||||
|
||||
## Tables
|
||||
|
||||
| Option | Description |
|
||||
| ------ | ----------- |
|
||||
| data | path to data files to supply the data that will be passed into templates. |
|
||||
| engine | engine to be used for processing templates. Handlebars is the default. |
|
||||
| ext | extension to be used for dest files. |
|
||||
|
||||
Right aligned columns
|
||||
|
||||
| Option | Description |
|
||||
| ------:| -----------:|
|
||||
| data | path to data files to supply the data that will be passed into templates. |
|
||||
| engine | engine to be used for processing templates. Handlebars is the default. |
|
||||
| ext | extension to be used for dest files. |
|
||||
|
||||
|
||||
## Links
|
||||
|
||||
[link text](http://dev.nodeca.com)
|
||||
|
||||
[link with title](http://nodeca.github.io/pica/demo/ "title text!")
|
||||
|
||||
Autoconverted link https://github.com/nodeca/pica (enable linkify to see)
|
||||
|
||||
|
||||
## Images
|
||||
|
||||

|
||||

|
||||
|
||||
Like links, Images also have a footnote style syntax
|
||||
|
||||
![Alt text][id]
|
||||
|
||||
With a reference later in the document defining the URL location:
|
||||
|
||||
[id]: https://octodex.github.com/images/dojocat.jpg "The Dojocat"
|
||||
|
||||
|
||||
## Plugins
|
||||
|
||||
The killer feature of `markdown-it` is very effective support of
|
||||
[syntax plugins](https://www.npmjs.org/browse/keyword/markdown-it-plugin).
|
||||
|
||||
|
||||
### [Emojies](https://github.com/markdown-it/markdown-it-emoji)
|
||||
|
||||
> Classic markup: :wink: :crush: :cry: :tear: :laughing: :yum:
|
||||
>
|
||||
> Shortcuts (emoticons): :-) :-( 8-) ;)
|
||||
|
||||
see [how to change output](https://github.com/markdown-it/markdown-it-emoji#change-output) with twemoji.
|
||||
|
||||
|
||||
### [Subscript](https://github.com/markdown-it/markdown-it-sub) / [Superscript](https://github.com/markdown-it/markdown-it-sup)
|
||||
|
||||
- 19^th^
|
||||
- H~2~O
|
||||
|
||||
|
||||
### [\<ins>](https://github.com/markdown-it/markdown-it-ins)
|
||||
|
||||
++Inserted text++
|
||||
|
||||
|
||||
### [\<mark>](https://github.com/markdown-it/markdown-it-mark)
|
||||
|
||||
==Marked text==
|
||||
|
||||
|
||||
### [Footnotes](https://github.com/markdown-it/markdown-it-footnote)
|
||||
|
||||
Footnote 1 link[^first].
|
||||
|
||||
Footnote 2 link[^second].
|
||||
|
||||
Inline footnote^[Text of inline footnote] definition.
|
||||
|
||||
Duplicated footnote reference[^second].
|
||||
|
||||
[^first]: Footnote **can have markup**
|
||||
|
||||
and multiple paragraphs.
|
||||
|
||||
[^second]: Footnote text.
|
||||
|
||||
|
||||
### [Definition lists](https://github.com/markdown-it/markdown-it-deflist)
|
||||
|
||||
Term 1
|
||||
|
||||
: Definition 1
|
||||
with lazy continuation.
|
||||
|
||||
Term 2 with *inline markup*
|
||||
|
||||
: Definition 2
|
||||
|
||||
{ some code, part of Definition 2 }
|
||||
|
||||
Third paragraph of definition 2.
|
||||
|
||||
_Compact style:_
|
||||
|
||||
Term 1
|
||||
~ Definition 1
|
||||
|
||||
Term 2
|
||||
~ Definition 2a
|
||||
~ Definition 2b
|
||||
|
||||
|
||||
### [Abbreviations](https://github.com/markdown-it/markdown-it-abbr)
|
||||
|
||||
This is HTML abbreviation example.
|
||||
|
||||
It converts "HTML", but keep intact partial entries like "xxxHTMLyyy" and so on.
|
||||
|
||||
*[HTML]: Hyper Text Markup Language
|
||||
|
||||
### [Custom containers](https://github.com/markdown-it/markdown-it-container)
|
||||
|
||||
::: warning
|
||||
*here be dragons*
|
||||
:::
|
||||
|
||||
[go to 3rd heading](#test-heading)
|
||||
25
docker-compose.dev.yml
Normal file
25
docker-compose.dev.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
peroxy_site_dev:
|
||||
container_name: peroxy_site_dev
|
||||
command: start
|
||||
restart: unless-stopped
|
||||
build:
|
||||
context: .
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.peroxy_site_dev.entrypoints=https"
|
||||
- "traefik.http.routers.peroxy_site_dev.rule=Host(`test.peroxy.dev`)"
|
||||
- "traefik.http.routers.peroxy_site_dev.tls=true"
|
||||
- "traefik.http.routers.peroxy_site_dev.tls.certresolver=http"
|
||||
- "traefik.http.routers.peroxy_site_dev.service=peroxy_site_dev-service"
|
||||
- "traefik.http.services.peroxy_site_dev-service.loadbalancer.server.port=3000"
|
||||
networks:
|
||||
- proxy
|
||||
volumes:
|
||||
- ./dev_site:/var/lib/website/dev
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
external: true
|
||||
25
docker-compose.yml
Normal file
25
docker-compose.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
peroxy_site:
|
||||
container_name: peroxy_site
|
||||
command: start
|
||||
restart: unless-stopped
|
||||
build:
|
||||
context: .
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.peroxy_site.entrypoints=https"
|
||||
- "traefik.http.routers.peroxy_site.rule=Host(`peroxy.dev`, `www.peroxy.dev`)"
|
||||
- "traefik.http.routers.peroxy_site.tls=true"
|
||||
- "traefik.http.routers.peroxy_site.tls.certresolver=http"
|
||||
- "traefik.http.routers.peroxy_site.service=peroxy_site-service"
|
||||
- "traefik.http.services.peroxy_site-service.loadbalancer.server.port=3000"
|
||||
networks:
|
||||
- proxy
|
||||
volumes:
|
||||
- ./dev_site:/var/lib/website/prod
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
external: true
|
||||
@@ -1,11 +1,12 @@
|
||||
import { PropsWithChildren } from "react";
|
||||
import { Navbar } from "../components/Navbar";
|
||||
import { PropsWithChildren } from 'react'
|
||||
import { R3Gradient } from '../components/R3Background'
|
||||
import { Navbar } from '../components/Navbar'
|
||||
|
||||
export const MainLayout = ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<div className="w-11/12 lg:w-2/3 m-auto overflow-x-hidden">
|
||||
<div className="w-full">
|
||||
<Navbar />
|
||||
<main className="mx-10">{children}</main>
|
||||
<main className="xl:p-5 p-4 px-5 xl:mx-0">{children}</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
7597
package-lock.json
generated
Normal file
7597
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@@ -9,21 +9,39 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentofuser/remark-oembed": "^1.0.4",
|
||||
"@mavrin/remark-typograf": "^2.2.0",
|
||||
"@react-three/drei": "^9.56.19",
|
||||
"@react-three/fiber": "^8.11.0",
|
||||
"@tailwindcss/typography": "^0.5.8",
|
||||
"@types/node": "18.11.11",
|
||||
"@types/react": "18.0.26",
|
||||
"@types/react-dom": "18.0.9",
|
||||
"@types/three": "^0.149.0",
|
||||
"framer-motion": "^8.0.2",
|
||||
"gray-matter": "^4.0.3",
|
||||
"next": "13.0.6",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"next": "^13.3.1",
|
||||
"parse-numeric-range": "^1.3.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^4.7.1",
|
||||
"react-markdown": "^8.0.4",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-youtube": "^10.1.0",
|
||||
"rehype-raw": "^6.1.1",
|
||||
"rehype-sanitize": "^5.0.1",
|
||||
"remark": "^14.0.2",
|
||||
"remark-directive": "^2.0.1",
|
||||
"remark-directive-rehype": "^0.4.2",
|
||||
"remark-emoji": "^3.1.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-html": "^15.0.1",
|
||||
"remark-smartypants": "^2.0.0",
|
||||
"three": "^0.149.0",
|
||||
"typescript": "4.9.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react-syntax-highlighter": "^15.5.5",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"postcss": "^8.4.19",
|
||||
"tailwindcss": "^3.2.4"
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { R3Gradient } from '../components/R3Background'
|
||||
import '../styles/globals.css'
|
||||
import type { AppProps } from 'next/app'
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
return <Component {...pageProps} />
|
||||
return (
|
||||
<>
|
||||
<R3Gradient />
|
||||
<Component {...pageProps} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,36 +1,30 @@
|
||||
import { GetStaticPaths, GetStaticProps } from "next";
|
||||
import fs from "fs";
|
||||
import { ParsedUrlQuery } from "querystring";
|
||||
import {
|
||||
BLOGS_PATH,
|
||||
getFileContentBySlug,
|
||||
renderMarkdown,
|
||||
} from "../../utils/markdown";
|
||||
import { BLOGS_PATH, getBlogContentBySlug } from "../../utils/markdown";
|
||||
import { MarkdownRenderingResult } from "../../types/types";
|
||||
import { MainLayout } from "../../layouts/MainLayout";
|
||||
import Link from "next/link";
|
||||
import { IoMdArrowRoundBack as BackIcon } from "react-icons/io";
|
||||
import { PostHeader } from "../../components/PostHeader";
|
||||
import { readingTime } from "../../utils/general";
|
||||
import { StyledMarkdown } from "../../components/StyledMarkdown";
|
||||
|
||||
export const BlogArticle = ({ frontMatter, html }: MarkdownRenderingResult) => {
|
||||
return (
|
||||
<MainLayout>
|
||||
<div className="rounded-md border border-gray-200 shadow-md shadow-gray-200 dark:shadow-gray-900 dark:border-gray-700 gap-4 bg-black bg-opacity-30 round">
|
||||
<div className="mx-10 mt-5 mb-10">
|
||||
<Link href="/blog">
|
||||
<button className="flex flex-row items-center gap-2 text-secondary hover:text-secondary/50 transition-all ease-in duration-75">
|
||||
<button className="flex flex-row items-center gap-2 text-primary hover:text-primary-dark dark:text-secondary dark:hover:text-secondary/50 transition-all ease-in duration-75">
|
||||
<BackIcon />
|
||||
<span>Go Back</span>
|
||||
</button>
|
||||
</Link>
|
||||
<div className="lg:mt-10 mt-4 mb-2 text-sm font-medium text-gray-500">
|
||||
{frontMatter.date}
|
||||
<PostHeader frontMatter={frontMatter} estTime={readingTime(html)} />
|
||||
<StyledMarkdown html={html} />
|
||||
</div>
|
||||
<div className="lg:text-5xl font-bold">{frontMatter.title}</div>
|
||||
<div className="mt-2 font-medium">
|
||||
By: <span className="text-action">@{frontMatter.author}</span>
|
||||
</div>
|
||||
<div
|
||||
className="mt-4 prose prose-headings:text-primary-text prose-p:text-gray-400"
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
</MainLayout>
|
||||
);
|
||||
};
|
||||
@@ -52,12 +46,11 @@ export const getStaticPaths: GetStaticPaths<BlogProps> = async () => {
|
||||
export const getStaticProps: GetStaticProps<MarkdownRenderingResult> = async ({
|
||||
params,
|
||||
}) => {
|
||||
const markdownContent = getFileContentBySlug(params?.slug as string);
|
||||
const renderedHTML = await renderMarkdown(markdownContent.content);
|
||||
const markdownContent = getBlogContentBySlug(params?.slug as string);
|
||||
return {
|
||||
props: {
|
||||
frontMatter: markdownContent.frontMatter,
|
||||
html: renderedHTML,
|
||||
html: markdownContent.content,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,33 +1,26 @@
|
||||
import { GetServerSideProps } from "next";
|
||||
import { MainLayout } from "../../layouts/MainLayout";
|
||||
import { BlogPost, getAllFilesFrontMatter } from "../../utils/markdown";
|
||||
import { FrontMatter } from "../../types/types";
|
||||
import { Post, getAllBlogsFrontMatter } from "../../utils/markdown";
|
||||
import Link from "next/link";
|
||||
import { formatDate } from "../../utils/general";
|
||||
import { BasicArticleProps } from "../../components/PostHeader";
|
||||
|
||||
interface BasicBlogProps extends FrontMatter {
|
||||
title: string;
|
||||
description: string;
|
||||
date: string;
|
||||
author: string;
|
||||
}
|
||||
|
||||
const BlogCard = ({ blog, slug }: { blog: BasicBlogProps; slug: string }) => {
|
||||
const format = (date: string) => {
|
||||
const [day, month, year] = date.split(".");
|
||||
const d = new Date(`${year}-${month}-${day}`);
|
||||
return d.toLocaleDateString("en-US", {
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
};
|
||||
const BlogCard = ({
|
||||
blog,
|
||||
slug,
|
||||
}: {
|
||||
blog: BasicArticleProps;
|
||||
slug: string;
|
||||
}) => {
|
||||
return (
|
||||
<div className="p-4 rounded-md border border-gray-700">
|
||||
<div className="p-4 rounded-md border border-gray-200 shadow-md shadow-gray-200 dark:shadow-gray-900 dark:border-gray-700 bg-black bg-opacity-60">
|
||||
<div className="text-sm font-medium text-gray-500">
|
||||
{format(blog.date)}
|
||||
{formatDate(blog.date)}
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold">{blog.title}</h2>
|
||||
<p className="text-gray-400 text-lg">{blog.description}</p>
|
||||
<p className="text-gray-600 dark:text-gray-400 text-sm">
|
||||
{blog.description}
|
||||
</p>
|
||||
<Link href={`blog/${slug}`}>
|
||||
<button className="bg-action px-2 py-1 rounded-md mt-4 hover:bg-action/60 transition-all ease-in duration-100 font-bold text-white">
|
||||
Read more
|
||||
@@ -37,16 +30,18 @@ const BlogCard = ({ blog, slug }: { blog: BasicBlogProps; slug: string }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const Blog = ({ posts }: { posts: BlogPost[] }) => {
|
||||
const Blog = ({ posts }: { posts: Post[] }) => {
|
||||
return (
|
||||
<MainLayout>
|
||||
<h1 className="text-3xl font-bold mt-10">Blog</h1>
|
||||
<div className="w-full h-0.5 bg-white/50 my-4 rounded-full" />
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<h1 className="text-3xl font-bold text-gray-800 dark:text-gray-200">
|
||||
Blog
|
||||
</h1>
|
||||
<div className="w-full h-0.5 bg-violet-200 dark:bg-white/20 my-4 rounded-full" />
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{posts.map((post, index) => (
|
||||
<BlogCard
|
||||
key={index}
|
||||
blog={post.frontMatter as BasicBlogProps}
|
||||
blog={post.frontMatter as BasicArticleProps}
|
||||
slug={post.slug}
|
||||
/>
|
||||
))}
|
||||
@@ -56,7 +51,7 @@ const Blog = ({ posts }: { posts: BlogPost[] }) => {
|
||||
};
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async () => {
|
||||
const posts = getAllFilesFrontMatter();
|
||||
const posts = getAllBlogsFrontMatter();
|
||||
return {
|
||||
props: {
|
||||
posts,
|
||||
|
||||
@@ -2,8 +2,8 @@ import { MainLayout } from "../layouts/MainLayout";
|
||||
export default function Home() {
|
||||
return (
|
||||
<MainLayout>
|
||||
<div className="grid lg:grid-cols-2 grid-cols-1 mt-4 lg:mt-20 gap-10 items-center">
|
||||
<div className="bg-primary-dark p-10 rounded-lg flex flex-col gap-10 h-fit lg:w-2/3 w-full text-lg text-primary-text/90 shadow-lg shadow-gradient-dark/20">
|
||||
<div className="grid lg:grid-cols-2 grid-cols-1 xl:mt-10 gap-10 items-center">
|
||||
<div className="bg-primary-dark/30 border-white/30 border p-10 rounded-lg flex flex-col gap-10 h-fit 3xl:w-2/3 w-full text-lg text-primary-text/90 shadow-lg shadow-gradient-dark/20">
|
||||
<div>
|
||||
<span className="font-bold text-xl text-primary-text">
|
||||
Lorem ipsum dolor{" "}
|
||||
|
||||
69
pages/project/[slug].tsx
Normal file
69
pages/project/[slug].tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import { GetStaticPaths, GetStaticProps } from "next";
|
||||
import fs from "fs";
|
||||
import { ParsedUrlQuery } from "querystring";
|
||||
import {
|
||||
getHeadings,
|
||||
getProjectContentBySlug,
|
||||
PROJECTS_PATH,
|
||||
} from "../../utils/markdown";
|
||||
import { MarkdownRenderingResult } from "../../types/types";
|
||||
import { MainLayout } from "../../layouts/MainLayout";
|
||||
import Link from "next/link";
|
||||
import { IoMdArrowRoundBack as BackIcon } from "react-icons/io";
|
||||
import { readingTime } from "../../utils/general";
|
||||
import { PostHeader } from "../../components/PostHeader";
|
||||
import { StyledMarkdown } from "../../components/StyledMarkdown";
|
||||
|
||||
export const ProjectArticle = ({
|
||||
frontMatter,
|
||||
html,
|
||||
}: MarkdownRenderingResult) => {
|
||||
return (
|
||||
<MainLayout>
|
||||
<div className="rounded-md border border-gray-200 shadow-md shadow-gray-200 dark:shadow-gray-900 dark:border-gray-700 gap-4 bg-black bg-opacity-30 round">
|
||||
<div className="mx-10 my-10">
|
||||
<Link href="/projects">
|
||||
<button className="flex flex-row items-center gap-2 text-primary hover:text-primary-dark dark:text-secondary dark:hover:text-secondary/50 transition-all ease-in duration-75">
|
||||
<BackIcon />
|
||||
<span>Go Back</span>
|
||||
</button>
|
||||
</Link>
|
||||
|
||||
<PostHeader frontMatter={frontMatter} estTime={readingTime(html)} />
|
||||
|
||||
<StyledMarkdown html={html} />
|
||||
</div>
|
||||
</div>
|
||||
</MainLayout>
|
||||
);
|
||||
};
|
||||
interface ProjectProps extends ParsedUrlQuery {
|
||||
slug: string;
|
||||
}
|
||||
export const getStaticPaths: GetStaticPaths<ProjectProps> = async () => {
|
||||
const paths = fs
|
||||
.readdirSync(PROJECTS_PATH)
|
||||
.map((path) => path.replace(/\.mdx?$/, ""))
|
||||
.map((slug) => ({ params: { slug } }));
|
||||
|
||||
return {
|
||||
paths,
|
||||
fallback: false,
|
||||
};
|
||||
};
|
||||
|
||||
export const getStaticProps: GetStaticProps<MarkdownRenderingResult> = async ({
|
||||
params,
|
||||
}) => {
|
||||
const markdownContent = getProjectContentBySlug(params?.slug as string);
|
||||
|
||||
console.log(getHeadings(markdownContent.content));
|
||||
|
||||
return {
|
||||
props: {
|
||||
frontMatter: markdownContent.frontMatter,
|
||||
html: markdownContent.content,
|
||||
},
|
||||
};
|
||||
};
|
||||
export default ProjectArticle;
|
||||
55
pages/projects/index.tsx
Normal file
55
pages/projects/index.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { GetServerSideProps } from 'next'
|
||||
import { MainLayout } from '../../layouts/MainLayout'
|
||||
import { Post, getAllProjectsFrontMatter } from '../../utils/markdown'
|
||||
import Link from 'next/link'
|
||||
import { formatDate } from '../../utils/general'
|
||||
import { BasicArticleProps } from '../../components/PostHeader'
|
||||
|
||||
const ProjectCard = ({ project, slug }: { project: BasicArticleProps; slug: string }) => {
|
||||
return (
|
||||
<div className="p-4 rounded-md border border-gray-200 shadow-md shadow-gray-200 dark:shadow-gray-900 dark:border-gray-700 grid grid-cols-1 xl:grid-cols-2 items-center gap-4 bg-black bg-opacity-60">
|
||||
<div className="order-last xl:order-1">
|
||||
<div className="text-sm font-medium text-gray-500">{formatDate(project.date)}</div>
|
||||
<h2 className="text-2xl font-bold">{project.title}</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 text-sm">{project.description}</p>
|
||||
<Link href={`project/${slug}`}>
|
||||
<button className="bg-action px-2 py-1 rounded-md mt-4 hover:bg-action/60 transition-all ease-in duration-100 font-bold text-white">
|
||||
Read more
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
{project.thumbnail && (
|
||||
<img
|
||||
src={project.thumbnail}
|
||||
alt={`${slug}-thumbnail`}
|
||||
className="w-full xl:h-40 h-auto object-cover rounded-md order-1 xl:order-last"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Projects = ({ posts }: { posts: Post[] }) => {
|
||||
return (
|
||||
<MainLayout>
|
||||
<h1 className="text-3xl font-bold text-gray-800 dark:text-gray-100">Projects</h1>
|
||||
<div className="w-full h-0.5 bg-violet-200 dark:bg-white/20 my-4 rounded-full" />
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{posts.map((post, index) => (
|
||||
<ProjectCard key={index} project={post.frontMatter as BasicArticleProps} slug={post.slug} />
|
||||
))}
|
||||
</div>
|
||||
</MainLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async () => {
|
||||
const posts = getAllProjectsFrontMatter()
|
||||
return {
|
||||
props: {
|
||||
posts,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default Projects
|
||||
@@ -2,6 +2,112 @@
|
||||
@import "tailwindcss/components";
|
||||
@import "tailwindcss/utilities";
|
||||
|
||||
body {
|
||||
@apply dark:bg-gradient-dark dark:text-primary-text bg-white text-gradient-dark;
|
||||
html {
|
||||
scroll-padding-top: 5rem;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
body {
|
||||
@apply dark:bg-gradient-dark dark:text-primary-text bg-slate-100 text-gradient-dark w-full mx-auto;
|
||||
}
|
||||
main {
|
||||
max-width: 800px !important;
|
||||
margin: auto !important;
|
||||
}
|
||||
.prose {
|
||||
margin: 0;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
pre {
|
||||
@apply dark:bg-black/30 bg-slate-200 text-slate-800 dark:text-slate-200 !important;
|
||||
}
|
||||
|
||||
pre > code {
|
||||
@apply bg-transparent text-gray-700 dark:text-slate-300 !important;
|
||||
}
|
||||
|
||||
.codeStyle {
|
||||
@apply bg-transparent !important;
|
||||
}
|
||||
|
||||
pre > div > div > button > svg:hover {
|
||||
@apply hover:text-action transition-all ease-in-out duration-100 !important;
|
||||
}
|
||||
|
||||
h1 > a,
|
||||
h2 > a,
|
||||
h3 > a,
|
||||
h4 > a {
|
||||
@apply cursor-pointer relative text-gray-800 dark:text-gray-100 decoration-transparent font-bold !important;
|
||||
}
|
||||
|
||||
h1 > a:hover::before,
|
||||
h2 > a:hover::before,
|
||||
h3 > a:hover::before,
|
||||
h4 > a:hover::before {
|
||||
@apply content-['#'] absolute -left-8 text-gray-300 dark:text-gray-400;
|
||||
}
|
||||
|
||||
h2 > a:hover::before {
|
||||
@apply -left-6;
|
||||
}
|
||||
|
||||
h3 > a:hover::before {
|
||||
@apply -left-5;
|
||||
}
|
||||
|
||||
h4 > a:hover::before {
|
||||
@apply -left-4;
|
||||
}
|
||||
|
||||
div > code,
|
||||
pre > code {
|
||||
@apply p-0 m-0 !important;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 1000px;
|
||||
height: 600px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.right-arrow {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0%;
|
||||
font-size: 2rem;
|
||||
rotate: 180deg;
|
||||
color: #bdbdbd;
|
||||
z-index: 10;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.left-arrow {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: -5%;
|
||||
font-size: 2rem;
|
||||
color: #bdbdbd;
|
||||
z-index: 10;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.slide {
|
||||
opacity: 0;
|
||||
transition-duration: 0.5s ease;
|
||||
}
|
||||
|
||||
.slide.active {
|
||||
opacity: 1;
|
||||
transition-duration: 0.5s;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
@@ -15,7 +15,7 @@ module.exports = {
|
||||
secondary: "#FFE600",
|
||||
"primary-text": "#D9D9D9",
|
||||
},
|
||||
}
|
||||
},
|
||||
plugins: [require('@tailwindcss/typography')],
|
||||
},
|
||||
plugins: [require("@tailwindcss/typography")],
|
||||
};
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
"jsx": "preserve",
|
||||
"incremental": true
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"desc.d.ts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
35
utils/general.ts
Normal file
35
utils/general.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export const formatDate = (date: string) => {
|
||||
if (!date) return null;
|
||||
const [day, month, year] = date.split(".");
|
||||
const d = new Date(`${year}-${month}-${day}`);
|
||||
return d.toLocaleDateString("en-US", {
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
export const generateSlug = (str: string) => {
|
||||
str = str?.replace(/^\s+|\s+$/g, "");
|
||||
str = str?.toLowerCase();
|
||||
const from = "àáãäâèéëêìíïîòóöôùúüûñç·/_,:;";
|
||||
const to = "aaaaaeeeeiiiioooouuuunc------";
|
||||
|
||||
for (let i = 0, l = from.length; i < l; i++) {
|
||||
str = str.replace(new RegExp(from.charAt(i), "g"), to.charAt(i));
|
||||
}
|
||||
|
||||
str = str
|
||||
?.replace(/[^a-z0-9 -]/g, "")
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/-+/g, "-");
|
||||
|
||||
return str;
|
||||
};
|
||||
|
||||
export const readingTime = (content: string) => {
|
||||
const wpm = 225;
|
||||
const words = content.trim().split(/\s+/).length;
|
||||
const time = Math.ceil(words / wpm);
|
||||
return time;
|
||||
};
|
||||
@@ -6,8 +6,21 @@ import { remark } from "remark";
|
||||
import html from "remark-html";
|
||||
|
||||
export const BLOGS_PATH = join(process.cwd(), "content/blogs");
|
||||
export const PROJECTS_PATH = join(process.cwd(), "content/projects");
|
||||
export interface Post {
|
||||
frontMatter: FrontMatter;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
export const getFileContentBySlug = (slug: string): MarkdownDocument => {
|
||||
const getDate = (date: string) => {
|
||||
if (typeof date === "string") {
|
||||
const d = date.split(".").map((v) => +v);
|
||||
return new Date(d[2], d[1] - 1, d[0]);
|
||||
}
|
||||
return new Date();
|
||||
};
|
||||
|
||||
export const getBlogContentBySlug = (slug: string): MarkdownDocument => {
|
||||
const filepath = join(BLOGS_PATH, `${slug}.md` || `${slug}.mdx`);
|
||||
const filecontents = fs.readFileSync(filepath);
|
||||
|
||||
@@ -18,21 +31,65 @@ export const getFileContentBySlug = (slug: string): MarkdownDocument => {
|
||||
content: content,
|
||||
};
|
||||
};
|
||||
export const getProjectContentBySlug = (slug: string): MarkdownDocument => {
|
||||
const filepath = join(PROJECTS_PATH, `${slug}.md` || `${slug}.mdx`);
|
||||
const filecontents = fs.readFileSync(filepath);
|
||||
|
||||
export interface BlogPost {
|
||||
frontMatter: FrontMatter;
|
||||
slug: string;
|
||||
}
|
||||
const { data, content } = matter(filecontents);
|
||||
|
||||
export const getAllFilesFrontMatter = (): BlogPost[] => {
|
||||
return {
|
||||
frontMatter: data,
|
||||
content: content,
|
||||
};
|
||||
};
|
||||
|
||||
export const getAllBlogsFrontMatter = (): Post[] => {
|
||||
const files = fs.readdirSync(BLOGS_PATH);
|
||||
|
||||
return files.reduce((allPosts: BlogPost[], postSlug: string) => {
|
||||
const blogs = files.reduce((allPosts: Post[], postSlug: string) => {
|
||||
const slug = postSlug.replace(".md", "");
|
||||
const post = getFileContentBySlug(slug);
|
||||
const post = getBlogContentBySlug(slug);
|
||||
|
||||
return [{ frontMatter: post.frontMatter, slug }, ...allPosts];
|
||||
}, []);
|
||||
|
||||
return blogs.sort(
|
||||
//@ts-ignore
|
||||
(a, b) => getDate(b.frontMatter.date) - getDate(a.frontMatter.date)
|
||||
);
|
||||
};
|
||||
export const getAllProjectsFrontMatter = (): Post[] => {
|
||||
const files = fs.readdirSync(PROJECTS_PATH);
|
||||
|
||||
const projects = files.reduce((allPosts: Post[], postSlug: string) => {
|
||||
const slug = postSlug.replace(".md", "");
|
||||
const post = getProjectContentBySlug(slug);
|
||||
|
||||
return [{ frontMatter: post.frontMatter, slug }, ...allPosts];
|
||||
}, []);
|
||||
|
||||
return projects.sort(
|
||||
//@ts-ignore
|
||||
(a, b) => getDate(b.frontMatter.date) - getDate(a.frontMatter.date)
|
||||
);
|
||||
};
|
||||
|
||||
export const getHeadings = (markdown: string): any => {
|
||||
const headingOne = markdown.match(/^(#{1})\s(.*)/gm);
|
||||
const headingTwo = markdown.match(/^(#{2})\s(.*)/gm);
|
||||
const headingThree = markdown.match(/^(#{3})\s(.*)/gm);
|
||||
const headingFour = markdown.match(/^(#{4})\s(.*)/gm);
|
||||
|
||||
const headings = markdown.match(/^(#{1,4})\s(.*)/gm);
|
||||
var tree: any[] = [];
|
||||
var prev = "";
|
||||
headings?.forEach((heading) => {
|
||||
if (headingOne?.includes(heading)) {
|
||||
tree.push(heading);
|
||||
}
|
||||
});
|
||||
|
||||
return tree;
|
||||
};
|
||||
|
||||
export async function markdownToHtml(markdown: any) {
|
||||
|
||||
Reference in New Issue
Block a user