Compare commits
11 Commits
c9f5dfcba7
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| f15a1fb167 | |||
| bab35fbfce | |||
| 84cc6d64db | |||
| 63245d9afd | |||
| 9b8b59d4e6 | |||
|
|
dc7e82b143 | ||
|
|
703b611dfd | ||
|
|
dd4f848071 | ||
|
|
46097b8390 | ||
| e78bc12c49 | |||
| aa3bb66d5e |
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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -13,7 +13,7 @@ export const Navbar = () => {
|
|||||||
const [showMenu, setShowMenu] = useState(false);
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="py-4 xl:px-10 px-4 flex flex-row justify-between sticky top-0 bg-slate-100 dark:bg-gradient-dark z-50 border-b dark:border-gradient-light border-gray-200">
|
<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
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
id="logo"
|
id="logo"
|
||||||
@@ -28,7 +28,7 @@ export const Navbar = () => {
|
|||||||
<div className="hidden xl: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 }) => (
|
{links.map(({ name, href }) => (
|
||||||
<Link href={href} key={name}>
|
<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 text-white">
|
<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}
|
{name}
|
||||||
</button>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useRef, useMemo } from 'react'
|
import { useRef, useMemo } from "react";
|
||||||
import { Canvas, useFrame } from '@react-three/fiber'
|
import { Canvas, useFrame } from "@react-three/fiber";
|
||||||
import { BufferGeometry, Material, MathUtils, Mesh } from "three";
|
import { BufferGeometry, Material, MathUtils, Mesh } from "three";
|
||||||
import { vertexShader } from './Shaders/Background/vertex';
|
import { vertexShader } from "./Shaders/Background/vertex";
|
||||||
import { fragmentShader } from './Shaders/Background/fragment';
|
import { fragmentShader } from "./Shaders/Background/fragment";
|
||||||
|
|
||||||
const Fragment = () => {
|
const Fragment = () => {
|
||||||
// This reference will give us direct access to the mesh
|
// This reference will give us direct access to the mesh
|
||||||
@@ -21,12 +21,13 @@ const Fragment = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useFrame((state) => {
|
useFrame((state) => {
|
||||||
if(!meshRef.current){
|
if (!meshRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { clock } = state;
|
const { clock } = state;
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
meshRef.current.material.uniforms.u_time.value = 0.4 * clock.getElapsedTime()/5;
|
meshRef.current.material.uniforms.u_time.value =
|
||||||
|
(0.4 * clock.getElapsedTime()) / 5;
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
meshRef.current.material.uniforms.u_intensity.value = MathUtils.lerp(
|
meshRef.current.material.uniforms.u_intensity.value = MathUtils.lerp(
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
@@ -37,7 +38,12 @@ const Fragment = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<mesh ref={meshRef} position={[0, 0, 0]} rotation={[-Math.PI/17, Math.PI/20, 0]} scale={1.5}>
|
<mesh
|
||||||
|
ref={meshRef}
|
||||||
|
position={[0, 0, 0]}
|
||||||
|
rotation={[-Math.PI / 17, Math.PI / 20, 0]}
|
||||||
|
scale={1.5}
|
||||||
|
>
|
||||||
<planeGeometry args={[30, 30, 200, 200]} />
|
<planeGeometry args={[30, 30, 200, 200]} />
|
||||||
<shaderMaterial
|
<shaderMaterial
|
||||||
fragmentShader={fragmentShader}
|
fragmentShader={fragmentShader}
|
||||||
@@ -49,13 +55,12 @@ const Fragment = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const R3Gradient = () => {
|
export const R3Gradient = () => {
|
||||||
return (
|
return (
|
||||||
<div className='-z-40 h-screen w-screen fixed bg-black opacity-60'>
|
<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] }}>
|
<Canvas camera={{ position: [0.0, 0.0, 5.0] }}>
|
||||||
<Fragment />
|
<Fragment />
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ import smartypants from "remark-smartypants";
|
|||||||
import rehypeRaw from "rehype-raw";
|
import rehypeRaw from "rehype-raw";
|
||||||
import emoji from "remark-emoji";
|
import emoji from "remark-emoji";
|
||||||
import oembed from "@agentofuser/remark-oembed";
|
import oembed from "@agentofuser/remark-oembed";
|
||||||
import YouTube from "react-youtube";
|
import remarkDirective from "remark-directive";
|
||||||
|
import remarkDirectiveRehype from "remark-directive-rehype";
|
||||||
|
import { YouTubeVideo } from "./Youtube";
|
||||||
|
import { ImageSlide } from "./ImageSlide";
|
||||||
//import rehypeSanitize from "rehype-sanitize";
|
//import rehypeSanitize from "rehype-sanitize";
|
||||||
|
|
||||||
SyntaxHighlighter.registerLanguage("tsx", tsx);
|
SyntaxHighlighter.registerLanguage("tsx", tsx);
|
||||||
@@ -27,28 +30,8 @@ SyntaxHighlighter.registerLanguage("bash", bash);
|
|||||||
SyntaxHighlighter.registerLanguage("markdown", markdown);
|
SyntaxHighlighter.registerLanguage("markdown", markdown);
|
||||||
SyntaxHighlighter.registerLanguage("json", json);
|
SyntaxHighlighter.registerLanguage("json", json);
|
||||||
|
|
||||||
|
|
||||||
export const StyledMarkdown = ({ html }: { html: string }) => {
|
export const StyledMarkdown = ({ html }: { html: string }) => {
|
||||||
const MarkdownComponents: Components = {
|
const MarkdownComponents: Components = {
|
||||||
|
|
||||||
a: (props : any) => {
|
|
||||||
try {
|
|
||||||
const url = new URL(props.href);
|
|
||||||
if (
|
|
||||||
url.origin.includes("youtube.com") &&
|
|
||||||
props.node.position.start.column === 1
|
|
||||||
){
|
|
||||||
return (
|
|
||||||
<YouTube videoId={url.searchParams.get("v") || "dQw4w9WgXcQ"} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
//console.log(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <a {...props} />;
|
|
||||||
},
|
|
||||||
|
|
||||||
h1: (props: any) => {
|
h1: (props: any) => {
|
||||||
const arr = props.children;
|
const arr = props.children;
|
||||||
let heading = "";
|
let heading = "";
|
||||||
@@ -126,11 +109,9 @@ export const StyledMarkdown = ({ html }: { html: string }) => {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
code({ node, inline, className, ...props }: any) {
|
code({ node, inline, className, ...props }: any) {
|
||||||
|
|
||||||
const match = /language-(\w+)/.exec(className || "");
|
const match = /language-(\w+)/.exec(className || "");
|
||||||
const hasMeta = node?.data?.meta;
|
const hasMeta = node?.data?.meta;
|
||||||
|
|
||||||
|
|
||||||
const applyHighlights: object = (applyHighlights: number) => {
|
const applyHighlights: object = (applyHighlights: number) => {
|
||||||
if (hasMeta) {
|
if (hasMeta) {
|
||||||
const RE = /{([\d,-]+)}/;
|
const RE = /{([\d,-]+)}/;
|
||||||
@@ -170,7 +151,6 @@ export const StyledMarkdown = ({ html }: { html: string }) => {
|
|||||||
return "Code";
|
return "Code";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return match ? (
|
return match ? (
|
||||||
<div>
|
<div>
|
||||||
<div className="w-full flex flex-row items-center gap-4">
|
<div className="w-full flex flex-row items-center gap-4">
|
||||||
@@ -199,16 +179,20 @@ export const StyledMarkdown = ({ html }: { html: string }) => {
|
|||||||
<code className={className} {...props} />
|
<code className={className} {...props} />
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
//custom directives
|
||||||
|
//@ts-ignore
|
||||||
|
yt: YouTubeVideo,
|
||||||
|
"img-slide": ImageSlide,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="
|
className="
|
||||||
prose
|
prose
|
||||||
prose-code:font-mono prose-code:text-gray-100 prose-code:bg-gray-700 prose-code:p-1 prose-code:m-1 prose-code:rounded-md
|
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-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-img:w-full prose-img:h-auto prose-img:object-cover
|
||||||
prose-li:text-gray-600 dark:prose-li:text-gray-300 prose-td:text-gray-600 dark:prose-td:text-gray-400
|
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
|
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
|
dark:prose-hr:bg-gray-200 prose-hr:bg-gray-400
|
||||||
"
|
"
|
||||||
@@ -216,7 +200,15 @@ export const StyledMarkdown = ({ html }: { html: string }) => {
|
|||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
components={MarkdownComponents}
|
components={MarkdownComponents}
|
||||||
rehypePlugins={[rehypeRaw]}
|
rehypePlugins={[rehypeRaw]}
|
||||||
remarkPlugins={[oembed, remarkGfm, remarkTypograf, smartypants, emoji]}
|
remarkPlugins={[
|
||||||
|
remarkDirective,
|
||||||
|
remarkDirectiveRehype,
|
||||||
|
oembed,
|
||||||
|
remarkGfm,
|
||||||
|
smartypants,
|
||||||
|
emoji,
|
||||||
|
remarkTypograf,
|
||||||
|
]}
|
||||||
children={html}
|
children={html}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
||||||
|
)
|
||||||
@@ -12,20 +12,13 @@ From concept drawing to modeling to game engine. A neo-noir Game Concept inspire
|
|||||||
### Sketches
|
### Sketches
|
||||||
|
|
||||||
#### Character Concept
|
#### 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
|
#### 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
|
### 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 Render
|
||||||

|

|
||||||
|
|||||||
@@ -36,8 +36,7 @@ A Challenging mobile endless space shooter game.
|
|||||||
|
|
||||||
|
|
||||||
### Gameplay Video
|
### Gameplay Video
|
||||||
|
::yt[Video of a cat in a box]{#1qquNEJrA10}
|
||||||
https://www.youtube.com/watch?v=1qquNEJrA10
|
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { PropsWithChildren } from "react";
|
import { PropsWithChildren } from 'react'
|
||||||
import { R3Gradient } from '../components/R3Background';
|
import { R3Gradient } from '../components/R3Background'
|
||||||
import { Navbar } from "../components/Navbar";
|
import { Navbar } from '../components/Navbar'
|
||||||
|
|
||||||
export const MainLayout = ({ children }: PropsWithChildren) => {
|
export const MainLayout = ({ children }: PropsWithChildren) => {
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<R3Gradient />
|
<main className="xl:p-5 p-4 px-5 xl:mx-0">{children}</main>
|
||||||
<main className="xl:p-5 p-4 px-5 xl:mx-0">{children}</main>
|
</div>
|
||||||
</div>
|
)
|
||||||
);
|
}
|
||||||
};
|
|
||||||
|
|||||||
@@ -20,10 +20,10 @@
|
|||||||
"@types/three": "^0.149.0",
|
"@types/three": "^0.149.0",
|
||||||
"framer-motion": "^8.0.2",
|
"framer-motion": "^8.0.2",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"next": "13.0.6",
|
"next": "^13.3.1",
|
||||||
"parse-numeric-range": "^1.3.0",
|
"parse-numeric-range": "^1.3.0",
|
||||||
"react": "18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-icons": "^4.7.1",
|
"react-icons": "^4.7.1",
|
||||||
"react-markdown": "^8.0.4",
|
"react-markdown": "^8.0.4",
|
||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
|
import { R3Gradient } from '../components/R3Background'
|
||||||
import '../styles/globals.css'
|
import '../styles/globals.css'
|
||||||
import type { AppProps } from 'next/app'
|
import type { AppProps } from 'next/app'
|
||||||
|
|
||||||
export default function App({ Component, pageProps }: AppProps) {
|
export default function App({ Component, pageProps }: AppProps) {
|
||||||
return <Component {...pageProps} />
|
return (
|
||||||
|
<>
|
||||||
|
<R3Gradient />
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const Blog = ({ posts }: { posts: Post[] }) => {
|
|||||||
<h1 className="text-3xl font-bold text-gray-800 dark:text-gray-200">
|
<h1 className="text-3xl font-bold text-gray-800 dark:text-gray-200">
|
||||||
Blog
|
Blog
|
||||||
</h1>
|
</h1>
|
||||||
<div className="w-full h-0.5 bg-violet-200 dark:bg-violet-700 my-4 rounded-full" />
|
<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">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
{posts.map((post, index) => (
|
{posts.map((post, index) => (
|
||||||
<BlogCard
|
<BlogCard
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ export default function Home() {
|
|||||||
return (
|
return (
|
||||||
<MainLayout>
|
<MainLayout>
|
||||||
<div className="grid lg:grid-cols-2 grid-cols-1 xl:mt-10 gap-10 items-center">
|
<div className="grid lg:grid-cols-2 grid-cols-1 xl:mt-10 gap-10 items-center">
|
||||||
<div className="bg-primary-dark 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 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>
|
<div>
|
||||||
<span className="font-bold text-xl text-primary-text">
|
<span className="font-bold text-xl text-primary-text">
|
||||||
Lorem ipsum dolor{" "}
|
Lorem ipsum dolor{" "}
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { GetStaticPaths, GetStaticProps } from "next";
|
import { GetStaticPaths, GetStaticProps } from "next";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { ParsedUrlQuery } from "querystring";
|
import { ParsedUrlQuery } from "querystring";
|
||||||
import { getProjectContentBySlug, PROJECTS_PATH } from "../../utils/markdown";
|
import {
|
||||||
|
getHeadings,
|
||||||
|
getProjectContentBySlug,
|
||||||
|
PROJECTS_PATH,
|
||||||
|
} from "../../utils/markdown";
|
||||||
import { MarkdownRenderingResult } from "../../types/types";
|
import { MarkdownRenderingResult } from "../../types/types";
|
||||||
import { MainLayout } from "../../layouts/MainLayout";
|
import { MainLayout } from "../../layouts/MainLayout";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -52,6 +56,9 @@ export const getStaticProps: GetStaticProps<MarkdownRenderingResult> = async ({
|
|||||||
params,
|
params,
|
||||||
}) => {
|
}) => {
|
||||||
const markdownContent = getProjectContentBySlug(params?.slug as string);
|
const markdownContent = getProjectContentBySlug(params?.slug as string);
|
||||||
|
|
||||||
|
console.log(getHeadings(markdownContent.content));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
frontMatter: markdownContent.frontMatter,
|
frontMatter: markdownContent.frontMatter,
|
||||||
|
|||||||
@@ -1,71 +1,55 @@
|
|||||||
import { GetServerSideProps } from "next";
|
import { GetServerSideProps } from 'next'
|
||||||
import { MainLayout } from "../../layouts/MainLayout";
|
import { MainLayout } from '../../layouts/MainLayout'
|
||||||
import { Post, getAllProjectsFrontMatter } from "../../utils/markdown";
|
import { Post, getAllProjectsFrontMatter } from '../../utils/markdown'
|
||||||
import Link from "next/link";
|
import Link from 'next/link'
|
||||||
import { formatDate } from "../../utils/general";
|
import { formatDate } from '../../utils/general'
|
||||||
import { BasicArticleProps } from "../../components/PostHeader";
|
import { BasicArticleProps } from '../../components/PostHeader'
|
||||||
|
|
||||||
const ProjectCard = ({
|
const ProjectCard = ({ project, slug }: { project: BasicArticleProps; slug: string }) => {
|
||||||
project,
|
return (
|
||||||
slug,
|
<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">
|
||||||
project: BasicArticleProps;
|
<div className="text-sm font-medium text-gray-500">{formatDate(project.date)}</div>
|
||||||
slug: string;
|
<h2 className="text-2xl font-bold">{project.title}</h2>
|
||||||
}) => {
|
<p className="text-gray-600 dark:text-gray-400 text-sm">{project.description}</p>
|
||||||
return (
|
<Link href={`project/${slug}`}>
|
||||||
<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">
|
<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">
|
||||||
<div className="order-last xl:order-1">
|
Read more
|
||||||
<div className="text-sm font-medium text-gray-500">
|
</button>
|
||||||
{formatDate(project.date)}
|
</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>
|
</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[] }) => {
|
const Projects = ({ posts }: { posts: Post[] }) => {
|
||||||
return (
|
return (
|
||||||
<MainLayout>
|
<MainLayout>
|
||||||
<h1 className="text-3xl font-bold text-gray-800 dark:text-gray-100">
|
<h1 className="text-3xl font-bold text-gray-800 dark:text-gray-100">Projects</h1>
|
||||||
Projects
|
<div className="w-full h-0.5 bg-violet-200 dark:bg-white/20 my-4 rounded-full" />
|
||||||
</h1>
|
<div className="grid grid-cols-1 gap-4">
|
||||||
<div className="w-full h-0.5 bg-violet-200 dark:bg-violet-700 my-4 rounded-full" />
|
{posts.map((post, index) => (
|
||||||
<div className="grid grid-cols-1 gap-4">
|
<ProjectCard key={index} project={post.frontMatter as BasicArticleProps} slug={post.slug} />
|
||||||
{posts.map((post, index) => (
|
))}
|
||||||
<ProjectCard
|
</div>
|
||||||
key={index}
|
</MainLayout>
|
||||||
project={post.frontMatter as BasicArticleProps}
|
)
|
||||||
slug={post.slug}
|
}
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</MainLayout>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async () => {
|
export const getServerSideProps: GetServerSideProps = async () => {
|
||||||
const posts = getAllProjectsFrontMatter();
|
const posts = getAllProjectsFrontMatter()
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
posts,
|
posts,
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default Projects;
|
export default Projects
|
||||||
|
|||||||
@@ -19,22 +19,25 @@ main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
@apply dark:bg-slate-800 bg-slate-200 text-slate-800 dark:text-slate-200 !important;
|
@apply dark:bg-black/30 bg-slate-200 text-slate-800 dark:text-slate-200 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre > code {
|
pre > code {
|
||||||
@apply bg-transparent text-gray-700 dark:text-slate-300 !important;
|
@apply bg-transparent text-gray-700 dark:text-slate-300 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.codeStyle > code {
|
.codeStyle {
|
||||||
background: transparent;
|
@apply bg-transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre > div > div > button > svg:hover {
|
pre > div > div > button > svg:hover {
|
||||||
@apply hover:text-action transition-all ease-in-out duration-100 !important;
|
@apply hover:text-action transition-all ease-in-out duration-100 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 > a, h2 > a, h3 > a, h4 > a {
|
h1 > a,
|
||||||
|
h2 > a,
|
||||||
|
h3 > a,
|
||||||
|
h4 > a {
|
||||||
@apply cursor-pointer relative text-gray-800 dark:text-gray-100 decoration-transparent font-bold !important;
|
@apply cursor-pointer relative text-gray-800 dark:text-gray-100 decoration-transparent font-bold !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,9 +45,69 @@ h1 > a:hover::before,
|
|||||||
h2 > a:hover::before,
|
h2 > a:hover::before,
|
||||||
h3 > a:hover::before,
|
h3 > a:hover::before,
|
||||||
h4 > a:hover::before {
|
h4 > a:hover::before {
|
||||||
@apply content-['#'] absolute -left-8 text-gray-300 dark:text-gray-600;
|
@apply content-['#'] absolute -left-8 text-gray-300 dark:text-gray-400;
|
||||||
}
|
}
|
||||||
|
|
||||||
div > code, pre > code {
|
h2 > a:hover::before {
|
||||||
@apply p-0 m-0 !important
|
@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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import fs from "fs";
|
|||||||
import { FrontMatter, MarkdownDocument } from "../types/types";
|
import { FrontMatter, MarkdownDocument } from "../types/types";
|
||||||
import { remark } from "remark";
|
import { remark } from "remark";
|
||||||
import html from "remark-html";
|
import html from "remark-html";
|
||||||
import remarkGfm from "remark-gfm";
|
|
||||||
|
|
||||||
|
|
||||||
export const BLOGS_PATH = join(process.cwd(), "content/blogs");
|
export const BLOGS_PATH = join(process.cwd(), "content/blogs");
|
||||||
export const PROJECTS_PATH = join(process.cwd(), "content/projects");
|
export const PROJECTS_PATH = join(process.cwd(), "content/projects");
|
||||||
@@ -14,6 +12,14 @@ export interface Post {
|
|||||||
slug: string;
|
slug: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 => {
|
export const getBlogContentBySlug = (slug: string): MarkdownDocument => {
|
||||||
const filepath = join(BLOGS_PATH, `${slug}.md` || `${slug}.mdx`);
|
const filepath = join(BLOGS_PATH, `${slug}.md` || `${slug}.mdx`);
|
||||||
const filecontents = fs.readFileSync(filepath);
|
const filecontents = fs.readFileSync(filepath);
|
||||||
@@ -40,22 +46,50 @@ export const getProjectContentBySlug = (slug: string): MarkdownDocument => {
|
|||||||
export const getAllBlogsFrontMatter = (): Post[] => {
|
export const getAllBlogsFrontMatter = (): Post[] => {
|
||||||
const files = fs.readdirSync(BLOGS_PATH);
|
const files = fs.readdirSync(BLOGS_PATH);
|
||||||
|
|
||||||
return files.reduce((allPosts: Post[], postSlug: string) => {
|
const blogs = files.reduce((allPosts: Post[], postSlug: string) => {
|
||||||
const slug = postSlug.replace(".md", "");
|
const slug = postSlug.replace(".md", "");
|
||||||
const post = getBlogContentBySlug(slug);
|
const post = getBlogContentBySlug(slug);
|
||||||
|
|
||||||
return [{ frontMatter: post.frontMatter, slug }, ...allPosts];
|
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[] => {
|
export const getAllProjectsFrontMatter = (): Post[] => {
|
||||||
const files = fs.readdirSync(PROJECTS_PATH);
|
const files = fs.readdirSync(PROJECTS_PATH);
|
||||||
|
|
||||||
return files.reduce((allPosts: Post[], postSlug: string) => {
|
const projects = files.reduce((allPosts: Post[], postSlug: string) => {
|
||||||
const slug = postSlug.replace(".md", "");
|
const slug = postSlug.replace(".md", "");
|
||||||
const post = getProjectContentBySlug(slug);
|
const post = getProjectContentBySlug(slug);
|
||||||
|
|
||||||
return [{ frontMatter: post.frontMatter, slug }, ...allPosts];
|
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) {
|
export async function markdownToHtml(markdown: any) {
|
||||||
|
|||||||
Reference in New Issue
Block a user