feat(portfolio): migrate to nextJS
This commit is contained in:
parent
2d600d724d
commit
953379b5e8
42 changed files with 476 additions and 2761 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -41,10 +41,10 @@
|
||||||
"katex",
|
"katex",
|
||||||
"kunukn",
|
"kunukn",
|
||||||
"Librewolf",
|
"Librewolf",
|
||||||
"linaria",
|
|
||||||
"mhchem",
|
"mhchem",
|
||||||
"microflash",
|
"microflash",
|
||||||
"nodedotjs",
|
"nodedotjs",
|
||||||
|
"nojs",
|
||||||
"noopener",
|
"noopener",
|
||||||
"noto",
|
"noto",
|
||||||
"planetscale",
|
"planetscale",
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
{
|
|
||||||
"root": true,
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"node": true,
|
|
||||||
"es2020": true
|
|
||||||
},
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": "latest",
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"plugins": [
|
|
||||||
"@typescript-eslint",
|
|
||||||
"react-refresh",
|
|
||||||
"prettier",
|
|
||||||
"simple-import-sort"
|
|
||||||
],
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:react-hooks/recommended",
|
|
||||||
"plugin:prettier/recommended"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"prettier/prettier": "error",
|
|
||||||
"react-refresh/only-export-components": "warn",
|
|
||||||
"simple-import-sort/imports": "error",
|
|
||||||
"simple-import-sort/exports": "error"
|
|
||||||
}
|
|
||||||
}
|
|
8
apps/portfolio/.eslintrc.cjs
Normal file
8
apps/portfolio/.eslintrc.cjs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: ["next/core-web-vitals", "@developomp-site/eslint-config"],
|
||||||
|
rules: {
|
||||||
|
"react-hooks/exhaustive-deps": "off",
|
||||||
|
"react/no-unescaped-entities": "off",
|
||||||
|
},
|
||||||
|
}
|
35
apps/portfolio/.gitignore
vendored
Normal file
35
apps/portfolio/.gitignore
vendored
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
|
@ -1,26 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
|
|
||||||
<meta name="description" content="developomp's portfolio" />
|
|
||||||
<title>portfolio</title>
|
|
||||||
|
|
||||||
<!-- OpenGraph -->
|
|
||||||
<meta property="og:title" content="Portfolio" />
|
|
||||||
<meta property="og:site_name" content="developomp's portfolio" />
|
|
||||||
<meta property="og:description" content="developomp's Portfolio" />
|
|
||||||
<meta property="og:type" content="website" />
|
|
||||||
<meta property="og:url" content="https://portfolio.developomp.com" />
|
|
||||||
<meta
|
|
||||||
property="og:image"
|
|
||||||
content="https://portfolio.developomp.com/favicon.svg"
|
|
||||||
/>
|
|
||||||
</head>
|
|
||||||
<body class="dark">
|
|
||||||
<div id="root"></div>
|
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
11
apps/portfolio/next.config.js
Normal file
11
apps/portfolio/next.config.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
output: "export",
|
||||||
|
distDir: "dist",
|
||||||
|
images: { unoptimized: true },
|
||||||
|
experimental: {
|
||||||
|
externalDir: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = nextConfig
|
|
@ -1,52 +1,47 @@
|
||||||
{
|
{
|
||||||
"name": "@developomp-site/portfolio",
|
"name": "@developomp-site/portfolio",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "open-cli http://localhost:5174 && next dev -p 5174",
|
||||||
"build": "tsc && vite build",
|
"build": "next build",
|
||||||
"lint": "eslint .",
|
"lint": "next lint",
|
||||||
"preview": "vite preview",
|
"clean": "rm -rf .next .turbo build node_modules"
|
||||||
"clean": "rm -rf .turbo node_modules dist vite.config.ts.timestamp-*"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@fontsource/noto-sans-kr": "^5.0.5",
|
|
||||||
"@fontsource/source-code-pro": "^5.0.5",
|
|
||||||
"@linaria/core": "^4.2.10",
|
|
||||||
"@linaria/react": "^4.3.8",
|
|
||||||
"hoofd": "^1.7.0",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-collapse": "^5.1.1",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"wouter": "^2.11.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@developomp-site/blog": "workspace:*",
|
"@developomp-site/blog": "workspace:*",
|
||||||
"@developomp-site/content": "workspace:*",
|
"@developomp-site/content": "workspace:*",
|
||||||
|
"@developomp-site/eslint-config": "workspace:*",
|
||||||
"@developomp-site/prettier-config": "workspace:*",
|
"@developomp-site/prettier-config": "workspace:*",
|
||||||
"@developomp-site/tailwind-config": "workspace:*",
|
"@developomp-site/tailwind-config": "workspace:*",
|
||||||
"@linaria/babel-preset": "^4.4.5",
|
"@fontsource/noto-sans-kr": "^5.0.5",
|
||||||
"@linaria/vite": "^4.2.11",
|
"@fontsource/source-code-pro": "^5.0.5",
|
||||||
"@types/react": "^18.2.14",
|
"@fortawesome/free-brands-svg-icons": "^6.4.0",
|
||||||
"@types/react-dom": "^18.2.6",
|
"@fortawesome/free-solid-svg-icons": "^6.4.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.61.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@typescript-eslint/parser": "^5.61.0",
|
"@kunukn/react-collapse": "^2.2.10",
|
||||||
"@vitejs/plugin-react": "^4.0.2",
|
"@types/highlight.js": "^10.1.0",
|
||||||
"autoprefixer": "^10.4.14",
|
"@types/katex": "^0.16.2",
|
||||||
"eslint": "^8.44.0",
|
"@types/node": "20.4.5",
|
||||||
"eslint-config-prettier": "^8.8.0",
|
"@types/react-collapse": "^5.0.1",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"@types/react-dom": "18.2.7",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"@types/react": "18.2.17",
|
||||||
"eslint-plugin-react-refresh": "^0.4.2",
|
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
||||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
"@typescript-eslint/parser": "^6.2.0",
|
||||||
"postcss": "^8.4.25",
|
"autoprefixer": "10.4.14",
|
||||||
|
"eslint-config-next": "13.4.12",
|
||||||
|
"eslint": "8.45.0",
|
||||||
|
"highlight.js": "^11.8.0",
|
||||||
|
"katex": "^0.16.8",
|
||||||
|
"next": "13.4.12",
|
||||||
|
"open-cli": "^7.2.0",
|
||||||
|
"postcss": "8.4.27",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.4.1",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"prettier-plugin-tailwindcss": "^0.3.0",
|
"react-collapse": "^5.1.1",
|
||||||
"tailwindcss": "^3.3.2",
|
"react-dom": "18.2.0",
|
||||||
"typescript": "^5.1.6",
|
"react": "18.2.0",
|
||||||
"vite": "^4.4.2",
|
"tailwindcss": "3.3.3",
|
||||||
"vite-plugin-dynamic-import": "^1.5.0",
|
"typescript": "5.1.6"
|
||||||
"vite-tsconfig-paths": "^4.2.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export default {
|
module.exports = {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
|
|
BIN
apps/portfolio/public/nojs.avif
Normal file
BIN
apps/portfolio/public/nojs.avif
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
|
@ -1,41 +0,0 @@
|
||||||
import { useTitleTemplate } from "hoofd"
|
|
||||||
import { type FC } from "react"
|
|
||||||
import { Route, Switch } from "wouter"
|
|
||||||
|
|
||||||
import Header from "@/components/Header"
|
|
||||||
import Home from "@/routes/Home"
|
|
||||||
import Loading from "@/routes/Loading"
|
|
||||||
import NotFound from "@/routes/NotFound"
|
|
||||||
import Project from "@/routes/Project"
|
|
||||||
|
|
||||||
const App: FC = () => {
|
|
||||||
useTitleTemplate("Portfolio | %s")
|
|
||||||
// no need to set title and meta tags here
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Header />
|
|
||||||
<div className="mb-10 mt-20 w-full max-w-screen-md px-4">
|
|
||||||
<Switch>
|
|
||||||
<Route path="/">
|
|
||||||
<Home />
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/project/:id">
|
|
||||||
<Project />
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/loading">
|
|
||||||
<Loading />
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route>
|
|
||||||
<NotFound />
|
|
||||||
</Route>
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App
|
|
|
@ -11,11 +11,7 @@ html {
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply flex min-h-screen w-full flex-col items-center;
|
@apply m-0 flex h-full min-h-screen w-full scroll-m-16 flex-col items-center p-0;
|
||||||
}
|
|
||||||
|
|
||||||
#root {
|
|
||||||
@apply m-0 flex h-full w-full scroll-m-16 flex-col items-center p-0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ul,
|
ul,
|
85
apps/portfolio/src/app/layout.tsx
Normal file
85
apps/portfolio/src/app/layout.tsx
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import "@fortawesome/fontawesome-svg-core/styles.css"
|
||||||
|
import "@fontsource/noto-sans-kr/400.css"
|
||||||
|
import "@fontsource/noto-sans-kr/700.css"
|
||||||
|
import "@fontsource/source-code-pro"
|
||||||
|
import "@developomp-site/blog/src/styles/anchor.scss"
|
||||||
|
import "@developomp-site/blog/src/styles/blockQuote.scss"
|
||||||
|
import "@developomp-site/blog/src/styles/button.scss"
|
||||||
|
import "@developomp-site/blog/src/styles/callout.scss"
|
||||||
|
import "@developomp-site/blog/src/styles/checkbox.scss"
|
||||||
|
import "@developomp-site/blog/src/styles/code.scss"
|
||||||
|
import "@developomp-site/blog/src/styles/colorChip.scss"
|
||||||
|
import "@developomp-site/blog/src/styles/heading.scss"
|
||||||
|
import "@developomp-site/blog/src/styles/hr.scss"
|
||||||
|
import "@developomp-site/blog/src/styles/img.scss"
|
||||||
|
import "@developomp-site/blog/src/styles/katex.scss"
|
||||||
|
import "@developomp-site/blog/src/styles/kbd.scss"
|
||||||
|
import "@developomp-site/blog/src/styles/list.scss"
|
||||||
|
import "@developomp-site/blog/src/styles/mark.scss"
|
||||||
|
import "@developomp-site/blog/src/styles/scrollbar.scss"
|
||||||
|
import "@developomp-site/blog/src/styles/subSup.scss"
|
||||||
|
import "@developomp-site/blog/src/styles/table.scss"
|
||||||
|
import "./global.scss"
|
||||||
|
|
||||||
|
import { type Metadata } from "next"
|
||||||
|
import Image from "next/image"
|
||||||
|
|
||||||
|
import Header from "@/components/Header"
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
metadataBase: new URL("https://portfolio.developomp.com"),
|
||||||
|
title: {
|
||||||
|
template: "pomp's portfolio | %s",
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
description: "developomp's portfolio",
|
||||||
|
openGraph: {
|
||||||
|
title: "pomp's portfolio",
|
||||||
|
siteName: "developomp's portfolio",
|
||||||
|
description: "developomp's portfolio",
|
||||||
|
type: "website",
|
||||||
|
url: "https://portfolio.developomp.com",
|
||||||
|
images: "https://portfolio.developomp.com/favicon.svg",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<html lang="en" className="dark">
|
||||||
|
<head>
|
||||||
|
<link
|
||||||
|
rel="shortcut icon"
|
||||||
|
type="image/svg+xml"
|
||||||
|
href="/favicon.svg"
|
||||||
|
/>
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<figure>
|
||||||
|
<Image
|
||||||
|
src="/img/nojs.avif"
|
||||||
|
height={500}
|
||||||
|
width={544}
|
||||||
|
alt="No javascript?"
|
||||||
|
/>
|
||||||
|
<figcaption>
|
||||||
|
Image compressed down to 4.5kB because you probably
|
||||||
|
have potato internet :D
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<Header />
|
||||||
|
<div className="mb-10 mt-20 w-full max-w-screen-md px-4">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
21
apps/portfolio/src/app/not-found.tsx
Normal file
21
apps/portfolio/src/app/not-found.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { type Metadata } from "next"
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
metadataBase: new URL("https://portfolio.developomp.com"),
|
||||||
|
title: "404",
|
||||||
|
openGraph: {
|
||||||
|
title: "pomp's portfolio | Page Not Found",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1 className="w-fit px-4 py-2 text-5xl dark:bg-dark-text-default dark:text-dark-ui-bg">
|
||||||
|
404
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<h2 className="glitch layers text-5xl">Page Not Found</h2>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,9 +1,6 @@
|
||||||
import "./style.scss"
|
|
||||||
|
|
||||||
import portfolio from "@developomp-site/content/dist/portfolio.json"
|
import portfolio from "@developomp-site/content/dist/portfolio.json"
|
||||||
import type { PortfolioProject } from "@developomp-site/content/src/types/types"
|
import type { PortfolioProject } from "@developomp-site/content/src/types/types"
|
||||||
import { useMeta, useTitle } from "hoofd"
|
import { Metadata } from "next"
|
||||||
import { type FC } from "react"
|
|
||||||
|
|
||||||
import Badge from "@/components/Badge"
|
import Badge from "@/components/Badge"
|
||||||
import ProjectCard from "@/components/ProjectCard"
|
import ProjectCard from "@/components/ProjectCard"
|
||||||
|
@ -27,10 +24,12 @@ for (const projectID in portfolio.projects) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Home: FC = () => {
|
export const metadata: Metadata = {
|
||||||
useTitle("Home")
|
metadataBase: new URL("https://blog.developomp.com"),
|
||||||
useMeta({ property: "og:title", content: "Home" })
|
title: "pomp's portfolio | Home",
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className="mb-8">developomp's Portfolio</h1>
|
<h1 className="mb-8">developomp's Portfolio</h1>
|
||||||
|
@ -40,5 +39,3 @@ const Home: FC = () => {
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Home
|
|
97
apps/portfolio/src/app/project/[id]/page.tsx
Normal file
97
apps/portfolio/src/app/project/[id]/page.tsx
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
import "./style.scss"
|
||||||
|
|
||||||
|
import Toc from "@developomp-site/blog/src/app/[category]/[[...slug]]/Toc"
|
||||||
|
import portfolio from "@developomp-site/content/dist/portfolio.json"
|
||||||
|
import { faGithub } from "@fortawesome/free-brands-svg-icons"
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||||
|
import { type Metadata } from "next"
|
||||||
|
import Link from "next/link"
|
||||||
|
|
||||||
|
import Badge from "@/components/Badge"
|
||||||
|
|
||||||
|
interface Data {
|
||||||
|
title: string
|
||||||
|
toc?: string
|
||||||
|
content: string
|
||||||
|
|
||||||
|
image: string // image url
|
||||||
|
overview: string
|
||||||
|
badges: string[]
|
||||||
|
repo: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Params {
|
||||||
|
id: keyof typeof portfolio.projects
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
params: Params
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getData(id: keyof typeof portfolio.projects): Promise<Data> {
|
||||||
|
const content = await import(
|
||||||
|
`@developomp-site/content/dist/content/projects/${id}.json`
|
||||||
|
)
|
||||||
|
const data = portfolio.projects[id]
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: content.content,
|
||||||
|
toc: content.toc,
|
||||||
|
title: data.name,
|
||||||
|
image: data.image,
|
||||||
|
overview: data.overview,
|
||||||
|
badges: data.badges,
|
||||||
|
repo: data.repo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateStaticParams(): Promise<Params[]> {
|
||||||
|
return (
|
||||||
|
Object.keys(portfolio.projects) as (keyof typeof portfolio.projects)[]
|
||||||
|
).map((id) => ({ id }))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||||
|
const data = await getData(params.id)
|
||||||
|
return {
|
||||||
|
metadataBase: new URL("https://portfolio.developomp.com"),
|
||||||
|
title: `pomp's portfolio | ${data.title}`,
|
||||||
|
openGraph: {
|
||||||
|
title: `pomp's portfolio | ${data.title}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Project({ params }: Props) {
|
||||||
|
const data = await getData(params.id)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h1 className="mb-4 text-4xl">{data.title}</h1>
|
||||||
|
<Link
|
||||||
|
href={data.repo}
|
||||||
|
className="text-dark-text-default duration-100 hover:text-gray-400"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon className="h-12" icon={faGithub} />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap">
|
||||||
|
{data.badges.map((slug) => {
|
||||||
|
return <Badge key={slug} slug={slug} />
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<Toc data={data.toc} />
|
||||||
|
|
||||||
|
{/* page content */}
|
||||||
|
<div
|
||||||
|
className="project-description"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: data.content,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
import "./style.scss"
|
import "./style.scss"
|
||||||
|
|
||||||
import { type Badge as BadgeType } from "@developomp-site/content/src/types/types"
|
import { type Badge as BadgeType } from "@developomp-site/content/src/types/types"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
import Link from "next/link"
|
||||||
import { type FC } from "react"
|
import { type FC } from "react"
|
||||||
import { Link } from "wouter"
|
|
||||||
|
|
||||||
const Header: FC = () => {
|
const Header: FC = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -7,9 +7,10 @@ const Header: FC = () => {
|
||||||
<div className="my-0 flex h-16 w-full max-w-5xl items-center">
|
<div className="my-0 flex h-16 w-full max-w-5xl items-center">
|
||||||
<Link
|
<Link
|
||||||
className="flex items-center"
|
className="flex items-center"
|
||||||
to="/"
|
href="/"
|
||||||
aria-label="homepage"
|
aria-label="homepage"
|
||||||
>
|
>
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<img
|
<img
|
||||||
className="m-4 block h-10 cursor-pointer"
|
className="m-4 block h-10 cursor-pointer"
|
||||||
src="/favicon.svg"
|
src="/favicon.svg"
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
import "./style.scss"
|
|
||||||
|
|
||||||
import { type FC } from "react"
|
|
||||||
|
|
||||||
const Loading: FC = () => {
|
|
||||||
return (
|
|
||||||
<div className="loading flex animate-bounce flex-col items-center justify-center text-center">
|
|
||||||
<h2 className="text-3xl">Loading...</h2>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Loading
|
|
|
@ -1,3 +0,0 @@
|
||||||
import Loading from "./Loading"
|
|
||||||
|
|
||||||
export default Loading
|
|
|
@ -1,18 +0,0 @@
|
||||||
.loading {
|
|
||||||
@apply transition-opacity ease-in;
|
|
||||||
|
|
||||||
animation: fade-in 2s;
|
|
||||||
@keyframes fade-in {
|
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
25% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
import "./style.scss"
|
import "./style.scss"
|
||||||
|
|
||||||
import { PortfolioProject } from "@developomp-site/content/src/types/types"
|
import { PortfolioProject } from "@developomp-site/content/src/types/types"
|
||||||
import { type FC, useEffect, useState } from "react"
|
import Link from "next/link"
|
||||||
|
|
||||||
import Badge from "@/components/Badge"
|
import Badge from "@/components/Badge"
|
||||||
|
|
||||||
|
@ -10,26 +10,23 @@ interface ProjectCardProps {
|
||||||
project: PortfolioProject
|
project: PortfolioProject
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProjectCard: FC<ProjectCardProps> = ({ projectID, project }) => {
|
export default function ProjectCard({ projectID, project }: ProjectCardProps) {
|
||||||
const [badges, setBadges] = useState<JSX.Element[]>([])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setBadges(
|
|
||||||
project.badges.map((badge) => <Badge key={badge} slug={badge} />)
|
|
||||||
)
|
|
||||||
}, [project.badges])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a href={`/project/${projectID}`}>
|
<Link href={`/project/${projectID}`}>
|
||||||
<div className="project">
|
<div className="project">
|
||||||
<h2>{project.name}</h2>
|
<h2 className="mb-4">{project.name}</h2>
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<img
|
<img
|
||||||
className="mb-4 w-full object-cover"
|
className="mb-4 w-full object-cover"
|
||||||
src={project.image}
|
src={project.image}
|
||||||
alt="project thumbnail"
|
alt="project thumbnail"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex flex-wrap">{badges}</div>
|
<div className="flex flex-wrap">
|
||||||
|
{project.badges.map((badge) => (
|
||||||
|
<Badge key={badge} slug={badge} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
<hr className="my-1" />
|
<hr className="my-1" />
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
|
@ -37,8 +34,6 @@ const ProjectCard: FC<ProjectCardProps> = ({ projectID, project }) => {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ProjectCard
|
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
import "./style.scss"
|
|
||||||
|
|
||||||
import { styled } from "@linaria/react"
|
|
||||||
import { type FC, useState } from "react"
|
|
||||||
import { Collapse } from "react-collapse"
|
|
||||||
|
|
||||||
const StyledTocToggleButton = styled.button`
|
|
||||||
cursor: pointer;
|
|
||||||
border: none;
|
|
||||||
text-align: left;
|
|
||||||
background-color: rgba(0, 0, 0, 0);
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.5rem;
|
|
||||||
`
|
|
||||||
|
|
||||||
const StyledCollapseContainer = styled.div`
|
|
||||||
* {
|
|
||||||
transition: height 200ms ease-out;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const Toc: FC<{ data?: string }> = (props) => {
|
|
||||||
const [isTocOpened, setIsTocOpened] = useState(false)
|
|
||||||
|
|
||||||
if (!props.data) return <></>
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<StyledTocToggleButton
|
|
||||||
className="text-light-text-high-contrast dark:text-dark-text-high-contrast"
|
|
||||||
onClick={() => {
|
|
||||||
setIsTocOpened((prev) => !prev)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<strong className="flex items-center justify-center gap-1 fill-light-text-high-contrast dark:fill-dark-text-high-contrast">
|
|
||||||
Table of Contents
|
|
||||||
{isTocOpened ? (
|
|
||||||
// Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc.
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
height="1em"
|
|
||||||
viewBox="0 0 320 512"
|
|
||||||
>
|
|
||||||
<path d="M182.6 137.4c-12.5-12.5-32.8-12.5-45.3 0l-128 128c-9.2 9.2-11.9 22.9-6.9 34.9s16.6 19.8 29.6 19.8H288c12.9 0 24.6-7.8 29.6-19.8s2.2-25.7-6.9-34.9l-128-128z" />
|
|
||||||
</svg>
|
|
||||||
) : (
|
|
||||||
// Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc.
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
height="1em"
|
|
||||||
viewBox="0 0 320 512"
|
|
||||||
>
|
|
||||||
<path d="M137.4 374.6c12.5 12.5 32.8 12.5 45.3 0l128-128c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8L32 192c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l128 128z" />
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
</strong>
|
|
||||||
</StyledTocToggleButton>
|
|
||||||
<StyledCollapseContainer>
|
|
||||||
<Collapse isOpened={isTocOpened}>
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: props.data }} />
|
|
||||||
</Collapse>
|
|
||||||
</StyledCollapseContainer>
|
|
||||||
<hr />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Toc
|
|
|
@ -1,3 +0,0 @@
|
||||||
import Toc from "./Toc"
|
|
||||||
|
|
||||||
export default Toc
|
|
|
@ -1,18 +0,0 @@
|
||||||
import "@fontsource/noto-sans-kr/400.css"
|
|
||||||
import "@fontsource/noto-sans-kr/700.css"
|
|
||||||
import "@fontsource/source-code-pro"
|
|
||||||
import "./index.scss"
|
|
||||||
import "@developomp-site/blog/src/styles/anchor.scss"
|
|
||||||
import "@developomp-site/blog/src/styles/callout.scss"
|
|
||||||
import "@developomp-site/blog/src/styles/colorChip.scss"
|
|
||||||
import "@developomp-site/blog/src/styles/heading.scss"
|
|
||||||
import "@developomp-site/blog/src/styles/img.scss"
|
|
||||||
import "@developomp-site/blog/src/styles/mark.scss"
|
|
||||||
|
|
||||||
import ReactDOM from "react-dom/client"
|
|
||||||
|
|
||||||
import App from "./App.tsx"
|
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
|
||||||
<App />
|
|
||||||
)
|
|
|
@ -1,3 +0,0 @@
|
||||||
import Home from "./Home"
|
|
||||||
|
|
||||||
export default Home
|
|
|
@ -1,5 +0,0 @@
|
||||||
.projects {
|
|
||||||
h2 {
|
|
||||||
@apply mb-4;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
import { useMeta, useTitle } from "hoofd"
|
|
||||||
import { type FC } from "react"
|
|
||||||
|
|
||||||
import Loading from "@/components/Loading"
|
|
||||||
|
|
||||||
const LoadingPage: FC = () => {
|
|
||||||
useTitle("Loading")
|
|
||||||
useMeta({ property: "og:title", content: "Loading" })
|
|
||||||
|
|
||||||
return <Loading />
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LoadingPage
|
|
|
@ -1,26 +0,0 @@
|
||||||
import "./style.css"
|
|
||||||
|
|
||||||
import { useMeta, useTitle } from "hoofd"
|
|
||||||
import { type FC } from "react"
|
|
||||||
|
|
||||||
const NotFound: FC = () => {
|
|
||||||
useTitle("404")
|
|
||||||
useMeta({ property: "og:title", content: "Page Not Found" })
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h1 className="w-fit px-4 py-2 text-7xl dark:bg-dark-text-default dark:text-dark-ui-bg">
|
|
||||||
404
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<h2
|
|
||||||
className="glitch layers text-8xl"
|
|
||||||
data-text="404 ERROR 404 ERROR"
|
|
||||||
>
|
|
||||||
Page Not Found
|
|
||||||
</h2>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NotFound
|
|
|
@ -1,3 +0,0 @@
|
||||||
import NotFound from "./NotFound"
|
|
||||||
|
|
||||||
export default NotFound
|
|
|
@ -1,389 +0,0 @@
|
||||||
/* glitch */
|
|
||||||
|
|
||||||
.glitch span {
|
|
||||||
animation: paths 5s step-end infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glitch::before {
|
|
||||||
animation: paths 5s step-end infinite, opacity 5s step-end infinite,
|
|
||||||
font 8s step-end infinite, movement 10s step-end infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glitch::after {
|
|
||||||
animation: paths 5s step-end infinite, opacity 5s step-end infinite,
|
|
||||||
font 7s step-end infinite, movement 8s step-end infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* layers */
|
|
||||||
|
|
||||||
.layers {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layers::before,
|
|
||||||
.layers::after {
|
|
||||||
content: attr(data-text);
|
|
||||||
position: absolute;
|
|
||||||
width: 110%;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layers::before {
|
|
||||||
top: 10px;
|
|
||||||
left: 15px;
|
|
||||||
color: #e0287d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layers::after {
|
|
||||||
top: 5px;
|
|
||||||
left: -10px;
|
|
||||||
color: #1bc7fb;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* keyframes */
|
|
||||||
|
|
||||||
@keyframes paths {
|
|
||||||
0% {
|
|
||||||
clip-path: polygon(
|
|
||||||
0% 43%,
|
|
||||||
83% 43%,
|
|
||||||
83% 22%,
|
|
||||||
23% 22%,
|
|
||||||
23% 24%,
|
|
||||||
91% 24%,
|
|
||||||
91% 26%,
|
|
||||||
18% 26%,
|
|
||||||
18% 83%,
|
|
||||||
29% 83%,
|
|
||||||
29% 17%,
|
|
||||||
41% 17%,
|
|
||||||
41% 39%,
|
|
||||||
18% 39%,
|
|
||||||
18% 82%,
|
|
||||||
54% 82%,
|
|
||||||
54% 88%,
|
|
||||||
19% 88%,
|
|
||||||
19% 4%,
|
|
||||||
39% 4%,
|
|
||||||
39% 14%,
|
|
||||||
76% 14%,
|
|
||||||
76% 52%,
|
|
||||||
23% 52%,
|
|
||||||
23% 35%,
|
|
||||||
19% 35%,
|
|
||||||
19% 8%,
|
|
||||||
36% 8%,
|
|
||||||
36% 31%,
|
|
||||||
73% 31%,
|
|
||||||
73% 16%,
|
|
||||||
1% 16%,
|
|
||||||
1% 56%,
|
|
||||||
50% 56%,
|
|
||||||
50% 8%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
5% {
|
|
||||||
clip-path: polygon(
|
|
||||||
0% 29%,
|
|
||||||
44% 29%,
|
|
||||||
44% 83%,
|
|
||||||
94% 83%,
|
|
||||||
94% 56%,
|
|
||||||
11% 56%,
|
|
||||||
11% 64%,
|
|
||||||
94% 64%,
|
|
||||||
94% 70%,
|
|
||||||
88% 70%,
|
|
||||||
88% 32%,
|
|
||||||
18% 32%,
|
|
||||||
18% 96%,
|
|
||||||
10% 96%,
|
|
||||||
10% 62%,
|
|
||||||
9% 62%,
|
|
||||||
9% 84%,
|
|
||||||
68% 84%,
|
|
||||||
68% 50%,
|
|
||||||
52% 50%,
|
|
||||||
52% 55%,
|
|
||||||
35% 55%,
|
|
||||||
35% 87%,
|
|
||||||
25% 87%,
|
|
||||||
25% 39%,
|
|
||||||
15% 39%,
|
|
||||||
15% 88%,
|
|
||||||
52% 88%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
30% {
|
|
||||||
clip-path: polygon(
|
|
||||||
0% 53%,
|
|
||||||
93% 53%,
|
|
||||||
93% 62%,
|
|
||||||
68% 62%,
|
|
||||||
68% 37%,
|
|
||||||
97% 37%,
|
|
||||||
97% 89%,
|
|
||||||
13% 89%,
|
|
||||||
13% 45%,
|
|
||||||
51% 45%,
|
|
||||||
51% 88%,
|
|
||||||
17% 88%,
|
|
||||||
17% 54%,
|
|
||||||
81% 54%,
|
|
||||||
81% 75%,
|
|
||||||
79% 75%,
|
|
||||||
79% 76%,
|
|
||||||
38% 76%,
|
|
||||||
38% 28%,
|
|
||||||
61% 28%,
|
|
||||||
61% 12%,
|
|
||||||
55% 12%,
|
|
||||||
55% 62%,
|
|
||||||
68% 62%,
|
|
||||||
68% 51%,
|
|
||||||
0% 51%,
|
|
||||||
0% 92%,
|
|
||||||
63% 92%,
|
|
||||||
63% 4%,
|
|
||||||
65% 4%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
45% {
|
|
||||||
clip-path: polygon(
|
|
||||||
0% 33%,
|
|
||||||
2% 33%,
|
|
||||||
2% 69%,
|
|
||||||
58% 69%,
|
|
||||||
58% 94%,
|
|
||||||
55% 94%,
|
|
||||||
55% 25%,
|
|
||||||
33% 25%,
|
|
||||||
33% 85%,
|
|
||||||
16% 85%,
|
|
||||||
16% 19%,
|
|
||||||
5% 19%,
|
|
||||||
5% 20%,
|
|
||||||
79% 20%,
|
|
||||||
79% 96%,
|
|
||||||
93% 96%,
|
|
||||||
93% 50%,
|
|
||||||
5% 50%,
|
|
||||||
5% 74%,
|
|
||||||
55% 74%,
|
|
||||||
55% 57%,
|
|
||||||
96% 57%,
|
|
||||||
96% 59%,
|
|
||||||
87% 59%,
|
|
||||||
87% 65%,
|
|
||||||
82% 65%,
|
|
||||||
82% 39%,
|
|
||||||
63% 39%,
|
|
||||||
63% 92%,
|
|
||||||
4% 92%,
|
|
||||||
4% 36%,
|
|
||||||
24% 36%,
|
|
||||||
24% 70%,
|
|
||||||
1% 70%,
|
|
||||||
1% 43%,
|
|
||||||
15% 43%,
|
|
||||||
15% 28%,
|
|
||||||
23% 28%,
|
|
||||||
23% 71%,
|
|
||||||
90% 71%,
|
|
||||||
90% 86%,
|
|
||||||
97% 86%,
|
|
||||||
97% 1%,
|
|
||||||
60% 1%,
|
|
||||||
60% 67%,
|
|
||||||
71% 67%,
|
|
||||||
71% 91%,
|
|
||||||
17% 91%,
|
|
||||||
17% 14%,
|
|
||||||
39% 14%,
|
|
||||||
39% 30%,
|
|
||||||
58% 30%,
|
|
||||||
58% 11%,
|
|
||||||
52% 11%,
|
|
||||||
52% 83%,
|
|
||||||
68% 83%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
76% {
|
|
||||||
clip-path: polygon(
|
|
||||||
0% 26%,
|
|
||||||
15% 26%,
|
|
||||||
15% 73%,
|
|
||||||
72% 73%,
|
|
||||||
72% 70%,
|
|
||||||
77% 70%,
|
|
||||||
77% 75%,
|
|
||||||
8% 75%,
|
|
||||||
8% 42%,
|
|
||||||
4% 42%,
|
|
||||||
4% 61%,
|
|
||||||
17% 61%,
|
|
||||||
17% 12%,
|
|
||||||
26% 12%,
|
|
||||||
26% 63%,
|
|
||||||
73% 63%,
|
|
||||||
73% 43%,
|
|
||||||
90% 43%,
|
|
||||||
90% 67%,
|
|
||||||
50% 67%,
|
|
||||||
50% 41%,
|
|
||||||
42% 41%,
|
|
||||||
42% 46%,
|
|
||||||
50% 46%,
|
|
||||||
50% 84%,
|
|
||||||
96% 84%,
|
|
||||||
96% 78%,
|
|
||||||
49% 78%,
|
|
||||||
49% 25%,
|
|
||||||
63% 25%,
|
|
||||||
63% 14%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
90% {
|
|
||||||
clip-path: polygon(
|
|
||||||
0% 41%,
|
|
||||||
13% 41%,
|
|
||||||
13% 6%,
|
|
||||||
87% 6%,
|
|
||||||
87% 93%,
|
|
||||||
10% 93%,
|
|
||||||
10% 13%,
|
|
||||||
89% 13%,
|
|
||||||
89% 6%,
|
|
||||||
3% 6%,
|
|
||||||
3% 8%,
|
|
||||||
16% 8%,
|
|
||||||
16% 79%,
|
|
||||||
0% 79%,
|
|
||||||
0% 99%,
|
|
||||||
92% 99%,
|
|
||||||
92% 90%,
|
|
||||||
5% 90%,
|
|
||||||
5% 60%,
|
|
||||||
0% 60%,
|
|
||||||
0% 48%,
|
|
||||||
89% 48%,
|
|
||||||
89% 13%,
|
|
||||||
80% 13%,
|
|
||||||
80% 43%,
|
|
||||||
95% 43%,
|
|
||||||
95% 19%,
|
|
||||||
80% 19%,
|
|
||||||
80% 85%,
|
|
||||||
38% 85%,
|
|
||||||
38% 62%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
1%,
|
|
||||||
7%,
|
|
||||||
33%,
|
|
||||||
47%,
|
|
||||||
78%,
|
|
||||||
93% {
|
|
||||||
clip-path: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes movement {
|
|
||||||
0% {
|
|
||||||
top: 0px;
|
|
||||||
left: -20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
15% {
|
|
||||||
top: 10px;
|
|
||||||
left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
60% {
|
|
||||||
top: 5px;
|
|
||||||
left: -10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
75% {
|
|
||||||
top: -5px;
|
|
||||||
left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
top: 10px;
|
|
||||||
left: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes opacity {
|
|
||||||
0% {
|
|
||||||
opacity: 0.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
5% {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
30% {
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
45% {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
76% {
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
90% {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
1%,
|
|
||||||
7%,
|
|
||||||
33%,
|
|
||||||
47%,
|
|
||||||
78%,
|
|
||||||
93% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes font {
|
|
||||||
0% {
|
|
||||||
font-weight: 100;
|
|
||||||
color: #e0287d;
|
|
||||||
filter: blur(3px);
|
|
||||||
}
|
|
||||||
|
|
||||||
20% {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #fff;
|
|
||||||
filter: blur(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
font-weight: 300;
|
|
||||||
color: #1bc7fb;
|
|
||||||
filter: blur(2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
60% {
|
|
||||||
font-weight: 700;
|
|
||||||
color: #fff;
|
|
||||||
filter: blur(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
90% {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #e0287d;
|
|
||||||
filter: blur(6px);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,104 +0,0 @@
|
||||||
import "./style.scss"
|
|
||||||
|
|
||||||
import portfolio from "@developomp-site/content/dist/portfolio.json"
|
|
||||||
import { useMeta, useTitle } from "hoofd"
|
|
||||||
import { type FC, useEffect, useState } from "react"
|
|
||||||
import { useRoute } from "wouter"
|
|
||||||
|
|
||||||
import Badge from "@/components/Badge"
|
|
||||||
import Loading from "@/components/Loading"
|
|
||||||
import Toc from "@/components/Toc"
|
|
||||||
import NotFound from "@/routes/NotFound"
|
|
||||||
|
|
||||||
export interface PageData {
|
|
||||||
title: string
|
|
||||||
toc?: string
|
|
||||||
content: string
|
|
||||||
|
|
||||||
image: string // image url
|
|
||||||
overview: string
|
|
||||||
badges: string[]
|
|
||||||
repo: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const Project: FC = () => {
|
|
||||||
const [pageData, setPageData] = useState<PageData | undefined>(undefined)
|
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
|
||||||
|
|
||||||
const [match, params] = useRoute("/project/:id")
|
|
||||||
|
|
||||||
useTitle(pageData?.title || "Loading")
|
|
||||||
useMeta({ property: "og:title", content: pageData?.title })
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!match) return
|
|
||||||
;(async () => {
|
|
||||||
try {
|
|
||||||
if (!(params.id in portfolio.projects)) return
|
|
||||||
|
|
||||||
const data =
|
|
||||||
portfolio.projects[
|
|
||||||
params.id as keyof typeof portfolio.projects
|
|
||||||
]
|
|
||||||
|
|
||||||
const fetched_content = await import(
|
|
||||||
`@developomp-site/content/dist/content/projects/${params.id}.json`
|
|
||||||
)
|
|
||||||
|
|
||||||
setPageData({
|
|
||||||
content: fetched_content.content,
|
|
||||||
toc: fetched_content.toc,
|
|
||||||
title: data.name,
|
|
||||||
image: data.image,
|
|
||||||
overview: data.overview,
|
|
||||||
badges: data.badges,
|
|
||||||
repo: data.repo,
|
|
||||||
})
|
|
||||||
} catch {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoading(false)
|
|
||||||
})()
|
|
||||||
}, [match, params])
|
|
||||||
|
|
||||||
if (!match) return <NotFound />
|
|
||||||
if (isLoading) return <Loading />
|
|
||||||
if (!pageData) return <NotFound />
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<h1 className="mb-4 text-4xl">{pageData.title}</h1>
|
|
||||||
{/* <!--! Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --> */}
|
|
||||||
<a href={pageData.repo}>
|
|
||||||
<svg
|
|
||||||
className="h-12 fill-dark-text-default duration-100 hover:fill-gray-400"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 496 512"
|
|
||||||
>
|
|
||||||
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z" />
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap">
|
|
||||||
{pageData.badges.map((slug) => {
|
|
||||||
return <Badge key={slug} slug={slug} />
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<Toc data={pageData.toc} />
|
|
||||||
|
|
||||||
{/* page content */}
|
|
||||||
<div
|
|
||||||
className="project-description"
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: pageData.content,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Project
|
|
|
@ -1,3 +0,0 @@
|
||||||
import Project from "./Project"
|
|
||||||
|
|
||||||
export default Project
|
|
|
@ -1,47 +0,0 @@
|
||||||
type Theme = "dark" | "light" | "system"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads theme from local storage
|
|
||||||
*/
|
|
||||||
export function readTheme(): Theme {
|
|
||||||
const data = localStorage.getItem("theme")
|
|
||||||
|
|
||||||
if (
|
|
||||||
!data || // data is falsy
|
|
||||||
(data && data != "dark" && data != "light" && data != "system") // data is a non-empty string that's not a valid Theme
|
|
||||||
) {
|
|
||||||
saveTheme("system")
|
|
||||||
return "system"
|
|
||||||
}
|
|
||||||
|
|
||||||
return data as Theme
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves and sets the theme of the site at the same time
|
|
||||||
*/
|
|
||||||
export function saveTheme(theme: Theme): void {
|
|
||||||
localStorage.setItem("theme", theme)
|
|
||||||
setTheme(theme)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the theme of the site without saving it
|
|
||||||
*/
|
|
||||||
export function setTheme(theme: Theme): void {
|
|
||||||
if (theme === "dark") document.documentElement.classList.add("dark")
|
|
||||||
else document.documentElement.classList.remove("dark")
|
|
||||||
}
|
|
||||||
|
|
||||||
// watch theme preference state
|
|
||||||
window
|
|
||||||
.matchMedia("(prefers-color-scheme: dark)")
|
|
||||||
.addEventListener("change", ({ matches }) => {
|
|
||||||
// only respond to the event if the theme is set to system
|
|
||||||
if (readTheme() != "system") return
|
|
||||||
|
|
||||||
document.documentElement.classList.add("dark")
|
|
||||||
document.documentElement.classList.remove("dark")
|
|
||||||
|
|
||||||
setTheme(matches ? "dark" : "light")
|
|
||||||
})
|
|
1
apps/portfolio/src/vite-env.d.ts
vendored
1
apps/portfolio/src/vite-env.d.ts
vendored
|
@ -1 +0,0 @@
|
||||||
/// <reference types="vite/client" />
|
|
|
@ -1,6 +1,9 @@
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
module.exports = {
|
||||||
presets: [require("@developomp-site/tailwind-config/tailwind.config")],
|
presets: [require("@developomp-site/tailwind-config/tailwind.config.js")],
|
||||||
darkMode: "class",
|
content: [
|
||||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,35 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "es5",
|
||||||
"useDefineForClassFields": true,
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
"allowJs": true,
|
||||||
"module": "ESNext",
|
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
/* Bundler mode */
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"jsx": "preserve",
|
||||||
"jsx": "react-jsx",
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
/* Linting */
|
{
|
||||||
"strict": true,
|
"name": "next"
|
||||||
"noUnusedLocals": true,
|
}
|
||||||
"noUnusedParameters": true,
|
],
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
|
|
||||||
/* alias */
|
|
||||||
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": [
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
"build/types/**/*.ts",
|
||||||
|
"dist/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowSyntheticDefaultImports": true
|
|
||||||
},
|
|
||||||
"include": ["vite.config.ts"]
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
import linaria from "@linaria/vite"
|
|
||||||
import react from "@vitejs/plugin-react"
|
|
||||||
import { defineConfig } from "vite"
|
|
||||||
import dynamicImport from "vite-plugin-dynamic-import"
|
|
||||||
import tsconfigPaths from "vite-tsconfig-paths"
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [react(), linaria(), dynamicImport(), tsconfigPaths()],
|
|
||||||
build: {
|
|
||||||
outDir: "dist",
|
|
||||||
},
|
|
||||||
server: {
|
|
||||||
open: true,
|
|
||||||
port: 5174,
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -22,12 +22,6 @@
|
||||||
"target": "portfolio",
|
"target": "portfolio",
|
||||||
"cleanUrls": true,
|
"cleanUrls": true,
|
||||||
"public": "apps/portfolio/dist",
|
"public": "apps/portfolio/dist",
|
||||||
"rewrites": [
|
|
||||||
{
|
|
||||||
"source": "**",
|
|
||||||
"destination": "/index.html"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ignore": ["**/.*"]
|
"ignore": ["**/.*"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
1945
pnpm-lock.yaml
generated
1945
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue