Merge pull request #1 from hazemKrimi/rebuild

Rebuild using Hugo
This commit is contained in:
Hazem Krimi
2023-11-24 16:30:19 +01:00
committed by GitHub
152 changed files with 1959 additions and 4565 deletions
-11
View File
@@ -1,11 +0,0 @@
{
"presets": ["next/babel"],
"plugins": [
[
"styled-components",
{
"ssr": true
}
]
]
}
-2
View File
@@ -1,2 +0,0 @@
NEXT_PUBLIC_FORMSPREE_KEY=FORMSPREE_KEY
NEXT_PUBLIC_GOOGLE_ANALYTICS_KEY=GOOGLE_ANALYTICS_KEY
+76
View File
@@ -0,0 +1,76 @@
# Sample workflow for building and deploying a Hugo site to GitHub Pages
name: Deploy to GitHub Pages
on:
# Runs on pushes targeting the default branch
push:
branches:
- rebuild
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: 'pages'
cancel-in-progress: false
# Default to bash
defaults:
run:
shell: bash
jobs:
# Build job
build:
runs-on: ubuntu-latest
env:
HUGO_VERSION: 0.120.2
steps:
- name: Install Hugo CLI
run: |
wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
&& sudo dpkg -i ${{ runner.temp }}/hugo.deb
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Setup Pages
id: pages
uses: actions/configure-pages@v3
- name: Install Node.js dependencies
run: '[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true'
- name: Build with Hugo
env:
# For maximum backward compatibility with Hugo modules
HUGO_ENVIRONMENT: production
HUGO_ENV: production
run: |
hugo \
--gc \
--minify \
--baseURL "${{ steps.pages.outputs.base_url }}/"
- name: Upload artifact
uses: actions/upload-pages-artifact@v2
with:
path: ./public
# Deployment job
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2
+3 -33
View File
@@ -1,34 +1,4 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. public/
resources/
# dependencies .hugo_build.lock
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
-32
View File
@@ -1,32 +0,0 @@
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
-66
View File
@@ -1,66 +0,0 @@
---
title: 'Astrobuild'
description: 'Prototype of a collaboration tool between stakeholders for building software projects'
date: '2023-06-11'
demo: 'https://astrobuild.vercel.app'
code: 'https://github.com/hazemKrimi/astrobuild'
tags: ['react', 'typescript', 'graphql', 'styled-components', 'apollographql', 'vite', 'react-flow']
---
# Introduction
This was my final year of studies projects where my collegue and I worked on a prototype for a project building solution for the agency we had an internship in called [Astrolab](https://astrolab-agency.com).
As there were lockdowns in Tunisia in 2020/2021 because of covid software agencies and clients could not have in person meetings to discuss a client's software project.
So the idea of Astrobuild is to reduce meetings and make the client participate with other stakeholders in the process of creating a software project by tracking and communicating with their associated product owner.
# Features
### Client
- Account management
- Project creation from choosing the templates, features and deliverables (Specification, design, MVP or full build)
- Project tracking
- Chat with associated product owner for tracking and support
- Payment (WIP)
### Product Owner
- Account management
- Management of templates
- Review of projects and transferring deliverables
- Chat with clients on their projects
### Developer
- Account management
- Features, categories and wireframes management
### Admin
- Identity and access management (WIP)
# Technologies Used
The frontend project is a [React](https://react.dev) application with [TypeScript](https://www.typescriptlang.org) which consumes a set of [GraphQL](https://graphql.org) APIs using [Apollo GraphQL](https://www.apollographql.com) and a some REST APIs.
A small components library with a custom theme was made for this project using [Styled Components](https://styled-components.com) which can be viewed at the components folder.
The prototyping feature was done using [React Flow](https://reactflow.dev) which is a library for creating and interacting with diagrams.
To view the full architecture of the application go [here](https://github.com/MedAmineFouzai/astrobuild-api/blob/main/README.md).
# Screenshots
### Project page for the client
![Project](https://github.com/hazemKrimi/astrobuild/blob/main/screenshots/project.png?raw=true)
### Template page for the product owner
![Template](https://github.com/hazemKrimi/astrobuild/blob/main/screenshots/template.png?raw=true)
### Prototype page for the developer
![Prototype](https://github.com/hazemKrimi/astrobuild/blob/main/screenshots/prototype.png?raw=true)
### Support page for the product owner
![Support](https://github.com/hazemKrimi/astrobuild/blob/main/screenshots/support.png?raw=true)
### User editing page for the admin
![Admin](https://github.com/hazemKrimi/astrobuild/blob/main/screenshots/admin.png?raw=true)
# Credits
- Mohamed Amine Fouzai: [GitHub](https://github.com/MedAmineFouzai), [LinkedIn](https://www.linkedin.com/in/amine-fouzai)
-23
View File
@@ -1,23 +0,0 @@
---
title: 'React Weather App'
description: 'Weather app made with React, TypeScript and OpenWeatherMap API'
date: '2021-09-19'
demo: 'https://hazemkrimi.github.io/react-weather-app'
code: 'https://github.com/hazemKrimi/react-weather-app'
tags: ['react', 'typescript', 'openweathermap']
---
# About the project
This is a project that I made as a step in the interview process for my final year internship.
# Technologies
- React
- TypeScript
- Styled Components
- OpenWeatherMap API
# Screenshots
![Desktop](https://res.cloudinary.com/dun9hhyz1/image/upload/v1643548378/personal-website/portfolio/react-weather-app/screenshot_ueu2a4.png)
+6
View File
@@ -0,0 +1,6 @@
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

+9
View File
@@ -0,0 +1,9 @@
section img {
float: right;
}
@media only screen and (max-width: 1024px) {
section img {
display: none;
}
}
+169
View File
@@ -0,0 +1,169 @@
@font-face {
font-family: 'Open Sans';
src: url('/fonts/OpenSans.ttf') format('ttf');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Open Sans';
src: url('/fonts/OpenSans-Italic.ttf') format('ttf');
font-weight: normal;
font-style: italic;
}
:root {
--black: #131314;
--white: white;
--crimson: #bd1839;
--light-gray: #e7e7e7;
--dark-background: #1d1b1b;
--light-background: #fbfbfb;
--partial-dark-background: var(--black);
--partial-light-background: #ececec;
--card-radius: 1.875rem;
--form-radius: 0.563rem;
--shadow: 0px 4px 8px rgba(0, 0, 0, 0.04);
}
*,
*::before,
*::after {
box-sizing: border-box;
}
* {
margin: 0;
}
::-webkit-scrollbar {
width: 0;
height: 0;
background: transparent;
}
::-webkit-scrollbar-thumb {
background: transparent;
}
::selection {
background: var(--text);
color: var(--background);
}
body {
font-family: 'Open Sans', sans-serif;
font-size: 16px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
background-color: var(--background);
color: var(--text);
isolation: isolate;
user-select: text;
}
body::-webkit-scrollbar {
width: 0.5rem !important;
}
body::-webkit-scrollbar-thumb {
background-color: var(--text) !important;
}
#links .linkedin,
#links .github,
#links .cv-paper-flip,
#links .mail,
#nav-toggler svg path,
.arrow,
.eye,
.calendar,
.clock,
.share {
stroke: var(--text);
}
#links .twitter,
#links .rss,
#links .cv,
#links .moon > path {
fill: var(--text);
}
main {
width: 85%;
min-height: 70vh;
margin: auto;
}
img,
picture,
video,
canvas,
svg {
max-width: 100%;
}
input,
button,
textarea,
select {
font: inherit;
}
p,
h1,
h2,
h3,
h4,
h5,
h6 {
overflow-wrap: break-word;
}
main h1,
main h2,
main h3 {
margin-bottom: 1rem;
}
main p {
margin-bottom: 2rem;
}
main h1 {
font-size: 4rem;
}
main h2 {
font-size: 3rem;
}
main h3 {
font-size: 2rem;
}
a {
color: var(--text);
}
a,
button {
cursor: pointer;
}
@media only screen and (max-width: 1024px) {
main h1 {
font-size: 2.5rem;
}
main h2 {
font-size: 1.75rem;
}
main h3 {
font-size: 1.25rem;
}
}
+75
View File
@@ -0,0 +1,75 @@
:root {
--first-action-dark-background: #353535;
--first-action-light-background: var(--partial-dark-background);
--second-action-dark-background: #f3f3f3;
--second-action-light-background: var(--partial-light-background);
}
main h1 {
text-transform: capitalize;
}
main h1 span,
main h1 a {
color: var(--crimson);
}
main h1 a {
text-decoration: none;
}
main #intro {
display: flex;
column-gap: 1rem;
}
main #intro img {
width: 19.6875rem;
height: 19.6875rem;
}
main #intro #action-buttons {
display: flex;
column-gap: 1rem;
}
main #intro #action-buttons a {
text-decoration: none;
border-radius: 0.5625rem;
padding: 1rem 2rem;
}
main #intro #action-buttons a:first-of-type {
background-color: var(--first-action-background);
color: var(--white);
}
main #intro #action-buttons a:last-of-type {
background-color: var(--second-action-background);
color: var(--black);
}
main section {
margin-bottom: 5rem;
}
main #projects > div:first-of-type,
main #blog > div:first-of-type {
display: flex;
align-items: center;
justify-content: space-between;
}
@media only screen and (max-width: 1024px) {
main section {
margin-bottom: 2rem;
}
main #intro #action-buttons {
margin-top: 1.5rem;
}
main #intro img {
display: none;
}
}
+18
View File
@@ -0,0 +1,18 @@
main #tags {
display: flex;
align-items: center;
column-gap: 1rem;
margin-bottom: 2rem;
}
main #tags a {
border-radius: 0.5625rem;
background-color: #5a5a5a;
color: var(--white);
padding: 0.5rem 1rem;
text-decoration: none;
}
main #tags .selected {
background-color: var(--crimson);
}
+418
View File
@@ -0,0 +1,418 @@
:root {
--about-card-light-background: var(--partial-light-background);
--about-card-dark-background: var(--partial-dark-background);
--button-light-background: var(--partial-dark-background);
--button-dark-background: #353535;
--card-light-background: var(--partial-light-background);
--card-dark-background: var(--partial-dark-background);
--input-light-background: var(--partial-light-background);
--input-dark-background: #2d2d2d;
--footer-light-background: var(--partial-light-background);
--footer-dark-background: var(--partial-dark-background);
--header-light-background: var(--partial-light-background);
--header-dark-background: #676666;
--nav-light-background: var(--partial-light-background);
--nav-dark-background: var(--partial-dark-background);
--toc-light-background: var(--partial-light-background);
--toc-dark-background: var(--partial-dark-background);
}
#about-card {
background-color: var(--about-card-background);
color: var(--text);
border-radius: 1.875rem;
padding: 3.5rem 2.5rem;
margin-bottom: 2rem;
display: flex;
column-gap: 2rem;
}
#about-card img {
width: 8.9375rem;
height: 8.9375rem;
}
#about-card h3 {
margin-bottom: 1.75rem;
text-transform: capitalize;
}
#about-card p {
margin-bottom: 4rem;
}
#about-card #links {
display: flex;
align-items: center;
justify-content: space-between;
}
.breadcrumb {
margin-bottom: 1.5rem;
}
.breadcrumb ol {
padding-left: 0;
}
.breadcrumb li {
display: inline;
}
.breadcrumb li:not(:last-child)::after {
content: '';
margin: 0rem 0.5rem;
}
.breadcrumb a {
text-transform: capitalize;
}
.breadcrumb .tag {
text-transform: lowercase;
}
.card {
background-color: var(--card-background);
color: var(--text);
border-radius: 1.875rem;
padding: 3.5rem 2.5rem;
margin-bottom: 2rem;
}
.card h3 {
margin-bottom: 1.75rem;
text-transform: capitalize;
}
.card .date {
font-weight: bold;
}
.card p {
margin-top: 1rem;
}
.card #links {
display: flex;
align-items: center;
justify-content: space-between;
}
.card .read-more,
.card .demo {
display: inline-flex;
align-items: center;
column-gap: 0.25rem;
}
.card .demo {
background-color: #353535;
color: var(--white);
border-radius: 0.4375rem;
text-decoration: none;
padding: 0.5rem 1rem;
}
.card .demo .eye {
stroke: var(--white);
}
input,
textarea,
button,
#submission-status {
border: none;
border-radius: 0.5625rem;
color: var(--text);
}
input,
textarea {
padding: 1.2rem 1.9rem;
background-color: var(--input-background);
}
textarea {
resize: none;
}
form {
display: flex;
flex-direction: column;
align-items: start;
justify-content: center;
row-gap: 1.7rem;
}
form input,
form textarea {
width: 100%;
}
form div {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
row-gap: 1.7rem;
}
form button,
form #submission-status {
padding: 1rem 2.8rem;
background-color: var(--button-background);
color: var(--white);
}
form #submission-status {
width: auto;
display: none;
}
footer #links,
header #links,
#mobile-navigation #links {
display: flex;
align-items: center;
column-gap: 1.5rem;
}
footer #links a,
header #links a,
header #menus a,
#mobile-navigation #links a {
display: inline-flex;
align-items: center;
justify-content: center;
}
footer {
width: 100%;
padding: 3rem 8rem;
margin-top: 2.5rem;
display: flex;
align-items: center;
flex-direction: column;
row-gap: 5rem;
background-color: var(--footer-background);
color: var(--text);
}
footer #footer-face {
display: flex;
align-items: center;
column-gap: 0.625rem;
}
footer #copyright {
font-size: 0.875rem;
}
header,
#mobile-navigation {
width: 85%;
border-radius: 0.75rem;
text-transform: uppercase;
color: var(--text);
}
header {
margin: 2.5rem auto;
padding: 0.938rem 2.188rem;
background-color: var(--header-background);
display: flex;
align-items: center;
justify-content: space-between;
}
#mobile-navigation {
background-color: var(--nav-background);
display: none;
position: fixed;
flex-direction: column;
}
header #header-face {
display: flex;
align-items: center;
column-gap: 0.625rem;
}
header #header-face span,
footer #footer-face span {
font-size: 1.17rem;
font-weight: 600;
}
header #menus a,
#mobile-navigation #menus a {
text-decoration: none;
font-weight: 600;
font-size: 15px;
}
header #menus {
display: flex;
align-items: center;
column-gap: 1.563rem;
}
#mobile-navigation #menus {
display: flex;
flex-direction: column;
align-items: end;
justify-content: center;
row-gap: 3.125rem;
}
#mobile-navigation hr {
margin-top: 3.125rem;
margin-bottom: 1.25rem;
}
#mobile-navigation #links {
display: flex;
justify-content: end;
column-gap: 1.5rem;
}
#mobile-navigation #links .theme-toggler {
margin-right: auto;
}
header #nav-toggler {
display: none;
}
.theme-toggler,
#nav-toggler {
cursor: pointer;
}
.pagination {
display: flex;
justify-content: end;
align-items: center;
}
.pagination {
display: flex;
column-gap: 1rem;
}
.pagination .pagination ul,
.pagination li {
list-style: none;
}
.pagination a {
border-radius: 0.5625rem;
background-color: #8b8b8b;
color: var(--white);
padding: 0.25rem 0.75rem;
display: inline-flex;
justify-content: center;
align-items: center;
text-decoration: none;
}
.pagination a.first,
.pagination a.last {
background-color: #606060;
}
.pagination a.active {
background-color: #232323;
}
#table-of-contents {
position: sticky;
top: 3rem;
margin-top: 3rem;
height: 25rem;
max-height: 25rem;
padding: 1.2rem 1.6rem;
border-radius: 0.75rem;
background-color: var(--toc-background);
}
#table-of-contents span {
font-weight: bold;
font-size: 1.5rem;
}
#table-of-contents nav {
margin-top: 0.5rem;
}
#table-of-contents nav ul {
list-style-type: none;
padding-inline-start: 2rem;
margin-bottom: 0rem;
}
#table-of-contents nav > ul:first-of-type {
padding: 0rem;
}
.vertical-separator {
border-left: 1px solid var(--text);
height: 24px;
opacity: 0.25;
}
hr {
color: var(--text);
opacity: 0.25;
}
@media only screen and (max-width: 768px) {
.pagination {
justify-content: center;
}
}
@media only screen and (max-width: 1024px) {
#about-card {
flex-direction: column;
row-gap: 2rem;
}
#about-card img {
align-self: center;
}
.card {
padding: 2.5rem 1.5rem;
}
.card h3 {
margin-bottom: 1.25rem;
}
.card p {
margin-bottom: 1.75rem;
}
footer {
padding: 3rem 5rem;
row-gap: 3rem;
}
header,
#mobile-navigation {
margin: 1.5rem auto;
padding: 0.938rem 1.25rem;
}
header #menus,
header #links {
display: none;
}
header #nav-toggler {
display: initial;
}
}
+66
View File
@@ -0,0 +1,66 @@
main #container {
display: grid;
grid-template-columns: auto minmax(15rem, 20rem);
column-gap: 2rem;
}
main #metadata {
margin-bottom: 2rem;
}
main #metadata div {
min-width: 100%;
display: flex;
column-gap: 2rem;
}
main #metadata div span {
display: inline-flex;
align-items: center;
column-gap: 0.25rem;
}
main #metadata div #share {
display: none;
}
main #metadata #share {
cursor: pointer;
}
main #content ul,
main #content ol,
main #content .highlight {
margin-bottom: 2rem;
}
main #content .highlight pre,
main #content .highlight div {
border-radius: 0.75rem;
}
main #content .highlight div {
padding: 2rem 1rem;
}
main #content .highlight pre {
padding: 0rem;
}
@media only screen and (max-width: 1024px) {
main #container {
display: block;
}
main #container #table-of-contents {
display: none;
}
main #metadata div {
column-gap: 0.75rem;
}
main #metadata div #share {
display: inline-flex;
}
}
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path class="arrow" d="M5 12H19" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path class="arrow" d="M12 5L19 12L12 19" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 340 B

+5
View File
@@ -0,0 +1,5 @@
<svg id='burger' width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 12H21" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 6H21" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 18H21" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 414 B

+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path class="calendar" d="M19 4H5C3.89543 4 3 4.89543 3 6V20C3 21.1046 3.89543 22 5 22H19C20.1046 22 21 21.1046 21 20V6C21 4.89543 20.1046 4 19 4Z" stroke="#CFCFCF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path class="calendar" d="M16 2V6" stroke="#CFCFCF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path class="calendar" d="M8 2V6" stroke="#CFCFCF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path class="calendar" d="M3 10H21" stroke="#CFCFCF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 695 B

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path class="clock" d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="#CFCFCF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path class="clock" d="M12 6V12L16 14" stroke="#CFCFCF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 449 B

+4
View File
@@ -0,0 +1,4 @@
<svg id='close' width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 6L18 18" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 318 B

+7
View File
@@ -0,0 +1,7 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path class='cv' d="M7.8002 15C7.64107 15 7.48845 14.9368 7.37593 14.8243C7.26341 14.7118 7.2002 14.5592 7.2002 14.4C7.2002 14.2409 7.26341 14.0883 7.37593 13.9758C7.48845 13.8633 7.64107 13.8 7.8002 13.8H16.2002C16.3593 13.8 16.5119 13.8633 16.6245 13.9758C16.737 14.0883 16.8002 14.2409 16.8002 14.4C16.8002 14.5592 16.737 14.7118 16.6245 14.8243C16.5119 14.9368 16.3593 15 16.2002 15H7.8002ZM7.8002 18C7.64107 18 7.48845 17.9368 7.37593 17.8243C7.26341 17.7118 7.2002 17.5592 7.2002 17.4C7.2002 17.2409 7.26341 17.0883 7.37593 16.9758C7.48845 16.8633 7.64107 16.8 7.8002 16.8H16.2002C16.3593 16.8 16.5119 16.8633 16.6245 16.9758C16.737 17.0883 16.8002 17.2409 16.8002 17.4C16.8002 17.5592 16.737 17.7118 16.6245 17.8243C16.5119 17.9368 16.3593 18 16.2002 18H7.8002Z" fill="black"/>
<path class='cv' d="M20.5501 8.64235V8.64231C20.55 8.15414 20.3667 7.68377 20.0366 7.32414C20.0366 7.32413 20.0366 7.32412 20.0366 7.32411L14.8598 1.68175C14.6771 1.48252 14.4549 1.32347 14.2073 1.21471C13.9598 1.10595 13.6924 1.04984 13.4221 1.04995C13.4221 1.04995 13.4221 1.04995 13.422 1.04995L5.4001 1.04995C4.88293 1.04995 4.38694 1.2554 4.02124 1.62109C3.65554 1.98679 3.4501 2.48278 3.4501 2.99995V21C3.4501 21.5171 3.65554 22.0131 4.02124 22.3788C4.38694 22.7445 4.88293 22.95 5.4001 22.95H18.6001C19.1173 22.95 19.6133 22.7445 19.979 22.3788C20.3446 22.0131 20.5501 21.5171 20.5501 21V8.64235ZM13.4221 2.54995L13.4223 2.54995C13.4847 2.54988 13.5465 2.56281 13.6037 2.58791C13.6608 2.61301 13.7122 2.64974 13.7544 2.69575L13.7544 2.69576L18.9312 8.33816L18.9313 8.3383C19.0076 8.42124 19.05 8.5298 19.0501 8.6425V21C19.0501 21.1193 19.0027 21.2338 18.9183 21.3182C18.8339 21.4025 18.7194 21.45 18.6001 21.45H5.4001C5.28075 21.45 5.16629 21.4025 5.0819 21.3181C4.99751 21.2338 4.9501 21.1193 4.9501 21V2.99995C4.9501 2.8806 4.99751 2.76614 5.0819 2.68175C5.16629 2.59736 5.28075 2.54995 5.4001 2.54995L13.4221 2.54995Z" fill="black" stroke="black" stroke-width="0.3"/>
<path class='cv-paper-flip' d="M13.8 2.52002V8.16002H19.44" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path class='cv' d="M9.76098 7.35952C9.93234 7.36476 10.103 7.33554 10.2629 7.27359C10.4227 7.21163 10.5685 7.11821 10.6916 6.99885C10.8147 6.87949 10.9125 6.73663 10.9793 6.57874C11.0461 6.42086 11.0806 6.25116 11.0806 6.07972C11.0806 5.90827 11.0461 5.73858 10.9793 5.58069C10.9125 5.4228 10.8147 5.27994 10.6916 5.16058C10.5685 5.04123 10.4227 4.9478 10.2629 4.88584C10.103 4.82389 9.93234 4.79467 9.76098 4.79992C9.42834 4.8101 9.11274 4.9494 8.88104 5.1883C8.64935 5.4272 8.51978 5.74692 8.51978 6.07972C8.51978 6.41251 8.64935 6.73223 8.88104 6.97113C9.11274 7.21003 9.42834 7.34933 9.76098 7.35952Z" fill="black"/>
<path class='cv' fill-rule="evenodd" clip-rule="evenodd" d="M12.3194 10.1329C12.3194 8.77207 11.1734 7.78687 9.7598 7.78687C8.3462 7.78687 7.2002 8.77087 7.2002 10.1329V10.7737C7.20051 10.8868 7.24566 10.9951 7.32574 11.075C7.40582 11.1548 7.5143 11.1997 7.6274 11.1997H11.8934C12.0063 11.1993 12.1145 11.1544 12.1943 11.0745C12.2741 10.9947 12.3191 10.8865 12.3194 10.7737V10.1329Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

+4
View File
@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13 17L18 12L13 7" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 17L11 12L6 7" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 319 B

+4
View File
@@ -0,0 +1,4 @@
<svg width="21" height="22" viewBox="0 0 21 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path class="eye" d="M0.875 11C0.875 11 4.375 4 10.5 4C16.625 4 20.125 11 20.125 11C20.125 11 16.625 18 10.5 18C4.375 18 0.875 11 0.875 11Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path class="eye" d="M10.5 13.625C11.9497 13.625 13.125 12.4497 13.125 11C13.125 9.55025 11.9497 8.375 10.5 8.375C9.05025 8.375 7.875 9.55025 7.875 11C7.875 12.4497 9.05025 13.625 10.5 13.625Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 604 B

+10
View File
@@ -0,0 +1,10 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_193_2670)">
<path class='github' d="M9 18.9999C4 20.4999 4 16.4999 2 15.9999M16 21.9999V18.1299C16.0375 17.6531 15.9731 17.1737 15.811 16.7237C15.6489 16.2737 15.3929 15.8634 15.06 15.5199C18.2 15.1699 21.5 13.9799 21.5 8.51994C21.4997 7.12376 20.9627 5.78114 20 4.76994C20.4559 3.54844 20.4236 2.19829 19.91 0.999938C19.91 0.999938 18.73 0.649938 16 2.47994C13.708 1.85876 11.292 1.85876 9 2.47994C6.27 0.649938 5.09 0.999938 5.09 0.999938C4.57638 2.19829 4.54414 3.54844 5 4.76994C4.03013 5.78864 3.49252 7.1434 3.5 8.54994C3.5 13.9699 6.8 15.1599 9.94 15.5499C9.611 15.8899 9.35726 16.2953 9.19531 16.7399C9.03335 17.1844 8.96681 17.658 9 18.1299V21.9999" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_193_2670">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 975 B

+5
View File
@@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path class='linkedin' d="M16 8C17.5913 8 19.1174 8.63214 20.2426 9.75736C21.3679 10.8826 22 12.4087 22 14V21H18V14C18 13.4696 17.7893 12.9609 17.4142 12.5858C17.0391 12.2107 16.5304 12 16 12C15.4696 12 14.9609 12.2107 14.5858 12.5858C14.2107 12.9609 14 13.4696 14 14V21H10V14C10 12.4087 10.6321 10.8826 11.7574 9.75736C12.8826 8.63214 14.4087 8 16 8Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path class='linkedin' d="M6 9H2V21H6V9Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path class='linkedin' d="M4 6C5.10457 6 6 5.10457 6 4C6 2.89543 5.10457 2 4 2C2.89543 2 2 2.89543 2 4C2 5.10457 2.89543 6 4 6Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 870 B

+4
View File
@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path class='mail' d="M4 4H20C21.1 4 22 4.9 22 6V18C22 19.1 21.1 20 20 20H4C2.9 20 2 19.1 2 18V6C2 4.9 2.9 4 4 4Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path class='mail' d="M22 6L12 13L2 6" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 419 B

+3
View File
@@ -0,0 +1,3 @@
<svg class='moon' width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M23.7259 14.3764C23.6052 14.2554 23.4538 14.1695 23.2879 14.128C23.1221 14.0866 22.9481 14.0911 22.7846 14.141C20.9897 14.6838 19.0812 14.7293 17.2625 14.2727C15.4437 13.8161 13.7829 12.8745 12.457 11.5484C11.1311 10.2221 10.1897 8.56099 9.73324 6.74189C9.27675 4.92278 9.32225 3.01388 9.86488 1.2186C9.91524 1.05501 9.92006 0.880785 9.87883 0.714661C9.83759 0.548537 9.75186 0.396799 9.63085 0.275766C9.50984 0.154733 9.35814 0.0689834 9.19205 0.0277403C9.02596 -0.0135029 8.85177 -0.00867944 8.68822 0.0416917C6.20667 0.802025 4.02809 2.32582 2.4625 4.39624C1.09335 6.21439 0.258206 8.37838 0.0508907 10.6451C-0.156425 12.9118 0.272303 15.1915 1.28891 17.2279C2.30551 19.2643 3.86972 20.9769 5.80579 22.1732C7.74186 23.3695 9.97308 24.0021 12.2488 23.9999C14.9037 24.0081 17.4881 23.1448 19.6053 21.5426C21.6753 19.9766 23.1988 17.7976 23.9589 15.3156C24.0087 15.1526 24.0133 14.9791 23.9723 14.8137C23.9313 14.6484 23.8461 14.4972 23.7259 14.3764ZM18.4733 20.0385C16.4795 21.5405 14.0104 22.2716 11.5204 22.0973C9.03039 21.9229 6.68719 20.8547 4.92212 19.0894C3.15705 17.3241 2.08893 14.9805 1.91438 12.49C1.73982 9.99955 2.47059 7.52982 3.97216 5.53548C4.95044 4.24331 6.21519 3.19585 7.66687 2.47553C7.58418 3.05601 7.5425 3.6416 7.54215 4.22794C7.54557 7.47309 8.83596 10.5843 11.1301 12.879C13.4243 15.1737 16.535 16.4643 19.7794 16.4678C20.3668 16.4676 20.9535 16.4259 21.535 16.343C20.8142 17.7952 19.7661 19.0603 18.4733 20.0385Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path class='rss' d="M9.53812 14.4619C10.2017 15.1221 10.7278 15.9073 11.086 16.772C11.4442 17.6368 11.6274 18.564 11.625 19.5001C11.625 19.7984 11.5065 20.0846 11.2955 20.2956C11.0845 20.5065 10.7984 20.6251 10.5 20.6251C10.2016 20.6251 9.91548 20.5065 9.7045 20.2956C9.49353 20.0846 9.375 19.7984 9.375 19.5001C9.375 18.2071 8.86139 16.9672 7.94715 16.0529C7.03291 15.1387 5.79293 14.6251 4.5 14.6251C4.20163 14.6251 3.91548 14.5065 3.70451 14.2956C3.49353 14.0846 3.375 13.7984 3.375 13.5001C3.375 13.2017 3.49353 12.9155 3.70451 12.7046C3.91548 12.4936 4.20163 12.3751 4.5 12.3751C5.43604 12.3725 6.36328 12.5557 7.22807 12.9139C8.09286 13.2721 8.87803 13.7983 9.53812 14.4619ZM4.5 7.87506C4.20163 7.87506 3.91548 7.99359 3.70451 8.20457C3.49353 8.41554 3.375 8.70169 3.375 9.00006C3.375 9.29843 3.49353 9.58458 3.70451 9.79556C3.91548 10.0065 4.20163 10.1251 4.5 10.1251C6.9864 10.1251 9.37097 11.1128 11.1291 12.8709C12.8873 14.6291 13.875 17.0137 13.875 19.5001C13.875 19.7984 13.9935 20.0846 14.2045 20.2956C14.4155 20.5065 14.7016 20.6251 15 20.6251C15.2984 20.6251 15.5845 20.5065 15.7955 20.2956C16.0065 20.0846 16.125 19.7984 16.125 19.5001C16.125 16.4169 14.9002 13.4601 12.7201 11.2799C10.54 9.09983 7.58314 7.87506 4.5 7.87506ZM15.9019 8.09819C14.4081 6.59603 12.6313 5.40511 10.6741 4.59438C8.71695 3.78365 6.61842 3.36921 4.5 3.37506C4.20163 3.37506 3.91548 3.49359 3.70451 3.70457C3.49353 3.91554 3.375 4.20169 3.375 4.50006C3.375 4.79843 3.49353 5.08458 3.70451 5.29556C3.91548 5.50653 4.20163 5.62506 4.5 5.62506C6.32283 5.61997 8.12857 5.97655 9.81264 6.67415C11.4967 7.37176 13.0257 8.39653 14.3109 9.68912C15.6035 10.9744 16.6283 12.5033 17.3259 14.1874C18.0235 15.8715 18.3801 17.6772 18.375 19.5001C18.375 19.7984 18.4935 20.0846 18.7045 20.2956C18.9155 20.5065 19.2016 20.6251 19.5 20.6251C19.7984 20.6251 20.0845 20.5065 20.2955 20.2956C20.5065 20.0846 20.625 19.7984 20.625 19.5001C20.6309 17.3816 20.2164 15.2831 19.4057 13.326C18.5949 11.3688 17.404 9.59192 15.9019 8.09819ZM4.875 17.6251C4.57833 17.6251 4.28832 17.713 4.04165 17.8779C3.79497 18.0427 3.60271 18.2769 3.48918 18.551C3.37565 18.8251 3.34594 19.1267 3.40382 19.4177C3.4617 19.7087 3.60456 19.9759 3.81434 20.1857C4.02412 20.3955 4.29139 20.5384 4.58237 20.5962C4.87334 20.6541 5.17494 20.6244 5.44903 20.5109C5.72311 20.3973 5.95738 20.2051 6.12221 19.9584C6.28703 19.7117 6.375 19.4217 6.375 19.1251C6.375 18.7272 6.21697 18.3457 5.93566 18.0644C5.65436 17.7831 5.27283 17.6251 4.875 17.6251Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

+7
View File
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path class='share' d="M18 8C19.6569 8 21 6.65685 21 5C21 3.34315 19.6569 2 18 2C16.3431 2 15 3.34315 15 5C15 6.65685 16.3431 8 18 8Z" stroke="#CFCFCF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path class='share' d="M6 15C7.65685 15 9 13.6569 9 12C9 10.3431 7.65685 9 6 9C4.34315 9 3 10.3431 3 12C3 13.6569 4.34315 15 6 15Z" stroke="#CFCFCF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path class='share' d="M18 22C19.6569 22 21 20.6569 21 19C21 17.3431 19.6569 16 18 16C16.3431 16 15 17.3431 15 19C15 20.6569 16.3431 22 18 22Z" stroke="#CFCFCF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path class='share' d="M8.59003 13.51L15.42 17.49" stroke="#CFCFCF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path class='share' d="M15.41 6.51001L8.59003 10.49" stroke="#CFCFCF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+11
View File
@@ -0,0 +1,11 @@
<svg class='sun' width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_193_2685)">
<path d="M12 17C14.7614 17 17 14.7614 17 12C17 9.23858 14.7614 7 12 7C9.23858 7 7 9.23858 7 12C7 14.7614 9.23858 17 12 17Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 1V3M12 21V23M4.22 4.22L5.64 5.64M18.36 18.36L19.78 19.78M1 12H3M21 12H23M4.22 19.78L5.64 18.36M18.36 5.64L19.78 4.22" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_193_2685">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 676 B

+10
View File
@@ -0,0 +1,10 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_193_2669)">
<path class='twitter' d="M18.2439 2.25H21.5519L14.3249 10.51L22.8269 21.75H16.1699L10.9559 14.933L4.98991 21.75H1.67991L9.40991 12.915L1.25391 2.25H8.07991L12.7929 8.481L18.2439 2.25ZM17.0829 19.77H18.9159L7.08391 4.126H5.11691L17.0829 19.77Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_193_2669">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 B

+121
View File
@@ -0,0 +1,121 @@
function initTheme() {
const persistedColorPreference = window.localStorage.getItem('theme');
const hasPersistedPreference = typeof persistedColorPreference === 'string';
if (hasPersistedPreference) {
return persistedColorPreference;
}
const mql = window.matchMedia('(prefers-color-scheme: dark)');
const hasMediaQueryPreference = typeof mql.matches === 'boolean';
if (hasMediaQueryPreference) {
return mql.matches ? 'dark' : 'light';
}
return 'light';
}
function loadTheme() {
root.style.setProperty('--theme', theme);
root.style.setProperty(
'--background',
theme === 'light' ? 'var(--light-background)' : 'var(--dark-background)'
);
root.style.setProperty(
'--header-background',
theme === 'light'
? 'var(--header-light-background)'
: 'var(--header-dark-background)'
);
root.style.setProperty(
'--nav-background',
theme === 'light'
? 'var(--nav-light-background)'
: 'var(--nav-dark-background)'
);
root.style.setProperty(
'--first-action-background',
theme === 'light'
? 'var(--first-action-light-background)'
: 'var(--first-action-dark-background)'
);
root.style.setProperty(
'--second-action-background',
theme === 'light'
? 'var(--second-action-light-background)'
: 'var(--second-action-dark-background)'
);
root.style.setProperty(
'--input-background',
theme === 'light'
? 'var(--input-light-background)'
: 'var(--input-dark-background)'
);
root.style.setProperty(
'--button-background',
theme === 'light'
? 'var(--button-light-background)'
: 'var(--button-dark-background)'
);
root.style.setProperty(
'--card-background',
theme === 'light'
? 'var(--card-light-background)'
: 'var(--card-dark-background)'
);
root.style.setProperty(
'--toc-background',
theme === 'light'
? 'var(--toc-light-background)'
: 'var(--toc-dark-background)'
);
root.style.setProperty(
'--about-card-background',
theme === 'light'
? 'var(--about-card-light-background)'
: 'var(--about-card-dark-background)'
);
root.style.setProperty(
'--footer-background',
theme === 'light'
? 'var(--footer-light-background)'
: 'var(--footer-dark-background)'
);
root.style.setProperty(
'--header-shadow',
theme === 'light' ? 'var(--shadow)' : 'none'
);
root.style.setProperty('--text', theme === 'light' ? 'black' : 'white');
root.style.setProperty('--color', theme === 'light' ? 'black' : 'white');
document.querySelector(
theme === 'light' ? 'header .moon' : 'header .sun'
).style.display = 'none';
document.querySelector(
theme === 'light' ? 'nav .moon' : 'nav .sun'
).style.display = 'none';
document.querySelector(
theme === 'light' ? 'header .sun' : 'header .moon'
).style.display = 'block';
document.querySelector(
theme === 'light' ? 'nav .sun' : 'nav .moon'
).style.display = 'block';
}
function updateTheme() {
theme = theme === 'light' ? 'dark' : 'light';
window.localStorage.setItem('theme', theme);
loadTheme();
}
const root = document.documentElement;
const themeTogglers = document.querySelectorAll('.theme-toggler');
let theme = initTheme();
document.addEventListener('DOMContentLoaded', loadTheme);
themeTogglers.forEach((themerToggler) =>
themerToggler.addEventListener('click', updateTheme)
);
+32
View File
@@ -0,0 +1,32 @@
const form = document.querySelector('form');
const submissionStatus = form.querySelector('#submission-status');
form.addEventListener('submit', (event) => {
event.preventDefault();
fetch(event.target.action, {
method: event.target.method,
body: new FormData(event.target),
headers: {
Accept: 'application/json',
},
})
.then((response) => {
if (response.ok) {
submissionStatus.innerHTML = 'Message sent successfully!';
submissionStatus.style.display = 'block';
form.reset();
}
})
.catch((error) => {
submissionStatus.innerHTML = 'Error sending message!';
submissionStatus.style.display = 'block';
console.error(error);
})
.finally(() => {
setTimeout(() => {
submissionStatus.innerHTML = '';
submissionStatus.style.display = 'none';
}, 5000);
});
});
+64
View File
@@ -0,0 +1,64 @@
function loadBurger() {
const headerInitialLeftPosition = header.getBoundingClientRect().x;
navToggler.querySelector(burgerOpen ? '#burger' : '#close').style.display =
'none';
navToggler.querySelector(burgerOpen ? '#close' : '#burger').style.display =
'block';
header.style.position = burgerOpen ? 'fixed' : 'initial';
header.style.top = burgerOpen ? '0px' : 'initial';
header.style.left = burgerOpen ? `${headerInitialLeftPosition}px` : 'initial';
mobileNavigation.style.display = burgerOpen ? 'flex' : 'none';
mobileNavigation.style.top = burgerOpen
? `calc(${header.getBoundingClientRect().y}px + ${
header.getBoundingClientRect().height
}px)`
: 'initial';
mobileNavigation.style.left = burgerOpen
? `${headerInitialLeftPosition}px`
: 'initial';
document.querySelector('main').style.marginTop = burgerOpen
? `calc(${header.getBoundingClientRect().height}px + 3rem)`
: '0px';
}
function updateBurger() {
burgerOpen = !burgerOpen;
loadBurger();
}
function resetBurger() {
burgerOpen = false;
loadBurger();
}
function resetBurgerWhenWindowResized() {
if (window.innerWidth > 1024) {
resetBurger();
}
}
function resetBurgerWhenClickedOutside(event) {
if (
mobileNavigation.style.display === 'flex' &&
event.target !== header &&
event.target !== mobileNavigation &&
!mobileNavigation.contains(event.target) &&
!navToggler.contains(event.target)
) {
resetBurger();
}
}
const navToggler = document.querySelector('#nav-toggler');
const header = document.querySelector('header');
const mobileNavigation = document.querySelector('nav');
let burgerOpen = false;
window.addEventListener('resize', resetBurgerWhenWindowResized);
document.addEventListener('DOMContentLoaded', resetBurger);
document.addEventListener('click', resetBurgerWhenClickedOutside);
navToggler.addEventListener('click', updateBurger);
-23
View File
@@ -1,23 +0,0 @@
import { Props } from './types';
import { StyledButton } from './styles';
const Button = ({
variant = 'text',
href,
target,
onClick,
children,
className,
}: Props) => (
<StyledButton
href={href}
target={target}
className={className}
onClick={onClick}
variant={variant}
>
{children}
</StyledButton>
);
export default Button;
-51
View File
@@ -1,51 +0,0 @@
import styled from 'styled-components';
import Link from 'next/link';
import { Props } from './types';
export const StyledButton = styled(Link)<Props>`
position: relative;
display: inline;
cursor: pointer;
background: none;
color: var(--text);
border: ${({ variant }) =>
variant === 'outline' ? '2px solid var(--text)' : '2px solid transparent'};
font-weight: bold;
text-transform: ${({ variant }) =>
variant === 'outline' ? 'uppercase' : 'inherit'};
padding: ${({ variant }) => (variant === 'outline' ? '.5rem 1rem' : '0rem')};
text-align: left;
text-decoration: none;
transition: color 250ms ease-in-out, border 250ms ease-in-out;
z-index: 1;
@media (max-width: 768px) {
padding: ${({ variant }) =>
variant === 'outline' ? '.5rem .75rem' : '0rem'};
}
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: -1;
background-color: ${({ variant }) =>
variant === 'outline' ? 'var(--text)' : 'inherit'};
transition: transform 250ms ease-in-out;
transform: scaleX(0);
transform-origin: left;
}
&:hover {
color: ${({ variant }) =>
variant === 'outline' ? 'var(--background)' : 'inherit'};
border: 2px solid transparent;
}
&:hover::before {
transform: scaleX(1);
}
`;
-8
View File
@@ -1,8 +0,0 @@
export type Props = {
variant?: 'outline' | 'text';
href: string;
target?: HTMLAnchorElement['target'];
onClick?: () => void;
children: React.ReactNode;
className?: string;
};
-41
View File
@@ -1,41 +0,0 @@
import Image from 'next/image';
import { StyledCard } from './styles';
import { Props } from './types';
const Card = ({
title,
description,
image,
tags,
href,
target,
onClick,
}: Props) => {
return (
<StyledCard
href={href}
onClick={onClick}
image={image ? Boolean(image) : undefined}
target={target}
>
<div className='card-content'>
<h3>{title}</h3>
<p>{description}</p>
{tags && (
<div className='tags-wrapper'>
{tags.map((tag, index) => (
<span key={index}>#{tag}&nbsp;</span>
))}
</div>
)}
</div>
{image && (
<div className='card-image'>
<Image alt={title} src={image} fill />
</div>
)}
</StyledCard>
);
};
export default Card;
-77
View File
@@ -1,77 +0,0 @@
import styled from 'styled-components';
import Link from 'next/link';
export const StyledCard = styled(Link)<{ image?: boolean }>`
cursor: pointer;
width: 100%;
display: grid;
grid-template-columns: ${({ image }) => (image ? 'auto 9.375rem' : 'auto')};
align-items: stretch;
transition: color 0ms ease-in-out;
text-decoration: none;
color: var(--text);
@media (max-width: 320px) {
grid-template-columns: ${({ image }) => (image ? 'auto 7.813rem' : 'auto')};
}
@media (min-width: 1440px) {
grid-template-columns: ${({ image }) =>
image ? 'auto 15.625rem' : 'auto'};
}
&:hover {
& > div {
background: ${({ theme }) => theme.colors.blue};
* {
color: ${({ theme }) => theme.colors.dark.text} !important;
}
}
img {
filter: ${({ image }) => (image ? 'grayscale(80%)' : 'none')};
}
}
.card-content {
padding: 1rem 0rem;
background: var(--secondary-background);
display: grid;
row-gap: 0.5rem;
@media (max-width: 768px) {
padding: 0.75rem 0rem;
}
}
.card-image {
position: relative;
width: 100%;
}
h3,
p,
.tags-wrapper {
padding: 0rem 1rem;
@media (max-width: 768px) {
padding: 0rem 0.5rem;
}
}
h3 {
font-size: 1.3rem;
}
.tags-wrapper {
display: flex;
flex-direction: row;
align-content: center;
flex-wrap: wrap;
}
span {
font-size: 0.7rem;
}
`;
-9
View File
@@ -1,9 +0,0 @@
export interface Props {
title: string;
description: string;
image?: string;
tags?: string[];
href: string;
target?: HTMLAnchorElement['target'];
onClick?: () => void;
}
-34
View File
@@ -1,34 +0,0 @@
import Highlight, { defaultProps, Language } from 'prism-react-renderer';
import theme from 'prism-react-renderer/themes/vsDark';
import { Props } from './types';
import { Line, LineContent, LineNo, Pre } from './styles';
const CodeBlock = ({ children, className }: Props) => {
const language = className.replace(/language-/, '') as Language;
return (
<Highlight
{...defaultProps}
theme={theme}
code={(children as string).trim()}
language={language}
>
{({ className, style, tokens, getLineProps, getTokenProps }) => (
<Pre className={className} style={style}>
{tokens.map((line, i) => (
<Line key={i} {...getLineProps({ line, key: i })}>
<LineNo>{i + 1}</LineNo>
<LineContent>
{line.map((token, key) => (
<span key={key} {...getTokenProps({ token, key })} />
))}
</LineContent>
</Line>
))}
</Pre>
)}
</Highlight>
);
};
export default CodeBlock;
-24
View File
@@ -1,24 +0,0 @@
import styled from 'styled-components';
export const Pre = styled.pre`
text-align: left;
margin: 1em 0;
padding: 0.5em;
overflow: scroll;
`;
export const Line = styled.div`
display: table-row;
`;
export const LineNo = styled.span`
display: table-cell;
text-align: right;
padding-right: 1em;
user-select: none;
opacity: 0.5;
`;
export const LineContent = styled.span`
display: table-cell;
`;
-4
View File
@@ -1,4 +0,0 @@
export interface Props {
className: string;
children: React.ReactNode;
}
-12
View File
@@ -1,12 +0,0 @@
import styled from 'styled-components';
const Container = styled.div`
width: 85%;
margin: auto;
@media (max-width: 768px) {
width: 95%;
}
`;
export default Container;
-54
View File
@@ -1,54 +0,0 @@
import { useContext } from 'react';
import { ThemeContext } from '../../styles/theme';
import { StyledFooter } from './styles';
import IconButton from '../IconButton';
const Footer = () => {
const { mode } = useContext(ThemeContext);
return (
<StyledFooter>
<div className='contact'>
<IconButton
alt='GitHub'
icon={
mode === 'dark'
? '/icons/light-github.svg'
: '/icons/dark-github.svg'
}
width={16}
height={16}
href='https://github.com/hazemKrimi'
target='_blank'
/>
<IconButton
alt='Twitter'
icon={
mode === 'dark'
? '/icons/light-twitter.svg'
: '/icons/dark-twitter.svg'
}
width={16}
height={16}
href='https://twitter.com/HazemKrimi'
target='_blank'
/>
<IconButton
alt='LinkedIn'
icon={
mode === 'dark'
? '/icons/light-linkedin.svg'
: '/icons/dark-linkedin.svg'
}
width={16}
height={16}
href='https://linkedin.com/in/hazemkrimi'
target='_blank'
/>
</div>
<p>Hazem Krimi &copy; {new Date().getFullYear()}</p>
</StyledFooter>
);
};
export default Footer;
-41
View File
@@ -1,41 +0,0 @@
import styled from 'styled-components';
export const StyledFooter = styled.footer`
position: absolute;
bottom: 0;
min-height: 100px;
width: 85%;
margin: auto;
display: grid;
grid-template-columns: repeat(2, 1fr);
column-gap: 2rem;
justify-content: flex-end;
align-content: center;
padding: 1rem 0rem;
@media (max-width: 768px) {
width: 95%;
}
.contact {
display: grid;
grid-template-columns: repeat(auto-fill, 16px);
column-gap: 1rem;
align-items: center;
justify-content: flex-start;
* {
user-select: none;
}
@media (max-width: 768px) {
column-gap: 0.5rem;
}
}
p {
display: inline;
text-align: right;
font-weight: bold;
}
`;
-72
View File
@@ -1,72 +0,0 @@
import { createGlobalStyle } from 'styled-components';
const GlobalStyles = createGlobalStyle`
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Source Code Pro', monospace;
font-size: 16px;
line-height: 1.5;
outline: none;
user-select: text;
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
@media(max-width: 768px) {
overflow-x: scroll;
}
&::-webkit-scrollbar {
width: 0;
height: 0;
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: transparent;
}
&::selection {
background: var(--text);
color: var(--background);
}
}
html {
position: relative;
min-height: 100%;
background: var(--background) !important;
}
* {
color: var(--text);
}
ul, ol {
margin-inline-start: 1.9rem;
}
body {
margin: 0 0 100px;
transition: color 250ms ease-in-out, background 250ms ease-in-out;
scroll-behavior: smooth;
#nprogress .bar {
background: var(--text) !important;
}
#nprogress .peg {
box-shadow: 0 0 10px var(--text), 0 0 5px var(--text) !important;
}
}
body::-webkit-scrollbar {
width: 0.5rem !important;
}
body::-webkit-scrollbar-thumb {
background-color: var(--text) !important;
}
`;
export default GlobalStyles;
-18
View File
@@ -1,18 +0,0 @@
import { Wrapper } from './styles';
import Image from 'next/image';
const Hero = () => (
<Wrapper>
<div className='intro'>
<h2>Hi, I am Hazem</h2>
<h2>I Like Building Software</h2>
<h2 className='blue'>Full Stack TypeScript Developer</h2>
<h2 className='blue'>Life Long Learner</h2>
</div>
<div className='photo'>
<Image alt='Hazem Krimi' src='/photo.jpg' width={515} height={535} />
</div>
</Wrapper>
);
export default Hero;
-35
View File
@@ -1,35 +0,0 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
min-height: 45vh;
display: grid;
grid-template-columns: 1fr 32.188rem;
align-items: center;
height: auto;
text-align: left;
@media (max-width: 1024px) {
min-height: 35vh;
grid-template-columns: 1fr;
.photo {
display: none;
}
}
h2 {
font-size: 1.5rem;
@media (min-width: 1440px) {
font-size: 2rem;
}
@media (min-width: 2560px) {
font-size: 3.5rem;
}
}
.blue {
color: ${({ theme }) => theme.colors.blue};
}
`;
-30
View File
@@ -1,30 +0,0 @@
import Image from 'next/image';
import { Props } from './types';
import { StyledButton, StyledLink } from './styles';
const IconButton = ({
alt,
icon,
href,
target,
onClick,
className,
width = 24,
height = 24,
}: Props) =>
href ? (
<StyledLink
href={href}
target={target}
onClick={onClick}
className={className}
>
<Image alt={alt} src={icon} width={width} height={height} />
</StyledLink>
) : (
<StyledButton onClick={onClick} className={className}>
<Image alt={alt} src={icon} width={width} height={height} />
</StyledButton>
);
export default IconButton;
-18
View File
@@ -1,18 +0,0 @@
import styled, { css } from 'styled-components';
import Link from 'next/link';
const sharedStyles = css`
cursor: pointer;
background: none;
border: none;
display: inline-flex;
align-items: center;
`;
export const StyledLink = styled(Link)`
${sharedStyles}
`;
export const StyledButton = styled.button`
${sharedStyles}
`;
-10
View File
@@ -1,10 +0,0 @@
export interface Props {
alt: string;
icon: string;
width?: number;
height?: number;
href?: string;
target?: HTMLAnchorElement['target'];
onClick?: () => void;
className?: string;
}
-37
View File
@@ -1,37 +0,0 @@
import { BigField, SmallField } from './styles';
import { Props } from './types';
const Input = ({
type = 'text',
variant = 'small',
name,
value,
required,
placeholder,
className,
onChange,
}: Props) => {
return variant === 'small' ? (
<SmallField
type={type}
name={name}
value={value}
required={required}
placeholder={placeholder}
className={className}
onChange={onChange}
/>
) : (
<BigField
name={name}
value={value}
required={required}
placeholder={placeholder}
className={className}
onChange={onChange}
rows={3}
/>
);
};
export default Input;
-16
View File
@@ -1,16 +0,0 @@
import styled from 'styled-components';
export const SmallField = styled.input`
border: none;
padding: 1rem;
background: var(--secondary-background);
color: var(--text);
`;
export const BigField = styled.textarea`
resize: none;
border: none;
padding: 1rem;
background: var(--secondary-background);
color: var(--text);
`;
-12
View File
@@ -1,12 +0,0 @@
export interface Props {
placeholder?: string;
type: 'text' | 'email';
variant: 'small' | 'big';
name: string;
value: string;
required?: boolean;
onChange?: (
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => void;
className?: string;
}
-42
View File
@@ -1,42 +0,0 @@
import { useContext } from 'react';
import { ThemeContext } from '../../styles/theme';
import { Props } from './types';
import { StyledLink, StyledButton } from './styles';
const MDXButton = ({
variant = 'text',
type = 'button',
link,
target,
children,
disabled,
className,
}: Props) => {
const { mode } = useContext(ThemeContext);
return link ? (
<StyledLink
href={link}
target={target}
variant={variant}
type={type}
mode={mode}
disabled={disabled}
className={className}
>
{children}
</StyledLink>
) : (
<StyledButton
variant={variant}
type={type}
mode={mode}
disabled={disabled}
className={className}
>
{children}
</StyledButton>
);
};
export default MDXButton;
-53
View File
@@ -1,53 +0,0 @@
import Link from 'next/link';
import styled, { css } from 'styled-components';
import { Props } from './types';
export const sharedStyles = css<Props>`
cursor: pointer;
display: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? 'block' : 'inline'};
width: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? '100%' : 'auto'};
background: ${({ variant }) => (variant === 'action' ? '#1573CA' : 'none')};
color: ${({ variant, mode }) =>
variant === 'action' ? 'white' : mode === 'dark' ? 'white' : 'black'};
border: ${({ variant, mode }) =>
variant === 'outline'
? `2px solid ${mode === 'dark' ? 'white' : 'black'}`
: 'none'};
font-weight: bold;
font-size: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? '1.05rem' : 'inherit'};
text-transform: ${({ variant }) =>
['action', 'outline'].includes(variant as string)
? 'uppercase'
: 'inherit'};
padding: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? '.5rem 1rem' : '0rem'};
text-align: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? 'center' : 'left'};
text-decoration: none;
transition: color 250ms ease-in-out;
${({ disabled }) =>
disabled &&
`
background: gray;
cursor: default;
`}
@media (max-width: 768px) {
padding: ${({ variant }) =>
['action', 'outline'].includes(variant as string)
? '.5rem .75rem'
: '0rem'};
}
`;
export const StyledLink = styled(Link)<Props>`
${sharedStyles}
`;
export const StyledButton = styled.button<Omit<Props, 'href'>>`
${sharedStyles}
`;
-10
View File
@@ -1,10 +0,0 @@
export type Props = {
variant?: 'outline' | 'text' | 'action';
type?: 'button' | 'submit';
link?: string;
target?: HTMLAnchorElement['target'];
mode?: string;
disabled?: boolean;
className?: string;
children: React.ReactNode;
};
-74
View File
@@ -1,74 +0,0 @@
import { useContext, useRef, useEffect } from 'react';
import { ThemeContext } from '../../styles/theme';
import { Props } from './types';
import { Bar } from './styles';
import IconButton from '../IconButton';
import Button from '../Button';
const MobileNav = ({ open, close }: Props) => {
const { mode, toggle } = useContext(ThemeContext);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
document.addEventListener('mousedown', (event: MouseEvent) => {
if (ref.current && ref.current.contains(event.target as Node)) {
document.addEventListener('mouseup', (event) => {
if (ref.current && !ref.current.contains(event.target as Node))
return;
});
} else {
document.addEventListener('mouseup', (event) => {
if (ref.current && !ref.current.contains(event.target as Node))
close();
});
}
});
return () => {
document.removeEventListener('mousedown', () => {});
document.removeEventListener('mouseup', () => {});
};
});
return (
<Bar open={open} ref={ref}>
<div className='close'>
<IconButton
alt='Theme toggler'
icon={
mode === 'dark' ? '/icons/dark-close.svg' : '/icons/light-close.svg'
}
onClick={close}
/>
</div>
<div className='mobile-button-wrapper'>
<Button
href='#'
onClick={() => {
toggle();
close();
}}
>
{mode === 'dark' ? 'Switch to Light Mode' : 'Switch to Dark Mode'}
</Button>
</div>
<div className='mobile-button-wrapper'>
<Button href='/projects' onClick={() => close()}>
Projects
</Button>
</div>
<div className='mobile-button-wrapper'>
<Button href='/blog' onClick={() => close()}>
Blog
</Button>
</div>
<div className='mobile-button-wrapper'>
<Button href='/contact' onClick={() => close()}>
Contact
</Button>
</div>
</Bar>
);
};
export default MobileNav;
-37
View File
@@ -1,37 +0,0 @@
import styled from 'styled-components';
import { StyledProps } from './types';
export const Bar = styled.nav<StyledProps>`
position: fixed;
z-index: 2;
top: 0;
right: 0;
transform-origin: right;
transform: ${({ open }) => (open ? 'translateX(0%)' : 'translateX(100%)')};
width: 80%;
height: 100vh;
background: var(--text);
transition: transform 250ms ease-in-out;
display: grid;
grid-template-rows: 30% repeat(4, 50px);
padding: 1rem 1rem 5rem 1rem;
@media (orientation: landscape) {
grid-template-rows: auto;
}
.close {
justify-self: flex-end;
align-self: flex-start;
margin-top: 0.5rem;
}
.mobile-button-wrapper {
display: flex;
margin: 0rem 1rem;
a {
color: var(--text-inverted) !important;
}
}
`;
-8
View File
@@ -1,8 +0,0 @@
export type Props = {
open: boolean;
close: () => void;
};
export type StyledProps = {
open: boolean;
};
-56
View File
@@ -1,56 +0,0 @@
import { useContext, useState } from 'react';
import { ThemeContext } from '../../styles/theme';
import { Bar } from './styles';
import Link from 'next/link';
import Image from 'next/image';
import Button from '../Button';
import IconButton from '../IconButton';
import MobileNav from '../MobileNav';
const Nav = () => {
const [mobileNavOpen, setMobileNavOpen] = useState<boolean>(false);
const { mode, toggle } = useContext(ThemeContext);
return (
<Bar>
<Link className='logo' href='/'>
<Image
className='logo-image'
src={mode === 'dark' ? '/light-logo.svg' : '/dark-logo.svg'}
alt='Logo Image'
width={48}
height={48}
/>
<h1>Hazem Krimi</h1>
</Link>
<div className='buttons'>
<IconButton
alt='Theme toggler'
icon={mode === 'dark' ? '/icons/sun.svg' : '/icons/moon.svg'}
onClick={toggle}
/>
<Button href='/projects'>Projects</Button>
<Button href='/blog'>Blog</Button>
<Button href='/contact'>Contact</Button>
<Button href='/resume.pdf' target='_blank' variant='outline'>
Resume
</Button>
</div>
<div className='mobile-buttons'>
<Button href='/resume.pdf' target='_blank' variant='outline'>
Resume
</Button>
<IconButton
alt='Hamburger menu'
icon={
mode === 'dark' ? '/icons/light-menu.svg' : '/icons/dark-menu.svg'
}
onClick={() => setMobileNavOpen(true)}
/>
</div>
<MobileNav open={mobileNavOpen} close={() => setMobileNavOpen(false)} />
</Bar>
);
};
export default Nav;
-59
View File
@@ -1,59 +0,0 @@
import styled from 'styled-components';
export const Bar = styled.nav`
width: 100%;
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
padding: 1rem 0rem;
* {
user-select: none;
}
h1 {
font-size: 1.7rem;
@media (max-width: 768px) {
font-size: 1rem;
}
}
div,
a.logo {
display: grid;
align-items: center;
column-gap: 1rem;
@media (max-width: 768px) {
column-gap: 0.5rem;
}
}
a.logo {
text-decoration: none;
color: var(--text);
cursor: pointer;
grid-template-columns: repeat(2, auto);
justify-content: flex-start;
}
.buttons {
grid-template-columns: repeat(5, auto);
justify-content: flex-end;
@media (max-width: 768px) {
display: none;
}
}
.mobile-buttons {
display: none;
@media (max-width: 768px) {
display: grid;
grid-template-columns: repeat(2, auto);
justify-content: flex-end;
}
}
`;
+4
View File
@@ -0,0 +1,4 @@
---
title: 'Home'
date: 2023-10-18T20:03:43+01:00
---
+33
View File
@@ -0,0 +1,33 @@
---
layout: 'about'
title: 'About'
date: 2023-10-18T20:03:43+01:00
---
## About
Hi again! So, you want to know more about me! We'll go through how I got into tech, my education and my career and some other things you might find interesting. Hopefully you enjoy reading about me and I am looking forward to chat!
### How I got into Computer Science and Software Engineering
Similar to many other software engineers stories I was fascinated by video games since childhood with the Atari, Nintendo Gameboy, N64, Game Cube and PC gaming. Some games I played when I was a kid are: Duck Hunt, Prince of Persia, IGI, Super Mario Bros (Of cource!) and many more.
### My Education
I am from Tunisia, and there you get to specialize in Computer Science early on in high school and that is exactly what I did in 2014. My first programming language was Pascal which is a similar language to Delphi. I learned the fundamentals of algorithms and data structures using Pascal, Networking fundamentals and got a little bit into webdev by the last year of high school. On my spare time, tinkered a bit with some game engines like Unity and Unreal 4 with some of my friends.
In the summer of 2018, I got more interested in web development especially with JavaScript. So, in university I went for a program that is focused on web development but I did learn other stuff like C, Java, Linux, a bit of Assembly and I got into the intecacies of Computer Architecture and Operating Systems.
The university program was mostly packed with the fundamentals but none of the technologies of today's world. So, I did a lot of learning by myself through small practice projects and participating in hackathons. Most of the technologies I work with today I learned by myself including: React.js and React Native, Node.js, TypeScript, GraphQL, MongoDB...
### My Career
I started my career as a Front End developer in a software agency in Tunisia called [EMIKETIC](https://www.emiketic.com) building mostly e-commerce websites for small to medium businesses and sometimes on custom solutions for clients. There I got to learn Next.js, Strapi and especially how to work effectively in a team environment where I got to hone my communication and collaboration skills.
Then I started working remotely in a company based in the UK called [Cielo Costa](https://cielocosta.com) which is all about providing custom solutions to improves internal business processes.
Currently, I am working at [Finteum](https://finteum.com) which is creating a global financial market for intraday liquidity, enabling interbank lending for hours at a time.
### My Hobbies
Even though I didn't pursue Game Development, I am still a gamer but I don't play that much anymore. Currently, I am more into Cycling, reading Mangas, Personal Development books and Computer Science related books. I also play [Chess](https://www.chess.com/member/hazemkrimi) and card games.
+8
View File
@@ -0,0 +1,8 @@
---
title: "Blog"
date: 2023-10-18T20:03:43+01:00
---
## Blog
These are articles about things I learned about software engineering.
+9
View File
@@ -0,0 +1,9 @@
---
layout: 'contact'
title: 'Contact'
date: 2023-10-18T20:03:43+01:00
---
## Contact
Here you can contact me personally for any questions or opportunities.
+8
View File
@@ -0,0 +1,8 @@
---
title: "Projects"
date: 2023-10-18T20:03:43+01:00
---
## Projects
These are all the projects I worked on personally and professionally.
@@ -0,0 +1,23 @@
---
title: 'React Weather App'
description: 'Weather app made with React, TypeScript and OpenWeatherMap API'
demo: "https://hazemkrimi.github.io/react-weather-app"
date: 2023-09-19
---
## About the project
This is a project that I made as a step in the interview process for my final year internship. Here you can find its [source code](https://github.com/hazemKrimi/react-weather-app) or you can view the [demo](https://hazemkrimi.github.io/react-weather-app).
The features are fetching the weather depending on the device's location or by using the search bar on the top right of the app. The weather data it fetches are the daily forecast, weekly forcast and today's wind and humidity.
## Technologies
- React
- TypeScript
- Styled Components
- OpenWeatherMap API
## Screenshots
![Project screenshot](react-weather-app-screenshot.webp)
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

+60
View File
@@ -0,0 +1,60 @@
languageCode = 'en'
title = 'Hazem Krimi'
paginate = 5
enableRobotsTXT = true
[outputs]
home = ['html', 'rss']
section = ['html', 'rss']
[sitemap]
changeFreq = ''
filename = 'sitemap.xml'
priority = -1
[taxonomies]
tag = 'tags'
[params]
formSpreeURL = 'https://formspree.io/f/xoqpgyge'
dateFormat = '02 January 2006'
defaultDescription = 'Personal website of Hazem Krimi'
defaultKeywords = 'Hazem Krimi, Software Engineer, Software Developer, Full Stack Developer, JavaScript, TypeScript, React, Node.js, Scala, Kotlin, Corda, SQL, GraphQL, MongoDB'
[params.author]
email = 'me@hazemkrimi.tech'
name = 'Hazem Krimi'
[module]
[menu]
[[menu.main]]
name = 'Home'
url = '/'
weight = 1
[[menu.main]]
name = 'About'
url = '/about'
weight = 2
[[menu.main]]
name = 'Projects'
url = '/projects'
weight = 3
[[menu.main]]
name = 'Blog'
url = '/blog'
weight = 4
[[menu.main]]
name = 'Contact'
url = '/contact'
weight = 5
[[deployment.matchers]]
# Cache static assets for 1 year.
pattern = "^.+\\.(js|css|svg|ttf)$"
cacheControl = "max-age=31536000, no-transform, public"
gzip = true
[[deployment.matchers]]
pattern = "^.+\\.(gif|png|jpg|webp)$"
cacheControl = "max-age=31536000, no-transform, public"
gzip = false
[[deployment.matchers]]
# Set custom content type for /sitemap.xml
pattern = "^sitemap\\.xml$"
contentType = "application/xml"
gzip = true
[[deployment.matchers]]
pattern = "^.+\\.(html|xml|json)$"
gzip = true
+8
View File
@@ -0,0 +1,8 @@
{{ define "styles" }}
{{ $styles := resources.Get "css/index.css" | toCSS | minify }}
<link rel="stylesheet" href="{{ $styles.Permalink }}" />
{{ end }}
{{ define "main" }}
<h2>Page Not Found</h2>
{{ end }}
@@ -0,0 +1 @@
<a href="{{ .Destination | safeURL }}"{{ with .Title }} title="{{ . }}"{{ end }}{{ if strings.HasPrefix .Destination "http" }} target="_blank" rel="noopener"{{ end }}>{{ .Text | safeHTML }}</a>
+19
View File
@@ -0,0 +1,19 @@
{{ define "styles" }}
{{ $styles := resources.Get "css/about.css" | toCSS | minify }}
<link rel="stylesheet" href="{{ $styles.Permalink }}" />
{{ end }}
{{ define "main" }}
{{ partial "breadcrumb.html" . }}
{{ $faceImage := resources.Get "images/big-face.webp" }}
<section>
<img src="{{ $faceImage.Permalink }}" alt="Hazem Krimi's face" loading="lazy">
{{ .Content }}
</section>
<h2>Contact</h2>
{{ partial "contact-form.html" . }}
{{ end }}
+79
View File
@@ -0,0 +1,79 @@
{{ $baseStyles := resources.Get "css/baseof.css" | toCSS | minify }}
{{ $partialsStyles := resources.Get "css/partials.css" | toCSS | minify }}
{{ $baseScripts := resources.Get "js/baseof.js" | js.Build | minify }}
{{ $mobileNavigationScripts := resources.Get "js/mobile-navigation.js" | js.Build | minify }}
{{ $contactFormScripts := resources.Get "js/contact-form.js" | js.Build | minify }}
{{ $androidChromeIcon := resources.Get "android-chrome-192x192.png" }}
{{ $appleTouchIcon := resources.Get "apple-touch-icon.png" }}
{{ $favIcon32 := resources.Get "favicon-32x32.png" }}
{{ $favIcon16 := resources.Get "favicon-16x16.png" }}
{{ $favIcon := resources.Get "favicon.ico" }}
{{ $faceImage := resources.Get "images/big-face.webp" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#BD1839" />
<meta
name="description"
content="{{ if .Params.description }}{{ .Params.description }}{{ else }}{{ .Site.Params.defaultDescription }}{{ end }}"
>
<meta
name="keywords"
content="{{ if .Params.keywords }}{{ .Params.keywords }}{{ else }}{{ .Site.Params.defaultKeywords }}{{ end }}"
>
<meta name="robots" content="index, follow">
<meta property="og:title" content="{{ .Page.Title }} | Hazem Krimi">
<meta
property="og:description"
content="{{ if .Params.description }}{{ .Params.description }}{{ else }}{{ .Site.Params.defaultDescription }}{{ end }}"
>
<meta
property="og:image"
content="{{ $faceImage.Permalink }}"
>
<meta property="og:url" content="{{ .Page.Permalink }}">
{{ block "meta" . }}{{ end }}
{{ with .OutputFormats.Get "rss" -}}
{{ printf `<link rel=%q type=%q href=%q title=%q>` .Rel .MediaType.Type .Permalink site.Title | safeHTML }}
{{ end }}
<link rel="canonical" href="{{ .Page.Permalink }}">
<link rel="icon" sizes="192x192" href="{{ $androidChromeIcon.Permalink }}">
<link rel="apple-touch-icon" sizes="180x180" href="{{ $appleTouchIcon.Permalink }}">
<link rel="icon" type="image/png" sizes="32x32" href="{{ $favIcon32.Permalink }}">
<link rel="icon" type="image/png" sizes="16x16" href="{{ $favIcon16.Permalink }}">
<link rel="icon" type="image/x-icon" href="{{ $favIcon.Permalink }}">
<link rel="stylesheet" href="{{ $baseStyles.Permalink }}">
<link rel="stylesheet" href="{{ $partialsStyles.Permalink }}">
{{ block "styles" . }}{{ end }}
<title>
{{ block "title" . }}
{{ .Page.Title }} | Hazem Krimi
{{ end }}
</title>
<script defer src="{{ $baseScripts.Permalink }}"></script>
<script defer src="{{ $mobileNavigationScripts.Permalink }}"></script>
<script defer src="{{ $contactFormScripts.Permalink }}"></script>
{{ block "scripts" . }}{{ end }}
</head>
<body>
{{ partial "header.html" . }}
{{ partial "mobile-navigation.html" . }}
<main>
{{ block "main" . }}{{ end }}
</main>
{{ partial "footer.html" . }}
</body>
</html>
+7
View File
@@ -0,0 +1,7 @@
{{ define "main" }}
{{ partial "breadcrumb.html" . }}
{{ .Content }}
{{ partial "contact-form.html" . }}
{{ end }}
+78
View File
@@ -0,0 +1,78 @@
{{- /* Deprecate site.Author.email in favor of site.Params.author.email */}}
{{- $authorEmail := "" }}
{{- with site.Params.author }}
{{- if reflect.IsMap . }}
{{- with .email }}
{{- $authorEmail = . }}
{{- end }}
{{- end }}
{{- else }}
{{- with site.Author.email }}
{{- $authorEmail = . }}
{{- warnf "The author key in site configuration is deprecated. Use params.author.email instead." }}
{{- end }}
{{- end }}
{{- /* Deprecate site.Author.name in favor of site.Params.author.name */}}
{{- $authorName := "" }}
{{- with site.Params.author }}
{{- if reflect.IsMap . }}
{{- with .name }}
{{- $authorName = . }}
{{- end }}
{{- else }}
{{- $authorName = . }}
{{- end }}
{{- else }}
{{- with site.Author.name }}
{{- $authorName = . }}
{{- warnf "The author key in site configuration is deprecated. Use params.author.name instead." }}
{{- end }}
{{- end }}
{{- $projectsPages := where .Site.RegularPages "Section" "projects" }}
{{- $blogPages := where .Site.RegularPages "Section" "blog" }}
{{- $limit := .Site.Config.Services.RSS.Limit }}
{{- if ge $limit 1 }}
{{- $projectsPages = $projectsPages | first $limit }}
{{- $blogPages = $blogPages | first $limit }}
{{- end }}
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Hazem Krimi's content</title>
<link>{{ .Permalink }}</link>
<description>Hazem Krimi's projects and blog</description>
<generator>Hugo -- gohugo.io</generator>
<language>{{ site.Language.LanguageCode }}</language>{{ with $authorEmail }}
<managingEditor>{{.}}{{ with $authorName }} ({{ . }}){{ end }}</managingEditor>{{ end }}{{ with $authorEmail }}
<webMaster>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</webMaster>{{ end }}{{ with .Site.Copyright }}
<copyright>{{ . }}</copyright>{{ end }}{{ if not .Date.IsZero }}
<lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
{{- with .OutputFormats.Get "RSS" }}
{{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
{{- end }}
{{- range $projectsPages }}
<item>
<title>{{ .Title }}</title>
<link>{{ .Permalink }}</link>
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
{{- with $authorEmail }}<author>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</author>{{ end }}
<guid>{{ .Permalink }}</guid>
<description>{{ .Params.description | html }}</description>
</item>
{{- end }}
{{- range $blogPages }}
<item>
<title>{{ .Title }}</title>
<link>{{ .Permalink }}</link>
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
{{- with $authorEmail }}<author>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</author>{{ end }}
<guid>{{ .Permalink }}</guid>
<description>{{ .Params.description | html }}</description>
</item>
{{- end }}
</channel>
</rss>
+44
View File
@@ -0,0 +1,44 @@
{{ define "styles" }}
{{ $styles := resources.Get "css/list.css" | toCSS | minify }}
<link rel="stylesheet" href="{{ $styles.Permalink }}" />
{{ end }}
{{ define "main" }}
{{ partial "breadcrumb.html" . }}
{{ .Content }}
{{ if or (.InSection ($.Site.GetPage "blog")) (findRESubmatch "tags" .RelPermalink) }}
{{ $currentTitle := .Page.Title }}
{{ if (findRESubmatch "tags" .RelPermalink) }}
<h2>Blog</h2>
<p>These are articles about things I learned about software engineering.</p>
{{ end }}
{{ if gt (len .Site.Taxonomies.tags) 0 }}
<section id="tags">
{{ range .Site.Taxonomies.tags }}
<a class="{{ if (eq $currentTitle .Page.Title) }}selected{{ end }}"
href="{{ .Page.Permalink }}"
>
{{ .Page.Title }}
</a>
{{ end }}
</section>
{{ end }}
{{ end }}
{{ if gt .Paginator.TotalPages 0 }}
<section>
{{ range .Paginator.Pages }}
{{ partial "card.html" . }}
{{ end }}
</section>
{{ else }}
<h2>Nothing for now</h2>
{{ end }}
{{ partial "pagination.html" . }}
{{ end }}
+52
View File
@@ -0,0 +1,52 @@
{{ define "styles" }}
{{ $styles := resources.Get "css/single.css" | toCSS | minify }}
<link rel="stylesheet" href="{{ $styles.Permalink }}" />
{{ end }}
{{ define "main" }}
<div id="container">
<div>
{{ partial "breadcrumb.html" . }}
<section id="metadata">
<h1>{{ .Title }}</h1>
<div>
<span>{{ readFile "assets/icons/calendar.svg" | safeHTML }} {{ .Date.Format .Site.Params.dateFormat }}</span>
<span>{{ readFile "assets/icons/clock.svg" | safeHTML }} {{ printf "%d minute(s) read" .ReadingTime }}</span>
<span
id="share"
data-title="{{ .Title }}"
data-description="{{ .Params.description }}"
data-url="{{ .Permalink }}"
>
{{ readFile "assets/icons/share.svg" | safeHTML }}
Share
</span>
</div>
</section>
<section id="content">
<div>
{{ .Content }}
</div>
<h2>Contact</h2>
{{ partial "contact-form.html" }}
</section>
</div>
{{ partial "table-of-contents.html" . }}
</div>
<script>
document.querySelector('#share').addEventListener('click', async event => {
await navigator.share({
title: event.target.getAttribute('data-title'),
description: event.target.getAttribute('data-description'),
url: event.target.getAttribute('data-url'),
});
});
</script>
{{ end }}
+67
View File
@@ -0,0 +1,67 @@
{{ define "styles" }}
{{ $styles := resources.Get "css/index.css" | toCSS | minify }}
<link rel="stylesheet" href="{{ $styles.Permalink }}" />
{{ end }}
{{ define "main" }}
{{ $faceImage := resources.Get "images/big-face.webp" }}
<section id="intro">
<div>
<h1>Hi! I am <span>Hazem</span>, a sofware engineer currently working at <a href="https://finteum.com" target="_blank">Finteum</a></h1>
<p>I have over two years of experience mainly working on web and cross platform mobile applications in E-Commerce, Fintech, Auditing and Compliance.</p>
<div id="action-buttons">
<a href="{{ urls.JoinPath .Site.BaseURL "blog" }}">Blog</a>
<a href="{{ urls.JoinPath .Site.BaseURL "contact" }}">Contact</a>
</div>
</div>
<img src="{{ $faceImage.Permalink }}" alt="Hazem Krimi's face" loading="lazy">
</section>
<section id="about">
<h2>About</h2>
<p>Tinkering is what got me to where I am now as a professional software engineer.</p>
{{ partial "about-card.html" . }}
</section>
{{ if (gt (len (where .Site.RegularPages "Section" "projects")) 0) }}
<section id="projects">
<div>
<h2>Projects</h2>
<a class="read-more" href="{{ urls.JoinPath .Site.BaseURL "projects" }}">
View all projects
</a>
</div>
<p>These are all the projects I worked on personally and professionally.</p>
{{ range (where .Site.Pages "Section" "projects") }}
{{ range first 2 .Pages }}
{{ partial "card.html" . }}
{{ end }}
{{ end }}
</section>
{{ end }}
{{ if (gt (len (where .Site.RegularPages "Section" "blog")) 0) }}
<section id="blog">
<div>
<h2>Blog</h2>
<a class="read-more" href="{{ urls.JoinPath .Site.BaseURL "blog" }}">
View all blog posts
</a>
</div>
<p>These are articles about things I learned about software engineering.</p>
{{ range (where .Site.Pages "Section" "blog") }}
{{ range first 2 .Pages }}
{{ partial "card.html" . }}
{{ end }}
{{ end }}
</section>
{{ end }}
<section id="contact">
<h2>Contact</h2>
<p>Here you can contact me personally for any questions or opportunities.</p>
{{ partial "contact-form.html" . }}
</section>
{{ end }}
+24
View File
@@ -0,0 +1,24 @@
{{ $cv := resources.Get "cv.pdf" }}
{{ $faceImage := resources.Get "images/borded-face.webp" }}
<div id="about-card">
<img src="{{ $faceImage.Permalink }}" alt="Hazem Krimi's face" />
<div>
<p>
My programming experience is mostly non-professional but it was essential
for my development. Even though I am working professionally for over two
years now, I got into programming much longer than that. I got into web
development in the summer of 2018 as I started learning the basics: HTML,
CSS, JS and most importantly how the web works by building very small
projects.
</p>
<div id="links">
<a href="{{ $cv.Permalink }}" target="_blank">
Download CV
</a>
<a class="read-more" href="{{ urls.JoinPath .Site.BaseURL "about" }}">
Read more about me
</a>
</div>
</div>
</div>
+24
View File
@@ -0,0 +1,24 @@
<nav aria-label="breadcrumb" class="breadcrumb">
<ol>
{{ range .Ancestors.Reverse }}
{{ if (eq .Title "Tags") }}
<li>
<a href="{{ urls.JoinPath .Site.BaseURL "blog" }}">Blog</a>
</li>
{{ else }}
<li>
<a href="{{ .Permalink }}">{{ .Title }}</a>
</li>
{{ end }}
{{ end }}
<li class="active">
<a
aria-current="page"
class="{{ if (eq .Parent.Title "Tags") }}tag{{ end }}"
href="{{ .Permalink }}"
>
{{ if (eq .Parent.Title "Tags") }}#{{ end }}{{ .Title }}
</a>
</li>
</ol>
</nav>
+23
View File
@@ -0,0 +1,23 @@
<article class="card">
<div>
<h3>{{ .Title }}</h3>
{{ if .InSection ($.Site.GetPage "blog") }}
<b>{{ .Date.Format .Site.Params.dateFormat }}</b>
{{ end }}
<p>{{ .Params.description }}</p>
<div id="links">
<a class="read-more" href="{{ .Permalink }}">
{{ if (.InSection ($.Site.GetPage "projects")) }}
Read this project {{ readFile "assets/icons/arrow.svg" | safeHTML }}
{{ else }}
Read this blog post {{ readFile "assets/icons/arrow.svg" | safeHTML }}
{{ end }}
</a>
{{ with (and (.InSection ($.Site.GetPage "projects")) .Params.demo) }}
<a class="demo" href="{{ . }}" target="_blank">
Demo {{ readFile "assets/icons/eye.svg" | safeHTML }}
</a>
{{ end }}
</div>
</div>
</article>
+18
View File
@@ -0,0 +1,18 @@
<form
action="{{ .Site.Params.formSpreeURL }}"
method="POST"
>
<input name="name" placeholder="Your name" required type="text" />
<input name="email" placeholder="Your email" required type="email" />
<textarea
cols="30"
name="message"
placeholder="Your message"
required
rows="10"
></textarea>
<div>
<button type="submit">Submit</button>
<div id="submission-status"></div>
</div>
</form>
+56
View File
@@ -0,0 +1,56 @@
{{ $faceImage := resources.Get "images/small-face.webp" }}
{{ $cv :=resources.Get "cv.pdf" }}
<footer>
<div id="footer-face">
<img src="{{ $faceImage.Permalink }}" alt="Hazem Krimi" />
<span>Hazem Krimi</span>
</div>
<div id="links">
<a
aria-label="Hazem Krimi's Linkedin profile"
href="https://www.linkedin.com/in/hazemkrimi"
target="_blank"
>
{{ readFile "assets/icons/linkedin.svg" | safeHTML }}
</a>
<a
aria-label="Hazem Krimi's Twitter or X profile"
href="https://twitter.com/HazemKrimi"
target="_blank"
>
{{ readFile "assets/icons/twitter.svg" | safeHTML }}
</a>
<a
aria-label="Hazem Krimi's Github profile"
href="https://github.com/hazemKrimi"
target="_blank"
>
{{ readFile "assets/icons/github.svg" | safeHTML }}
</a>
<a
aria-label="Hazem Krimi's content RSS feed"
href="{{ urls.JoinPath .Site.BaseURL "index.xml" }}"
target="_blank"> {{ readFile "assets/icons/rss.svg" | safeHTML }}
</a>
<a
aria-label="Hazem Krimi's Email"
href="mailto:me@hazemkrimi.tech"
target="_blank"
>
{{ readFile "assets/icons/mail.svg" | safeHTML }}
</a>
<a
aria-label="Hazem Krimi's CV"
href="{{ $cv.Permalink }}" target="_blank"
>
{{ readFile "assets/icons/cv.svg" | safeHTML }}
</a>
</div>
<p id="copyright"></p>
<script>
const copyright = (document.querySelector(
'#copyright'
).innerHTML = `Hazem Krimi &copy ${new Date().getFullYear()}`);
</script>
</footer>
+72
View File
@@ -0,0 +1,72 @@
{{ $faceImage := resources.Get "images/small-face.webp" }}
{{ $cv := resources.Get "cv.pdf" }}
<header>
<div id="header-face">
<img src="{{ $faceImage.Permalink }}" alt="Hazem Krimi's face" />
<span>Hazem Krimi</span>
</div>
<div id="menus">
{{ range site.Menus.main.Sort.ByWeight }}
<a {{ printf "href=%q" .URL | safeHTMLAttr }}>{{ .Name }}</a>
{{ end }}
</div>
<div id="links">
<a
aria-label="Hazem Krimi's Linkedin profile"
href="https://www.linkedin.com/in/hazemkrimi" target="_blank">
{{ readFile "assets/icons/linkedin.svg" | safeHTML }}
</a>
<a
aria-label="Hazem Krimi's Twitter or X profile"
href="https://twitter.com/HazemKrimi"
target="_blank"
>
{{ readFile "assets/icons/twitter.svg" | safeHTML }}
</a>
<a
aria-label="Hazem Krimi's Github profile"
href="https://github.com/hazemKrimi" target="_blank"
>
{{ readFile "assets/icons/github.svg" | safeHTML }}
</a>
<a
aria-label="Hazem Krimi's content RSS feed"
href="{{ urls.JoinPath .Site.BaseURL "index.xml" }}"
target="_blank"
>
{{ readFile "assets/icons/rss.svg" | safeHTML }}
</a>
<a
aria-label="Hazem Krimi's Email"
href="mailto:me@hazemkrimi.tech"
target="_blank"
>
{{ readFile "assets/icons/mail.svg" | safeHTML }}
</a>
<a
aria-label="Hazem Krimi's CV"
href="{{ $cv.Permalink }}"
target="_blank"
>
{{ readFile "assets/icons/cv.svg" | safeHTML }}
</a>
<div class="vertical-separator"></div>
<a
href="#"
class="theme-toggler"
aria-label="Theme toggler"
>
{{ readFile "assets/icons/moon.svg" | safeHTML }}
{{ readFile "assets/icons/sun.svg" | safeHTML }}
</a>
</div>
<a
href="#"
id="nav-toggler"
aria-label="Mobile navigation toggler"
>
{{ readFile "assets/icons/burger.svg" | safeHTML }}
{{ readFile "assets/icons/close.svg" | safeHTML }}
</a>
</header>
+59
View File
@@ -0,0 +1,59 @@
{{ $cv := resources.Get "cv.pdf" }}
<nav id="mobile-navigation">
<div id="menus">
{{ range site.Menus.main.Sort.ByWeight }}
<a {{ printf "href=%q" .URL | safeHTMLAttr }}>{{ .Name }}</a>
{{ end }}
</div>
<hr>
<div id="links">
<a
href="#"
class="theme-toggler"
aria-label="Theme toggler"
>
{{ readFile "assets/icons/moon.svg" | safeHTML }}
{{ readFile "assets/icons/sun.svg" | safeHTML }}
</a>
<a
aria-label="Hazem Krimi's CV"
href="{{ $cv.Permalink }}" target="_blank"
>
{{ readFile "assets/icons/cv.svg" | safeHTML }}
</a>
<a
aria-label="Hazem Krimi's Email"
href="mailto:me@hazemkrimi.tech"
target="_blank"
>
{{ readFile "assets/icons/mail.svg" | safeHTML }}
</a>
<a
aria-label="Hazem Krimi's content RSS feed"
href="{{ urls.JoinPath .Site.BaseURL "index.xml" }}"
target="_blank"> {{ readFile "assets/icons/rss.svg" | safeHTML }}
</a>
<a
aria-label="Hazem Krimi's Github profile"
href="https://github.com/hazemKrimi"
target="_blank"
>
{{ readFile "assets/icons/github.svg" | safeHTML }}
</a>
<a
aria-label="Hazem Krimi's Twitter or X profile"
href="https://twitter.com/HazemKrimi"
target="_blank"
>
{{ readFile "assets/icons/twitter.svg" | safeHTML }}
</a>
<a
aria-label="Hazem Krimi's Linkedin profile"
href="https://www.linkedin.com/in/hazemkrimi"
target="_blank"
>
{{ readFile "assets/icons/linkedin.svg" | safeHTML }}
</a>
</div>
</nav>
+29
View File
@@ -0,0 +1,29 @@
{{ $paginator := .Paginator }}
{{ if gt $paginator.TotalPages 1 }}
<div class="pagination">
<a class="first {{ if eq $paginator.PageNumber $paginator.First.PageNumber }} active {{ end }}" href="{{ $paginator.First.URL }}">
{{ $paginator.First.PageNumber }}
</a>
{{ with $paginator.HasPrev }}
<a href="{{ $paginator.Prev.URL }}">
«
</a>
{{ end }}
{{ range $index, $pager := $paginator.Pagers }}
{{ with and (ne $index 0) (ne $index (sub (len $paginator.Pagers) 1)) }}
<a class="{{ if eq $pager $paginator }} active {{ end }}" href="{{ $pager.URL }}">
{{ $pager.PageNumber }}
</a>
{{ end }}
{{ end }}
{{ with $paginator.HasNext }}
<a href="{{ $paginator.Next.URL }}">
»
</a>
{{ end }}
<a class="last {{ if eq $paginator.PageNumber $paginator.Last.PageNumber }} active {{ end }}" href="{{ $paginator.Last.URL }}">
{{ $paginator.Last.PageNumber }}
</a>
</div>
{{ end }}
+6
View File
@@ -0,0 +1,6 @@
{{ with .TableOfContents }}
<aside id="table-of-contents">
<span>Table of contents</span>
{{ . }}
</aside>
{{ end }}
Vendored
-14
View File
@@ -1,14 +0,0 @@
declare module '@mdx-js/react' {
import * as React from 'react';
export type Components = {
[key]?: React.ComponentType<any>;
};
export interface MDXProviderProps {
children: React.ReactNode;
components?: Components;
}
export class MDXProvider extends React.Component<MDXProviderProps> {}
}

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