98 Commits

Author SHA1 Message Date
hazemKrimi 0bb8954903 Actual update to README 2025-04-12 12:57:11 +01:00
hazemKrimi ece597a5f4 Update README and add nginx.conf used for deployment to VPS 2025-04-12 12:54:44 +01:00
hazemKrimi 5d1d1d8e9c Remove redundant SSH setup commands 2025-04-12 11:09:59 +01:00
hazemKrimi 403d482230 Remove google analytics 2025-04-10 19:39:10 +01:00
hazemKrimi c69a1893ff Setup SSH 2025-04-10 19:37:35 +01:00
hazemKrimi d050ce20c2 GitHub action workflow to deploy to VPS 2025-04-10 19:33:40 +01:00
hazemKrimi ca52e055e3 Add project demo 2025-04-07 21:57:52 +01:00
hazemKrimi 93773f3e33 Fix typo 2025-04-07 13:06:07 +01:00
hazemKrimi 3cd9ae8768 Rename project 2025-03-31 21:15:20 +01:00
hazemKrimi 4d51e01f24 Update CV 2025-03-30 02:57:30 +01:00
hazemKrimi 91b9ff4978 Projects and resume updates 2025-03-29 22:49:32 +01:00
hazemKrimi a0ad73de86 Add README.md 2025-03-27 14:31:31 +01:00
hazemKrimi c6cf888547 New hugo github actions workflow 2025-02-28 17:53:10 +01:00
hazemKrimi 62dcafdd88 Update hugo version and add the touch programming project 2025-02-28 17:50:27 +01:00
Hazem Krimi 58bf7f0647 Remove the weather app 2024-12-07 09:48:14 +01:00
Hazem Krimi de1846944e Update CV 2024-10-09 13:12:45 +01:00
Hazem Krimi c170415a4f Remove non working build options 2024-05-05 16:18:44 +01:00
Hazem Krimi 43541f851e Layout updates 2024-05-05 16:14:33 +01:00
Hazem Krimi 23e1d62948 Update resume 2024-05-04 16:09:00 +01:00
Hazem Krimi c3324f12f8 Fix fonts 2024-01-28 00:37:35 +01:00
Hazem Krimi 4cc71b0634 Rename resume file 2024-01-26 00:42:49 +01:00
Hazem Krimi f5adf3bde1 styles improvements 2024-01-26 00:39:08 +01:00
Hazem Krimi 67a42d867b Update line-height 2024-01-24 17:29:20 +01:00
Hazem Krimi 9a9e8a640b Capitalize menu 2024-01-23 18:09:14 +01:00
Hazem Krimi 7777828e13 Update font size and menu 2024-01-23 18:04:15 +01:00
Hazem Krimi c869602115 Upscale images 2024-01-23 17:22:51 +01:00
Hazem Krimi d957bfd07b Use fixed width for big screens 2023-12-29 13:20:45 +01:00
Hazem Krimi a51858269d Update responsive widths 2023-12-29 13:17:30 +01:00
Hazem Krimi 713613cc32 Remove main image from home page 2023-12-16 19:44:07 +01:00
Hazem Krimi 9b0bc24b6b Small adjustments 2023-12-16 19:35:50 +01:00
Hazem Krimi 2169fcdb11 Fix typo 2023-11-24 18:06:07 +01:00
Hazem Krimi b5f33039db Fix contact form bug in content page 2023-11-24 17:49:01 +01:00
Hazem Krimi 366806f6fa Add google analytics 2023-11-24 17:28:25 +01:00
Hazem Krimi 483b9dc431 Fix property 2023-11-24 17:17:19 +01:00
Hazem Krimi 6a39e94c48 Update baseURL in production 2023-11-24 17:16:13 +01:00
Hazem Krimi 77d40614a6 Add google analytics 2023-11-24 16:38:18 +01:00
Hazem Krimi e53ea74824 Update deployment action 2023-11-24 16:33:05 +01:00
Hazem Krimi dd4083ef92 Merge pull request #1 from hazemKrimi/rebuild
Rebuild using Hugo
2023-11-24 16:30:19 +01:00
Hazem Krimi a393540173 Update og:title 2023-11-24 14:22:15 +01:00
Hazem Krimi 578bc39d47 Add og:image and update page titles 2023-11-24 14:17:16 +01:00
Hazem Krimi 6245182727 About info and some improvements to styles and markup 2023-11-23 19:40:11 +01:00
Hazem Krimi 41a5505631 Improve accessible navigation 2023-11-22 19:27:27 +01:00
Hazem Krimi 077c4fe728 Improve SEO 2023-11-22 19:03:03 +01:00
Hazem Krimi e05564729d Improve loading performance 2023-11-22 18:18:23 +01:00
Hazem Krimi b790da4c73 More styles improvements 2023-11-22 14:31:52 +01:00
Hazem Krimi 40ec57c634 Custom scrollbar 2023-11-21 17:49:05 +01:00
Hazem Krimi bcf61c1b09 Make h3 slightly bigger on small screens 2023-11-21 17:45:56 +01:00
Hazem Krimi 91d691d7c1 Code refactoring 2023-11-21 17:40:40 +01:00
Hazem Krimi b70e0e9e93 Custom rss template 2023-11-20 19:13:04 +01:00
Hazem Krimi d963b389af Set static theme color 2023-11-18 14:55:47 +01:00
Hazem Krimi 4598a12887 Tags, rss, sitemap and robots.txt 2023-11-17 18:49:54 +01:00
Hazem Krimi 5ea292e909 Styles for link and code block in single page 2023-11-16 18:20:57 +01:00
Hazem Krimi ad19dc44c3 Finish table of contents and pagination 2023-11-16 16:59:45 +01:00
Hazem Krimi 64723c3860 Content pages and table of contents 2023-11-15 16:22:38 +01:00
Hazem Krimi 1a5a9fbfef Fix about heading size 2023-11-14 20:16:51 +01:00
Hazem Krimi 10ef4654ea Add more spacing for breadcrumb 2023-11-14 20:13:53 +01:00
Hazem Krimi 6d9b8345e9 Improve breadcrumb styles and fix urls 2023-11-14 20:10:28 +01:00
Hazem Krimi 84cd9fde72 Breadcrumb partial 2023-11-14 19:57:44 +01:00
Hazem Krimi 33b8d792cf About page and home page 2023-11-14 19:34:03 +01:00
Hazem Krimi 6df17ff272 Add 404, blog section and unify the card partial 2023-11-13 19:47:23 +01:00
Hazem Krimi 3a07259ebc Quick style fix 2023-11-09 21:17:25 +01:00
Hazem Krimi 3211097afc More styles adjustments 2023-11-09 21:12:36 +01:00
Hazem Krimi 6d21953612 Add projects section to home page 2023-11-09 21:07:33 +01:00
Hazem Krimi 24f3aae9e6 Adjust card padding 2023-11-09 20:58:21 +01:00
Hazem Krimi 319ebbeb31 Projects list page with project card 2023-11-09 20:55:17 +01:00
Hazem Krimi e81eba1637 Add contact section to home page 2023-11-08 21:49:23 +01:00
Hazem Krimi 7780db6443 Improve contact form styles 2023-11-08 19:14:26 +01:00
Hazem Krimi 892ddca71a Handle form without redirect 2023-11-08 19:07:54 +01:00
Hazem Krimi 770beea258 Contact form script wip 2023-11-08 18:10:39 +01:00
Hazem Krimi a517624330 Reset mobile menu on page load 2023-11-08 15:04:18 +01:00
Hazem Krimi e413703745 Prevent textarea resize 2023-11-07 20:14:33 +01:00
Hazem Krimi 164cf73180 Contact page 2023-11-07 20:11:09 +01:00
Hazem Krimi 420e348cad Add favicons 2023-11-07 18:50:33 +01:00
Hazem Krimi 8c0894205b Media queries improvements 2023-11-07 02:08:43 +01:00
Hazem Krimi bc6a43b578 Fix footer mobile styles 2023-11-07 02:01:33 +01:00
Hazem Krimi de4bd80df8 Footer mobile 2023-11-07 01:47:59 +01:00
Hazem Krimi 2ccb68d1b6 Footer wip 2023-11-06 23:50:46 +01:00
Hazem Krimi 254d473e45 No fetching of icons 2023-11-04 19:34:08 +01:00
Hazem Krimi c906b26105 Change fetch icons url 2023-11-03 23:50:01 +01:00
Hazem Krimi 113620e4da Mount assets 2023-11-03 23:45:24 +01:00
Hazem Krimi b2ae7e64aa Update fetching icons urls 2023-11-03 23:34:39 +01:00
Hazem Krimi 8b7a3a6973 Use permalink 2023-11-03 23:32:17 +01:00
Hazem Krimi 2d68203066 Fix build error 2023-11-03 23:19:41 +01:00
Hazem Krimi 0177c8d378 Publish some icons 2023-11-03 23:16:27 +01:00
Hazem Krimi 931f307fac Dynamic menu 2023-11-03 22:48:38 +01:00
Hazem Krimi f19583d172 Update base url 2023-11-03 18:24:46 +01:00
Hazem Krimi 605c637e08 Update workflow 2023-11-03 18:21:16 +01:00
Hazem Krimi 6570af8f96 Use relative links 2023-11-03 18:13:50 +01:00
Hazem Krimi 81b2326fdf Deploy to GitHub pages 2023-11-03 17:57:32 +01:00
Hazem Krimi 21aa9a293a Vercel preview hugo config 2023-11-03 17:51:57 +01:00
Hazem Krimi 3f8f491da0 Header partial 2023-11-02 19:33:35 +01:00
Hazem Krimi 8eeb9fc974 Enable media queries 2023-11-02 15:49:57 +01:00
Hazem Krimi c463e30f43 Desktop header 2023-10-31 23:51:54 +01:00
Hazem Krimi 4e31b2877b Header partial wip 2023-10-31 00:10:41 +01:00
Hazem Krimi 44705e17f7 Fix font import and build js 2023-10-18 22:58:43 +01:00
Hazem Krimi f5a54d528f Assets and sample color theme switcher 2023-10-18 21:00:04 +01:00
Hazem Krimi 521727983c Add .gitignore 2023-10-18 19:44:22 +01:00
Hazem Krimi 357ac123c1 Init hugo project 2023-10-18 19:27:12 +01:00
152 changed files with 1923 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
+55
View File
@@ -0,0 +1,55 @@
name: Deployment to VPS
on:
push:
branches:
- main
workflow_dispatch:
concurrency:
group: "pages"
cancel-in-progress: false
defaults:
run:
shell: bash
jobs:
deploy:
runs-on: ubuntu-latest
env:
HUGO_VERSION: 0.145.0
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: Build with Hugo
env:
HUGO_CACHEDIR: ${{ runner.temp }}/hugo_cache
HUGO_ENVIRONMENT: production
TZ: UTC
run: |
hugo \
--gc \
--minify \
- name: Setup SSH
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.DEPLOY_SSH_KEY }}
- name: Deploy to VPS
env:
SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
run: |
ssh-keyscan -H hazemkrimi.tech >> ~/.ssh/known_hosts
rsync -avz --delete public deploy@hazemkrimi.tech:/var/www/hazemkrimi.tech
+3 -33
View File
@@ -1,34 +1,4 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
public/
resources/
# 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
.hugo_build.lock
-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
+49
View File
@@ -0,0 +1,49 @@
# My Personal Website
This repo contains the source code for [my personal website](https://hazemkrimi.tech/) madee using Hugo.
## Requirements To Run Locally
1. Install [Hugo](https://gohugo.io/installation/).
2. Clone the repo:
```
git clone https://github.com/hazemKrimi/personal-website
cd personal-website
```
3. To run the webserver that reloads on saved changes run the following command:
```
hugo server
```
## Deployment
I am using `nginx` with `certbot` as a seemless webserver with an SSL certificate to get HTTPS. Here is how I setup the deployment assuming you already have `nginx` and `certbot` installed:
1. Create a file to to be the webserver config under `/etc/nginx/sites-available` with its content being what is in `deploy/nginx.conf` of the repo. (You will need to use your own domain)
2. Symlink the config into `/etc/nginx/sites-enabled`:
```
sudo ln -s /etc/nginx/sites-available/<webserver-config> /etc/nginx/sites-enabled/<webserver-config>
```
3. Restart `nginx` and its service if you're using `systemd`:
```
sudo nginx -t
sudo systemctl restart nginx
```
4. Run `certbot` to get an SSL certificate for your domain:
```
sudo certbot --nginx
```
5. If you forked this repo you can use the actions workflow that I am using but you will need to add an SSH private key as an action secret. You will find this setting under repo settings > security > secrets and variables > actions. You will add your key as `DEPLOY_SSH_KEY` in repository secrets.
6. If you are not using GitHub Actions or not deploying on a VPS you probably know what you are doing but you can still refer to the [Hugo Deployment Guides](https://gohugo.io/host-and-deploy/).
-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

+167
View File
@@ -0,0 +1,167 @@
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,300;1,400;1,500;1,600;1,700;1,800&display=swap');
:root {
--black: #131314;
--white: #dddddd;
--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: normal;
-webkit-font-smoothing: antialiased;
background-color: var(--background);
color: var(--text);
isolation: isolate;
user-select: text;
}
body::-webkit-scrollbar {
width: 0.5rem;
}
body::-webkit-scrollbar-thumb {
background-color: var(--text);
}
#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;
}
}
@media only screen and (min-width: 1440px) {
body {
font-size: 18px;
}
main {
width: 1368px;
}
}
+70
View File
@@ -0,0 +1,70 @@
: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;
justify-content: space-between;
}
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: 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);
}
+431
View File
@@ -0,0 +1,431 @@
: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: var(--partial-dark-background);
--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: white;
border-radius: 0.4375rem;
text-decoration: none;
padding: 0.5rem 1rem;
}
.card .demo .eye {
stroke: 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: capitalize;
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,
footer #footer-face {
display: flex;
align-items: center;
column-gap: 0.625rem;
}
header #header-face img,
footer #footer-face img {
width: 2rem;
height: 2rem;
}
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;
}
}
@media only screen and (min-width: 1440px) {
header {
width: 1368px;
}
}
+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;
}
}
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.
+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: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

+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' ? 'var(--black)' : 'var(--white)');
root.style.setProperty('--color', theme === 'light' ? 'var(--black)' : 'var(--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-18
---
+34
View File
@@ -0,0 +1,34 @@
---
layout: 'about'
title: 'About'
description: 'Summary on the career path of Hazem Krimi'
date: 2023-10-18
---
## 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.
+9
View File
@@ -0,0 +1,9 @@
---
title: "Blog"
description: 'Blog covering Computer Science and Software Engineering topic by Hazem Krimi'
date: 2023-10-18
---
## Blog
These are articles about things I learned about software engineering.
+9
View File
@@ -0,0 +1,9 @@
---
title: 'Projects'
description: 'List of project that Hazem Krimi created or worked on'
date: 2023-10-18
---
## Projects
These are all the projects I worked on personally and professionally.
+7
View File
@@ -0,0 +1,7 @@
---
title: 'Crimson Quirks UI'
description: 'UI component library utilizing Vite and Storybook to be used in my personal projects.'
source: 'https://github.com/hazemKrimi/crimson-quirks-ui'
demo: 'https://www.npmjs.com/package/crimson-quirks-ui'
date: 2025-03-17
---
+6
View File
@@ -0,0 +1,6 @@
---
title: 'Discord Bot (Archived)'
description: 'A discord bot that plays audio tracks from facebook, youtube and podcast websites.'
source: 'https://github.com/hazemKrimi/discord-bot'
date: 2020-03-10
---
+7
View File
@@ -0,0 +1,7 @@
---
title: 'Hack Assembler'
description: 'Assembler for The Hack language from the Nand to Tetris course witten in Rust.'
source: 'https://github.com/hazemKrimi/hack-assembler'
demo: 'https://github.com/hazemKrimi/hack-assembler/releases/tag/v1.0.0'
date: 2025-03-13
---
+6
View File
@@ -0,0 +1,6 @@
---
title: 'Jack VM Translator (Archived)'
description: 'VM Translator from The Jack language VM code to The Hack language assembly code as part of the Nand to Tetris course'
source: 'https://github.com/hazemKrimi/jack-vm-translator'
date: 2024-05-05
---
+7
View File
@@ -0,0 +1,7 @@
---
title: 'Touch Programming'
description: 'Master touch typing with real code snippets from your favorite programming languages, powered by AI.'
source: 'https://github.com/hazemKrimi/touch-programming'
demo: 'https://touch-programming.hazemkrimi.tech'
date: 2025-02-28
---
+17
View File
@@ -0,0 +1,17 @@
server {
server_name hazemkrimi.tech;
access_log /var/log/nginx/access.log;
root /var/www/hazemkrimi.tech;
index index.html;
location / {
try_files $uri $uri/ =404;
}
location ~* \.(?:css|js|ico|ttf|png|svg|webm) {
expires 1M;
access_log off;
add_header Cache-Control "public";
}
}
+61
View File
@@ -0,0 +1,61 @@
baseURL = 'https://hazemkrimi.tech'
languageCode = 'en'
title = 'Hazem Krimi'
[pagination]
pagerSize = 5
enableRobotsTXT = true
[outputs]
home = ['html', 'rss']
section = ['html', 'rss']
[sitemap]
changeFreq = ''
filename = 'sitemap.xml'
priority = -1
[taxonomies]
tag = 'tags'
[params]
dateFormat = '02 January 2006'
defaultDescription = 'Personal website of Hazem Krimi'
defaultKeywords = 'Hazem Krimi, Software Engineer, Software Developer, Full Stack Developer, JavaScript, TypeScript, React.js, 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 = 'Resume'
url = '/hazem-krimi.pdf'
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>
+7
View File
@@ -0,0 +1,7 @@
{{ define "main" }}
{{ partial "breadcrumb.html" . }}
<section>
{{ .Content }}
</section>
{{ end }}
+83
View File
@@ -0,0 +1,83 @@
{{ $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" }}
{{ $normalFont := resources.Get "fonts/OpenSans.ttf" }}
{{ $italicFont := resources.Get "fonts/OpenSans-Italic.ttf" }}
{{ $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" . }}
{{ template "_internal/google_analytics.html" . }}
</body>
</html>
+66
View File
@@ -0,0 +1,66 @@
{{- /* 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 }}
{{- $blogPages := where .Site.RegularPages "Section" "blog" }}
{{- $limit := .Site.Config.Services.RSS.Limit }}
{{- if ge $limit 1 }}
{{- $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 $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 }}
+49
View File
@@ -0,0 +1,49 @@
{{ 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>
</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 }}
+56
View File
@@ -0,0 +1,56 @@
{{ define "styles" }}
{{ $styles := resources.Get "css/index.css" | toCSS | minify }}
<link rel="stylesheet" href="{{ $styles.Permalink }}" />
{{ end }}
{{ define "main" }}
<section id="intro">
<div>
<h1>Hi! I am <span>Hazem</span>,<br> a software engineer currently working at <a href="https://finteum.com" target="_blank">Finteum</a>.</h1>
<p>I have over three 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>
</div>
</div>
</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 3 .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 }}
{{ end }}
+19
View File
@@ -0,0 +1,19 @@
{{ $cv := resources.Get "hazem-krimi.pdf" }}
{{ $faceImage := resources.Get "images/borded-face.webp" }}
<div id="about-card">
<img src="{{ $faceImage.Permalink }}" alt="Hazem Krimi's face" />
<div>
<p>
I am a software engineer with an extensive experience building AI-powered and user-friendly web and cross-platform mobile applications using various technologies including React, React Native, TypeScript, Golang, Rust, Kotlin and Scala to name a few.
</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>
+27
View File
@@ -0,0 +1,27 @@
<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="{{ if (.InSection ($.Site.GetPage "projects")) }} {{ .Params.source }} {{ else }} {{ .Permalink }} {{ end }}"
target="{{ if (.InSection ($.Site.GetPage "projects")) }} _blank {{ else }} _self {{ end }}"
>
{{ if (.InSection ($.Site.GetPage "projects")) }}
Source code {{ 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>
+49
View File
@@ -0,0 +1,49 @@
{{ $faceImage := resources.Get "images/small-face.webp" }}
<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 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>
<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>
</div>
<p id="copyright"></p>
<script>
const copyright = (document.querySelector(
'#copyright'
).innerHTML = `Hazem Krimi &copy ${new Date().getFullYear()}`);
</script>
</footer>
+64
View File
@@ -0,0 +1,64 @@
{{ $faceImage := resources.Get "images/small-face.webp" }}
<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 }} {{ if eq .Weight 5 }}target="_blank"{{ end }}>{{ .Name }}</a>
{{ end }}
</div>
<div id="links">
<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>
<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>
<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>
+51
View File
@@ -0,0 +1,51 @@
<nav id="mobile-navigation">
<div id="menus">
{{ range site.Menus.main.Sort.ByWeight }}
<a {{ printf "href=%q" .URL | safeHTMLAttr }} {{ if eq .Weight 5 }}target="_blank"{{ end }}>{{ .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 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 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>
</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> {}
}
-5
View File
@@ -1,5 +0,0 @@
/// <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.

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