1
0
mirror of https://github.com/kremalicious/portfolio.git synced 2024-12-22 01:03:20 +01:00

Migrate to Next.js + TypeScript (#1038)

* next.js + typescript

* more testing

* script updates

* fixes

* favicon generation

* testing

* readme updates

* tweaks

* tweaks

* move tests

* image tweaks

* ci tweaks

* commit next-env.d.ts for ci

* migrations

* fixes

* fixes

* ci tweaks

* new animations

* project preview tweaks

* add codeclimate config

* dark mode refactor, test tweaks

* readme updates

* animation tweaks

* animate in loaded images

* test update

* update humans.txt
This commit is contained in:
Matthias Kretschmann 2022-11-15 23:14:59 +00:00 committed by GitHub
parent 18ee307bdf
commit 447cada700
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
254 changed files with 6386 additions and 35804 deletions

39
.codeclimate.yml Normal file
View File

@ -0,0 +1,39 @@
# https://docs.codeclimate.com/docs/default-analysis-configuration
# https://docs.codeclimate.com/docs/advanced-configuration
version: '2' # required to adjust maintainability checks
checks:
complex-logic:
config:
threshold: 8
method-complexity:
config:
threshold: 8
method-lines:
config:
threshold: 40
exclude_patterns:
- 'config/'
- 'db/'
- 'dist/'
- 'features/'
- '**/node_modules/'
- 'script/'
- '**/spec/'
- '**/test/'
- '**/tests/'
- 'Tests/'
- '**/vendor/'
- '**/*_test.go'
- '**/*.d.ts'
- '**/@types/'
- '**/interfaces/'
- '**/_types.*'
- '**/*.stories.*'
- '**/*.test.*'
- '.storybook/'
- 'tests/'
- 'coverage/'
- '.next/'
- '.swc/'

View File

@ -1,15 +0,0 @@
node_modules
npm-debug.log
Dockerfile*
docker-compose*
.dockerignore
.git
.gitignore
README.md
LICENSE
.vscode
public
.cache
package-lock.json
README.md
coverage

View File

@ -1,2 +1,2 @@
GATSBY_GITHUB_TOKEN=xxx
GATSBY_TYPEKIT_ID=xxx
GITHUB_TOKEN=xxx
NEXT_PUBLIC_TYPEKIT_ID=xxx

View File

@ -1,31 +0,0 @@
{
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:testing-library/dom",
"plugin:testing-library/react"
],
"plugins": ["react", "graphql", "prettier", "react-hooks", "testing-library"],
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": { "jsx": true }
},
"env": {
"browser": true,
"node": true,
"es2020": true,
"jest": true
},
"rules": {
"prettier/prettier": "error",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"testing-library/no-node-access": "off",
"testing-library/no-container": "off"
},
"settings": {
"react": {
"version": "detect"
}
}
}

3
.eslintrc.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

View File

@ -20,8 +20,8 @@ jobs:
with:
node-version: '16'
- name: Cache node modules
uses: actions/cache@v1
- name: Cache node_modules
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
@ -60,43 +60,40 @@ jobs:
with:
node-version: '16'
- name: Cache node modules
uses: actions/cache@v1
- name: Cache node_modules & Next.js build output
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: ${{ runner.os }}-node-
- name: Cache Gatsby build output
uses: actions/cache@v1
with:
path: public
key: ${{ runner.os }}-public
path: |
~/.npm
${{ github.workspace }}/.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
restore-keys: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
- run: npm ci
- run: npm run build
env:
GATSBY_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NEXT_PUBLIC_TYPEKIT_ID: ${{ secrets.NEXT_PUBLIC_TYPEKIT_ID }}
- uses: actions/upload-artifact@v1
if: github.ref == 'refs/heads/main'
with:
name: public
path: public
# - uses: actions/upload-artifact@v1
# if: github.ref == 'refs/heads/main'
# with:
# name: public
# path: public
deploy:
needs: build
if: success() && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
# deploy:
# needs: build
# if: success() && github.ref == 'refs/heads/main'
# runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/download-artifact@v1
with:
name: public
- name: Deploy to S3
run: npm run deploy:s3
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
# steps:
# - uses: actions/checkout@v2
# - uses: actions/download-artifact@v1
# with:
# name: public
# - name: Deploy to S3
# run: npm run deploy:s3
# env:
# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
# AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}

45
.gitignore vendored
View File

@ -1,17 +1,40 @@
# Project dependencies
.cache
node_modules
yarn-error.log
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# Build directory
/public
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
plugins/gatsby-plugin-matomo
coverage
.env
static/matomo.js
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
.env
# vercel
.vercel
size-plugin.json
# typescript
*.tsbuildinfo
.swc
coverage
public/matomo.js
public/favicon/

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
16

View File

@ -1,24 +0,0 @@
# Dockerfile for local development just installing dependencies.
# Use together with `docker-compose up`
FROM node:alpine
RUN mkdir -p /portfolio
WORKDIR /portfolio
COPY package.json .
RUN apk add --no-cache --virtual .build-deps \
g++ \
make \
autoconf \
automake \
libtool \
nasm \
libc6-compat \
libjpeg-turbo-dev \
libpng-dev \
git \
bash \
&& rm -rf /var/cache/apk/* \
&& npm install \
&& npm cache clean --force \
&& apk del .build-deps

109
README.md
View File

@ -1,8 +1,8 @@
<p align="center">
<a href="https://matthiaskretschmann.com"><img src="src/images/github-header.png" /></a>
<a href="https://matthiaskretschmann.com"><img src="public/github-header.png" /></a>
</p>
<h2 align="center">
👔 Portfolio thingy, built with <a href="https://www.gatsbyjs.org">Gatsby</a>.
👔 Portfolio thingy.
</h2>
<p align="center">
<a href="https://matthiaskretschmann.com">matthiaskretschmann.com</a>
@ -13,17 +13,16 @@
<a href="https://codeclimate.com/github/kremalicious/portfolio/test_coverage"><img src="https://api.codeclimate.com/v1/badges/8f561ec93e0f8c6b15d9/test_coverage" /></a>
</p>
---
- [🎉 Features](#-features)
- [💍 One data file to rule all pages](#-one-data-file-to-rule-all-pages)
- [🗂 JSON Resume](#-json-resume)
- [🖼 Project images](#-project-images)
- [🐱 GitHub repositories](#-github-repositories)
- [📍 Location](#-location)
- [💅 Theme switcher](#-theme-switcher)
- [🏆 SEO component](#-seo-component)
- [📇 Client-side vCard creation](#-client-side-vcard-creation)
- [💫 Page transitions](#-page-transitions)
- [📈 Matomo (formerly Piwik) analytics tracking](#-matomo-formerly-piwik-analytics-tracking)
- [🖼 Project images](#-project-images)
- [💎 Importing SVG assets](#-importing-svg-assets)
- [🍬 Typekit component](#-typekit-component)
- [✨ Development](#-development)
@ -37,28 +36,27 @@
## 🎉 Features
The whole [portfolio](https://matthiaskretschmann.com) is a React-based single page app built with [Gatsby v3](https://www.gatsbyjs.org).
The whole [portfolio](https://matthiaskretschmann.com) is a React-based single page app built with [Next.js](https://nextjs.org) in Typescript, using only statically generated pages.
Most metadata is powered by one `resume.json` file based on [🗂 JSON Resume](#-json-resume), and one `projects.yml` file to [define the displayed projects](#-one-data-file-to-rule-all-pages).
If you are looking for the former Gatsby-based app, it is archived in the [`gatsby-deprecated`](https://github.com/kremalicious/portfolio/tree/gatsby-deprecated) branch.
### 💍 One data file to rule all pages
All displayed project content is powered by one YAML file where all the portfolio's projects are defined. The project description itself is transformed from Markdown written inside the YAML file into HTML on build time.
Gatsby automatically creates pages from each item in that file utilizing the [`{ProjectsYaml.slug}.jsx`](src/pages/{ProjectsYaml.slug}.jsx) template.
Next.js automatically creates pages from each item in that file utilizing the [`[slug].tsx`](src/pages/[slug].tsx) template.
- [`content/projects.yml`](content/projects.yml)
- [`src/pages/{ProjectsYaml.slug}.jsx`](src/pages/{ProjectsYaml.slug}.jsx)
- [`_content/projects.yml`](_content/projects.yml)
- [`src/pages/[slug].tsx`](src/pages/[slug].tsx)
### 🗂 JSON Resume
### 🖼 Project images
Most site metadata and social profiles are defined in [`content/resume.json`](content/resume.json) based on the [JSON Resume](https://jsonresume.org) standard and used throughout the site as a custom React hook. Additionally, a resume page is created under `/resume`.
All project images live under `public/images` and are automatically attached to each project based on the inclusion of the project's `slug` in their filenames.
If you want to know how, have a look at the respective components:
Next.js with `next/image` generates all required image sizes for delivering responsible, responsive images to visitors, including lazy loading of all images. For this to work, images are analyzed on build time and various image metadata is passed down as props.
- [`content/resume.json`](content/resume.json)
- [`src/pages/resume/index.jsx`](src/pages/resume/index.jsx)
- [`src/hooks/use-resume.js`](src/hooks/use-resume.js)
- [`src/components/ProjectImage/index.tsx`](src/components/ProjectImage/index.tsx)
- [`src/lib/content.ts`](src/lib/content.ts)
### 🐱 GitHub repositories
@ -68,20 +66,20 @@ On build time, all my public repositories are fetched from GitHub, then filtered
If you want to know how, have a look at the respective components:
- [`gatsby-node.js`](gatsby-node.js)
- [`content/repos.yml`](content/repos.yml)
- [`src/components/molecules/Repository.jsx`](src/components/molecules/Repository.jsx)
- [`src/lib/github.ts`](src/lib/github.ts)
- [`_content/repos.json`](_content/repos.json)
- [`src/components/Repository/index.tsx`](src/components/Repository/index.tsx)
### 📍 Location
On client-side, my current and, if known, my next physical location on a city level is fetched from my (private) [nomadlist.com](https://nomadlist.com) profile and displayed in the header.
Fetching is split up into a serverless function, a hook, and display component. Fetching is done with a serverless function as to not expose the whole profile response into the browser.
Fetching is split up into an external serverless function, a hook, and display component. Fetching is done with a serverless function as to not expose the whole profile response into the browser.
If you want to know how, have a look at the respective components:
- [`src/hooks/useLocation.js`](src/hooks/useLocation.js)
- [`src/components/molecules/Location.jsx`](src/components/molecules/Location.jsx)
- [`src/hooks/useLocation.ts`](src/hooks/useLocation.ts)
- [`src/components/Location/index.tsx`](src/components/Location/index.tsx)
- [kremalicious/location](https://github.com/kremalicious/location)
### 💅 Theme switcher
@ -90,8 +88,8 @@ Includes a theme switcher which allows user to toggle between a light and a dark
If you want to know how, have a look at the respective components:
- [`src/components/molecules/ThemeSwitch.jsx`](src/components/molecules/ThemeSwitch.jsx)
- [`src/hooks/useDarkMode.js`](src/hooks/useDarkMode.js)
- [`src/components/ThemeSwitch/index.tsx`](src/components/ThemeSwitch/index.tsx)
### 🏆 SEO component
@ -99,7 +97,7 @@ Includes a SEO component which automatically switches all required `meta` tags f
If you want to know how, have a look at the respective component:
- [`src/components/atoms/SEO.jsx`](src/components/atoms/SEO.jsx)
- [`src/components/Meta/index.tsx`](src/components/Meta/index.tsx)
### 📇 Client-side vCard creation
@ -107,44 +105,14 @@ The _Add to addressbook_ link in the footer automatically creates a downloadable
If you want to know how, have a look at the respective component:
- [`src/components/atoms/Vcard.jsx`](src/components/atoms/Vcard.jsx)
### 💫 Page transitions
Includes mechanism for transitioning between route changes with full page transitions defined with [Framer Motion](https://www.framer.com/motion/).
If you want to know how, have a look at the respective components:
- [`src/components/Layout.jsx`](src/components/Layout.jsx)
- [`src/helpers/wrapPageElement.jsx`](src/helpers/wrapPageElement.jsx)
- [`gatsby-browser.js`](gatsby-browser.js)
- [`gatsby-ssr.js`](gatsby-ssr.js)
### 📈 Matomo (formerly Piwik) analytics tracking
Site sends usage statistics to my own [Matomo](https://matomo.org) installation. To make this work in Gatsby, I created and open sourced a plugin, [gatsby-plugin-matomo](https://github.com/kremalicious/gatsby-plugin-matomo), which is in use on this site.
- [gatsby-plugin-matomo](https://github.com/kremalicious/gatsby-plugin-matomo)
### 🖼 Project images
All project images live under `content/images` and are automatically attached to each project based on the inclusion of the project's `slug` in their filenames.
All project images make use of the excellent [gatsby-plugin-image](https://www.gatsbyjs.com/docs/reference/built-in-components/gatsby-plugin-image/) plugin, working in tandem with [gatsby-plugin-sharp](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-sharp) and [gatsby-transformer-sharp](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-transformer-sharp).
All together, Gatsby automatically generates all required image sizes for delivering responsible, responsive images to visitors, including lazy loading of all images. Also includes the [intersection-observer polyfill](https://github.com/w3c/IntersectionObserver) to make lazy loading work properly in Safari.
All project images use one single component where one main GraphQL query fragment is defined, which then gets used throughout other GraphQL queries.
- [`src/components/atoms/ProjectImage.jsx`](src/components/atoms/ProjectImage.jsx)
- [`src/components/Vcard/index.tsx`](src/components/Vcard/index.tsx)
### 💎 Importing SVG assets
All SVG assets under `src/images/` will be converted to React components with the help of [gatsby-plugin-svgr](https://github.com/zabute/gatsby-plugin-svgr). Makes use of [SVGR](https://github.com/smooth-code/svgr) so SVG assets can be imported like so:
All SVG assets will be converted to React components with the help of [@svgr/webpack](https://react-svgr.com). Makes use of [SVGR](https://github.com/smooth-code/svgr) so SVG assets can be imported like so:
```js
import { ReactComponent as Logo } from './components/svg/Logo'
import Logo from './components/svg/Logo'
return <Logo />
```
@ -154,32 +122,26 @@ Includes a component for adding the Typekit snippet.
If you want to know how, have a look at the respective component:
- [`src/components/atoms/Typekit.jsx`](src/components/atoms/Typekit.jsx)
- [`src/components/Typekit/index.tsx`](src/components/Typekit/index.tsx)
## ✨ Development
You can simply use [Docker](https://www.docker.com) & [Docker Compose](https://docs.docker.com/compose/) or install and run dependencies on your local system.
```bash
git clone git@github.com:kremalicious/portfolio.git
cd portfolio/
# GATSBY_GITHUB_TOKEN is required for some parts
# GITHUB_TOKEN is required for some parts
# See https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token
cp .env.sample .env
vi .env
# use Docker
docker-compose up
# or go with local system
npm i
npm start
npm run dev
```
### 🔮 Linting
ESlint, Prettier, and Stylelint are setup for all linting purposes:
ESLint, Prettier, and Stylelint are setup for all linting purposes:
```bash
npm run lint
@ -189,20 +151,19 @@ To automatically format all code files:
```bash
npm run format
npm run format:css
```
### 👩‍🔬 Testing
Test suite is setup with [Jest](https://jestjs.io) and [react-testing-library](https://github.com/kentcdodds/react-testing-library).
To run all tests, including all linting tests:
To run all tests, including type checking and linting of all files:
```bash
npm test
```
Most test files live beside the respective component. Testing setup, fixtures, and mocks can be found in the `./tests/` folder.
Most test files live beside the respective component. Testing setup, fixtures, and mocks can be found in the `tests/` folder.
### 🎈 Add a new project
@ -212,9 +173,9 @@ To add a new project, run the following command. This adds a new item to the top
npm run new -- "Hello"
```
Then continue modifying the new entry in [`content/projects.yml`](content/projects.yml).
Then continue modifying the new entry in [`_content/projects.yml`](_content/projects.yml).
Finally, add as many images as needed with the file name format and put into `content/images/`:
Finally, add as many images as needed with the file name format and put into `public/images/`:
```text
SLUG-01.png
@ -241,7 +202,7 @@ Upon live deployment, deploy script also pings search engines. GitHub requires t
## 🏛 Licenses
**© Copyright 2019 Matthias Kretschmann**
**© Copyright 2022 Matthias Kretschmann**
All images and projects are plain ol' copyright, most displayed projects are subject to the copyright of their respective owners.

21
_content/meta.json Normal file
View File

@ -0,0 +1,21 @@
{
"description": "Portfolio of web & ui designer/developer Matthias Kretschmann.",
"img": "twitter-card.png",
"url": "https://matthiaskretschmann.com",
"availability": {
"status": false,
"available": "👔 Available for new projects. <a href=\"mailto:m@kretschmann.io\">Lets talk</a>!",
"unavailable": "Not available for new projects."
},
"gpg": "https://kretschmann.io/pub.gpg",
"addressbook": "/matthias-kretschmann.vcf",
"bugs": "https://github.com/kremalicious/portfolio/issues/new",
"matomoUrl": "https://analytics.kremalicious.com",
"matomoSite": "2",
"allowedHosts": [
"matthiaskretschmann.com",
"beta.matthiaskretschmann.com",
"localhost",
"05.local"
]
}

View File

@ -1,7 +1,6 @@
- title: 'Ocean Market'
slug: '/oceanprotocol-market/'
img: 'images/oceanprotocol-market-01.png'
description: >
- title: Ocean Market
slug: oceanprotocol-market
description: |
As part of Ocean Protocol v3, I was leading the planning and execution of the Ocean Market product and decentralized web app in 2020. Ocean Market allows simple tokenization, secure sharing, and monetization of data assets within the Ethereum network with the help of data tokens.
Assets can be exposed to dynamic price discovery by attaching Automated Market Maker (AMM) pools to them. Metadata is stored on-chain, and all actions like downloading or Compute-to-Data are done via exchanging data tokens, creating trustless provenance records in the process.
@ -10,16 +9,17 @@
links:
- title: Ocean Makes Multi-Network Even Easier
icon: FileText
url: https://kremalicious.com/ocean-makes-multi-network-even-easier
url: 'https://kremalicious.com/ocean-makes-multi-network-even-easier'
- title: 'Ocean Market: An Open-Source Community Marketplace for Data'
icon: FileText
url: https://blog.oceanprotocol.com/ocean-market-an-open-source-community-marketplace-for-data-4b99bedacdc3
url: |
https://blog.oceanprotocol.com/ocean-market-an-open-source-community-marketplace-for-data-4b99bedacdc3
- title: market.oceanprotocol.com
icon: Compass
url: https://market.oceanprotocol.com
url: 'https://market.oceanprotocol.com'
- title: '@oceanprotocol/market'
icon: GitHub
url: https://github.com/oceanprotocol/market
url: 'https://github.com/oceanprotocol/market'
techstack:
- HTML
- CSS
@ -30,24 +30,23 @@
- 3Box
- Polygon
- Binance Smart Chain
- title: 'Ocean Protocol - IPFS Integration'
slug: '/oceanprotocol-ipfs/'
img: 'images/oceanprotocol-ipfs-01.png'
description: >
- title: Ocean Protocol - IPFS Integration
slug: oceanprotocol-ipfs
description: |
In 2019 I was leading the integration of IPFS into the Ocean Protocol stack, from the core to the libraries, up to the UI in multiple touchpoints.
For making IPFS as decentralized file storage solution as seemless as possible to use within Ocean Protocol, a public IPFS node was created with a simple user-facing UI and API. The integration was completed by allowing users to add their files to IPFS directly in the [Commons](/oceanprotocol-commons/) publish flow.
links:
- title: Ocean Protocol and IPFS, Sitting In The Merkle Tree
- title: 'Ocean Protocol and IPFS, Sitting In The Merkle Tree'
icon: FileText
url: https://kremalicious.com/ocean-protocol-and-ipfs-sitting-in-the-merkle-tree
url: |
https://kremalicious.com/ocean-protocol-and-ipfs-sitting-in-the-merkle-tree
- title: ipfs.oceanprotocol.com
icon: Compass
url: https://ipfs.oceanprotocol.com
url: 'https://ipfs.oceanprotocol.com'
- title: '@oceanprotocol/ipfs'
icon: GitHub
url: https://github.com/oceanprotocol/ipfs
url: 'https://github.com/oceanprotocol/ipfs'
techstack:
- HTML
- CSS
@ -58,11 +57,9 @@
- Kubernetes
- Jest
- IPFS
- title: 'Ocean Protocol - Commons'
slug: '/oceanprotocol-commons/'
img: 'images/oceanprotocol-commons-01.png'
description: >
- title: Ocean Protocol - Commons
slug: oceanprotocol-commons
description: |
From 20182019 I was leading the design and development of the Commons marketplace, a Web3 data marketplace to explore, download, and publish open data sets registered in the [Ocean Protocol](/oceanprotocol) network.
As the main front-facing UI of Ocean Protocol v1 & v2, it served as a product and showcase bringing together the complete Ocean Protocol stack in a simple interface. Additionally, the project was created with developers in mind, making Commons the most complete boilerplate for creating dApps on top of Ocean Protocol.
@ -71,16 +68,16 @@
links:
- title: The Commons Marketplace in Pacific Network
icon: FileText
url: https://kremalicious.com/the-commons-marketplace-in-pacific-network
url: 'https://kremalicious.com/the-commons-marketplace-in-pacific-network'
- title: The Commons Marketplace
icon: FileText
url: https://kremalicious.com/the-commons-marketplace
url: 'https://kremalicious.com/the-commons-marketplace'
- title: commons.oceanprotocol.com
icon: Compass
url: https://commons.oceanprotocol.com
url: 'https://commons.oceanprotocol.com'
- title: '@oceanprotocol/commons'
icon: GitHub
url: https://github.com/oceanprotocol/commons
url: 'https://github.com/oceanprotocol/commons'
techstack:
- HTML
- SCSS
@ -93,11 +90,9 @@
- Cypress
- Jest
- IPFS
- title: 'Ocean Protocol v1'
slug: '/oceanprotocol-v1/'
img: 'images/oceanprotocol-v1-01.png'
description: >
- title: Ocean Protocol v1
slug: oceanprotocol-v1
description: |
Since 2017 I'm leading the UI design & development of Ocean Protocol, iterating on a components-based UI design system spanning all of Ocean Protocol's web properties. Additionally, I conceptualize, execute and iterate on the creative and visual direction of the Ocean Protocol brand.
Most web interfaces are single-page JavaScript applications built with React, pulling their data from multiple sources. All design & development is embedded in continuous deployment processes via GitHub, Travis, Kubernetes, and Vercel.
@ -105,23 +100,21 @@
In 2020 I was leading the refresh of Ocean Protocol's visual identity for the release of v3 and the [Ocean Market](/oceanprotocol-market/).
Initial website in collaboration with [Balance](https://balance.io/). Key visuals in collaboration with [Wojciech Hupert](https://twitter.com/wojciechhupert).
links:
- title: oceanprotocol.com
icon: Compass
url: https://oceanprotocol.com
url: 'https://oceanprotocol.com'
- title: Styleguide
url: https://oceanprotocol.com/art
url: 'https://oceanprotocol.com/art'
- title: docs.oceanprotocol.com
icon: Compass
url: https://docs.oceanprotocol.com
url: 'https://docs.oceanprotocol.com'
- title: '@oceanprotocol/art'
icon: GitHub
url: https://github.com/oceanprotocol/art
url: 'https://github.com/oceanprotocol/art'
- title: '@oceanprotocol/docs'
icon: GitHub
url: https://github.com/oceanprotocol/docs
url: 'https://github.com/oceanprotocol/docs'
techstack:
- Sketch
- Affinity Designer
@ -139,17 +132,14 @@
- Docker
- Kubernetes
- IPFS
- title: IPDB
slug: /ipdb/
img: 'images/ipdb-01.png'
description: >
slug: ipdb
description: |
From 20152017 I was leading the UI design & development of all IPDB web properties and additionally iterated on the creative and visual direction of the IPDB brand.
The main website is a static site built with Jekyll and a custom Gulp-based build pipeline in front of it. All design & development is embedded in a continuous deployment process via GitHub & Travis.
Branding and key visuals in collaboration with [Wojciech Hupert](https://twitter.com/wojciechhupert).
techstack:
- Sketch
- Jekyll
@ -161,17 +151,14 @@
- AWS S3
- Cloudflare
- 3Scale
links:
- title: GitHub
url: https://github.com/ipdb/website
icon: GitHub
url: 'https://github.com/ipdb/website'
- title: Berlin Innovation Ventures
slug: /biv/
img: images/biv-01.png
description: >
slug: biv
description: |
I designed & developed the website and a basic branding for the Berlin-based VC firm Berlin Innovation Ventures. The main website is a static site built with Jekyll and a custom Gulp-based build pipeline in front of it.
techstack:
- Sketch
- Jekyll
@ -179,22 +166,21 @@
- HTML
- SCSS
- JavaScript
links:
- title: Link
url: http://berlininnovation.vc
- title: '9984 >> Summit 2017'
slug: /9984/
img: images/9984-01.png
icon: Compass
url: 'http://berlininnovation.vc'
- title: 9984 >> Summit 2017
slug: '9984'
img: /images/9984-01.png
links:
- title: Link
url: https://2017.9984.io
url: 'https://2017.9984.io'
- title: Styleguide
url: https://2017.9984.io/styleguide/
url: 'https://2017.9984.io/styleguide'
- title: GitHub
url: https://github.com/9984/2017.9984.io
description: >
url: 'https://github.com/9984/2017.9984.io'
description: |
In 2017 I was leading the UI design & development for the 9984 >> Summit, the first joint summit of BigchainDB & IPDB. Additionally, I conceptualized, executed and iterated on the creative and visual direction of the 9984 brand.
The main website is a static site built with Jekyll and a custom Gulp-based build pipeline in front of it. All design & development is embedded in a continuous deployment process via GitHub & Travis.
@ -210,17 +196,14 @@
- Travis
- AWS S3
- Cloudflare
- title: BigchainDB
slug: /bigchaindb/
img: images/bigchaindb-01.png
description: >
slug: bigchaindb
description: |
From 20162019 I was leading the UI design & development of all BigchainDB web properties. I created the initial BigchainDB brand and further conceptualized, executed and iterated on the creative and visual direction of BigchainDB. This included creating and iterating on a components-based UI design system for all of BigchainDB's web properties.
The main website is a static site built with Jekyll and a custom Gulp-based build pipeline in front of it, pulling data from various external sources and microservices. All design & development is embedded in a continuous deployment process via GitHub & Travis.
Branding & key visuals in collaboration with [Wojciech Hupert](https://twitter.com/wojciechhupert).
techstack:
- BigchainDB
- Sketch
@ -234,27 +217,23 @@
- Travis
- AWS S3
- Cloudflare
links:
- title: Link
url: https://www.bigchaindb.com
url: 'https://www.bigchaindb.com'
- title: Styleguide
url: https://www.bigchaindb.com/styleguide/
url: 'https://www.bigchaindb.com/styleguide'
- title: GitHub
url: https://github.com/bigchaindb/site
url: 'https://github.com/bigchaindb/site'
- title: Dribbble
url: https://dribbble.com/shots/2522184-BigchainDB-site
url: 'https://dribbble.com/shots/2522184-BigchainDB-site'
- title: ascribe
slug: /ascribe/
img: images/ascribe-01.png
description: >
slug: ascribe
description: |
With ascribe, users were able to tokenize digital art and real life assets onto the Bitcoin blockchain. It was one of the very first decentralized apps using a decentralized ledger as its backend, all before Ethereum even existed. The principles around digital art as entries in a blockchain, given out in editions, would later inspire the ERC-721 NFT standard on the Ethereum blockchain. Finally, the learnings around a decentralized approach for storing structured data led to the creation of [BigchainDB](/bigchaindb).
From 2015-2017 I worked on the UI development for multiple web touch points, including the main web app built with one of the first versions of React. The service and web app was [discontinued in 2017](https://www.ascribe.io/).
Branding & key visuals in collaboration with [Wojciech Hupert](https://twitter.com/wojciechhupert).
techstack:
- ascribe
- Sketch
@ -269,17 +248,14 @@
- Cloudflare
- React
- Bitcoin
links:
- title: Link
url: https://www.ascribe.io
url: 'https://www.ascribe.io'
- title: GitHub
url: https://github.com/ascribe
url: 'https://github.com/ascribe'
- title: ChartMogul
slug: /chartmogul/
img: images/chartmogul-01.png
description: >
slug: chartmogul
description: |
From 20152017 I was co-designing and leading the UI design & development of various ChartMogul web properties. This included the creation of a components-based UI design system and implementing it across all web touch points.
The main website with its landing pages is a static site built with Jekyll and a custom Gulp-based build pipeline in front of it, while the blog is running on WordPress with its own custom theme. All embedded in an automated development & deployment workflow via GitHub and Travis.
@ -287,7 +263,6 @@
Besides designing and implementing new features, I maintained the front-end of the ChartMogul application and implemented the UI design system by refactoring most of its front-end codebase.
All branding, design & key visuals directed by Michelle Myung.
techstack:
- Sketch
- Affinity Designer
@ -304,23 +279,19 @@
- Cloudflare
- Ruby on Rails
- Backbone.js
links:
- title: Link
url: https://chartmogul.com/
url: 'https://chartmogul.com'
- title: Styleguide
url: https://chartmogul.com/styleguide/
url: 'https://chartmogul.com/styleguide'
- title: Dribbble
url: https://dribbble.com/kremalicious/projects/311439-ChartMogul
url: 'https://dribbble.com/kremalicious/projects/311439-ChartMogul'
- title: ShareTheMeal
slug: /sharethemeal/
img: images/sharethemeal-01.png
description: >
slug: sharethemeal
description: |
ShareTheMeal is an app from the United Nations World Food Programme (WFP) that enables people to "share their meals" with children in need. In 2015 I was consulting, co-designing and leading the front-end development of the ShareTheMeal website and various parts of the ShareTheMeal apps for iOS & Android.
The main website is a static site built with Jekyll and a custom Gulp-based build pipeline in front of it, embedded in a continuous deployment process via GitHub & Travis.
techstack:
- Sketch
- Illustrator
@ -333,21 +304,17 @@
- AWS S3
- Cloudflare
- Node.js
links:
- title: Link
url: https://sharethemeal.org/
url: 'https://sharethemeal.org'
- title: ezeep
slug: /ezeep/
img: images/ezeep-01.png
description: >
From 20122015 I worked at ezeep, where I helped creating an unprecedented, market-leading & award-winning user experience based on the principles of emotional design way ahead of all competitors. This included conceptualizing executing, and iterating on the creative & visual direction of the ezeep brand.
slug: ezeep
description: |
From 20122015 I helped create an unprecedented, market-leading & award-winning user experience based on the principles of emotional design way ahead of all competitors. This included conceptualizing executing, and iterating on the creative & visual direction of the ezeep brand.
I was leading the UI design & development of all ezeep touch points and - as a product designer - defined the ezeep product based on user and market research in an iterative process. On top of that, I designed and helped building all app experiences of ezeep on Windows, macOS, iOS, and Android.
ezeep was acquired by [Cortado AG](https://www.cortado.com) in 2015 and became part of their [ThinPrint Cloud Services](https://www.thinprintcloud.com) suite of products.
techstack:
- Photoshop
- Illustrator
@ -364,55 +331,43 @@
- Node.js
- Backbone.js
- Electron
links:
- title: Info
url: https://kremalicious.com/enterprise-software-sucks/
url: 'https://kremalicious.com/enterprise-software-sucks'
- title: Dribbble
url: https://dribbble.com/kremalicious/projects/84318-ezeep
url: 'https://dribbble.com/kremalicious/projects/84318-ezeep'
- title: Mr. Reader
slug: /mrreader/
img: images/mrreader-01.png
description: >
slug: mrreader
description: |
While working with indy iOS developer Curious Times in 2012, I designed the app icon, a custom theme, and various promotion materials for Mr. Reader, a powerful and highly loved RSS feed reader for iPad.
techstack:
- Photoshop
- title: iPixelPad
slug: /ipixelpad/
img: images/ipixelpad-01.png
description: >
slug: ipixelpad
description: |
So, what to do when everyone seem to release iPad icons but fail to include some crisp small size icons? Pushing the pixels for yourself of course. So heres my take on the smaller sizes of an Apple iPad icon, called iPixelPad.
Released as a goodie on [kremalicious.com](https://kremalicious.com/ipixelpad/).
techstack:
- Photoshop
links:
- title: Info & Download
url: https://kremalicious.com/ipixelpad/
url: 'https://kremalicious.com/ipixelpad'
- title: Out Of Whale Oil
slug: /outofwhaleoil/
img: images/outofwhaleoil-01.jpg
description: >
slug: outofwhaleoil
description: |
Tribute wallpaper pack inspired by the Futurama movie _Into The Wild Green Yonder_. Released as a goodie on [kremalicious.com](https://kremalicious.com/out-of-whale-oil/).
links:
- title: Info & Download
url: https://kremalicious.com/out-of-whale-oil/
url: 'https://kremalicious.com/out-of-whale-oil'
techstack:
- Photoshop
- title: 'Martin-Luther-Universität Halle-Wittenberg'
slug: /unihalle/
img: images/unihalle-01.png
description: >
- title: Martin-Luther-Universität Halle-Wittenberg
slug: unihalle
description: |
From 20092012 I worked at the IT services department of [Martin-Luther-Universität Halle-Wittenberg](http://www.uni-halle.de) where I conceptualized, designed & implemented numerous in-house and public facing interfaces for thousands of students and staff.
Additionally, I conceptualized, designed, created, and maintained the blog network & community for all students & staff.
techstack:
- Photoshop
- Illustrator
@ -424,46 +379,34 @@
- WordPress
- Ilias
- Stud.IP
links:
- title: Link
url: http://blogs.urz-uni-halle.de
url: 'http://blogs.urz-uni-halle.de'
- title: Dribbble
url: https://dribbble.com/kremalicious/projects/690029-MLU
url: 'https://dribbble.com/kremalicious/projects/690029-MLU'
- title: Coffee Cup
slug: /coffeecup/
img: images/coffeecup-01.png
description: >
Desktop icons showing the fuel of most designers. Released as a goodie
on [kremalicious.com](https://kremalicious.com/coffee-cup-icon/).
slug: coffeecup
description: |
Desktop icons showing the fuel of most designers. Released as a goodie on [kremalicious.com](https://kremalicious.com/coffee-cup-icon/).
techstack:
- Photoshop
links:
- title: Info & Download
url: https://kremalicious.com/coffee-cup-icon/
url: 'https://kremalicious.com/coffee-cup-icon'
- title: Project Purple
slug: /projectpurple/
img: images/projectpurple-01.png
description: >
slug: projectpurple
description: |
It had been revealed the original iPhone was developed in a locked down building under the name Project Purple and because of the secrecy involved, the team decorated the building with Fight Club references. Perfect story to create a wallpaper out of it.
Released as a goodie on [kremalicious.com](https://kremalicious.com/projectpurple/).
techstack:
- Photoshop
links:
- title: Info & Download
url: https://kremalicious.com/projectpurple/
url: 'https://kremalicious.com/projectpurple'
- title: Allinnia Creative Group
slug: /allinnia/
img: images/allinnia-01.png
description: >
slug: allinnia
description: |
In 2009 I created the branding, website, and various key visuals for professional music production studio Allinnia Creative Group, reflecting their own musical compositions.
The website was built from scratch as a simple PHP application with a store and music listening functionality.
@ -473,38 +416,30 @@
- CSS
- JavaScript
- PHP
- title: Aperture Loupe
slug: /apertureloupe/
img: images/apertureloupe-01.png
description: >
slug: apertureloupe
description: |
When Apple released their professional photography app _Aperture_ in 2008, the loupe tool in there was something really novel and just fun to play with. Inspired by that, I created this macOS desktop icon.
techstack:
- Photoshop
- title: Adiumeetie
slug: /adiumeetie/
img: images/adiumeetie-01.png
description: >
slug: adiumeetie
description: |
A macOS replacement desktop icon created in 2009 for the popular Mac IM client Adium following the style of atebits excellent Tweetie for Mac icon. Released as a goodie on [kremalicious.com](https://kremalicious.com/adiumeetie/).
techstack:
- Photoshop
links:
- title: Download
url: https://kremalicious.com/adiumeetie/
- title: "Niépce's Camera Obscura"
slug: /niepces-camera-obscura/
img: images/niepces-camera-obscura-01.jpg
description: >
url: 'https://kremalicious.com/adiumeetie'
- title: Niépce's Camera Obscura
slug: niepces-camera-obscura
description: |
In 2008 I used the camera obscura as it was used by Nicéphore Niépce in 1826 to create an Aperture and iPhoto replacement icon.
Nicéphore Niépce made it first possible to preserve an image taken with a camera obscura in 1826 or 1827 by using a special mixture of bitumen on a glass or metal plate, naming it Heliography. This first preserved image 'View from the Window at Le Gras' is the one you can see in the iPhoto icon.
links:
- title: Info
url: https://kremalicious.com/niepces-camera-obscura-and-the-history-of-the-first-photograph/
url: |
https://kremalicious.com/niepces-camera-obscura-and-the-history-of-the-first-photograph
techstack:
- Photoshop

12
_content/repos.json Normal file
View File

@ -0,0 +1,12 @@
[
"kremalicious/portfolio",
"kremalicious/blog",
"kremalicious/blowfish",
"kremalicious/gatsby-plugin-matomo",
"kremalicious/gatsby-redirect-from",
"kremalicious/hyper-mac-pro",
"kremalicious/appstorebadges",
"kremalicious/kbdfun",
"kremalicious/ipfs",
"oceanprotocol/market"
]

View File

@ -1,24 +0,0 @@
# most personal metadata can be found in ./resume.json
- description: Portfolio of web &amp; ui designer/developer hybrid Matthias Kretschmann.
img: ../src/images/twitter-card.png
availability:
status: false
available: '👔 Available for new projects. <a href="mailto:m@kretschmann.io">Lets talk</a>!'
unavailable: Not available for new projects.
# Footer actions
gpg: https://kretschmann.io/pub.gpg
addressbook: /matthias-kretschmann.vcf
bugs: https://github.com/kremalicious/portfolio/issues/new
# Analytics tools
matomoUrl: https://analytics.kremalicious.com
matomoSite: 2
allowedHosts:
- matthiaskretschmann.com
- beta.matthiaskretschmann.com
- localhost
- 05.local

View File

@ -1,10 +0,0 @@
- kremalicious/portfolio
- kremalicious/blog
- kremalicious/blowfish
- kremalicious/gatsby-plugin-matomo
- kremalicious/gatsby-redirect-from
- kremalicious/hyper-mac-pro
- kremalicious/appstorebadges
- kremalicious/kbdfun
- kremalicious/ipfs
- oceanprotocol/market

View File

@ -1,10 +0,0 @@
version: '3'
services:
dev:
build: .
command: npm start
volumes:
- .:/portfolio
- /portfolio/node_modules
ports:
- '8000:8000'

View File

@ -1,36 +0,0 @@
import wrapPageElementWithLayout from './src/helpers/wrapPageElement'
// Global styles
import './src/styles/global.css'
import './src/styles/_toast.css'
// IntersectionObserver polyfill for gatsby-image (Safari, IE)
if (typeof window !== 'undefined' && !window.IntersectionObserver) {
import('intersection-observer')
}
// Layout with Page Transitions
export const wrapPageElement = wrapPageElementWithLayout
export const shouldUpdateScroll = ({
routerProps: { location },
getSavedScrollPosition
}) => {
if (location.action === 'PUSH') {
window.setTimeout(() => window.scrollTo(0, 0), 200)
} else {
const savedPosition = getSavedScrollPosition(location)
window.setTimeout(() => window.scrollTo(...(savedPosition || [0, 0])), 200)
}
return false
}
// Display a message when a service worker updates
// https://www.gatsbyjs.org/docs/add-offline-support-with-a-service-worker/#displaying-a-message-when-a-service-worker-updates
export const onServiceWorkerUpdateReady = () => {
const div = document.createElement('div')
div.id = 'toast'
div.classList.add('alert', 'alert-info')
div.innerHTML = `<button onClick="window.location.reload()">Updates are available. <span>Click to Reload</span>.</button>`
document.body.append(div)
}

View File

@ -1,79 +0,0 @@
const path = require('path')
const fs = require('fs')
const yaml = require('js-yaml')
const meta = yaml.load(fs.readFileSync('./content/meta.yml', 'utf8'))
const resume = require('./content/resume.json')
const { matomoSite, matomoUrl } = meta[0]
const { name, website } = resume.basics
require('dotenv').config()
if (
!process.env.GATSBY_GITHUB_TOKEN ||
process.env.GATSBY_GITHUB_TOKEN === 'xxx'
) {
throw Error(`
A GitHub token as GATSBY_GITHUB_TOKEN is required to build some parts of the blog.
Check the README https://github.com/kremalicious/portfolio#-development.
`)
}
module.exports = {
siteMetadata: {
siteUrl: `${website}`
},
plugins: [
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'content',
path: path.join(__dirname, 'content')
}
},
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'images',
path: path.join(__dirname, 'src', 'images')
}
},
'gatsby-transformer-yaml',
'gatsby-transformer-json',
'gatsby-plugin-image',
'gatsby-plugin-sharp',
'gatsby-transformer-sharp',
{
resolve: 'gatsby-plugin-svgr',
options: {
icon: true
}
},
{
resolve: 'gatsby-plugin-matomo',
options: {
siteId: `${matomoSite}`,
siteUrl: `${website}`,
matomoUrl: `${matomoUrl}`,
localScript: '/matomo.js',
trackLoad: false
}
},
{
resolve: 'gatsby-plugin-manifest',
options: {
name: name.toLowerCase(),
short_name: 'mk',
start_url: '/',
background_color: '#e7eef4',
theme_color: '#e7eef4',
icon: 'src/images/favicon.png',
display: 'standalone',
cache_busting_mode: 'name',
theme_color_in_head: false // dynamically set in ThemeSwitch
}
},
'gatsby-plugin-react-helmet',
'gatsby-plugin-sitemap',
'gatsby-plugin-offline'
]
}

View File

@ -1,113 +0,0 @@
/* eslint-disable no-console */
const remark = require('remark')
const parse = require('remark-parse')
const html = require('remark-html')
const fs = require('fs')
const yaml = require('js-yaml')
const reposYaml = yaml.load(fs.readFileSync('./content/repos.yml', 'utf8'))
const { performance } = require('perf_hooks')
const chalk = require('chalk')
const { execSync } = require('child_process')
const { getGithubRepos } = require('./scripts/github')
function truncate(n, useWordBoundary) {
if (this.length <= n) {
return this
}
const subString = this.substr(0, n - 1)
return (
(useWordBoundary
? subString.substr(0, subString.lastIndexOf(' '))
: subString) + '...'
)
}
//
// Fetch matomo.js
//
execSync(`node ./scripts/fetch-matomo-js > static/matomo.js`, {
stdio: 'inherit'
})
//
// Get GitHub repos once and store for later build stages
//
let repos
exports.onPreBootstrap = async () => {
const t0 = performance.now()
try {
repos = await getGithubRepos(reposYaml)
const t1 = performance.now()
const ms = t1 - t0
const s = ((ms / 1000) % 60).toFixed(3)
console.log(
chalk.green('success ') + `getGithubRepos: ${repos.length} repos - ${s} s`
)
} catch (error) {
throw Error(error.message)
}
}
//
// Add pageContext
//
exports.onCreatePage = async ({ page, actions }) => {
const { createPage, deletePage } = actions
// Regex for auto-attaching project images to pages based on slug.
// Image file names follow the pattern slug-01.png.
// Regex inspiration from https://stackoverflow.com/a/7124976
const imageRegex = `/${page.path.replace(/\//g, '')}+?(?=-\\d)/`
deletePage(page)
createPage({
...page,
context: {
...page.context,
imageRegex,
// Add repos only to front page's context
...(page.path === '/' && { repos })
}
})
}
exports.onCreateNode = ({ node, actions }) => {
const { createNodeField } = actions
// Projects YAML nodes
if (node.internal.type === 'ProjectsYaml') {
// Add transformed Markdown descriptions
const description = node.description
const descriptionWithLineBreaks = description.split('\n').join('\n\n')
let descriptionHtml
remark()
.use(parse, { gfm: true, commonmark: true, pedantic: true })
.use(html)
.process(descriptionWithLineBreaks, (err, file) => {
if (err) throw Error('Could not transform project description')
descriptionHtml = file.contents
return descriptionHtml
})
createNodeField({
node,
name: 'descriptionHtml',
value: descriptionHtml
})
// Create excerpt from description
const excerpt = truncate.apply(description, [320, true])
createNodeField({
node,
name: 'excerpt',
value: excerpt
})
}
}

View File

@ -1,4 +0,0 @@
import wrapPageElementWithLayout from './src/helpers/wrapPageElement'
// Layout with Page Transitions
export const wrapPageElement = wrapPageElementWithLayout

5
next-env.d.ts vendored Normal file
View File

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

32
next.config.js Normal file
View File

@ -0,0 +1,32 @@
// @ts-check
const next = (phase, { defaultConfig }) => {
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
webpack: (config, options) => {
config.module.rules.push(
{
test: /\.svg$/,
issuer: /\.(tsx|ts)$/,
use: [{ loader: '@svgr/webpack', options: { icon: true } }]
},
{
test: /\.gif$/,
// yay for webpack 5
// https://webpack.js.org/guides/asset-management/#loading-images
type: 'asset/resource'
}
)
return typeof defaultConfig.webpack === 'function'
? defaultConfig.webpack(config, options)
: config
}
}
return nextConfig
}
export default next

34495
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,83 +1,72 @@
{
"name": "@kremalicious/portfolio",
"description": "Portfolio thingy, built with Gatsby",
"description": "Portfolio thingy",
"version": "0.1.0",
"homepage": "https://matthiaskretschmann.com",
"repository": "github:kremalicious/portfolio",
"license": "MIT",
"author": "Matthias Kretschmann <m@kretschmann.io>",
"type": "module",
"scripts": {
"start": "gatsby develop --host 0.0.0.0",
"build": "gatsby build",
"ssr": "npm run build && serve -s public/",
"lint:js": "eslint ./gatsby-*.js && eslint ./src/**/*.{js,jsx}",
"lint:css": "stylelint ./src/**/*.{css,scss}",
"start": "npm run pregenerate && next",
"build": "npm run pregenerate && next build",
"preview": "npm run build && next start",
"export": "npm run pregenerate && next export",
"typecheck": "tsc",
"lint:js": "next lint",
"lint:css": "stylelint ./src/**/*.css",
"lint": "npm run lint:js && npm run lint:css",
"format": "prettier --write 'src/**/*.{js,jsx,css,scss}'",
"test": "NODE_ENV=test npm run lint && jest --coverage -c tests/jest.config.js",
"test:watch": "NODE_ENV=test npm run lint && jest --coverage --watch -c tests/jest.config.js",
"format": "prettier --write 'src/**/*.{ts,tsx,css}'",
"jest": "jest --coverage -c tests/jest.config.ts",
"test": "NODE_ENV=test npm run lint && npm run typecheck && npm run jest",
"deploy:s3": "./scripts/deploy-s3.sh",
"new": "babel-node ./scripts/new.js"
"new": "ts-node-esm ./scripts/new.ts",
"favicon": "ts-node-esm ./scripts/favicon.ts",
"pregenerate": "npm run favicon"
},
"dependencies": {
"@giphy/js-fetch-api": "^4.4.0",
"@kremalicious/react-feather": "^2.1.0",
"@socialgouv/matomo-next": "^1.4.0",
"@yaireo/relative-time": "^1.0.2",
"axios": "^1.1.3",
"file-saver": "^2.0.5",
"framer-motion": "^7.6.4",
"gatsby": "^4.24.0",
"gatsby-plugin-image": "^2.24.0",
"gatsby-plugin-manifest": "^4.24.0",
"gatsby-plugin-matomo": "^0.13.0",
"gatsby-plugin-offline": "^5.24.0",
"gatsby-plugin-react-helmet": "^5.24.0",
"gatsby-plugin-sharp": "^4.24.0",
"gatsby-plugin-sitemap": "^5.24.0",
"gatsby-plugin-svgr": "^3.0.0-beta.0",
"gatsby-source-filesystem": "^4.24.0",
"gatsby-transformer-json": "^4.24.0",
"gatsby-transformer-sharp": "^4.24.0",
"gatsby-transformer-yaml": "^4.24.0",
"intersection-observer": "^0.12.2",
"next": "13.0.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-helmet": "^6.1.0",
"remark": "13.0.0",
"remark-breaks": "2.0.2",
"remark-html": "13.0.2",
"remark-parse": "9.0.0",
"remark-react": "8.0.0",
"remark": "^14.0.2",
"remark-gfm": "^3.0.1",
"remark-html": "^15.0.1",
"vcf": "^2.1.1"
},
"devDependencies": {
"@babel/node": "^7.20.2",
"@babel/preset-env": "^7.20.2",
"@svgr/webpack": "^6.5.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@welldone-software/why-did-you-render": "^7.0.1",
"babel-preset-gatsby": "^2.24.0",
"chalk": "4.1.2",
"@types/jest": "^29.2.2",
"@types/js-yaml": "^4.0.5",
"@types/sharp": "^0.31.0",
"chalk": "^5.1.2",
"eslint": "^8.27.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-graphql": "^4.0.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.31.10",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-testing-library": "^5.9.1",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.2.2",
"eslint-config-next": "^13.0.3",
"favicons": "^7.0.2",
"jest": "^29.3.1",
"jest-canvas-mock": "^2.4.0",
"jest-environment-jsdom": "^29.2.2",
"jest-environment-jsdom": "^29.3.1",
"js-yaml": "^4.1.0",
"ora": "^6.1.2",
"prepend": "^1.0.2",
"prettier": "^2.7.1",
"sharp": "^0.31.2",
"slugify": "^1.6.5",
"stylelint": "^14.14.1",
"stylelint-config-prettier": "^9.0.3",
"stylelint-prettier": "^2.0.0"
"stylelint-config-prettier": "^9.0.4",
"stylelint-prettier": "^2.0.0",
"ts-node": "^10.9.1",
"typescript": "^4.8.4"
},
"engines": {
"node": "16.x"
},
"browserslist": [
"> 0.2%",

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -4,14 +4,14 @@
/* TEAM */
design & development : matthias kretschmann
Site : https://matthiaskretschmann.com
Site : https://matthiaskretschmann.com
Twitter : @kremalicious
GitHub : @kremalicious
Location : Berlin, Germany
GitHub : @kremalicious
Location : Lisboa, Portugal
/* SITE */
Typography : Brandon Grotesque, FF Tisa Sans Web Pro
Software : Gatsby.js, React, VS Code, Sketch, macOS
Typography : Brandon Grotesque, FF Tisa Sans Web Pro
Software : Next.js, React, VS Code, macOS
<!--

View File

Before

Width:  |  Height:  |  Size: 878 KiB

After

Width:  |  Height:  |  Size: 878 KiB

View File

Before

Width:  |  Height:  |  Size: 668 KiB

After

Width:  |  Height:  |  Size: 668 KiB

View File

Before

Width:  |  Height:  |  Size: 784 KiB

After

Width:  |  Height:  |  Size: 784 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

View File

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

View File

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 154 KiB

View File

Before

Width:  |  Height:  |  Size: 283 KiB

After

Width:  |  Height:  |  Size: 283 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

Before

Width:  |  Height:  |  Size: 437 KiB

After

Width:  |  Height:  |  Size: 437 KiB

View File

Before

Width:  |  Height:  |  Size: 625 KiB

After

Width:  |  Height:  |  Size: 625 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

Before

Width:  |  Height:  |  Size: 578 KiB

After

Width:  |  Height:  |  Size: 578 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

View File

Before

Width:  |  Height:  |  Size: 901 KiB

After

Width:  |  Height:  |  Size: 901 KiB

View File

Before

Width:  |  Height:  |  Size: 504 KiB

After

Width:  |  Height:  |  Size: 504 KiB

View File

Before

Width:  |  Height:  |  Size: 530 KiB

After

Width:  |  Height:  |  Size: 530 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

Before

Width:  |  Height:  |  Size: 319 KiB

After

Width:  |  Height:  |  Size: 319 KiB

View File

Before

Width:  |  Height:  |  Size: 797 KiB

After

Width:  |  Height:  |  Size: 797 KiB

View File

Before

Width:  |  Height:  |  Size: 515 KiB

After

Width:  |  Height:  |  Size: 515 KiB

View File

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

View File

Before

Width:  |  Height:  |  Size: 333 KiB

After

Width:  |  Height:  |  Size: 333 KiB

View File

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 137 KiB

View File

Before

Width:  |  Height:  |  Size: 592 KiB

After

Width:  |  Height:  |  Size: 592 KiB

View File

Before

Width:  |  Height:  |  Size: 291 KiB

After

Width:  |  Height:  |  Size: 291 KiB

View File

Before

Width:  |  Height:  |  Size: 534 KiB

After

Width:  |  Height:  |  Size: 534 KiB

View File

Before

Width:  |  Height:  |  Size: 415 KiB

After

Width:  |  Height:  |  Size: 415 KiB

View File

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

Before

Width:  |  Height:  |  Size: 425 KiB

After

Width:  |  Height:  |  Size: 425 KiB

View File

Before

Width:  |  Height:  |  Size: 414 KiB

After

Width:  |  Height:  |  Size: 414 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

Before

Width:  |  Height:  |  Size: 726 KiB

After

Width:  |  Height:  |  Size: 726 KiB

View File

Before

Width:  |  Height:  |  Size: 714 KiB

After

Width:  |  Height:  |  Size: 714 KiB

View File

Before

Width:  |  Height:  |  Size: 445 KiB

After

Width:  |  Height:  |  Size: 445 KiB

View File

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

View File

Before

Width:  |  Height:  |  Size: 229 KiB

After

Width:  |  Height:  |  Size: 229 KiB

View File

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

View File

Before

Width:  |  Height:  |  Size: 796 KiB

After

Width:  |  Height:  |  Size: 796 KiB

View File

Before

Width:  |  Height:  |  Size: 305 KiB

After

Width:  |  Height:  |  Size: 305 KiB

View File

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

View File

Before

Width:  |  Height:  |  Size: 181 KiB

After

Width:  |  Height:  |  Size: 181 KiB

View File

Before

Width:  |  Height:  |  Size: 532 KiB

After

Width:  |  Height:  |  Size: 532 KiB

View File

Before

Width:  |  Height:  |  Size: 530 KiB

After

Width:  |  Height:  |  Size: 530 KiB

View File

Before

Width:  |  Height:  |  Size: 455 KiB

After

Width:  |  Height:  |  Size: 455 KiB

View File

Before

Width:  |  Height:  |  Size: 446 KiB

After

Width:  |  Height:  |  Size: 446 KiB

View File

Before

Width:  |  Height:  |  Size: 620 KiB

After

Width:  |  Height:  |  Size: 620 KiB

View File

Before

Width:  |  Height:  |  Size: 538 KiB

After

Width:  |  Height:  |  Size: 538 KiB

View File

Before

Width:  |  Height:  |  Size: 381 KiB

After

Width:  |  Height:  |  Size: 381 KiB

View File

Before

Width:  |  Height:  |  Size: 298 KiB

After

Width:  |  Height:  |  Size: 298 KiB

View File

Before

Width:  |  Height:  |  Size: 882 KiB

After

Width:  |  Height:  |  Size: 882 KiB

View File

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

Before

Width:  |  Height:  |  Size: 629 KiB

After

Width:  |  Height:  |  Size: 629 KiB

View File

Before

Width:  |  Height:  |  Size: 622 KiB

After

Width:  |  Height:  |  Size: 622 KiB

View File

Before

Width:  |  Height:  |  Size: 751 KiB

After

Width:  |  Height:  |  Size: 751 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

Before

Width:  |  Height:  |  Size: 742 KiB

After

Width:  |  Height:  |  Size: 742 KiB

View File

Before

Width:  |  Height:  |  Size: 428 KiB

After

Width:  |  Height:  |  Size: 428 KiB

View File

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -1,3 +0,0 @@
{
"presets": ["@babel/env"]
}

90
scripts/favicon.ts Normal file
View File

@ -0,0 +1,90 @@
import { favicons, FaviconImage } from 'favicons'
import path from 'path'
import fs from 'fs'
const imagesDirectory = path.resolve(path.join(process.cwd(), 'src', 'images'))
const source = `${imagesDirectory}/favicon.png`
const output = path.resolve(path.join(process.cwd(), 'public', 'favicon'))
const configuration = {
path: '/favicon', // Path for overriding default icons path. `string`
appName: 'matthias kretschmann',
appShortName: 'mk',
appDescription: null,
developerName: null,
developerURL: null,
dir: 'auto',
lang: 'en-US',
appleStatusBarStyle: 'black-translucent', // Style for Apple status bar: "black-translucent", "default", "black". `string`
display: 'minimal-ui', // Preferred display mode: "fullscreen", "standalone", "minimal-ui" or "browser". `string`
orientation: 'any', // Default orientation: "any", "natural", "portrait" or "landscape". `string`
scope: '/', // set of URLs that the browser considers within your app
start_url: '/?homescreen=1',
background_color: '#e7eef4',
theme_color: '#e7eef4',
preferRelatedApplications: false,
relatedApplications: undefined,
version: '1.0',
pixel_art: false, // Keeps pixels "sharp" when scaling up, for pixel art. Only supported in offline mode.
loadManifestWithCredentials: false,
manifestMaskable: `${imagesDirectory}/logo.svg`, // Maskable source image(s) for manifest.json. "true" to use default source. More information at https://web.dev/maskable-icon/. `boolean`, `string`, `buffer` or array of `string`
icons: {
android: true,
appleIcon: true,
appleStartup: false,
favicons: true,
windows: true,
yandex: false
}
}
async function buildFavicons() {
try {
const response = await favicons(source, configuration)
const allFilesToWrite = response.images.concat(response.files as any)
fs.rmSync(output, { recursive: true, force: true })
allFilesToWrite.forEach((file) => {
const { name, contents } = file
const destination = `${output}/${name}`
try {
fs.readFileSync(destination, 'utf8')
} catch (error) {
// if there is no file, get data and write a fresh file
if (error.code === 'ENOENT') {
try {
fs.mkdirSync(output, { recursive: true })
fs.writeFileSync(destination, contents)
} catch (error) {
throw new Error("Couldn't write favicon file")
}
}
}
})
const destinationHtml = `${output}/_meta.html`
try {
fs.readFileSync(destinationHtml, 'utf8')
} catch (error) {
// if there is no file, get data and write a fresh file
if (error.code === 'ENOENT') {
try {
fs.mkdirSync(output, { recursive: true })
fs.writeFileSync(
destinationHtml,
response.html.reverse().toString().replaceAll(',', '')
)
} catch (error) {
throw new Error("Couldn't write favicon file")
}
}
}
} catch (error) {
console.error(error.message)
}
}
buildFavicons()

View File

@ -1,11 +0,0 @@
#!/usr/bin/env node
'use strict'
const axios = require('axios')
const url =
'https://raw.githubusercontent.com/matomo-org/matomo/4.x-dev/matomo.js'
axios(url).then((response) => {
process.stdout.write(`${response.data}`)
})

View File

@ -1,42 +0,0 @@
const axios = require('axios')
//
// Get GitHub repos
//
const gitHubConfig = {
headers: {
'User-Agent': 'kremalicious/portfolio',
Authorization: `token ${process.env.GATSBY_GITHUB_TOKEN}`
}
}
async function getGithubRepos(data) {
let repos = []
let holder = {}
for (let item of data) {
const user = item.split('/')[0]
const repoName = item.split('/')[1]
const repo = await axios.get(
`https://api.github.com/repos/${user}/${repoName}`,
gitHubConfig
)
holder.name = repo.data.name
holder.full_name = repo.data.full_name
holder.description = repo.data.description
holder.html_url = repo.data.html_url
holder.homepage = repo.data.homepage
holder.stargazers_count = repo.data.stargazers_count
holder.pushed_at = repo.data.pushed_at
repos.push(holder)
holder = {}
}
// sort by pushed to, newest first
repos = repos.sort((a, b) => b.pushed_at.localeCompare(a.pushed_at))
return repos
}
module.exports = { getGithubRepos }

View File

@ -1,10 +1,12 @@
#!/usr/bin/env ts-node
import fs from 'fs'
import path from 'path'
import prepend from 'prepend'
import slugify from 'slugify'
import ora from 'ora'
const templatePath = path.join(__dirname, 'new.yml')
const templatePath = path.join(process.cwd(), 'scripts', 'new.yml')
const template = fs.readFileSync(templatePath).toString()
const spinner = ora('Adding new project').start()
@ -17,14 +19,14 @@ const title = process.argv[2]
spinner.text = `Adding '${title}'.`
const titleSlug = slugify(title, { lower: true })
const projects = path.join(__dirname, '..', 'content', 'projects.yml')
const projects = path.join(process.cwd(), '_content', 'projects.yml')
const newContents = template
.split('TITLE')
.join(title)
.split('SLUG')
.join(titleSlug)
prepend(projects, newContents, error => {
prepend(projects, newContents, (error) => {
if (error) spinner.fail(error)
spinner.succeed(`Added '${title}' to top of projects.yml file.`)
})

View File

@ -1,12 +0,0 @@
#!/usr/bin/env bash
set -e
SRC='./src/images'
OUT='./src/components/svg'
printf "Creating SVG components...\\n\\n"
# Usage: svgr [-d out-dir] [src-dir]
./node_modules/@svgr/cli/bin/svgr --icon -d $OUT $SRC
printf "\\n🎉 Successfully created SVG components\\n\\n"

Some files were not shown because too many files have changed in this diff Show More