Compare commits
98 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0bb8954903 | |||
| ece597a5f4 | |||
| 5d1d1d8e9c | |||
| 403d482230 | |||
| c69a1893ff | |||
| d050ce20c2 | |||
| ca52e055e3 | |||
| 93773f3e33 | |||
| 3cd9ae8768 | |||
| 4d51e01f24 | |||
| 91b9ff4978 | |||
| a0ad73de86 | |||
| c6cf888547 | |||
| 62dcafdd88 | |||
| 58bf7f0647 | |||
| de1846944e | |||
| c170415a4f | |||
| 43541f851e | |||
| 23e1d62948 | |||
| c3324f12f8 | |||
| 4cc71b0634 | |||
| f5adf3bde1 | |||
| 67a42d867b | |||
| 9a9e8a640b | |||
| 7777828e13 | |||
| c869602115 | |||
| d957bfd07b | |||
| a51858269d | |||
| 713613cc32 | |||
| 9b0bc24b6b | |||
| 2169fcdb11 | |||
| b5f33039db | |||
| 366806f6fa | |||
| 483b9dc431 | |||
| 6a39e94c48 | |||
| 77d40614a6 | |||
| e53ea74824 | |||
| dd4083ef92 | |||
| a393540173 | |||
| 578bc39d47 | |||
| 6245182727 | |||
| 41a5505631 | |||
| 077c4fe728 | |||
| e05564729d | |||
| b790da4c73 | |||
| 40ec57c634 | |||
| bcf61c1b09 | |||
| 91d691d7c1 | |||
| b70e0e9e93 | |||
| d963b389af | |||
| 4598a12887 | |||
| 5ea292e909 | |||
| ad19dc44c3 | |||
| 64723c3860 | |||
| 1a5a9fbfef | |||
| 10ef4654ea | |||
| 6d9b8345e9 | |||
| 84cd9fde72 | |||
| 33b8d792cf | |||
| 6df17ff272 | |||
| 3a07259ebc | |||
| 3211097afc | |||
| 6d21953612 | |||
| 24f3aae9e6 | |||
| 319ebbeb31 | |||
| e81eba1637 | |||
| 7780db6443 | |||
| 892ddca71a | |||
| 770beea258 | |||
| a517624330 | |||
| e413703745 | |||
| 164cf73180 | |||
| 420e348cad | |||
| 8c0894205b | |||
| bc6a43b578 | |||
| de4bd80df8 | |||
| 2ccb68d1b6 | |||
| 254d473e45 | |||
| c906b26105 | |||
| 113620e4da | |||
| b2ae7e64aa | |||
| 8b7a3a6973 | |||
| 2d68203066 | |||
| 0177c8d378 | |||
| 931f307fac | |||
| f19583d172 | |||
| 605c637e08 | |||
| 6570af8f96 | |||
| 81b2326fdf | |||
| 21aa9a293a | |||
| 3f8f491da0 | |||
| 8eeb9fc974 | |||
| c463e30f43 | |||
| 4e31b2877b | |||
| 44705e17f7 | |||
| f5a54d528f | |||
| 521727983c | |||
| 357ac123c1 |
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": ["next/babel"],
|
|
||||||
"plugins": [
|
|
||||||
[
|
|
||||||
"styled-components",
|
|
||||||
{
|
|
||||||
"ssr": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
NEXT_PUBLIC_FORMSPREE_KEY=FORMSPREE_KEY
|
|
||||||
NEXT_PUBLIC_GOOGLE_ANALYTICS_KEY=GOOGLE_ANALYTICS_KEY
|
|
||||||
@@ -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
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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/).
|
||||||
@@ -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
|
|
||||||

|
|
||||||
|
|
||||||
### Template page for the product owner
|
|
||||||

|
|
||||||
|
|
||||||
### Prototype page for the developer
|
|
||||||

|
|
||||||
|
|
||||||
### Support page for the product owner
|
|
||||||

|
|
||||||
|
|
||||||
### User editing page for the admin
|
|
||||||

|
|
||||||
|
|
||||||
# Credits
|
|
||||||
|
|
||||||
- Mohamed Amine Fouzai: [GitHub](https://github.com/MedAmineFouzai), [LinkedIn](https://www.linkedin.com/in/amine-fouzai)
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||

|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
title: "{{ replace .Name "-" " " | title }}"
|
||||||
|
date: {{ .Date }}
|
||||||
|
draft: true
|
||||||
|
---
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 40 KiB |
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 905 B |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 84 KiB |
@@ -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)
|
||||||
|
);
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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);
|
||||||
@@ -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;
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
export type Props = {
|
|
||||||
variant?: 'outline' | 'text';
|
|
||||||
href: string;
|
|
||||||
target?: HTMLAnchorElement['target'];
|
|
||||||
onClick?: () => void;
|
|
||||||
children: React.ReactNode;
|
|
||||||
className?: string;
|
|
||||||
};
|
|
||||||
@@ -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} </span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{image && (
|
|
||||||
<div className='card-image'>
|
|
||||||
<Image alt={title} src={image} fill />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</StyledCard>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Card;
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
export interface Props {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
image?: string;
|
|
||||||
tags?: string[];
|
|
||||||
href: string;
|
|
||||||
target?: HTMLAnchorElement['target'];
|
|
||||||
onClick?: () => void;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
`;
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export interface Props {
|
|
||||||
className: string;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
@@ -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 © {new Date().getFullYear()}</p>
|
|
||||||
</StyledFooter>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Footer;
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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}
|
|
||||||
`;
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
export interface Props {
|
|
||||||
alt: string;
|
|
||||||
icon: string;
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
href?: string;
|
|
||||||
target?: HTMLAnchorElement['target'];
|
|
||||||
onClick?: () => void;
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
@@ -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);
|
|
||||||
`;
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
@@ -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}
|
|
||||||
`;
|
|
||||||
@@ -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;
|
|
||||||
};
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
export type Props = {
|
|
||||||
open: boolean;
|
|
||||||
close: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type StyledProps = {
|
|
||||||
open: boolean;
|
|
||||||
};
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: 'Home'
|
||||||
|
date: 2023-10-18
|
||||||
|
---
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
|
---
|
||||||
@@ -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
|
||||||
|
---
|
||||||
@@ -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
|
||||||
|
---
|
||||||
@@ -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
|
||||||
|
---
|
||||||
@@ -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
|
||||||
|
---
|
||||||
@@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{{ define "main" }}
|
||||||
|
{{ partial "breadcrumb.html" . }}
|
||||||
|
|
||||||
|
<section>
|
||||||
|
{{ .Content }}
|
||||||
|
</section>
|
||||||
|
{{ end }}
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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 }}
|
||||||
@@ -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 }}
|
||||||
@@ -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 }}
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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 © ${new Date().getFullYear()}`);
|
||||||
|
</script>
|
||||||
|
</footer>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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 }}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{{ with .TableOfContents }}
|
||||||
|
<aside id="table-of-contents">
|
||||||
|
<span>Table of contents</span>
|
||||||
|
{{ . }}
|
||||||
|
</aside>
|
||||||
|
{{ end }}
|
||||||
@@ -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> {}
|
|
||||||
}
|
|
||||||
@@ -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.
|
|
||||||