feat: add portfolio site

This commit is contained in:
Kim, Jimin 2023-06-29 12:28:25 +09:00
parent 8090f62f1e
commit 4a6d765c86
80 changed files with 3891 additions and 1248 deletions

View file

@ -10,6 +10,9 @@
], ],
"blog": [ "blog": [
"developomp-site-blog" "developomp-site-blog"
],
"portfolio": [
"developomp-site-portfolio"
] ]
} }
} }

View file

@ -19,6 +19,7 @@
"Exyle", "Exyle",
"exyleio", "exyleio",
"Fontawesome", "Fontawesome",
"Fonticons",
"fontsource", "fontsource",
"fortawesome", "fortawesome",
"Freedesktop", "Freedesktop",
@ -30,10 +31,12 @@
"heroicon", "heroicon",
"hljs", "hljs",
"hongik", "hongik",
"hoofd",
"inqling", "inqling",
"Jimin", "Jimin",
"katex", "katex",
"Librewolf", "Librewolf",
"linaria",
"nodedotjs", "nodedotjs",
"noto", "noto",
"pnpm", "pnpm",
@ -53,6 +56,7 @@
"unixporn", "unixporn",
"wbtimeline", "wbtimeline",
"webassembly", "webassembly",
"wouter",
"YYYYMMDD" "YYYYMMDD"
], ],
"eslint.workingDirectories": [{ "mode": "auto" }], "eslint.workingDirectories": [{ "mode": "auto" }],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 759 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 KiB

View file

@ -15,7 +15,6 @@ import Home from "./pages/Home"
import Search from "./pages/Search" import Search from "./pages/Search"
import Page from "./pages/Page" import Page from "./pages/Page"
import NotFound from "./pages/NotFound" import NotFound from "./pages/NotFound"
import Portfolio from "./pages/Portfolio"
import GlobalStyle from "./styles/globalStyle" import GlobalStyle from "./styles/globalStyle"
@ -83,7 +82,6 @@ export default function App() {
<Routes> <Routes>
<Route index element={<Home />} /> <Route index element={<Home />} />
<Route path="search" element={<Search />} /> <Route path="search" element={<Search />} />
<Route path="portfolio" element={<Portfolio />} />
<Route path="404" element={<NotFound />} /> <Route path="404" element={<NotFound />} />
<Route path="loading" element={<Loading />} /> <Route path="loading" element={<Loading />} />
<Route path="*" element={<Page />} /> <Route path="*" element={<Page />} />

View file

@ -1,67 +0,0 @@
import dark from "@developomp-site/theme/dist/dark.json"
import light from "@developomp-site/theme/dist/light.json"
import { Badge } from "@developomp-site/blog-content/src/types/types"
import { useEffect, useState } from "react"
import styled from "styled-components"
const StyledBadge = styled.div<{ color: string; isDark: boolean }>`
vertical-align: middle;
display: inline-block;
padding: 0.2rem 0.4rem 0 0.4rem;
margin-right: 0.4rem;
margin-bottom: 0.4rem;
font-size: 0.8rem;
background-color: ${(props) => props.color};
color: ${(props) =>
props.isDark ? dark.color.text.default : light.color.text.default};
`
const StyledSVG = styled.div<{ isDark: boolean }>`
display: inline-block;
vertical-align: middle;
margin-right: 0.2rem;
svg {
height: 16px;
fill: ${(props) =>
props.isDark
? dark.color.text.default
: light.color.text.default} !important;
}
`
interface BadgeProps {
slug: string
}
export default (props: BadgeProps) => {
const [badgeData, setBadgeData] = useState<Badge | undefined>(undefined)
const { slug } = props
const getBadgeData = async () => {
return await require(`@developomp-site/blog-content/dist/icons/${slug}.json`)
}
useEffect(() => {
getBadgeData().then((data) => {
setBadgeData(data)
})
}, [])
if (!badgeData) return <></>
return (
<StyledBadge color={badgeData.hex} isDark={badgeData.isDark}>
<StyledSVG
isDark={badgeData.isDark}
dangerouslySetInnerHTML={{ __html: badgeData.svg }}
/>
<span>{badgeData.title}</span>
</StyledBadge>
)
}

View file

@ -26,7 +26,7 @@ const NavbarData: Item[] = [
}, },
{ {
title: "Portfolio", title: "Portfolio",
path: "/portfolio", path: "https://portfolio.developomp.com",
icon: <FontAwesomeIcon icon={faFileLines} />, icon: <FontAwesomeIcon icon={faFileLines} />,
}, },
{ {

View file

@ -19,10 +19,10 @@ const NotFound = () => {
<meta property="og:title" content="Page Not Found" /> <meta property="og:title" content="Page Not Found" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:url" content="http://developomp.com" /> <meta property="og:url" content="http://blog.developomp.com" />
<meta <meta
property="og:image" property="og:image"
content="http://developomp.com/icon/icon.svg" content="http://blog.developomp.com/icon/icon.svg"
/> />
<meta property="og:description" content="Page does not exist" /> <meta property="og:description" content="Page does not exist" />
</Helmet> </Helmet>

View file

@ -3,12 +3,10 @@ import { Helmet } from "react-helmet-async"
import { useLocation } from "react-router-dom" import { useLocation } from "react-router-dom"
import styled from "styled-components" import styled from "styled-components"
import GithubLinkIcon from "../../components/GithubLinkIcon"
import MainContent from "../../components/MainContent" import MainContent from "../../components/MainContent"
import PostCard from "../../components/PostCard" import PostCard from "../../components/PostCard"
import Loading from "../../components/Loading" import Loading from "../../components/Loading"
import TagList from "../../components/TagList" import TagList from "../../components/TagList"
import Badge from "../../components/Badge"
import Tag from "../../components/Tag" import Tag from "../../components/Tag"
import NotFound from "../NotFound" import NotFound from "../NotFound"
@ -26,21 +24,10 @@ import type { PageData } from "@developomp-site/blog-content/src/types/types"
import contentMap from "../../contentMap" import contentMap from "../../contentMap"
const StyledTitle = styled.h1<{ pageType: PageType }>` const StyledTitle = styled.h1`
margin-bottom: 1rem; margin-bottom: 1rem;
word-wrap: break-word; word-wrap: break-word;
${(props) => {
if (props.pageType == PageType.PORTFOLIO_PROJECT) {
return "margin-right: 3rem;"
}
}}
`
const PortfolioGithubLinkContainer = styled.div`
float: right;
margin-top: 1rem;
` `
const ProjectImage = styled.img` const ProjectImage = styled.img`
@ -97,17 +84,7 @@ export default function Page() {
/> />
)} )}
{pageType == PageType.PORTFOLIO_PROJECT && pageData.repo && ( <StyledTitle>{pageData.title}</StyledTitle>
<PortfolioGithubLinkContainer>
<GithubLinkIcon link={pageData.repo} />
</PortfolioGithubLinkContainer>
)}
<StyledTitle pageType={PageType.PORTFOLIO_PROJECT}>
{pageData.title}
</StyledTitle>
{pageType == PageType.PORTFOLIO_PROJECT &&
pageData.badges.map((badge) => <Badge key={badge} slug={badge} />)}
<small> <small>
{/* Post tags */} {/* Post tags */}
@ -136,10 +113,6 @@ export default function Page() {
{/* add table of contents if it exists */} {/* add table of contents if it exists */}
<Toc data={pageData.toc} /> <Toc data={pageData.toc} />
{pageType == PageType.PORTFOLIO_PROJECT && (
<ProjectImage src={pageData.image} alt="project example image" />
)}
{/* page content */} {/* page content */}
<div <div
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{

View file

@ -1,85 +0,0 @@
import { useEffect, useState } from "react"
import { Helmet } from "react-helmet-async"
import MainContent from "../../components/MainContent"
import Badge from "../../components/Badge"
import ProjectCard from "./ProjectCard"
import portfolio from "@developomp-site/blog-content/dist/portfolio.json"
import type { PortfolioProject } from "@developomp-site/blog-content/src/types/types"
const Portfolio = () => {
const [projects, setProjects] = useState<JSX.Element[]>([])
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [skills, setSkills] = useState<JSX.Element[]>([])
useEffect(() => {
const _projects: JSX.Element[] = []
for (const projectID in portfolio.projects) {
_projects.push(
<ProjectCard
key={projectID}
projectID={projectID}
project={
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
portfolio.projects[projectID] as PortfolioProject
}
/>
)
}
setProjects(_projects)
setSkills(
portfolio.skills.map((slug) => {
return <Badge key={slug} slug={slug} />
})
)
}, [])
return (
<>
<Helmet>
<title>pomp | Portfolio</title>
<meta property="og:title" content="Portfolio" />
<meta property="og:type" content="website" />
<meta property="og:url" content="http://developomp.com" />
<meta
property="og:image"
content="http://developomp.com/icon/icon.svg"
/>
<meta property="og:description" content="developomp's Portfolio" />
</Helmet>
<MainContent>
<h1>Portfolio</h1>
<hr />
{/* Projects */}
<h2 id="projects">
<a className="header-anchor" href="#projects">
#
</a>
{" Projects"}
</h2>
{/* todo: filter projects by skill */}
{skills}
<br />
<br />
{projects}
</MainContent>
</>
)
}
export default Portfolio

View file

@ -1,64 +0,0 @@
import { useEffect, useState } from "react"
import styled from "styled-components"
import { Link } from "react-router-dom"
import Badge from "../../components/Badge"
import { cardCSS } from "../../components/Card"
import { PortfolioProject } from "@developomp-site/blog-content/src/types/types"
const StyledProjectCard = styled.div`
${cardCSS}
color: ${(props) => props.theme.theme.color.text.default};
margin-bottom: 2rem;
word-wrap: break-word;
:hover {
cursor: pointer;
box-shadow: 0 4px 10px
${(props) => props.theme.theme.component.card.color.hoverGlow};
}
`
const StyledImg = styled.img`
width: 100%;
object-fit: cover;
margin-bottom: 1rem;
`
interface ProjectCardProps {
projectID: string
project: PortfolioProject
}
const ProjectCard = (props: ProjectCardProps) => {
const { projectID, project } = props
const [badges, setBadges] = useState<JSX.Element[]>([])
useEffect(() => {
setBadges(project.badges.map((badge) => <Badge key={badge} slug={badge} />))
}, [])
return (
<Link to={projectID}>
<StyledProjectCard>
<h1>{project.name}</h1>
<StyledImg src={project.image} />
{badges}
<hr />
<div
dangerouslySetInnerHTML={{
__html: project.overview,
}}
/>
</StyledProjectCard>
</Link>
)
}
export default ProjectCard

View file

@ -1,3 +0,0 @@
import Portfolio from "./Portfolio"
export default Portfolio

30
apps/portfolio/.eslintrc Normal file
View file

@ -0,0 +1,30 @@
{
"root": true,
"env": {
"browser": 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": {
"react-refresh/only-export-components": "warn",
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error"
}
}

24
apps/portfolio/.gitignore vendored Normal file
View file

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View file

@ -0,0 +1,5 @@
{
"semi": false,
"tabWidth": 4,
"plugins": ["prettier-plugin-tailwindcss"]
}

23
apps/portfolio/index.html Normal file
View file

@ -0,0 +1,23 @@
<!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="pomp's portfolio" />
<meta property="og:description" content="developomp's Portfolio" />
<meta property="og:type" content="website" />
<meta property="og:url" content="http://portfolio.developomp.com" />
<meta property="og:image" content="/favicon.svg" />
</head>
<body class="dark">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View file

@ -0,0 +1,49 @@
{
"name": "@developomp-site/portfolio",
"version": "0.0.0",
"type": "module",
"private": true,
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
},
"dependencies": {
"@fontsource/noto-sans-kr": "^5.0.3",
"@fontsource/source-code-pro": "^5.0.3",
"@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": {
"@developomp-site/blog-content": "workspace:*",
"@linaria/babel-preset": "^4.4.5",
"@linaria/vite": "^4.2.11",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@typescript-eslint/eslint-plugin": "^5.60.1",
"@typescript-eslint/parser": "^5.60.1",
"@vitejs/plugin-react": "^4.0.1",
"autoprefixer": "^10.4.14",
"eslint": "^8.43.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.1",
"eslint-plugin-simple-import-sort": "^10.0.0",
"postcss": "^8.4.24",
"prettier": "^2.8.8",
"prettier-plugin-tailwindcss": "^0.3.0",
"tailwindcss": "^3.3.2",
"typescript": "^5.1.5",
"vite": "^4.3.9",
"vite-plugin-dynamic-import": "^1.4.1",
"vite-tsconfig-paths": "^4.2.0"
}
}

View file

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View file

@ -0,0 +1,35 @@
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 NotFound from "./routes/NotFound"
import Project from "./routes/Project"
const App: FC = () => {
useTitleTemplate("Portfolio | %s")
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>
<NotFound />
</Route>
</Switch>
</div>
</>
)
}
export default App

View file

@ -0,0 +1,45 @@
import "./style.scss"
import { type Badge as BadgeType } from "@developomp-site/blog-content/src/types/types"
import { type FC, useEffect, useState } from "react"
interface BadgeProps {
slug: string
}
const Badge: FC<BadgeProps> = ({ slug }) => {
const [badgeData, setBadgeData] = useState<BadgeType | undefined>(undefined)
useEffect(() => {
;(async () => {
setBadgeData(
await import(
`@developomp-site/blog-content/dist/icons/${slug}.json`
)
)
})()
}, [slug])
if (!badgeData) return <></>
return (
<div
style={{ backgroundColor: badgeData.hex }}
className={`mb-2 mr-2 flex w-fit items-center px-2 py-1 text-xs ${
badgeData.isDark
? "text-dark-text-default"
: "text-light-text-default"
}`}
>
<div
className={`${
badgeData.isDark ? "dark-badge" : "light-badge"
} badge mr-1 inline-block w-6 align-middle`}
dangerouslySetInnerHTML={{ __html: badgeData.svg }}
/>
<span>{badgeData.title}</span>
</div>
)
}
export default Badge

View file

@ -0,0 +1,3 @@
import Badge from "./Badge"
export default Badge

View file

@ -0,0 +1,11 @@
.light-badge {
svg {
@apply dark:fill-light-text-default;
}
}
.dark-badge {
svg {
@apply dark:fill-dark-text-default;
}
}

View file

@ -0,0 +1,24 @@
import { type FC } from "react"
import { Link } from "wouter"
const Header: FC = () => {
return (
<header className="fixed top-0 z-50 flex w-screen justify-center dark:bg-dark-ui dark:text-dark-text-default">
<div className="my-0 flex h-16 w-full max-w-5xl items-center">
<Link
className="flex items-center"
to="/"
aria-label="homepage"
>
<img
className="m-4 block h-10 cursor-pointer"
src="/favicon.svg"
alt="logo"
/>
</Link>
</div>
</header>
)
}
export default Header

View file

@ -0,0 +1,3 @@
import Header from "./Header"
export default Header

View file

@ -0,0 +1,143 @@
/**
* inspired by https://codepen.io/avstorm/pen/RwNzPNN
*/
// import styled from "styled-components"
// import MainContent from "./MainContent"
// const StyledContainer = styled(MainContent)`
// display: flex;
// flex-direction: column;
// justify-content: center;
// align-items: center;
// text-align: center;
// animation: fadein 2s;
// @keyframes fadein {
// from {
// opacity: 0;
// }
// 50% {
// opacity: 0;
// }
// to {
// opacity: 1;
// }
// }
// `
// const StyledSVG = styled.svg`
// --color: ${({ theme }) => theme.theme.color.text.default};
// display: block;
// margin: 1rem;
// margin-bottom: 4.5rem;
// transform: scale(2);
// #teabag {
// transform-origin: top center;
// transform: rotate(3deg);
// animation: swingAnimation 2s infinite;
// }
// #steamL {
// stroke-dasharray: 13;
// stroke-dashoffset: 13;
// animation: steamLargeAnimation 2s infinite;
// }
// #steamR {
// stroke-dasharray: 9;
// stroke-dashoffset: 9;
// animation: steamSmallAnimation 2s infinite;
// }
// @keyframes swingAnimation {
// 50% {
// transform: rotate(-3deg);
// }
// }
// @keyframes steamLargeAnimation {
// 0% {
// stroke-dashoffset: 13;
// opacity: 0.6;
// }
// 100% {
// stroke-dashoffset: 39;
// opacity: 0;
// }
// }
// @keyframes steamSmallAnimation {
// 10% {
// stroke-dashoffset: 9;
// opacity: 0.6;
// }
// 80% {
// stroke-dashoffset: 27;
// opacity: 0;
// }
// 100% {
// stroke-dashoffset: 27;
// opacity: 0;
// }
// }
// `
// const Loading = () => {
// return (
// <StyledContainer>
// <StyledSVG
// width="37"
// height="48"
// viewBox="0 0 37 48"
// fill="none"
// xmlns="http://www.w3.org/2000/svg"
// >
// <path
// d="M27.0819 17H3.02508C1.91076 17 1.01376 17.9059 1.0485 19.0197C1.15761 22.5177 1.49703 29.7374 2.5 34C4.07125 40.6778 7.18553 44.8868 8.44856 46.3845C8.79051 46.79 9.29799 47 9.82843 47H20.0218C20.639 47 21.2193 46.7159 21.5659 46.2052C22.6765 44.5687 25.2312 40.4282 27.5 34C28.9757 29.8188 29.084 22.4043 29.0441 18.9156C29.0319 17.8436 28.1539 17 27.0819 17Z"
// stroke="var(--color)"
// strokeWidth="2"
// />
// <path
// d="M29 23.5C29 23.5 34.5 20.5 35.5 25.4999C36.0986 28.4926 34.2033 31.5383 32 32.8713C29.4555 34.4108 28 34 28 34"
// stroke="var(--color)"
// strokeWidth="2"
// />
// <path
// id="teabag"
// fill="var(--color)"
// fillRule="evenodd"
// clipRule="evenodd"
// d="M16 25V17H14V25H12C10.3431 25 9 26.3431 9 28V34C9 35.6569 10.3431 37 12 37H18C19.6569 37 21 35.6569 21 34V28C21 26.3431 19.6569 25 18 25H16ZM11 28C11 27.4477 11.4477 27 12 27H18C18.5523 27 19 27.4477 19 28V34C19 34.5523 18.5523 35 18 35H12C11.4477 35 11 34.5523 11 34V28Z"
// />
// <path
// id="steamL"
// d="M17 1C17 1 17 4.5 14 6.5C11 8.5 11 12 11 12"
// strokeWidth="2"
// strokeLinecap="round"
// strokeLinejoin="round"
// stroke="var(--color)"
// />
// <path
// id="steamR"
// d="M21 6C21 6 21 8.22727 19 9.5C17 10.7727 17 13 17 13"
// stroke="var(--color)"
// strokeWidth="2"
// strokeLinecap="round"
// strokeLinejoin="round"
// />
// </StyledSVG>
// <h2>Loading...</h2>
// </StyledContainer>
// )
// }
const Loading = () => {
return <>Loading</>
}
export default Loading

View file

@ -0,0 +1,44 @@
import "./style.scss"
import { PortfolioProject } from "@developomp-site/blog-content/src/types/types"
import { type FC, useEffect, useState } from "react"
import Badge from "@/components/Badge"
interface ProjectCardProps {
projectID: string
project: PortfolioProject
}
const ProjectCard: FC<ProjectCardProps> = ({ projectID, project }) => {
const [badges, setBadges] = useState<JSX.Element[]>([])
useEffect(() => {
setBadges(
project.badges.map((badge) => <Badge key={badge} slug={badge} />)
)
}, [project.badges])
return (
<a href={`/project/${projectID}`}>
<div className="project">
<h2>{project.name}</h2>
<img
className="mb-4 w-full object-cover"
src={project.image}
alt="project thumbnail"
/>
<div className="flex flex-wrap">{badges}</div>
<hr className="my-1" />
<div
dangerouslySetInnerHTML={{
__html: project.overview,
}}
/>
</div>
</a>
)
}
export default ProjectCard

View file

@ -0,0 +1,3 @@
import ProjectCard from "./ProjectCard"
export default ProjectCard

View file

@ -0,0 +1,17 @@
.project {
// general
@apply cursor-pointer rounded-md;
// spacing
@apply m-auto mb-8 p-8;
// color
@apply bg-dark-card-bg dark:text-dark-text-default;
// glow
@apply duration-75 hover:shadow-glow dark:hover:shadow-dark-text-default;
h2 {
@apply text-3xl;
}
}

View file

@ -0,0 +1,75 @@
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;
}
ul,
ol {
list-style: circle;
padding-left: 2.5rem;
list-style-position: inside;
}
`
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

View file

@ -0,0 +1,3 @@
import Toc from "./Toc"
export default Toc

View file

@ -0,0 +1,40 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
* {
@apply scroll-m-16;
}
html {
@apply bg-dark-ui-bg font-noto-sans text-dark-text-default;
}
body {
@apply flex min-h-screen w-full flex-col items-center;
}
#root {
@apply m-0 flex h-full w-full scroll-m-16 flex-col items-center p-0;
}
a {
@apply text-anchor visited:text-anchor hover:text-anchor-accent active:text-anchor-accent;
}
img {
@apply w-fit;
}
h1,
h2,
h3,
h4,
h5,
h6 {
@apply font-bold;
a {
@apply dark:text-dark-anchor-header;
}
}

View file

@ -0,0 +1,12 @@
import "@fontsource/noto-sans-kr/400.css"
import "@fontsource/noto-sans-kr/700.css"
import "@fontsource/source-code-pro"
import "./index.scss"
import ReactDOM from "react-dom/client"
import App from "./App.tsx"
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<App />
)

View file

@ -0,0 +1,43 @@
import "./style.scss"
import portfolio from "@developomp-site/blog-content/dist/portfolio.json"
import type { PortfolioProject } from "@developomp-site/blog-content/src/types/types"
import { useTitle } from "hoofd"
import { type FC } from "react"
import Badge from "@/components/Badge"
import ProjectCard from "@/components/ProjectCard"
const projects: JSX.Element[] = []
const skills: JSX.Element[] = portfolio.skills.map((slug) => {
return <Badge key={slug} slug={slug} />
})
for (const projectID in portfolio.projects) {
projects.push(
<ProjectCard
key={projectID}
projectID={projectID}
project={
portfolio.projects[
projectID as keyof typeof portfolio.projects
] as PortfolioProject
}
/>
)
}
const Home: FC = () => {
useTitle("Home")
return (
<>
<h1 className="mb-8 text-5xl">developomp's Portfolio</h1>
<hr />
<div className="my-4 flex flex-wrap">{skills}</div>
<div className="projects">{projects}</div>
</>
)
}
export default Home

View file

@ -0,0 +1,3 @@
import Home from "./Home"
export default Home

View file

@ -0,0 +1,5 @@
.projects {
h2 {
@apply mb-4;
}
}

View file

@ -0,0 +1,22 @@
import "./style.css"
import { type FC } from "react"
const NotFound: FC = () => {
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

View file

@ -0,0 +1,3 @@
import NotFound from "./NotFound"
export default NotFound

View file

@ -0,0 +1,389 @@
/* 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);
}
}

View file

@ -0,0 +1,92 @@
import "./style.scss"
import portfolio from "@developomp-site/blog-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/blog-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 (
<>
<h1 className="mb-4 text-4xl">{pageData.title}</h1>
<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

View file

@ -0,0 +1,3 @@
import Project from "./Project"
export default Project

View file

@ -0,0 +1,29 @@
.project-description {
h2,
h3,
h4,
h5,
h6 {
@apply mb-2;
}
h2 {
@apply mt-10 text-3xl;
}
h3 {
@apply mt-6 indent-2 text-xl;
}
h4 {
@apply mt-6 indent-4 text-base;
}
h5 {
@apply mt-6 indent-6 text-base;
}
h6 {
@apply mt-6 indent-8 text-base;
}
}

View file

@ -0,0 +1,47 @@
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 Normal file
View file

@ -0,0 +1 @@
/// <reference types="vite/client" />

View file

@ -0,0 +1,134 @@
/** @type {import('tailwindcss').Config} */
export default {
darkMode: "class",
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
colors: {
// UI
/***/ "dark-ui": "#202225",
/**/ "light-ui": "#FFFFFF",
/***/ "dark-ui-hover": "#3F3F46",
/**/ "light-ui-hover": "#EEEEEE",
/***/ "dark-ui-bg": "#36393F",
/**/ "light-ui-bg": "#F7F7F7",
/***/ "dark-ui-border": "#555",
/**/ "light-ui-border": "#CCC",
// text
/***/ "dark-text-default": "#EEEEEE",
/**/ "light-text-default": "#111111",
/***/ "dark-text-high-contrast": "#FFFFFF",
/**/ "light-text-high-contrast": "#000000",
/***/ "dark-text-gray": "#CCC",
/**/ "light-text-gray": "#555",
// anchor
/*********/ anchor: "#66AAFF",
/********/ "anchor-accent": "#4592F7",
/***/ "dark-anchor-header": "#778899",
/**/ "light-anchor-header": "#D3D3D3",
// card
/***/ "dark-card-bg": "#2F3136",
/**/ "light-card-bg": "#FFFFFF",
/***/ "dark-card-glow": "#FFFFFF33",
/**/ "light-card-glow": "#00000040",
// blockquote
/***/ "dark-blockquote-bg": "#FFFFFF12",
/**/ "light-blockquote-bg": "#0000000D",
/***/ "dark-blockquote-accent": "#FFFFFF4D",
/**/ "light-blockquote-accent": "#0000001A",
// inline code
/***/ "dark-inline-code-bg": "#444",
/**/ "light-inline-code-bg": "#EEE",
/***/ "dark-inline-code-text": "#FFFFFF",
/**/ "light-inline-code-text": "#000000",
/***/ "dark-inline-code-border": "#666",
/**/ "light-inline-code-border": "#BBB",
// block code
// light theme using: highlight.js/styles/default.css
// dark theme using: highlight.js/styles/atom-one-dark-reasonable.css
/***/ "dark-block-code-border": "#555",
/**/ "light-block-code-border": "#BBB",
/***/ "dark-block-code-highlight": "#14161A",
/**/ "light-block-code-highlight": "#DDDDDD",
// footer
/***/ "dark-footer-bg": "#000000",
/**/ "light-footer-bg": "#FFFFFF",
/***/ "dark-footer-text": "#808080",
/* */ "light-footer-text": "#808080",
// header
/***/ "dark-header-bg": "#202225",
/**/ "light-header-bg": "",
/***/ "dark-header-hover": "#3F3F46",
/**/ "light-header-hover": "",
/***/ "dark-header-text": "#D4D4D8",
/**/ "light-header-text": "",
// input
/***/ "dark-input-bg": "#36393F",
/**/ "light-input-bg": "#EEEEEE",
/***/ "dark-input-item-hover": "#202225",
/**/ "light-input-item-hover": "#FFFFFF",
/***/ "dark-input-border": "#555555",
/**/ "light-input-border": "#CCCCCC",
/***/ "dark-input-border-hover": "#808080",
/**/ "light-input-border-hover": "#808080",
/***/ "dark-input-border-focus": "#A3A3A3",
/**/ "light-input-border-focus": "#000000",
/***/ "dark-input-placeholder": "#A9A9A9",
/**/ "light-input-placeholder": "#777777",
// kbd
/***/ "dark-kbd-bg": "#000000",
/**/ "light-kbd-bg": "#F7F7F7",
/***/ "dark-kbd-text": "#FFFFFF",
/**/ "light-kbd-text": "#333333",
/***/ "dark-kbd-border": "#555555",
/**/ "light-kbd-border": "#CCCCCC",
/***/ "dark-kbd-outer-shadow": "#FFFFFF4D",
/**/ "light-kbd-outer-shadow": "#00000033",
/***/ "dark-kbd-inner-shadow": "#000000",
/**/ "light-kbd-inner-shadow": "#FFFFFF",
// mark
/***/ "dark-mark-bg": "#FFFF0080",
/**/ "light-mark-bg": "#FFFF00BF",
/***/ "dark-mark-text": "#FFFFFF",
/**/ "light-mark-text": "#000000",
// scrollbar
/***/ "dark-scrollbar-track": "#18181B",
/**/ "light-scrollbar-track": "#FFFFFF",
/***/ "dark-scrollbar-thumb": "#888888",
/**/ "light-scrollbar-thumb": "#DDDDDD",
// scroll progress
/***/ "dark-scroll-progress-bg": "#52525B",
/**/ "light-scroll-progress-bg": "#D4D4D8",
/***/ "dark-scroll-progress-fg": "#D4D4D8",
/**/ "light-scroll-progress-fg": "#52525B",
// table
/***/ "dark-table-border": "#777777",
/**/ "light-table-border": "#DDD",
/***/ "dark-table-alt": "#21272E",
/**/ "light-table-alt": "#F2F2F2",
},
fontFamily: {
"noto-sans": ['"Noto Sans KR"', "sans-serif"],
"source-code-pro": ['"Source Code Pro"', "monospace"],
},
boxShadow: {
glow: "0 0px 10px",
},
},
},
plugins: [],
}

View file

@ -0,0 +1,32 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
/* alias */
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View file

@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View file

@ -0,0 +1,13 @@
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",
},
})

View file

@ -23,6 +23,18 @@
} }
], ],
"ignore": ["**/.*"] "ignore": ["**/.*"]
},
{
"target": "portfolio",
"cleanUrls": true,
"public": "apps/portfolio/dist",
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
],
"ignore": ["**/.*"]
} }
] ]
} }

View file

@ -1,7 +1,7 @@
--- ---
name: developomp-site name: developomp-site
overview: my websites for blogging, portfolio, resume, etc. overview: my websites for blogging, portfolio, resume, etc.
image: /img/portfolio/developomp.com.png image: /img/portfolio/developomp.com.avif
repo: https://github.com/developomp/developomp-site repo: https://github.com/developomp/developomp-site
badges: badges:
- githubactions - githubactions

View file

@ -1,7 +1,7 @@
--- ---
name: Exyle.io name: Exyle.io
overview: A free and simple community-driven competitive online multiplayer fps game overview: A free and simple community-driven competitive online multiplayer fps game
image: /img/portfolio/exyleio.png image: /img/portfolio/exyleio.avif
repo: https://github.com/exyleio repo: https://github.com/exyleio
badges: badges:
- githubactions - githubactions

View file

@ -1,7 +1,7 @@
--- ---
name: Arch Linux setup script name: Arch Linux setup script
overview: My Arch Linux desktop setup overview: My Arch Linux desktop setup
image: /img/portfolio/linux-setup-script.png image: /img/portfolio/linux-setup-script.avif
repo: https://github.com/developomp/setup repo: https://github.com/developomp/setup
badges: badges:
- githubpages - githubpages

View file

@ -1,7 +1,7 @@
--- ---
name: Llama Bot name: Llama Bot
overview: A discord bot. overview: A discord bot.
image: /img/portfolio/llama-bot.png image: /img/portfolio/llama-bot.avif
repo: https://github.com/developomp/llama-bot repo: https://github.com/developomp/llama-bot
badges: badges:
- nodedotjs - nodedotjs

View file

@ -1,7 +1,7 @@
--- ---
name: Mocha Downloader name: Mocha Downloader
overview: A cross-platform desktop download manager built with web technologies. overview: A cross-platform desktop download manager built with web technologies.
image: /img/portfolio/mocha-downloader.png image: /img/portfolio/mocha-downloader.avif
repo: https://github.com/Mocha-Downloader repo: https://github.com/Mocha-Downloader
badges: badges:
- githubactions - githubactions

View file

@ -1,7 +1,7 @@
--- ---
name: pomky name: pomky
overview: A gtk-based, [conky](https://github.com/brndnmtthws/conky)-like system monitor written in rust. overview: A gtk-based, [conky](https://github.com/brndnmtthws/conky)-like system monitor written in rust.
image: /img/portfolio/pomky.png image: /img/portfolio/pomky.avif
repo: https://github.com/developomp/pomky repo: https://github.com/developomp/pomky
badges: badges:
- rust - rust

View file

@ -1,7 +1,7 @@
--- ---
name: War Brokers Mods name: War Brokers Mods
overview: A game mod for a unity game. Provides in-game UI and OBS overlays. overview: A game mod for a unity game. Provides in-game UI and OBS overlays.
image: /img/portfolio/wbm.png image: /img/portfolio/wbm.avif
repo: https://github.com/War-Brokers-Mods repo: https://github.com/War-Brokers-Mods
badges: badges:
- githubactions - githubactions
@ -28,7 +28,7 @@ Built with C#, it uses [BepInEx](https://github.com/BepInEx/BepInEx) framework t
## OBS Overlay ## OBS Overlay
<p align="center"> <p align="center">
<img alt="Overlay image" src="/img/portfolio/wbm-overlays.png" /> <img alt="Overlay image" src="/img/portfolio/wbm-overlays.avif" />
</p> </p>
Overlays for [OBS studio](https://github.com/obsproject/obs-studio). Built with standard web technologies (html, css, js). Overlays for [OBS studio](https://github.com/obsproject/obs-studio). Built with standard web technologies (html, css, js).
@ -36,7 +36,7 @@ Overlays for [OBS studio](https://github.com/obsproject/obs-studio). Built with
## Installer ## Installer
<p align="center"> <p align="center">
<img alt="Installer image" src="/img/portfolio/wbm-installer.png" /> <img alt="Installer image" src="/img/portfolio/wbm-installer.avif" />
</p> </p>
A simple cross-platform installer and update manager. Built with [tauri](https://github.com/tauri-apps/tauri), [rust](https://github.com/rust-lang/rust), [svelte](https://github.com/sveltejs/svelte), and [tailwind css](https://github.com/tailwindlabs/tailwindcss). A simple cross-platform installer and update manager. Built with [tauri](https://github.com/tauri-apps/tauri), [rust](https://github.com/rust-lang/rust), [svelte](https://github.com/sveltejs/svelte), and [tailwind css](https://github.com/tailwindlabs/tailwindcss).

View file

@ -1,7 +1,7 @@
--- ---
name: War Brokers Timeline name: War Brokers Timeline
overview: A list of events happened in the War Brokers community in a chronological order. overview: A list of events happened in the War Brokers community in a chronological order.
image: /img/portfolio/wbtimeline.png image: /img/portfolio/wbtimeline.avif
repo: https://github.com/developomp/wbtimeline repo: https://github.com/developomp/wbtimeline
badges: badges:
- githubactions - githubactions

View file

@ -63,7 +63,7 @@ if (!fs.lstatSync(markdownPath + "/series").isDirectory())
recursiveParse(ParseMode.POSTS, markdownPath + "/posts") recursiveParse(ParseMode.POSTS, markdownPath + "/posts")
recursiveParse(ParseMode.UNSEARCHABLE, markdownPath + "/unsearchable") recursiveParse(ParseMode.UNSEARCHABLE, markdownPath + "/unsearchable")
recursiveParse(ParseMode.SERIES, markdownPath + "/series") recursiveParse(ParseMode.SERIES, markdownPath + "/series")
recursiveParse(ParseMode.PORTFOLIO, markdownPath + "/portfolio") recursiveParse(ParseMode.PORTFOLIO, markdownPath + "/projects")
/** /**
* Post-process * Post-process

View file

@ -7,7 +7,7 @@ import parseMarkdown from "../parseMarkdown"
import parsePost from "./parsePost" import parsePost from "./parsePost"
import parseSeries from "./parseSeries" import parseSeries from "./parseSeries"
import parseUnsearchable from "./parseUnsearchable" import parseUnsearchable from "./parseUnsearchable"
import parsePortfolio from "./parsePortfolio" import parseProjects from "./parseProjects"
import { ParseMode } from "../types/types" import { ParseMode } from "../types/types"
@ -102,7 +102,7 @@ function parseFile(mode: ParseMode, path: string): void {
break break
case ParseMode.PORTFOLIO: case ParseMode.PORTFOLIO:
parsePortfolio(dataToPass) parseProjects(dataToPass)
break break
} }
} }

View file

@ -8,7 +8,7 @@ import { writeToFile } from "../util"
import { portfolioData } from ".." import { portfolioData } from ".."
import { DataToPass } from "." import { DataToPass } from "."
export default function parsePortfolio(data: DataToPass): void { export default function parseProjects(data: DataToPass): void {
const { urlPath, markdownRaw, markdownData } = data const { urlPath, markdownRaw, markdownData } = data
if (markdownData.badges) { if (markdownData.badges) {
@ -35,7 +35,8 @@ export default function parsePortfolio(data: DataToPass): void {
}) })
} }
portfolioData.projects[urlPath] = { // remove /projects/ prefix
portfolioData.projects[urlPath.replace("/projects/", "")] = {
name: markdownData.name as string, name: markdownData.name as string,
image: markdownData.image as string, image: markdownData.image as string,
overview: markdownData.overview as string, overview: markdownData.overview as string,

3409
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff