Completely reworked website

dependabot/npm_and_yarn/typescript-eslint/parser-4.33.0
Guus van Meerveld 2 years ago
parent 8d72a759f9
commit d85095095d
Signed by: Guusvanmeerveld
GPG Key ID: 2BA7D7912771966E

@ -1,9 +0,0 @@
**
!next-i18next.config.js
!next.config.js
!tsconfig.json
!package.json
!yarn.lock
!public
!src

@ -1 +1,5 @@
CDN_ENDPOINT=cdn.guusvanmeerveld.dev VITE_GITHUB_USERNAME=Guusvanmeerveld
VITE_TWITTER_USERNAME=Guusvanmeerveld
VITE_KOFI_USERNAME=Guusvanmeerveld
VITE_YOUTUBE_CHANNEL_ID=UCYuqpoMay5SezCBrA_HKVWQ
VITE_MASTODON_URL=https://c.im/web/@guusvanmeerveld

@ -1,7 +0,0 @@
{
"plugins": ["prettier"],
"extends": "next/core-web-vitals",
"rules": {
"prettier/prettier": "error"
}
}

@ -1,88 +1,42 @@
name: Build / test application and deploy to Docker hub & Github Pages name: "Deploy website"
on: on:
push: push:
branches: branches:
- main - main
paths:
- src/**
- package.json
- yarn.lock
- tsconfig.*
- vite.config.ts
- .env
- index.html
- public
jobs: jobs:
check: deploy_to_pages:
name: Build pages
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Setup checkout - name: Setup
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Setup NodeJS v12 - name: Setup Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: '12.x' node-version: "16.x"
cache: "yarn"
- name: Install Dependencies - name: Install NPM dependencies
run: yarn install run: yarn install
- name: ESlint check - name: Build client
run: yarn lint run: yarn run build
pages: - name: Deploy to pages
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: '12.x'
- name: Install dependencies
run: yarn install
- name: Export website
run: yarn export
env:
GITHUB_USERNAME: ${{ GITHUB_REPOSITORY_OWNER }}
- name: Create .nojekyll file
run: touch out/.nojekyll
- name: Deploy
uses: peaceiris/actions-gh-pages@v3 uses: peaceiris/actions-gh-pages@v3
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: out publish_dir: ./dist
cname: guusvanmeerveld.dev
docker:
needs: check
runs-on: ubuntu-latest
steps:
- name: Setup checkout
uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v1.10.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Build and push
uses: docker/build-push-action@v2
with:
push: true
tags: guusvanmeerveld/portfolio:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
builder: ${{ steps.buildx.outputs.name }}
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}

52
.gitignore vendored

@ -1,34 +1,24 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # Logs
logs
# dependencies *.log
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
pnpm-debug.log*
# local env files lerna-debug.log*
.env.local
.env.development.local node_modules
.env.test.local dist
.env.production.local dist-ssr
*.local
# vercel
.vercel # Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -1,12 +0,0 @@
dist
node_modules
README.md
.vscode
yarn.lock
package-lock.json
LICENSE
.env
.prettierrc
.next
next-env.d.ts
out

@ -1,9 +1,27 @@
{ {
"trailingComma": "es5", "trailingComma": "none",
"useTabs": true, "useTabs": true,
"semi": true, "semi": true,
"singleQuote": true, "printWidth": 80,
"tabWidth": 2, "arrowParens": "always",
"printWidth": 100, "importOrderSeparation": true,
"arrowParens": "always" "importOrder": [
"@rollup/.*",
"rollup-.*",
"^..?/.*",
"^use-local-storage-state$",
"^styled-components$",
"^react.*",
"^preact$",
"^preact.*",
"^axios.*",
"^@src/.*",
"^@models/.*",
"^@interfaces/.*",
"^@styles/.*",
"^@shared/.*",
"^@utils/.*",
"^@components/.*",
"^@svg/.*"
]
} }

@ -1,3 +1,3 @@
{ {
"prettier.configPath": ".prettierrc" "prettier.configPath": ".prettierrc"
} }

@ -1,34 +0,0 @@
FROM node:12-alpine AS deps
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
FROM node:12-alpine AS builder
WORKDIR /app
COPY . .
COPY --from=deps /app/node_modules ./node_modules
ENV NEXT_TELEMETRY_DISABLED 1;
RUN yarn build && yarn install --production --ignore-scripts --prefer-offline
FROM node:12-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/next-i18next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
USER nextjs
EXPOSE 3000
CMD ["yarn", "start"]

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2021 Guus van Meerveld
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -1,6 +1,5 @@
<h1 align="center">Portfolio</h1> # Portfolio
<p align="center">
<img src="https://api.netlify.com/api/v1/badges/abf23579-f44f-432d-9fb6-6822a7a1f1b2/deploy-status"> This is my portfolio website to show off all of my projects and provide my socials.
<img src="https://img.shields.io/website-up-down-green-red/https/guusvanmeerveld.dev.svg">
<img src="https://img.shields.io/github/license/Guusvanmeerveld/Portfolio.svg"> It's build using Preact and Styled-Components and uses Vite as its build tool.
</p>

@ -1,7 +0,0 @@
version: '3'
services:
app:
container_name: portfolio
build: .
env_file: .env

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Guus' Portfolio</title>
<style>
body {
background-color: #000000;
color: #eee;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
<noscript>Javascript needs to be enabled to run this application</noscript>
</body>
</html>

6
next-env.d.ts vendored

@ -1,6 +0,0 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

@ -1,18 +0,0 @@
// @ts-check
/**
* @type {import('next').NextConfig}
*/
module.exports = {
images: {
loader: 'imgix',
path: process.env.IMGIX_PATH || 'https://guusvanmeerveld.imgix.net',
},
env: {
CDN_ENDPOINT: process.env.CDN_ENDPOINT,
},
webpack: (config) => {
config.module.rules.push({ test: /\.svg$/, use: ['@svgr/webpack'] });
return config;
},
};

@ -1,36 +1,39 @@
{ {
"name": "portfolio", "name": "portfolio",
"version": "0.1.0", "private": true,
"private": true, "version": "0.1.0",
"scripts": { "type": "module",
"dev": "next dev", "scripts": {
"build": "next build", "format": "prettier src --write",
"export": "next build && next export", "dev": "vite",
"start": "next start", "build": "tsc && vite build",
"prettify": "prettier --write .", "preview": "vite preview"
"lint": "next lint" },
}, "dependencies": {
"dependencies": { "@fontsource/montserrat": "^4.5.11",
"axios": "^0.21.4", "@fontsource/prompt": "^4.5.8",
"chalk": "^4.1.2", "@tanstack/react-query": "^4.0.10",
"milligram": "^1.4.1", "@tanstack/react-query-devtools": "^4.0.10",
"next": "^11.1.2", "axios": "^0.27.2",
"next-themes": "^0.0.15", "bootstrap": "^5.2.0",
"react": "^17.0.2", "preact": "^10.9.0",
"react-dom": "^17.0.2", "react-bootstrap": "^2.4.0",
"react-icons": "^4.2.0", "react-icons": "^4.4.0",
"sass": "^1.42.1", "react-router-dom": "6",
"sharp": "^0.29.1", "react-spring": "^9.5.2",
"swr": "^1.0.1" "reset-css": "^5.0.1",
}, "styled-components": "^5.3.5",
"devDependencies": { "use-local-storage-state": "^18.1.0",
"@svgr/webpack": "^5.5.0", "vite-plugin-imagemin": "^0.6.1"
"@types/node": "^15.6.1", },
"@types/react": "^17.0.6", "devDependencies": {
"eslint": "^7.27.0", "@preact/preset-vite": "^2.3.0",
"eslint-config-next": "11.1.2", "@rollup/plugin-alias": "^3.1.9",
"eslint-plugin-prettier": "^3.4.0", "@trivago/prettier-plugin-sort-imports": "^3.3.0",
"prettier": "^2.3.0", "@types/styled-components": "^5.1.25",
"typescript": "^4.4.3" "prettier": "^2.7.1",
} "typescript": "^4.6.4",
"vite": "^3.0.0",
"vite-tsconfig-paths": "^3.5.0"
}
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

@ -1,16 +0,0 @@
{
"short_name": "Porfolio",
"name": "Guus van Meerveld's portfolio",
"icons": [
{
"src": "/favicon.ico",
"type": "image/png",
"sizes": "192x192"
}
],
"background_color": "#FFFFFF",
"display": "standalone",
"scope": "/",
"theme_color": "#388e3c",
"description": "A simple portfolio website to display my projects."
}

@ -1,2 +0,0 @@
User-agent: *
Disallow:

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 89 KiB

@ -0,0 +1,24 @@
import { createGlobalStyle } from "styled-components";
const Baseline = createGlobalStyle`
body {
color: ${({ theme }) => theme.palette.text.primary};
background-color: ${({ theme }) => theme.palette.background.primary};
font-family: "Montserrat", sans-serif;
background-image: url("/topography.svg");
background-repeat: repeat;
}
a {
color: ${({ theme }) => theme.palette.text.primary};
text-decoration: none;
:hover {
color: ${({ theme }) => theme.palette.text.secondary};
}
}
`;
const CssBaseline = Baseline as () => JSX.Element;
export default CssBaseline;

@ -0,0 +1,290 @@
import styled, { useTheme } from "styled-components";
import { FunctionalComponent } from "preact";
const Container = styled.div`
width: 100%;
transform: ${(props) => (props.left ? "scale(-1, -1)" : "none")};
`;
const Dots: FunctionalComponent<{
left?: boolean;
}> = ({ left }) => {
const theme = useTheme();
return (
<Container left={left}>
<svg
version="1.0"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 645.000000 456.000000"
preserveAspectRatio="xMidYMid meet"
>
<g
transform="translate(0.000000,456.000000) scale(0.100000,-0.100000)"
fill={theme.palette.secondary}
stroke="none"
>
<path
d="M3825 4548 c-28 -16 -35 -28 -35 -64 0 -79 106 -88 133 -11 9 25 -18
73 -45 81 -29 7 -29 7 -53 -6z"
/>
<path
d="M4455 4548 c-28 -16 -35 -28 -35 -64 0 -39 27 -64 68 -64 58 0 90 76
49 117 -19 18 -60 24 -82 11z"
/>
<path
d="M5077 4540 c-20 -16 -27 -30 -27 -55 0 -64 80 -89 120 -38 50 64 -29
143 -93 93z"
/>
<path
d="M5706 4539 c-50 -39 -22 -119 43 -119 20 0 36 9 50 26 27 35 26 59
-4 89 -30 30 -54 31 -89 4z"
/>
<path
d="M6336 4539 c-49 -39 -22 -119 42 -119 33 0 45 8 61 37 17 33 14 51
-14 78 -30 30 -54 31 -89 4z"
/>
<path
d="M3825 3918 c-28 -16 -35 -28 -35 -64 0 -38 27 -64 67 -64 69 0 91
100 28 128 -31 14 -35 14 -60 0z"
/>
<path
d="M4447 3910 c-20 -16 -27 -31 -27 -54 0 -66 79 -91 120 -39 50 64 -29
143 -93 93z"
/>
<path
d="M5077 3910 c-20 -16 -27 -31 -27 -54 0 -66 79 -91 120 -39 50 64 -29
143 -93 93z"
/>
<path
d="M5706 3909 c-50 -39 -24 -119 39 -119 78 0 101 103 29 130 -34 13
-38 13 -68 -11z"
/>
<path
d="M6336 3909 c-50 -39 -22 -119 43 -119 75 0 95 104 25 130 -34 13 -38
13 -68 -11z"
/>
<path
d="M3816 3279 c-52 -41 -25 -119 41 -119 69 0 91 100 28 128 -34 16 -38
15 -69 -9z"
/>
<path
d="M4446 3279 c-32 -25 -36 -77 -7 -103 28 -25 79 -17 104 16 17 23 18
31 8 56 -11 26 -46 52 -70 52 -5 0 -20 -9 -35 -21z"
/>
<path
d="M5100 3293 c-50 -18 -68 -84 -32 -116 19 -17 68 -22 88 -9 21 13 34
52 28 77 -5 20 -51 58 -66 54 -2 0 -10 -3 -18 -6z"
/>
<path
d="M5730 3293 c-31 -12 -50 -37 -50 -68 0 -78 111 -91 135 -16 9 28 -12
70 -40 81 -15 6 -27 10 -28 9 -1 0 -9 -3 -17 -6z"
/>
<path
d="M6360 3293 c-73 -27 -62 -133 14 -133 42 0 64 17 72 55 9 48 -42 94
-86 78z"
/>
<path
d="M1939 2658 c-36 -12 -55 -58 -38 -90 16 -31 28 -38 65 -38 77 0 88
107 14 132 -8 3 -27 1 -41 -4z"
/>
<path
d="M2567 2658 c-27 -10 -45 -46 -38 -78 5 -28 34 -50 66 -50 78 0 90
107 15 132 -8 3 -27 1 -43 -4z"
/>
<path
d="M3210 2662 c-49 -16 -68 -83 -32 -115 25 -22 75 -21 95 1 43 47 -5
133 -63 114z"
/>
<path
d="M3840 2662 c-49 -15 -68 -83 -32 -115 24 -22 75 -22 95 1 19 22 23
40 14 74 -7 27 -49 49 -77 40z"
/>
<path
d="M4470 2662 c-30 -10 -50 -36 -50 -67 0 -39 26 -65 66 -65 35 0 47 8
63 38 25 47 -29 110 -79 94z"
/>
<path
d="M5100 2662 c-30 -10 -50 -36 -50 -67 0 -39 26 -65 66 -65 35 0 47 8
63 38 25 47 -29 110 -79 94z"
/>
<path
d="M5730 2662 c-30 -10 -50 -36 -50 -67 0 -78 111 -91 135 -16 14 44
-40 97 -85 83z"
/>
<path
d="M6360 2662 c-48 -15 -64 -67 -34 -110 32 -46 124 -16 124 41 0 46
-47 82 -90 69z"
/>
<path
d="M1924 2022 c-6 -4 -17 -18 -23 -30 -25 -48 23 -104 79 -93 48 9 68
82 32 114 -19 17 -68 22 -88 9z"
/>
<path
d="M2547 2013 c-19 -23 -23 -41 -14 -75 7 -28 43 -46 77 -39 48 9 68 82
32 114 -24 22 -75 22 -95 0z"
/>
<path
d="M3177 2012 c-39 -42 -8 -114 48 -114 57 0 88 78 47 115 -25 22 -75
21 -95 -1z"
/>
<path
d="M3807 2012 c-33 -37 -14 -104 33 -113 34 -7 70 11 77 39 9 34 5 52
-14 75 -21 23 -75 22 -96 -1z"
/>
<path
d="M4437 2012 c-33 -37 -14 -104 33 -113 56 -11 104 45 79 93 -6 12 -17
26 -23 30 -21 14 -73 8 -89 -10z"
/>
<path
d="M5067 2012 c-33 -37 -14 -104 33 -113 56 -11 104 45 79 93 -6 12 -17
26 -23 30 -21 14 -73 8 -89 -10z"
/>
<path
d="M5697 2012 c-22 -24 -22 -75 1 -95 54 -48 133 -1 116 69 -11 45 -86
61 -117 26z"
/>
<path
d="M6326 2008 c-21 -30 -20 -71 2 -91 45 -40 122 -10 122 48 0 59 -91
90 -124 43z"
/>
<path
d="M20 1380 c-41 -41 -11 -113 48 -113 62 0 92 60 56 111 -20 29 -76 30
-104 2z"
/>
<path
d="M656 1384 c-38 -37 -27 -96 20 -114 67 -26 128 66 76 113 -23 21 -76
22 -96 1z"
/>
<path
d="M1294 1392 c-21 -13 -34 -52 -28 -77 7 -30 54 -58 81 -48 53 18 72
83 35 116 -19 17 -68 22 -88 9z"
/>
<path
d="M1911 1374 c-24 -31 -25 -35 -10 -67 31 -67 129 -46 129 28 0 64 -79
90 -119 39z"
/>
<path
d="M2547 1383 c-33 -38 -22 -94 24 -113 27 -11 33 -11 57 7 33 25 41 76
16 104 -22 24 -76 25 -97 2z"
/>
<path
d="M3177 1382 c-34 -38 -16 -101 33 -117 20 -6 59 9 72 29 14 21 8 73
-10 89 -25 22 -75 21 -95 -1z"
/>
<path
d="M3806 1381 c-25 -28 -17 -79 16 -104 64 -48 135 45 81 106 -21 23
-75 22 -97 -2z"
/>
<path
d="M4437 1382 c-48 -53 12 -140 77 -110 14 6 30 24 37 40 10 25 9 33 -8
56 -26 34 -81 42 -106 14z"
/>
<path
d="M5067 1382 c-51 -56 15 -142 82 -107 45 23 49 90 7 117 -21 14 -73 8
-89 -10z"
/>
<path
d="M5697 1382 c-48 -53 11 -138 78 -112 45 17 55 79 19 115 -21 21 -77
19 -97 -3z"
/>
<path
d="M6326 1378 c-36 -51 -6 -111 56 -111 36 0 68 32 68 69 0 58 -92 89
-124 42z"
/>
<path
d="M26 754 c-40 -40 -24 -105 30 -118 62 -16 110 68 65 113 -25 25 -73
27 -95 5z"
/>
<path
d="M656 754 c-61 -61 15 -157 83 -106 53 39 30 122 -34 122 -18 0 -41
-7 -49 -16z"
/>
<path
d="M1280 743 c-50 -64 29 -143 93 -93 20 16 27 31 27 54 0 66 -79 91
-120 39z"
/>
<path
d="M1910 743 c-50 -64 29 -143 93 -93 20 16 27 31 27 54 0 66 -79 91
-120 39z"
/>
<path
d="M2548 753 c-36 -43 -27 -93 22 -113 29 -12 65 -1 82 24 14 21 8 73
-10 89 -24 22 -75 22 -94 0z"
/>
<path
d="M3177 752 c-34 -38 -16 -101 33 -117 20 -6 59 9 72 29 14 21 8 73
-10 89 -25 22 -75 21 -95 -1z"
/>
<path
d="M3807 752 c-17 -19 -22 -68 -9 -88 4 -6 18 -17 30 -23 48 -25 104 23
93 79 -9 48 -82 68 -114 32z"
/>
<path
d="M4436 751 c-25 -27 -18 -79 15 -103 36 -27 76 -15 97 28 15 32 15 36
-5 62 -26 35 -81 42 -107 13z"
/>
<path
d="M5067 752 c-27 -29 -22 -77 10 -102 64 -50 143 29 93 93 -26 33 -78
37 -103 9z"
/>
<path
d="M5695 746 c-22 -33 -15 -75 16 -98 68 -51 144 45 83 106 -25 25 -81
20 -99 -8z"
/>
<path
d="M6325 746 c-22 -33 -15 -75 16 -98 68 -51 144 45 83 106 -25 25 -81
20 -99 -8z"
/>
<path
d="M26 124 c-61 -61 14 -158 82 -107 35 26 41 74 13 102 -25 25 -73 27
-95 5z"
/>
<path
d="M651 114 c-12 -15 -21 -35 -21 -46 0 -24 44 -68 68 -68 33 0 72 38
72 71 0 65 -80 93 -119 43z"
/>
<path
d="M1280 113 c-50 -64 29 -143 93 -93 20 16 27 30 27 55 0 64 -80 89
-120 38z"
/>
<path
d="M1911 114 c-24 -31 -24 -34 -11 -69 26 -69 130 -47 130 29 0 65 -79
91 -119 40z"
/>
<path
d="M2548 123 c-25 -29 -29 -53 -13 -82 23 -45 90 -49 117 -7 14 21 8 73
-10 89 -24 22 -75 22 -94 0z"
/>
<path
d="M3177 122 c-34 -38 -16 -101 33 -117 20 -6 59 9 72 29 14 21 8 73
-10 89 -25 22 -75 21 -95 -1z"
/>
<path
d="M3807 122 c-17 -19 -22 -68 -9 -88 13 -21 52 -34 77 -28 30 7 58 54
48 81 -18 53 -83 72 -116 35z"
/>
<path
d="M4437 122 c-21 -23 -22 -75 -2 -96 38 -38 101 -23 118 28 20 61 -73
116 -116 68z"
/>
<path
d="M5067 122 c-27 -29 -22 -77 10 -102 64 -50 143 29 93 93 -26 33 -78
37 -103 9z"
/>
<path
d="M5700 120 c-41 -41 -7 -120 52 -120 24 0 68 44 68 68 0 59 -79 93
-120 52z"
/>
<path
d="M6329 119 c-28 -28 -22 -76 13 -102 68 -51 143 46 82 107 -22 22 -70
20 -95 -5z"
/>
</g>
</svg>
</Container>
);
};
export default Dots;

@ -1,28 +0,0 @@
@import '../styles/colors.scss';
.body {
background-color: $bg-secondary;
border-top: 0.1rem solid $borders;
padding: 3rem;
}
.branding {
display: inline-block;
font-size: 2rem;
margin-left: 1rem;
vertical-align: top;
}
.socials {
margin-left: 4rem;
display: inline-block;
height: 100%;
padding: 1rem 0;
}
.socialLink {
height: 3rem;
width: 3rem;
margin-right: 2rem;
}

@ -1,50 +1,61 @@
import Link from 'next/link'; import styled, { DefaultTheme } from "styled-components";
import { FaTwitter, FaYoutube, FaCoffee, FaGithub } from 'react-icons/fa'; import Col from "react-bootstrap/Col";
import Container from "react-bootstrap/Container";
import Row from "react-bootstrap/Row";
import { FC } from 'react'; import { FunctionalComponent } from "preact";
import { ProfileImage } from '@svg/index'; import socials from "@utils/socials";
import styles from './Footer.module.scss'; import Header from "@components/Header";
const Footer: FC = () => { const Body = styled.div`
background-color: ${({ theme }: { theme: DefaultTheme }) =>
theme.palette.background.secondary};
border-top: 2px solid
${({ theme }: { theme: DefaultTheme }) => theme.palette.border};
padding: 2rem;
`;
const ListHeader = styled(Header)`
font-size: 2rem;
`;
const ListItem = styled.li`
font-size: 1.25rem;
margin-top: 1rem;
`;
const ListItemIcon = styled.span`
margin-right: 1rem;
`;
const Footer: FunctionalComponent = () => {
return ( return (
<footer className={styles.body}> <Body>
<div className="container"> <Container>
<ProfileImage className={styles.profile + ' profile'} width={50} height={50} /> <Row>
<div className={styles.branding}> <Col md={4}>
Guus van Meerveld <br /> <Header gutter>Guus van Meerveld</Header>
&#169; 2017 - {new Date().getFullYear()} </Col>
</div> <Col md={4}>
<div className={styles.socials}> <ul>
<Link passHref href="https://twitter.com/GuusvanMeerveld" aria-label="twitter link"> <ListHeader>Socials</ListHeader>
<a> {socials.map((social) => (
<FaTwitter className={styles.socialLink} /> <ListItem>
</a> <a href={social.url}>
</Link> <ListItemIcon>{social.icon}</ListItemIcon>
<Link {social.name}
passHref </a>
href="https://www.youtube.com/channel/UCYuqpoMay5SezCBrA_HKVWQ" </ListItem>
aria-label="youtube link" ))}
> </ul>
<a> </Col>
<FaYoutube className={styles.socialLink} /> </Row>
</a> </Container>
</Link> </Body>
<Link passHref href="https://ko-fi.com/guusvanmeerveld" aria-label="kofi link">
<a>
<FaCoffee className={styles.socialLink} />
</a>
</Link>
<Link passHref href="https://github.com/guusvanmeerveld" aria-label="github link">
<a>
<FaGithub className={styles.socialLink} />
</a>
</Link>
</div>
</div>
</footer>
); );
}; };

@ -0,0 +1,10 @@
import styled from "styled-components";
const Header = styled.h1`
font-family: "Prompt", sans-serif;
margin-bottom: 1rem;
font-size: 3rem;
margin-bottom: ${(props) => (props.gutter ? "1rem" : "0")};
`;
export default Header;

@ -0,0 +1,11 @@
import styled from "styled-components";
const Image = styled.img.attrs(() => ({ draggable: false }))`
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-o-user-select: none;
user-select: none;
`;
export default Image;

@ -1,14 +1,14 @@
import { FC } from 'react'; import { FunctionalComponent } from "preact";
import Navbar from '@components/Navbar'; import Footer from "@components/Footer";
import Footer from '@components/Footer';
const Layout: FC<{ children: JSX.Element | JSX.Element[] }> = ({ children }) => ( const Layout: FunctionalComponent = ({ children }) => {
<> return (
<Navbar /> <>
{children} {children}
<Footer /> <Footer />
</> </>
); );
};
export default Layout; export default Layout;

@ -0,0 +1,7 @@
import styled, { DefaultTheme } from "styled-components";
const Link = styled.a`
color: ${({ theme }: { theme: DefaultTheme }) => theme.palette.primary};
`;
export default Link;

@ -1,65 +0,0 @@
@import '../styles/colors.scss';
.bar {
z-index: 1;
background-color: $bg-secondary;
border-bottom: 0.1rem solid $borders;
position: fixed;
top: 0;
padding: 1.5rem 0;
width: 100%;
}
.content {
display: flex;
align-items: center;
}
.header {
flex: 1;
display: flex;
align-items: center;
}
.select {
width: 7.5rem;
margin-bottom: 0;
background-color: $bg-primary;
}
.items {
display: flex;
justify-content: center;
align-items: center;
a {
font-size: 1.75rem;
margin-left: 1rem;
}
}
.moon,
.sun {
font-size: 2rem;
margin: 0.25rem 1.5rem;
cursor: pointer;
}
.moon {
display: none;
}
[data-theme='dark'] {
.sun {
display: none;
}
.moon {
display: inline-block !important;
}
}

@ -1,50 +0,0 @@
import { useTheme } from 'next-themes';
import Link from 'next/link';
import { useTranslation } from 'next-i18next';
import React, { FC } from 'react';
import { BiMoon } from 'react-icons/bi';
import { ImSun } from 'react-icons/im';
import styles from './Navbar.module.scss';
import { ProfileImage } from '@svg/index';
const Navbar: FC = () => {
const { theme, setTheme } = useTheme();
const switchTheme = (): void => setTheme(theme == 'dark' ? 'light' : 'dark');
return (
<nav className={styles.bar}>
<div className="container">
<div className={styles.content}>
<div className={styles.header}>
<Link href="/">
<a>
<ProfileImage className="profile" width={32} height={32} />
</a>
</Link>
</div>
<div className={styles.items}>
<Link href="/#projects">
<a>Projects</a>
</Link>
<Link href="/blog">
<a>Contact</a>
</Link>
<Link href="https://github.com/guusvanmeerveld/portfolio">
<a>Github</a>
</Link>
<BiMoon onClick={switchTheme} className={styles.moon} />
<ImSun onClick={switchTheme} className={styles.sun} />
</div>
</div>
</div>
</nav>
);
};
export default Navbar;

@ -1,25 +0,0 @@
import Head from 'next/head';
import { FC } from 'react';
const Page: FC<{
title: string;
description: string;
children: JSX.Element[] | JSX.Element;
}> = ({ title, description, children }) => (
<>
<Head>
<meta charSet="UTF-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Guus van Meerveld | {title}</title>
<meta name="description" content={description} />
<meta name="keywords" content="guus van meerveld argo magister tempo discord" />
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
<link rel="apple-touch-icon" sizes="192x192" href="/favicon.ico" />
</Head>
{children}
</>
);
export default Page;

@ -1,21 +0,0 @@
.body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.header {
font-size: 5rem;
}
.subtitle {
font-size: 3rem;
}
.link {
margin-top: 1.5rem;
}

@ -1,25 +0,0 @@
import Link from 'next/link';
import { FC } from 'react';
import styles from './PageBuilder.module.scss';
const PageBuilder: FC<{ header: string; subtitle: string; button: string }> = ({
header,
subtitle,
button,
children,
}) => (
<div className={styles.body}>
<div>
{children}
<div className={styles.header}>{header}</div>
<div className={styles.subtitle}>{subtitle}</div>
<Link href="/">
<a className={styles.link + ' button'}>{button}</a>
</Link>
</div>
</div>
);
export default PageBuilder;

@ -0,0 +1,9 @@
import styled from "styled-components";
const Paragraph = styled.p`
font-family: "Montserrat", sans-serif;
font-size: 1.2rem;
margin-bottom: ${(props) => (props.gutter ? ".5rem" : "0")};
`;
export default Paragraph;

@ -1,53 +0,0 @@
.body {
border-top: 0.1rem solid var(--borders);
padding: 5rem 0;
}
.description {
margin-bottom: 2rem;
}
.right {
text-align: right;
}
.content {
display: flex;
}
.info,
.right {
font-size: 2rem;
width: 50%;
}
.button:not(:last-child) {
margin-right: 1rem;
}
.cover {
width: 50%;
display: flex;
justify-content: center;
align-items: center;
}
@media only screen and (max-width: 600px) {
.cover,
.info,
.right {
width: 100%;
margin: 2rem 0;
text-align: center;
img {
height: 15rem;
width: 15rem;
}
}
.content {
display: block;
}
}

@ -1,38 +0,0 @@
import Image from 'next/image';
import Link from 'next/link';
import { FC } from 'react';
import ProjectType from '@models/project';
import styles from './Project.module.scss';
interface ProjectComponent extends ProjectType {
right: boolean;
}
const Project: FC<ProjectComponent> = ({ name, description, buttons, cover, right }) => {
return (
<div className={styles.body}>
<div className={styles.content}>
<div className={right ? styles.right : styles.info}>
<h2>{name}</h2>
<div className={styles.description}>{description}</div>
{buttons.map((button, i) => (
<Link href={button.link} key={i}>
<a className={styles.button + ' button'}>{button.text}</a>
</Link>
))}
</div>
{cover ? (
<div className={styles.cover}>
<Image src={cover} width={200} height={200} alt={name} />
</div>
) : null}
</div>
</div>
);
};
export default Project;

@ -0,0 +1,153 @@
import { useQuery } from "@tanstack/react-query";
import styled, { DefaultTheme } from "styled-components";
import { Spinner } from "react-bootstrap";
import Col from "react-bootstrap/Col";
import Row from "react-bootstrap/Row";
import { AiOutlineStar, AiOutlineFork } from "react-icons/ai";
import { FunctionalComponent } from "preact";
import axios, { AxiosError } from "axios";
import Repo from "@interfaces/repo";
import Header from "@components/Header";
import Link from "@components/Link";
import Paragraph from "@components/Paragraph";
const SpinnerContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
`;
const ProjectItem = styled(Row)`
margin-bottom: 5rem;
`;
const Hero = styled.div`
padding: 2rem;
background-color: ${({ theme }: { theme: DefaultTheme }) =>
theme.palette.background.secondary};
border-radius: 3px;
border: 2px solid
${({ theme }: { theme: DefaultTheme }) => theme.palette.border};
`;
const Heading = styled(Header)`
font-size: 2rem;
margin-bottom: 0.5rem;
`;
const Leading = styled.div`
margin-bottom: 1rem;
`;
const Subtitle = styled.h4`
color: ${({ theme }: { theme: DefaultTheme }) =>
theme.palette.text.secondary};
`;
const IconsTray = styled.div`
display: flex;
margin-top: 1rem;
font-size: 1.5rem;
`;
const IconItem = styled(Paragraph)`
margin-right: 1rem;
`;
const Icon = styled.span`
margin-left: 0.5rem;
color: ${({ theme }: { theme: DefaultTheme }) => theme.palette.primary};
`;
const TopicsTray = styled.div`
margin-top: 1rem;
display: flex;
align-items: center;
flex-direction: row;
flex-wrap: wrap;
`;
const Topic = styled.div`
background-color: ${({ theme }: { theme: DefaultTheme }) =>
theme.palette.primary};
border-radius: 1.25rem;
margin-top: 0.5rem;
margin-left: 0.5rem;
padding: 0.75rem;
`;
const ListItem: FunctionalComponent<{
repoFullName: string;
rtl?: boolean;
}> = ({ repoFullName, rtl }) => {
const { data, isLoading, error } = useQuery<Repo, AxiosError>(
["repo", repoFullName],
() =>
axios
.get(`https://api.github.com/repos/${repoFullName}`)
.then((res) => res.data)
);
const body = [
<Col md={8}>
<Hero>
{isLoading && !data && (
<SpinnerContainer>
<Spinner animation="border" role="status">
<span className="visually-hidden">Loading {repoFullName}</span>
</Spinner>
</SpinnerContainer>
)}
{data && !isLoading && (
<>
<Leading>
<Heading>
<a href={data.html_url}>{data.full_name}</a>
</Heading>
{data.homepage && (
<Subtitle>
Website: <Link href={data.homepage}>{data.homepage}</Link>
</Subtitle>
)}
</Leading>
<Paragraph gutter>{data.description}</Paragraph>
<IconsTray>
<IconItem>
{data.stargazers_count}
<Icon>
<AiOutlineStar />
</Icon>
</IconItem>
<IconItem>
{data.forks_count}
<Icon>
<AiOutlineFork />
</Icon>
</IconItem>
</IconsTray>
<TopicsTray>
<Paragraph>Tags:</Paragraph>
{data.topics.map((topic) => (
<Topic>{topic}</Topic>
))}
</TopicsTray>
</>
)}
</Hero>
</Col>,
<Col md={4}></Col>
];
return <ProjectItem>{rtl ? body.reverse() : body}</ProjectItem>;
};
export default ListItem;

@ -0,0 +1,76 @@
import { useQuery } from "@tanstack/react-query";
import styled from "styled-components";
import { Spinner } from "react-bootstrap";
import { FunctionalComponent } from "preact";
import axios, { AxiosError } from "axios";
import PinnedRepo from "@interfaces/pinnedRepo";
import Paragraph from "@components/Paragraph";
import ListItem from "@components/Project/Item";
const SpinnerContainer = styled.div`
display: flex;
justify-content: center;
`;
const ErrorText = styled(Paragraph)`
text-align: center;
`;
const List: FunctionalComponent = () => {
const { data, isLoading, error } = useQuery<PinnedRepo[], AxiosError>(
["pinnedRepos"],
() =>
axios
.get(
`https://gh-pinned-repos.egoist.sh/?username=${
import.meta.env.VITE_GITHUB_USERNAME
}`
)
.then((res) => res.data)
);
if (isLoading)
return (
<SpinnerContainer>
<Spinner animation="border" role="status">
<span className="visually-hidden">Loading pinned repos...</span>
</Spinner>
</SpinnerContainer>
);
if (error)
return (
<ErrorText>
Status {error.response?.status ?? "Unknown"} - {error.response?.data}
</ErrorText>
);
if (!error && data) {
if (Array.isArray(data))
return (
<>
{data.map((repo, i) => {
const repoFullName = `${repo.owner}/${repo.repo}`;
return (
<ListItem
key={repoFullName}
rtl={!(i % 2)}
repoFullName={repoFullName}
/>
);
})}
</>
);
else return <ErrorText>Unknown data returned from api</ErrorText>;
}
return <></>;
};
export default List;

@ -0,0 +1,12 @@
export default interface PinnedRepo {
owner: string;
repo: string;
link: string;
description: string;
image: string;
website: string;
language: string;
languageColor: string;
stars: string;
forks: number;
}

@ -0,0 +1,112 @@
export default interface Repo {
id: number;
node_id: string;
name: string;
full_name: string;
private: boolean;
owner: Owner;
html_url: string;
description: string;
fork: boolean;
url: string;
forks_url: string;
keys_url: string;
collaborators_url: string;
teams_url: string;
hooks_url: string;
issue_events_url: string;
events_url: string;
assignees_url: string;
branches_url: string;
tags_url: string;
blobs_url: string;
git_tags_url: string;
git_refs_url: string;
trees_url: string;
statuses_url: string;
languages_url: string;
stargazers_url: string;
contributors_url: string;
subscribers_url: string;
subscription_url: string;
commits_url: string;
git_commits_url: string;
comments_url: string;
issue_comment_url: string;
contents_url: string;
compare_url: string;
merges_url: string;
archive_url: string;
downloads_url: string;
issues_url: string;
pulls_url: string;
milestones_url: string;
notifications_url: string;
labels_url: string;
releases_url: string;
deployments_url: string;
created_at: Date;
updated_at: Date;
pushed_at: Date;
git_url: string;
ssh_url: string;
clone_url: string;
svn_url: string;
homepage: string;
size: number;
stargazers_count: number;
watchers_count: number;
language: string;
has_issues: boolean;
has_projects: boolean;
has_downloads: boolean;
has_wiki: boolean;
has_pages: boolean;
forks_count: number;
mirror_url: null;
archived: boolean;
disabled: boolean;
open_issues_count: number;
license: License;
allow_forking: boolean;
is_template: boolean;
web_commit_signoff_required: boolean;
topics: string[];
visibility: string;
forks: number;
open_issues: number;
watchers: number;
default_branch: string;
temp_clone_token: null;
network_count: number;
subscribers_count: number;
}
export interface License {
key: string;
name: string;
spdx_id: string;
url: string;
node_id: string;
}
export interface Owner {
login: string;
id: number;
node_id: string;
avatar_url: string;
gravatar_id: string;
url: string;
html_url: string;
followers_url: string;
following_url: string;
gists_url: string;
starred_url: string;
subscriptions_url: string;
organizations_url: string;
repos_url: string;
events_url: string;
received_events_url: string;
type: string;
site_admin: boolean;
}

@ -0,0 +1,10 @@
import "@fontsource/montserrat";
import "@fontsource/prompt";
import "bootstrap/dist/css/bootstrap.min.css";
import "reset-css";
import App from "./pages/app";
import { render } from "preact";
render(<App />, document.getElementById("app") as HTMLElement);

@ -1,11 +0,0 @@
export default interface Project {
name: string;
description: string;
cover?: string;
buttons: Button[];
}
interface Button {
link: string;
text: string;
}

@ -1,5 +0,0 @@
declare module '*svg' {
import React from 'react';
const SVG: React.VFC<React.SVGProps<SVGSVGElement>>;
export default SVG;
}

@ -1,22 +1,5 @@
import { GetStaticProps, NextPage } from 'next'; const NotFound = () => {
import { useTranslation } from 'next-i18next'; return <div>Page not found</div>;
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import Page from '@components/Page';
import Layout from '@components/Layout';
import PageBuilder from '@components/PageBuilder';
const NotFound: NextPage = () => {
const title = 'Page not found';
const description = "This page either doesn't exist or has been deleted";
return (
<Page title={title} description={description}>
<Layout>
<PageBuilder button="Go back" header={title} subtitle={description} />
</Layout>
</Page>
);
}; };
export default NotFound; export default NotFound;

@ -1,29 +0,0 @@
.body {
background: var(--secondary);
margin-top: 6rem;
text-align: center;
}
.content {
padding: 7rem 3rem;
}
.profile {
margin: auto;
margin-bottom: 3rem;
}
.subtitle {
margin-top: 2rem;
margin-bottom: 4rem;
}
.projectsHeader {
text-align: center;
margin-bottom: 3rem;
}
.projects {
margin-top: 3rem;
}

@ -1,19 +0,0 @@
import { ThemeProvider } from 'next-themes';
import type { AppProps } from 'next/app';
import 'milligram';
import '@styles/montserrat.css';
import '@styles/globals.scss';
const App = ({ Component, pageProps }: AppProps): JSX.Element => {
return (
<ThemeProvider>
<Component {...pageProps} />
</ThemeProvider>
);
};
export default App;

@ -0,0 +1,40 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import NotFound from "./404";
import Index from "./index";
import useLocalStorageState from "use-local-storage-state";
import { ThemeProvider } from "styled-components";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { FunctionalComponent } from "preact";
import { lightTheme, darkTheme } from "@utils/theme";
import CssBaseline from "@components/CssBaseline";
const queryClient = new QueryClient();
const App: FunctionalComponent = () => {
const [darkMode] = useLocalStorageState("darkMode", { defaultValue: true });
return (
<ThemeProvider theme={darkMode ? darkTheme : lightTheme}>
<CssBaseline />
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools initialIsOpen={false} />
<BrowserRouter>
<Routes>
<Route path="/" element={<Index />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
</QueryClientProvider>
</ThemeProvider>
);
};
export default App;

@ -1,85 +1,147 @@
import { GetStaticProps, NextPage } from 'next'; import useLocalStorageState from "use-local-storage-state";
import styled, { DefaultTheme } from "styled-components";
import Col from "react-bootstrap/Col";
import Container from "react-bootstrap/Container";
import Row from "react-bootstrap/Row";
import { animated, useSpring } from "react-spring";
import { FunctionalComponent } from "preact";
import socials from "@utils/socials";
import Dots from "@components/Dots";
import Header from "@components/Header";
import Image from "@components/Image";
import Layout from "@components/Layout";
import Paragraph from "@components/Paragraph";
import ProjectsList from "@components/Project/List";
const Presentation = styled.div`
height: 100vh;
display: flex;
align-items: center;
`;
const Projects = styled.div`
margin-top: 5rem;
padding: 5rem 0;
`;
const Hero = styled.div`
/* background-color: ${({ theme }: { theme: DefaultTheme }) =>
theme.palette.background.secondary}; */
justify-content: ${(props) => (props.right ? "right" : "left")};
display: flex;
align-items: center;
height: 100%;
`;
const Logo = styled.div`
margin: 2rem 0;
display: flex;
justify-content: center;
width: 100%;
`;
const ProjectsHeader = styled(Header)`
text-align: center;
margin-bottom: 5rem;
`;
const IconsTray = styled.div`
display: flex;
justify-content: right;
margin-top: 1rem;
`;
const Icon = styled.div`
margin-left: 1.5rem;
font-size: 2rem;
`;
const Index: FunctionalComponent = () => {
const [darkMode] = useLocalStorageState("darkMode");
const fadeIn = useSpring({
to: { opacity: 1 },
from: { opacity: 0 },
config: {
mass: 5
}
});
import axios, { AxiosError } from 'axios';
import chalk from 'chalk';
import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import ProjectModel from '@models/project';
import Layout from '@components/Layout';
import Page from '@components/Page';
import Project from '@components/Project';
import { ProfileImage } from '@svg/index';
import styles from './Index.module.scss';
const Home: NextPage<{ projects: ProjectModel[] }> = ({ projects }) => {
return ( return (
<Page description="A simple portfolio website to display my projects." title="Home"> <Layout>
<Layout> <Presentation>
<div className={styles.body}> <Container>
<div className={styles.content + ' container'}> <animated.div style={fadeIn}>
<div className={styles.profile}> <Row>
<ProfileImage height={100} width={100} className="profile" /> <Col md={8}>
</div> <Hero>
<div>
<h1>Guus van Meerveld</h1> <Header gutter>Welcome</Header>
<h4 className={styles.subtitle}>Full-stack developer</h4> <Paragraph gutter>
My name is Guus van Meerveld, and I am a web developer.
<a href="#projects" className="button"> </Paragraph>
Check out my projects <Paragraph>
</a> This is my portfolio website, to showcase my projects.
</div> </Paragraph>
</div> </div>
</Hero>
<div className={styles.projects}> </Col>
<div className="container"> <Col md={4}>
<h1 className={styles.projectsHeader} id="projects"> <Dots />
Projects </Col>
</h1> </Row>
<Row>
{projects.map((project, i) => { <Logo>
const props = { ...project, right: (i + 1) % 2 == 0 }; <Image
return <Project key={project.name} {...props} />; // onClick={() => setDarkMode(!darkMode)}
})} // style={{ cursor: "pointer" }}
</div> height={64}
</div> src={darkMode ? "logo-dark.png" : "logo-light.png"}
</Layout> alt=""
</Page> />
</Logo>
</Row>
<Row>
<Col md={4}>
<Dots left />
</Col>
<Col md={8}>
<Hero right>
<div>
<Header>Follow me:</Header>
<IconsTray>
{socials.map((social) => (
<Icon>
<a href={social.url}>{social.icon}</a>
</Icon>
))}
</IconsTray>
</div>
</Hero>
</Col>
</Row>
</animated.div>
</Container>
</Presentation>
<Projects>
<Container>
<Row>
<ProjectsHeader>Projects</ProjectsHeader>
</Row>
<Row>
<ProjectsList />
</Row>
</Container>
</Projects>
</Layout>
); );
}; };
export const getStaticProps: GetStaticProps = async () => { export default Index;
const projects: ProjectModel[] | undefined = await axios
.get(`https://${process.env.CDN_ENDPOINT}/portfolio/projects-en.json`)
.then(({ data }) => {
console.log(
chalk`{magenta event} - retrieved projects from ` +
process.env.CDN_ENDPOINT +
' successfully'
);
return data;
})
.catch((error: AxiosError) => {
console.log(chalk`{red error} - failed to retrieve projects:`);
console.log(error.message);
});
if (projects) {
return {
props: {
projects,
},
};
}
throw new Error('Failed to retrieve projects from ' + process.env.CDN_ENDPOINT);
};
export default Home;

1
src/preact.d.ts vendored

@ -0,0 +1 @@
import JSX = preact.JSX;

21
src/styled.d.ts vendored

@ -0,0 +1,21 @@
import Theme from "./interfaces/theme";
import "styled-components";
declare module "styled-components" {
export interface DefaultTheme {
palette: {
primary: string;
secondary: string;
background: {
primary: string;
secondary: string;
};
text: {
primary: string;
secondary: string;
};
border: string;
};
}
}

@ -1,5 +0,0 @@
$primary: var(--primary);
$bg-primary: var(--background);
$bg-secondary: var(--secondary);
$borders: var(--borders);
$text: var(--text);

Binary file not shown.

@ -1,7 +0,0 @@
body {
margin: 0;
background-color: $bg-primary;
color: $text;
font-family: 'Montserrat';
}

@ -1,4 +0,0 @@
.button {
background-color: var(--primary);
border-color: var(--primary);
}

@ -1,18 +0,0 @@
input,
textarea,
select {
&:focus {
border-color: $primary !important;
}
color: $text;
border-color: $borders !important;
font-size: 1.5rem;
}
select {
&:focus {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 8" width="30"><path fill="%23388e3c" d="M0,0l6,8l6-8"/></svg>');
}
}

@ -1,3 +0,0 @@
a {
color: var(--primary);
}

@ -1,4 +0,0 @@
td,
th {
border-color: var(--borders);
}

@ -1,27 +0,0 @@
:root {
--primary: #9ccc65;
--secondary: #f4f5f6;
--background: white;
--borders: #e4e4e4;
--text: #606c76;
}
[data-theme='dark'] {
.profile {
filter: invert(1);
}
--primary: #388e3c;
--secondary: #1c1c1c;
--background: #212123;
--borders: #3c3838;
--text: rgb(236, 235, 235);
}
@import './colors.scss';
@import './global/body.scss';
@import './global/link.scss';
@import './global/input.scss';
@import './global/table.scss';
@import './global/button.scss';

@ -1,10 +0,0 @@
/* latin */
@font-face {
font-family: 'Montserrat';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('./fonts/montserrat.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F,
U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@ -1 +0,0 @@
export { default as ProfileImage } from './profile.svg';

@ -1 +0,0 @@
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 229.6 255.68"><defs><style>.cls-1{fill:#262626;}</style></defs><path class="cls-1" d="M292.88,264.68l-.06-68.53h55.65v88s-30.82,42.12-102.12,41.18C104.92,323.46,120,186.43,118.87,186c0,0,20.23,91.9,114.8,93.31C233.67,279.35,256.71,282.33,292.88,264.68Z" transform="translate(-118.87 -69.67)"/><path class="cls-1" d="M244.12,69.76c61.84-2.22,95.29,34.36,95.29,34.36l-29.17,41.53s-21.53-24-63.18-24.71c-82.4-1.4-71.47,88.06-71.47,88.06s-20.41-17.35-23-57.82C152.59,151.18,145.76,73.29,244.12,69.76Z" transform="translate(-118.87 -69.67)"/></svg>

Before

Width:  |  Height:  |  Size: 629 B

@ -0,0 +1,34 @@
import { BsTwitter, BsYoutube, BsMastodon, BsGithub } from "react-icons/bs";
import { SiKofi } from "react-icons/si";
const socials: { name: string; url: string; icon: JSX.Element }[] = [
{
name: "Twitter",
url: `https://twitter.com/${import.meta.env.VITE_TWITTER_USERNAME}`,
icon: <BsTwitter />
},
{
name: "Youtube",
url: `https://youtube.com/channel/${
import.meta.env.VITE_YOUTUBE_CHANNEL_ID
}`,
icon: <BsYoutube />
},
{
name: "Mastodon",
url: import.meta.env.VITE_MASTODON_URL,
icon: <BsMastodon />
},
{
name: "Ko-fi",
url: `https://ko-fi.com/${import.meta.env.VITE_KOFI_USERNAME}`,
icon: <SiKofi />
},
{
name: "Github",
url: `https://github.com/${import.meta.env.VITE_GITHUB_USERNAME}`,
icon: <BsGithub />
}
];
export default socials;

@ -0,0 +1,33 @@
import { DefaultTheme } from "styled-components";
export const lightTheme: DefaultTheme = {
palette: {
background: {
primary: "#FBF8F1",
secondary: "#F7ECDE"
},
border: "#dcdcdc",
primary: "#E9DAC1",
secondary: "#54BAB9",
text: {
primary: "#111111",
secondary: "#4d4d4d"
}
}
};
export const darkTheme: DefaultTheme = {
palette: {
background: {
primary: "#000000",
secondary: "#1d1d1d"
},
border: "#454545",
primary: "#DC0067",
secondary: "#00dc75",
text: {
primary: "#eee",
secondary: "#ccc"
}
}
};

11
src/vite-env.d.ts vendored

@ -0,0 +1,11 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_GITHUB_USERNAME: string;
readonly VITE_TWITTER_USERNAME: string;
readonly VITE_YOUTUBE_CHANNEL_ID: string;
readonly VITE_MASTODON_URL: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

@ -1,28 +1,30 @@
{ {
"compilerOptions": { "compilerOptions": {
"baseUrl": "src", "target": "ESNext",
"paths": { "useDefineForClassFields": true,
"@components/*": ["components/*"], "lib": ["DOM", "DOM.Iterable", "ESNext"],
"@svg/*": ["svg/*"], "allowJs": false,
"@styles/*": ["styles/*"],
"@models/*": ["models/*"],
"@config/*": ["config/*"],
"@src/*": ["./*"]
},
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": false, "esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"noEmit": true, "module": "ESNext",
"esModuleInterop": true, "moduleResolution": "Node",
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve" "noEmit": true,
"paths": {
"@components/*": ["./src/components/*"],
"@interfaces/*": ["./src/interfaces/*"],
"@utils/*": ["./src/utils/*"],
"react": ["./node_modules/preact/compat/"],
"react-dom": ["./node_modules/preact/compat/"]
},
"jsx": "preserve",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment"
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "include": ["src"],
"exclude": ["node_modules"] "references": [{ "path": "./tsconfig.node.json" }]
} }

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

@ -0,0 +1,48 @@
import preact from "@preact/preset-vite";
import { defineConfig } from "vite";
import viteImagemin from "vite-plugin-imagemin";
import tsconfigPaths from "vite-tsconfig-paths";
import alias from "@rollup/plugin-alias";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
preact(),
alias({
entries: [
{ find: "react", replacement: "preact/compat" },
{ find: "react-dom/test-utils", replacement: "preact/test-utils" },
{ find: "react-dom", replacement: "preact/compat" },
{ find: "react/jsx-runtime", replacement: "preact/jsx-runtime" }
]
}),
tsconfigPaths(),
viteImagemin({
optipng: {
optimizationLevel: 7
},
mozjpeg: {
quality: 20
},
pngquant: {
quality: [0.8, 0.9],
speed: 4
},
svgo: {
plugins: [
{
name: "removeViewBox"
},
{
name: "removeEmptyAttrs",
active: false
}
]
}
})
],
esbuild: {
logOverride: { "this-is-undefined-in-esm": "silent" }
}
});

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save