Compare commits

..
19 Commits
Author SHA1 Message Date
Riyyi 74d571c10f Put all binaries in git 2025-04-09 00:02:54 +02:00
Riyyi 9bbbb80e75 Implement sitemap.xml and robots.txt 2025-04-08 23:36:17 +02:00
Riyyi f610edf22c Prepend baseURL to favicon.ico 2025-04-07 20:35:53 +02:00
Riyyi 5aa314e40e Finish blaze lisp article 2025-04-07 19:59:24 +02:00
Riyyi 07fde286c4 Add two more programming articles 2025-04-06 23:02:54 +02:00
Riyyi d76464d4f3 Finish utility library article 2025-04-06 21:47:13 +02:00
Riyyi d033523e88 Add hardware articles 2025-04-06 12:32:25 +02:00
Riyyi 1fe48cfed8 Add software projects pages 2025-04-05 23:17:49 +02:00
Riyyi 48d768d53b Add algorithm pages 2025-04-05 20:27:59 +02:00
Riyyi 71de01045e Create sections for software, hardware, algorithms 2025-04-05 18:39:42 +02:00
Riyyi a3400f401c Use tags instead of subdescription, display date on articles 2025-04-05 17:26:05 +02:00
Riyyi aebff49be9 Fix article image path on index page 2025-04-05 16:53:10 +02:00
Riyyi 551478d15a Order articles by date DESC 2025-04-05 14:59:59 +02:00
Riyyi 2c58f39f07 Filename convention to lowercase-with-dash 2025-04-05 12:19:50 +02:00
Riyyi 232dcf5f14 Create personal website blogpost 2025-04-05 11:04:07 +02:00
Riyyi 88af6052b3 Tweak Markdown page rendering 2025-04-05 11:04:07 +02:00
Riyyi 3067d482f3 Test image rendering 2025-04-04 20:36:18 +02:00
Riyyi 6ac81c61d9 Cleanup 2025-04-04 07:21:06 +02:00
Riyyi 64a8aecf25 Persistent color theme 2025-04-04 07:21:06 +02:00
167 changed files with 3008 additions and 480 deletions
+18 -36
View File
@@ -9,7 +9,7 @@ Website, written in Typescript.
Install dependencies:
#+BEGIN_SRC sh
$ bun install
$ npm install
#+END_SRC
** Development Server
@@ -17,8 +17,8 @@ $ bun install
Start the development server on =http://localhost:3000=:
#+BEGIN_SRC sh
$ bun run dev
$ bun run dev -o # automatically open in the browser
$ npm run dev
$ npm run dev -- -o # automatically open in the browser
#+END_SRC
** Production
@@ -26,14 +26,14 @@ $ bun run dev -o # automatically open in the browser
Build for production:
#+BEGIN_SRC sh
$ bun run build # for server-side rendering or hybrid
$ bun run generate # for static site generation
$ npm run build # for server-side rendering or hybrid
$ npm run generate # for static site generation
#+END_SRC
Locally preview production build:
#+BEGIN_SRC sh
$ bun run preview # requires building first!
$ npm run preview # requires building first!
#+END_SRC
Reference: [[https://nuxt.com/docs/getting-started/deployment#client-side-only-rendering][nuxt/docs/deployment#client-side-only-rendering]]
@@ -43,38 +43,20 @@ Reference: [[https://nuxt.com/docs/getting-started/deployment#client-side-only-r
- [[https://github.com/vuejs/core][Vue3]] JavaScript framework
- [[https://github.com/nuxt/nuxt][Nuxt]] Vue framework
- [[https://github.com/vuejs/pinia][Pinia]] State management
- [[https://github.com/primefaces/primevue][PrimeVue]] Vue component library
- [[https://github.com/twbs/bootstrap][Bootstrap5]] CSS library
- [[https://github.com/colinhacks/zod][Zod]] Schema validation
- [[https://github.com/microsoft/TypeScript][Typescript]] Static types in JavaScript
- [[https://github.com/eslint/eslint][ESLint]] Code analyses tool
** Package Reproduction
* Todo
The following bun commands have been run to fill the =package.json=:
#+BEGIN_SRC sh
$ bun x nuxi@latest init website
# Development
$ bun nuxi module add eslint
$ bun install --dev typescript
$ bun install --dev @vue/language-server
$ bun install --dev @vue/typescript-plugin
$ bun install -g typescript
$ bun install -g @vue/language-server
$ bun install -g @vue/typescript-plugin
# Pinia
$ bun install @pinia/nuxt # also add it to nuxt.config modules!
$ bun install pinia-plugin-persistedstate # same as above
# PrimeVue
$ bun install primevue primeicons @primevue/themes @primevue/forms
$ bun install --dev @primevue/nuxt-module
# Zod
$ bun install zod
# UUID
$ bun install uuid
#+END_SRC
- [X] dark-mode
- [X] persistence in localstorage
- [X] <h> tag scroll fix
- [ ] pull request? https://github.com/nuxt-modules/mdc/tree/main/src/runtime/components/prose
- [X] table of contents: https://github.com/hanyujie2002/nuxt-toc/blob/main/src/runtime/components/TableOfContents.vue
- [ ] for mobile via dropdown?
- [X] nuke: bootstrap-vue-next
- [X] update readme: bun -> npm, primevue -> bootstrap
- [X] better popup code copy button
- [ ] sitemap.xml
+12 -4
View File
@@ -1,13 +1,21 @@
import { defineContentConfig, defineCollection } from '@nuxt/content'
import { defineContentConfig, defineCollection, z } from '@nuxt/content'
import { asSitemapCollection } from "@nuxtjs/sitemap/content"
// Lookup resolved from path: rootDir/content
// see: https://github.com/nuxt/content/issues/3161
export default defineContentConfig({
collections: {
content: defineCollection({
content: defineCollection(
asSitemapCollection({
type: "page",
source: "**/*.md"
source: "**/*.md",
schema: z.object({
date: z.date(),
img: z.string(),
tags: z.array(z.string()),
})
})
)
}
})
});
+91
View File
@@ -0,0 +1,91 @@
---
title: "Bank ATM"
description: "Bank ATM."
navigation: true
date: "2018-03-18"
img: "/img/bank-atm/arduino-fritzing.png"
tags:
- C++14
- Qt4
- Software
- Hardware
---
<small>Bank ATM.<br>
The goal of this project was to create a banking system, including an ATM
client. It was created for one of the courses (project 3) at Hogeschool
Rotterdam and was written in C++14 and Qt4.
The
[slides](https://docs.google.com/presentation/d/1W0L4e7r0Vqp5Qp3RaNcMhqrt1gkLSrU4T6heOZz_IqQ){target="_blank"}
for the presentation that I gave at Hogeschool Rotterdam.
## Product demo
Korte video waarin de meeste functionaliteit van de bank ATM wordt getoond.
::VideoLazy{:src="/img/bank-atm/demo.webm"}
::
## Overzicht componenten
- Bank
- MySQL server
- Native applicatie
- Arduino Nano
- RFID-RC522
- Matrix membrane 4x4 keypad
- USB kabel voor seriële communicatie
![component summary](/img/bank-atm/component-summary.png "component summary")
## Overzicht systeem
De applicatie bestaat uit 3 threads, welke allemaal een unieke functie hebben.
De main thread bevat de GUI. De database thread voert alle queries uit en slaat
deze data op. En als laatst de serial thread, deze thread bevat een listener die
alle seriële data ontvangt. Afhankelijk van wat de serial thread aan user input
ontvangt zal de SerialCommand class "commando's" uitvoeren die de GUI en
database thread gebruiken.
![system summary](/img/bank-atm/system-summary.png "system summary")
## Database ERD
Hieronder is de ER (Entity-relationship) diagram te vinden.
![database ERD](/img/bank-atm/database-erd.png "database ERD")
## Database ontwerp
- MySQL server
- Lokaal opgeslagen
Hieronder is de EER (Enhanced entity-relationship) diagram te vinden.
![database EER](/img/bank-atm/database-design.png "database EER")
## Arduino - fritzing
- Arduino Nano
- RFID-RC522
- Matrix membrane 4x4 keypad
![arduino design](/img/bank-atm/arduino-fritzing.png "arduino design")
## GUI
- Native applicatie
- Geschreven in C++14 en Qt4
- Cross platform (build tool: qmake)
- Multithreaded
Ten alle tijden kan de sessie worden afgebroken door de pas uit de pinautomaat
te verwijderen, de interface wordt daarna naar de inlogpagina doorgestuurd. Alle
velden waar de gebruiker gegevens kan invoeren, hebben de mogelijkheid voor het
weergeven van foutmeldingen.
Hieronder zijn schermvoorbeelden te vinden.
![GUI flow](/img/bank-atm/gui-flow.png "GUI flow")
+93
View File
@@ -0,0 +1,93 @@
---
title: "blaze lisp"
description: "An implementation of the MAL (Make a Lisp) project."
navigation: false
date: "2023-03-18"
img: "/img/mal-steps.png"
tags:
- C++20
- CMake
- Software
---
<small>An implementation of the MAL (Make a Lisp) project.<br>
Repository at
[GitHub](https://github.com/riyyi/blaze){target="_blank"},
[GitLab](https://gitlab.com/riyyi/blaze){target="_blank"} or
[Gitea](https://git.riyyi.com/riyyi/blaze){target="_blank"}.
</small>
After having implemented the JSON serializer for my [utility
library](/articles/utility-library), I became interested in interpreters. The
utility library just has the `tokenization` step of an interpreter, so my tought
was, what's next? That's right, `parsing`.
Having used Emacs quite a bit, I wasn't shy of Lisp. Then, I came across the MAL
([Make a Lisp](https://github.com/kanaka/mal){target="_blank"}) project, and
decided to look into it. The project lays out the implementation steps of making
a Lisp programming language in 10 or so steps and also provides you with unit
tests that have to pass to proceed to the next step. There are also a bunch of
reference implementations to refer to, in case you get stuck.
Feeling like implementing a fully interpreted language was a bit of an
undertaking and realizing that in a Lisp `parsing` and `interpreting` is
basically combined into one step, it seemed achievable and I decided to give the
MAL project a go.
The final architechture that has to be implemented is pictured below.
<div class="row">
<div class="col-lg-9 col-xl-6">
![](/img/mal-steps.png "")
</div>
</div>
I managed to do a full implementation, including `variables`, `if` statements,
`loops`, `functions`, `try/catch` exceptions, `lists`, `arrays`, `hash-maps` and
even `macros`.
With the REPL you can try things out and play around easily.
```
./repl
Blaze [C++]
user>
```
```elisp
user> 123
123
user> (+ 1 2.5)
3.5
user> (- 10 (- 5 3))
8
user> "hello world"
"hello world"
user> (= 2 3)
true
user> (if (> 2 4) "yes" "no")
"no"
user> (count (list 3 6 2 9))
4
user> (count '(3 6 2 9)) ; list shorthand
4
user> (list? [2.1 nil "text"]) ; array / vector
false
user> {:a (+ 7 8)} ; hash-map
{"a" 15}
user> (def! mul (fn* [x y] (* x y))) ; defining a function
#<user-function>(0x5f35f38815d0)
user> (mul 4 5)
20
```
This project was really interesting and the language is quite powerful, powerful
enough to accomplish self-hosting! This means that an interpreter (or rather,
eval+apply) _for_ this language, can be written _in_ the language.
If you're curious about reading more examples, a good place is probably the
testsuite for the project, those have a bunch of unit tests and are available
[here](https://github.com/kanaka/mal/tree/master/impls/tests).
+160
View File
@@ -0,0 +1,160 @@
---
title: "Dijkstra's Shortest Path"
description: "Dijkstra's shortest path."
navigation: false
date: "2021-03-02 02:31:20"
img: "/img/algorithms/dijkstras-shortest-path.png"
tags:
- Algorithm
---
Implementation of the shortest path algorithm, invented by Dijkstra.
![dijkstra's shortest path](/img/algorithms/dijkstras-shortest-path.png "dijkstra's shortest path")
```python
#!/usr/bin/python
import os
import time
# List of all the cities
cities = [
"Ab'Dendriel",
"Ankrahmun",
"Carlin",
"Darashia",
"Edron",
"Liberty Bay",
"Port Hope",
"Roshamuul",
"Oramond",
"Svargrond",
"Thais",
"Venore",
"Yalahar",
"Krailos",
"Issavi",
"Cormaya",
]
# Possible boat fares
fares = [
[None, None, 80, None, 70, None, None, None, None, None, 130, 90, 160, None, None, None], # Ab'Dendriel
[None, None, None, 100, 160, 90, 80, None, None, None, None, 150, 230, None, None, None], # Ankrahmun
[ 80, None, None, None, 110, None, None, None, None, 110, 110, 130, 185, None, None, None], # Carlin
[None, 100, None, None, None, 200, 180, None, None, None, None, 60, 210, 110, 130, None], # Darashia
[ 70, 160, 110, None, None, 170, 150, None, None, None, 160, 40, None, 100, None, 20], # Edron
[None, 90, None, 200, 170, None, 50, None, None, None, 180, 180, 275, None, None, None], # Liberty Bay
[None, 110, None, 180, 150, 50, None, None, None, None, 160, 160, 260, None, None, None], # Port Hope
[None, None, None, None, None, None, None, None, None, None, 210, None, None, None, None, None], # Roshamuul
[None, None, None, None, 110, None, 200, None, None, None, 150, 130, None, 60, 120, None], # Oramond
[None, None, 110, None, None, None, None, None, None, None, 180, 150, None, None, None, None], # Svargrond
[ 130, None, 110, None, 160, 180, 160, 210, 150, 180, None, 170, 200, None, None, None], # Thais
[ 90, 150, 130, 60, 40, 180, 160, None, None, 150, 170, None, 185, 110, 130, None], # Venore
[ 160, 230, 185, 210, None, 275, 260, None, None, None, 200, 185, None, None, None, None], # Yalahar
[None, None, None, 110, 100, None, None, None, 60, None, None, 110, None, None, 70, None], # Krailos
[None, None, None, 80, None, None, None, None, 100, None, None, 80, None, 80, None, None], # Issavi
[None, None, None, None, 20, None, None, None, None, None, None, None, None, None, None, None], # Cormaya
]
# Say phrase
def say(string):
time.sleep(0.2)
os.system('xdotool key Return')
os.system('xdotool keydown Alt')
os.system('xdotool type "' + string + '"')
os.system('xdotool keyup Alt')
os.system('xdotool key Return')
# Say fare
def typeFare(city):
say("hi")
time.sleep(1.0)
say(city)
say("yes")
def dijkstraShortestPath(sourceVertex):
explored = []
unexplored = []
# Setup vertices
for i, fare in enumerate(fares[sourceVertex]):
unexplored.append([i, None, None])
# Set the source vertex distance to 0
if i == sourceVertex:
unexplored[-1][1] = 0
while len(unexplored) > 0:
# Select the closest vertex
closest = None
distance = None
for i, vertex in enumerate(unexplored):
if (distance == None and vertex[1] != None) or \
(vertex[1] != None and vertex[1] < distance):
closest = i
distance = vertex[1]
# Move the closest vertex to explored
explored.append(unexplored.pop(closest))
ID = explored[-1][0]
# Explore this vertex
for i, vertex in enumerate(unexplored):
# Is linked
if fares[ID][vertex[0]] != None:
# If it hasnt been set before
if vertex[1] == None:
vertex[1] = fares[ID][vertex[0]] + explored[-1][1]
vertex[2] = ID
# Or is cheaper
elif vertex[1] > fares[ID][vertex[0]] + explored[-1][1]:
vertex[1] = fares[ID][vertex[0]] + explored[-1][1]
vertex[2] = ID
return explored
def dijkstraTraversePath(sourceVertex, targetVertex, explored):
find = targetVertex
path = [targetVertex]
while find != sourceVertex:
for i, vertex in enumerate(explored):
if vertex[0] == find:
find = vertex[2]
path.append(vertex[2])
break
path.pop()
path.reverse()
return path
def main():
locations = '\n'.join(cities)
current = os.popen('echo "' + locations + '" | rofi -dmenu -i -p "Where are you now?"').read().rstrip()
destination = os.popen('echo "' + locations + '" | rofi -dmenu -i -p "Where are you going?"').read().rstrip()
if not current or not destination: return
currentIndex = cities.index(current)
destinationIndex = cities.index(destination)
# If a direct route is possible
if fares[currentIndex][destinationIndex] != None:
typeFare(destination)
return
# Calculate possible non-direct route
data = dijkstraShortestPath(currentIndex)
path = dijkstraTraversePath(currentIndex, destinationIndex, data)
for index in path:
typeFare(cities[index])
if __name__ == "__main__":
main()
```
Source:<br>
[Dijkstra Algorithm - Example (YouTube)](https://www.youtube.com/watch?v=JcN_nq1EAr4){ target=_blank }
+37
View File
@@ -0,0 +1,37 @@
---
title: "Elevator Floor Module"
description: "Elevator floor module."
navigation: false
date: "2017-12-02"
img: "/img/elevator-floor-module/backside-view-final.jpg"
tags:
- Hardware
---
The goal of this project was to create a working miniature elevator. Each member
of the team made his own level, which we would stack on top of each other in the
end. The engine room was a combined effort of the team and it would talk to all
the levels via I2C. This was my first project at Hogeschool Rotterdam.
Het houten frame van de verdieping.
![](/img/elevator-floor-module/wooden-frame.jpg "")
Hardware schema van alle sensoren, de Arduino Mega representeerd de motorkamer.
![](/img/elevator-floor-module/hardware-schematic.png "")
Het bedieningspaneel aan de voorkant van de lift. De 2 pushbuttons worden
gebruikt om de lift op te roepen. Het segment display geeft de huidige locatie
van de liftkooi weer en het ledje aan de bovenkant simmuleert de deur.
![](/img/elevator-floor-module/frontside-view.jpg "")
De sensoren worden aangestuurt met een Arduino Uno.
De uiteindelijke aansluiting ziet er als volgt uit.
![](/img/elevator-floor-module/backside-view-final.jpg "")
Alle lift verdiepingen van ons project groepje opgestapeld tot een toren.
![](/img/elevator-floor-module/tower.png "")
-83
View File
@@ -1,83 +0,0 @@
---
title: "My First Blog Post"
description: "This is a test article."
navigation: true
---
## Hello World
Foobarbazbuz.
## Component Rendering
::ExampleComponent
#namedslot
This is a Named Slot
#default
This is the Default Slot
::
### Testing Some Markdown Features
A link: [website-vue](https://github.com/riyyi/website-vue)
A codeblock:
```js [file.js]{2} meta-info=val
export default () => {
console.log('Code block')
}
```
```cpp
int i = 0;
```
```php
protected static $router;
$path = parse_url($_SERVER['REQUEST_URI'])['path'];
```
Inline `hightlight` looks like so.
Inline highlight with language `const code: string = 'highlighted code inline'`{lang="ts"} like this.
- An
- Unordered
- List
## Heading With No Subheading
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum a tempor
dolor. Nullam mattis sapien vel finibus dignissim. Etiam et diam ultrices,
aliquam enim nec, commodo sem. Cras ut faucibus risus. Suspendisse vel faucibus
ipsum. Duis vel orci nec arcu porttitor fermentum eu quis est. Phasellus elit
odio, elementum ac placerat at, feugiat sit amet sapien.
Vestibulum dapibus pharetra metus. Integer volutpat lacus nec enim euismod, id
dignissim felis rhoncus. Cras commodo tempus turpis, eu vehicula mi lacinia
eget. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per
inceptos himenaeos. Integer rhoncus dolor ut dolor gravida vestibulum. Ut
dignissim orci a ornare ullamcorper. Nam facilisis mauris sit amet nunc
fermentum pretium. Nullam in nisi at risus luctus viverra. Mauris non congue
dolor, vel finibus lectus. Nam quis leo pretium, sodales augue et, dictum sem.
Curabitur velit ante, imperdiet in eros eu, iaculis gravida risus. Nullam ut
feugiat eros, viverra vehicula sem. Aliquam finibus mi magna, eu fringilla
tellus ullamcorper vel. Fusce eget auctor mi. Mauris venenatis pellentesque
arcu. Nam ac diam sem. Nulla suscipit sed risus non vehicula. Cras molestie
lectus et tincidunt tempor. Ut tempus lorem id augue semper convallis. Aliquam
vel dui quis dolor cursus faucibus eget a mi. Curabitur tempus justo diam, a
facilisis ligula tincidunt viverra. Donec sodales quis dolor at dignissim.
Nullam placerat vitae urna quis bibendum.
Integer a magna a velit bibendum mollis. Donec lobortis molestie diam at rutrum.
Nunc viverra gravida metus at facilisis. Nulla sit amet erat sodales, commodo
elit sed, ultrices magna. Aliquam vel purus fringilla, gravida enim quis,
aliquam augue. Duis interdum et erat nec feugiat. Praesent vitae lacinia leo,
non suscipit purus.
Aenean massa magna, imperdiet id ex quis, mollis auctor lectus. Cras velit
nulla, volutpat eu neque id, semper venenatis urna. Integer in blandit ex, non
scelerisque nisi. Ut sagittis tincidunt enim at volutpat. Sed hendrerit metus ac
interdum laoreet. In rutrum turpis in nulla vulputate suscipit. Nunc dictum nisl
id magna laoreet dapibus.
+170
View File
@@ -0,0 +1,170 @@
---
title: "FizzBuzz"
description: "FizzBuzz."
navigation: false
date: "2021-03-02 02:31:02"
tags:
- Algorithm
---
Implementation of the classic FizzBuzz interview question, done in two ways.
```cpp
#include <iostream>
void calculate()
{
for (int i = 1; i < 101; i++) {
if (i % 15 == 0) {
std::cout << "FizzBuzz" << std::endl;
}
else if (i % 5 == 0) {
std::cout << "Buzz" << std::endl;
}
else if (i % 3 == 0) {
std::cout << "Fizz" << std::endl;
}
else {
std::cout << i << std::endl;
}
}
}
void pattern()
{
const char* pattern[] = {
"%d\n",
"%d\n",
"Fizz\n",
"%d\n",
"Buzz\n",
"Fizz\n",
"%d\n",
"%d\n",
"Fizz\n",
"Buzz\n",
"%d\n",
"Fizz\n",
"%d\n",
"%d\n",
"FizzBuzz\n",
};
for (int i = 0; i < 100; i++) {
printf(pattern[i % 15], i + 1);
}
}
int main(int argc, char* argv[])
{
calculate();
pattern();
return 0;
}
```
Output (x2):
```
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
Fizz
22
23
Fizz
Buzz
26
Fizz
28
29
FizzBuzz
31
32
Fizz
34
Buzz
Fizz
37
38
Fizz
Buzz
41
Fizz
43
44
FizzBuzz
46
47
Fizz
49
Buzz
Fizz
52
53
Fizz
Buzz
56
Fizz
58
59
FizzBuzz
61
62
Fizz
64
Buzz
Fizz
67
68
Fizz
Buzz
71
Fizz
73
74
FizzBuzz
76
77
Fizz
79
Buzz
Fizz
82
83
Fizz
Buzz
86
Fizz
88
89
FizzBuzz
91
92
Fizz
94
Buzz
Fizz
97
98
Fizz
Buzz
```
+33
View File
@@ -0,0 +1,33 @@
---
title: "GarbAGE (Garbage Accurate GameBoy Emulator)"
description: "GameBoy Emulator that is not that accurate."
navigation: false
date: "2022-08-17"
img: "/img/garbage.png"
tags:
- C++20
- CMake
- Software
---
<small>GarbAGE (Garbage Accurate GameBoy Emulator).<br>
Repository at
[GitHub](https://github.com/riyyi/garbage){target="_blank"},
[GitLab](https://gitlab.com/riyyi/garbage){target="_blank"} or
[Gitea](https://git.riyyi.com/riyyi/garbage){target="_blank"}.
</small>
This is an exploration into emulators by me and a friend of mine. The only thing
thats really implemented are the CPU opcodes. The cool thing however, is that
for the rendering portion of the application we are using my own
[game engine](/articles/inferno)! No other libraries were used.
The simplest game to test is Dr. Mario and even that one doesnt render
correctly, but we do get graphics that you can make out, pretty cool!
![garbage](/img/garbage.png "garbage")
Preview video of the boot sequence and intro of the Dr. Mario game.
::VideoLazy{:src="/img/garbage-preview.webm"}
::
+130
View File
@@ -0,0 +1,130 @@
---
title: "Guinea Pig Cage"
description: "Pet guinea pig cage."
navigation: true
date: "2018-06"
img: "/img/guinea-pig-cage/pig.jpg"
tags:
- Hardware
---
This is a custom designed and build cage for the guinea pigs of my girlfriend.
## Design
3D model of the cage.<br>
[View](https://www.tinkercad.com/embed/jtFlKzD78Hj)
the model in 3D.<br>
See the project at
[Thinkercad](https://www.tinkercad.com/things/jtFlKzD78Hj).
![](/img/guinea-pig-cage/3d-model.png "")
::GridWithImage
---
rows:
- title: Saw
- title: File
- title: Sandpaper
- title: Drill
- title: Phillips head screwdriver
- title: Paint brush
- title: Tape measure
---
::
## Shopping list
::GridWithImage
---
rows:
- title: OSB (244x122cm)
src: "/img/guinea-pig-cage/osb.jpg"
- title: Pond liner (200x250cm)
src: "/img/guinea-pig-cage/pond-liner.jpg"
- title: Timberboards (250cm)
src: "/img/guinea-pig-cage/timberboard-before.jpg"
count: 3
- title: Dimensional lumber (270cm)
src: "/img/guinea-pig-cage/dimensional-lumber.jpg"
count: 4
- title: Screws (5cm)
src: "/img/guinea-pig-cage/screws-5cm.jpg"
count: 50
- title: Screws (3.5cm)
src: "/img/guinea-pig-cage/screws-3-5cm.jpg"
count: 10
- title: Wind hook / window buckle
src: "/img/guinea-pig-cage/wind-hook.jpg"
- title: Metal rings
src: "/img/guinea-pig-cage/metal-rings.jpg"
count: 9
- title: Hinges
src: "/img/guinea-pig-cage/hinges.jpg"
count: 1
- title: Child safe paint (375ml)
src: "/img/guinea-pig-cage/paint.jpg"
- title: Brick
---
::
## Build
All measurements of the wooden materials.
| OSB | | Timberboard | | Dimensional lumber |
|---------------------------------------------------|---|------------------------------------------------------------|---|------------------------------------------------------------------|
| ![](/img/guinea-pig-cage/osb-measurements.jpg "") | | ![](/img/guinea-pig-cage/timberboards-measurements.jpg "") | | ![](/img/guinea-pig-cage/dimensional-lumber-measurements.jpg "") |
\* = optional<br>
~ = around
### Construction
![](/img/guinea-pig-cage/saw-file-beams.jpg "")
![](/img/guinea-pig-cage/osb-bottom.jpg "")
![](/img/guinea-pig-cage/osb-flipped.jpg "")
![](/img/guinea-pig-cage/osb-pond-liner.jpg "")
The timberboard has to be painted on the inward facing side, for wear protection.
![](/img/guinea-pig-cage/timberboard-before.jpg "")
![](/img/guinea-pig-cage/timberboard-after.jpg "")
![](/img/guinea-pig-cage/saw-marks-door.jpg "")
![](/img/guinea-pig-cage/saw-door.jpg "")
![](/img/guinea-pig-cage/one-board.jpg "")
![](/img/guinea-pig-cage/all-boards.jpg "")
![](/img/guinea-pig-cage/upper-level.jpg "")
![](/img/guinea-pig-cage/upper-level-lumber.jpg "")
![](/img/guinea-pig-cage/upper-level-front.jpg "")
![](/img/guinea-pig-cage/upper-level-back.jpg "")
![](/img/guinea-pig-cage/water-bottle-line.jpg "")
![](/img/guinea-pig-cage/water-bottle-hole.jpg "")
![](/img/guinea-pig-cage/water-bottle-done.jpg "")
![](/img/guinea-pig-cage/water-bottle-show.jpg "")
![](/img/guinea-pig-cage/legs.jpg "")
![](/img/guinea-pig-cage/door.jpg "")
![](/img/guinea-pig-cage/water-bottle.jpg "")
![](/img/guinea-pig-cage/complete.jpg "")
![](/img/guinea-pig-cage/pig.jpg "")
+432
View File
@@ -0,0 +1,432 @@
---
title: "Inferno"
description: "An open-source game engine."
navigation: true
date: "2019-12-10"
img: "/img/inferno/preview.png"
tags:
- C++20
- GLSL
- Lua
- CMake
- Software
---
<small>An open-source game engine.<br>
Repository at
[GitHub](https://github.com/riyyi/inferno){target="_blank"},
[GitLab](https://gitlab.com/riyyi/inferno){target="_blank"} or
[Gitea](https://git.riyyi.com/riyyi/inferno){target="_blank"}.
</small>
Video games have always been a hobby of mine, but what if I could combine my
profession with this hobby? Then you get this project, a game engine!
This open-source game engine is written in C++20, GLSL and Lua with the
libraries EnTT, glad, GLFW, GLM, sol3 and stb, using the build tool CMake.
## Abstractions
Making a game engine is not an easy task, if the project has any chance of
success it needs a solid foundation. Therefor, the main focus of the project
thus far has been to work on solid, easy-to-use abstractions. The additional
benefit of this approach is that I learned a lot about software design in the
process.
### Events
The library used for window and context creation and receiving input is GLFW,
this is abstracted away in a "Window" wrapper class. This "Window" is created in
the "Application" class, which also binds the callback function for event
handling.
```cpp
// Make it more readable
#define NF_BIND_EVENT(f) std::bind(&f, this, std::placeholders::_1)
m_window = std::make_unique<Window>();
m_window->setEventCallback(NF_BIND_EVENT(Application::onEvent));
```
The event callback is stored in a std::function.
```cpp
inline void setEventCallback(const std::function<void(Event&)>& callback) { m_eventCallback = callback; }
```
Inside the "Window" wrapper, we first associate the wrapper to the internal "GLFWwindow" object.
```cpp
glfwSetWindowUserPointer(m_window, this);
```
Then, it can set the appropriate callback functions. In this example, we set the
position of the mouse. It gets the "Window" wrapper from the association, then
creates an event object, which is passed on to the callback function.
```cpp
// Mouse position callback
glfwSetCursorPosCallback(m_window, [](GLFWwindow* window, double xPos, double yPos) {
Window& w = *(Window*)glfwGetWindowUserPointer(window);
MousePositionEvent event(xPos, yPos);
w.m_eventCallback(event);
});
```
All we have to do to handle these incoming events is create an event dispatcher
and call dispatch. The dispatcher simply checks if the current event is of the
same type as the provided event and calls the function if this is the case.
```cpp
void Application::onEvent(Event& e)
{
EventDispatcher dispatcher(e);
dispatcher.dispatch<MousePositionEvent>(NF_BIND_EVENT(Application::onMousePosition));
}
```
### Logging
I wanted logging to work with both `printf` and `std::cout` style logging. To
make logging fast and easy to use, it also has to be compatible with
user-defined types. The class hierarchy looks like the following:
```
.
└── LogStream
└── BufferedLogStream
├── DebugLogStream
└── StringLogStream
```
"LogStream" is an abstract class with a pure virtual function named "write", to
be used as the interface. We need both variants of the write function in order
to also print extended ASCII characters.
```cpp
class LogStream {
public:
virtual void write(const char* characters, int length) const = 0;
virtual void write(const unsigned char* characters, int length) const = 0;
};
```
To extend the functionality of the "LogStream" class, we override the bitwise
left shift `<<` operator. Support for user-defined types is added in the same way.
```cpp
const LogStream& operator<<(const LogStream& stream, const std::string& value)
{
stream.write(value.c_str(), value.length());
return stream;
}
const LogStream& operator<<(const LogStream& stream, bool value)
{
return stream << (value ? "true": "false");
}
```
In order to keep I/O performance high, logging is done in a buffered manner.
When a "BufferedLogStream" is created it allocates an array of 128 bytes in
length on the stack, which you can then write to. If the buffer size is
exceeded, the buffer grows automatically to the required size in chunks of 128,
this time on the heap.
```cpp
// Append to buffer
memcpy(buffer() + m_count, characters, length);
// Buffer is increased in chunks of 128 bytes
size_t newCapacity = (m_count + bytes + BUFFER_SIZE - 1) & ~(BUFFER_SIZE - 1);
```
The "DebugLogStream" class is used to display messages. It has two variables to
configure this behavior, "newline" and "type", to indicate if a newline should
be printed and the color of the message. When the object goes out of scope, it
will print everything that was added to the buffer in one go.
```cpp
fwrite(buffer(), 1, count(), stdout);
```
Now we get to the actual usage of the system. Using helper functions, printing
colored and formatted messages is easy. Each helper function also has a
non-newline variant as newlines are optional. They simply create a
"DebugLogStream" and return it.
```cpp
DebugLogStream dbg(bool newline);
DebugLogStream info(bool newline);
DebugLogStream warn(bool newline);
DebugLogStream danger(bool newline);
DebugLogStream success(bool newline);
dbg() << "Hello World! " << 2.5 << " " << true;
info() << "This will be printed in blue.";
```
The printf style logging is more complicated than the previous use case. This is
because the function needs to be able to process any number of arguments and of
any type, this is accomplished using a template parameter pack. We use recursion
to loop through all the parameters in this pack, with an overload for when there
is no parameter expansion. Because the "DebugLogStream" is used for printing,
all the type overloads are available to us, so we don't have to specify the type
like usual with `printf`.
```cpp
void dbgln(Log type, bool newline);
void dbgln(Log type, bool newline, const char* format);
template<typename T, typename... P>
void dbgln(Log type, bool newline, const char* format, T value, P&&... parameters)
{
std::string_view view { format };
for(uint32_t i = 0; format[i] != '\0'; i++) {
if (format[i] == '{' && format[i + 1] == '}') {
DebugLogStream(type, false) << view.substr(0, i) << value;
dbgln(type, newline, format + i + 2, parameters...);
return;
}
}
}
```
This style of logging also has a bunch of helper functions to make using them
quick and easy.
```cpp
template<typename... P> void dbgln(const char* format, P&&... parameters) { dbgln(Log::None, true, format, std::forward<P>(parameters)...); }
template<typename... P> void infoln(const char* format, P&&... parameters) { dbgln(Log::Info, true, format, std::forward<P>(parameters)...); }
template<typename... P> void warnln(const char* format, P&&... parameters) { dbgln(Log::Warn, true, format, std::forward<P>(parameters)...); }
template<typename... P> void dangerln(const char* format, P&&... parameters) { dbgln(Log::Danger, true, format, std::forward<P>(parameters)...); }
template<typename... P> void successln(const char* format, P&&... parameters) { dbgln(Log::Success, true, format, std::forward<P>(parameters)...); }
dbgln("Hello {}, print anything! {} {}", "World", 2.5, true);
dangerln("This will print in {}.", "red");
```
Finally, "StringLogStream" is used to convert any supported type into a
std::string. This is achieved by simply converting the buffer in
"BufferedLogStream" to a string and setting the provided string to it when the
object goes out of scope.
```cpp
StringLogStream str(std::string* fill);
std::string result;
str(&result) << "Add " << "anything " << 2.5 << " " << false;
```
### Shaders
"Shader" functionality is split into two classes, "Shader" and "ShaderManager".
The manager does exactly what the name would suggest, it manages the resources.
Using convenient functions you can `add`, `load`, `check existence`, `remove`
shaders. The shaders get stored in a hash table (hash map), with the key being
its name and the value a `std::shared_ptr` to the shader object. Adding anything
to the manager that has already been loaded will simply return the existing
instance, to prevent duplication. The other pairs "Texture/TextureManager",
"Font/FontManager" and "Gltf/GltfManager" are structured similarly.
```cpp
void add(const std::string& name, const std::shared_ptr<Shader>& shader);
std::shared_ptr<Shader> load(const std::string& name);
std::shared_ptr<Shader> load(const std::string& vertexSource,
const std::string& fragmentSource);
bool exists(const std::string& name);
void remove(const std::string& name);
void remove(const std::shared_ptr<Shader>& shader);
std::unordered_map<std::string, std::shared_ptr<Shader>> m_shaderList;
```
To construct a "Shader", only a name needs to be provided. It will then load,
compile and link both the vertex and fragment shader files. Any errors like
files not existing or GLSL syntax errors will be printed to the console.
```cpp
// Get file contents
std::string vertexSrc = File::read(name + ".vert");
std::string fragmentSrc = File::read(name + ".frag");
// Compile shaders
uint32_t vertexID = compileShader(GL_VERTEX_SHADER, vertexSrc.c_str());
uint32_t fragmentID = compileShader(GL_FRAGMENT_SHADER, fragmentSrc.c_str());
// Link shaders
if (vertexID > 0 && fragmentID > 0) {
m_id = linkShader(vertexID, fragmentID);
}
```
An uniform is a global "Shader" variable, to set uniform data, there are
functions for the most common used data types. ASSERT is a wrapper for `dbgln`,
which can print any registered data types, explained in the
[Logging](#logging)
section.
```cpp
int32_t findUniform(const std::string& name) const;
void setInt(const std::string& name, int value);
void setInt(const std::string& name, int* values, uint32_t count);
void setFloat(const std::string& name, float value) const;
void setFloat(const std::string& name, float v1, float v2, float v3, float v4) const;
void setFloat(const std::string& name, glm::vec2 value) const;
void setFloat(const std::string& name, glm::vec3 value) const;
void setFloat(const std::string& name, glm::vec4 value) const;
void setFloat(const std::string& name, glm::mat3 matrix) const;
void setFloat(const std::string& name, glm::mat4 matrix) const;
void bind() const;
void unbind() const;
int32_t Shader::findUniform(const std::string& name) const
{
int32_t location = glGetUniformLocation(m_id, name.c_str());
ASSERT(location != -1, "Shader could not find uniform '{}'", name);
return location;
}
void Shader::setInt(const std::string& name, int value)
{
// Set unifrom int
glUniform1i(findUniform(name), value);
}
```
In the renderer we only need to do the following. The `the` function in
"ShaderManager" is a static function that gets the instance of the singleton.
```cpp
m_shader = ShaderManager::the().load("assets/glsl/batch-quad");
m_shader->bind();
m_shader->setFloat("u_projectionView", cameraProjectionView);
m_shader->unbind();
```
### Buffers
Rendering in OpenGL is done using a set of two buffers, the vertex buffer and
the index buffer. The vertex buffer is used to store geometry data, in the
format of points. From these points, you can construct triangles, which can
actually be rendered. The constructed triangles are stored as indexes to points
in the index buffer, the simplest form of an index buffer of a single triangle
looks like the following: `[0, 1, 2]`. When you have these two buffers set up
correctly, you can draw a triangle.
```cpp
void RenderCommand::drawIndexed(const VertexArray& vertexArray, uint32_t indexCount)
{
uint32_t count = indexCount ? indexCount : vertexArray.getIndexBuffer()->getCount();
glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, nullptr);
}
```
To create a vertex buffer a lot of manual setup is needed. This includes offset
calculation, which is very prone to errors, so these steps are abstracted away
in the "VertexBuffer" class. A "VertexBuffer" object stores a "BufferLayout",
which stores a `std::vector` of "BufferElements". "BufferElements" are what
OpenGL calls vertex attributes, which allows us to specify any input we want,
because of this we also have to specify how the data should be interpreted. The
"BufferElement" objects hold the variables `type`, the types `size`, `offset` in
the buffer and if the data is normalized, these can be accessed via getter
functions. Now for the fun part of vertex buffers, the "BufferLayout", via a
`std::initializer_list` they can be constructed very easily.
```cpp
BufferLayout::BufferLayout(const std::initializer_list<BufferElement>& elements)
: m_elements(elements)
{
calculateOffsetsAndStride();
}
void BufferLayout::calculateOffsetsAndStride()
{
m_stride = 0;
for (auto& element : m_elements) {
element.setOffset(m_stride);
m_stride += element.getSize();
}
}
```
Because of OpenGL's C-like API, rendering requires manual binding of the vertex
attribute configurations and vertex buffers, which is a lot of boilerplate. In
order to simplify this, a new concept was added called a vertex array object
(also known as VAO), which is actually required in OpenGL's core profile. VAOs
store the vertex attribute configuration and the associated vertex buffers, they
are abstracted away in the "VertexArray" class which stores the "VertexBuffers"
and an "IndexBuffer". When adding a "VertexBuffer" to a "VertexArray", it will
set up the vertex attribute configuration.
```cpp
void VertexArray::addVertexBuffer(std::shared_ptr<VertexBuffer> vertexBuffer)
{
const auto& layout = vertexBuffer->getLayout();
ASSERT(layout.getElements().size(), "VertexBuffer has no layout");
bind();
vertexBuffer->bind();
uint32_t index = 0;
for (const auto& element : layout) {
glEnableVertexAttribArray(index);
glVertexAttribPointer(
index,
element.getTypeCount(),
element.getTypeGL(),
element.getNormalized() ? GL_TRUE : GL_FALSE,
layout.getStride(),
reinterpret_cast<const void*>(element.getOffset()));
index++;
}
m_vertexBuffers.push_back(std::move(vertexBuffer));
unbind();
vertexBuffer->unbind();
}
```
The final usage looks like the following, notice how the layout is created
easily using the `std::initializer_list` in the `setLayout` function.
```cpp
// Create vertex array
m_vertexArray = std::make_shared<VertexArray>();
// Create vertex buffer
auto vertexBuffer = std::make_shared<VertexBuffer>(sizeof(QuadVertex) * vertexCount);
vertexBuffer->setLayout({
{ BufferElementType::Vec3, "a_position" },
{ BufferElementType::Vec4, "a_color" },
{ BufferElementType::Vec2, "a_textureCoordinates" },
{ BufferElementType::Float, "a_textureIndex" },
});
m_vertexArray->addVertexBuffer(vertexBuffer);
uint32_t indices[] { 0, 1, 2, 2, 3, 0 };
// Create index buffer
auto indexBuffer = std::make_shared<IndexBuffer>(indices, sizeof(uint32_t) * indexCount);
m_vertexArray->setIndexBuffer(indexBuffer);
```
## Design Structure
Pictured below are all the classes currently in the codebase and the
relationships between them.
![design](/img/inferno/design.png "design")
## Preview
There isn't much visual to show as of now.
![preview](/img/inferno/preview.png "preview")
+154
View File
@@ -0,0 +1,154 @@
---
title: "Invert Binary Tree"
description: "Invert binary tree."
navigation: false
date: "2021-03-02 02:31:09"
img: "/img/algorithms/invert-binary-tree.png"
tags:
- Algorithm
---
Implementation of inverting a binary tree.
![invert binary tree](/img/algorithms/invert-binary-tree.png "invert binary tree")
binarytree.h
```cpp
#ifndef BINARYTREE_H
#define BINARYTREE_H
#include <cstdint>
class BinaryTree {
public:
// Contructors, Destructors
BinaryTree(uint32_t value, BinaryTree* left = nullptr,
BinaryTree* right = nullptr);
// Functions
void print();
void invert();
// Getters, Setters
inline uint32_t value() { return m_value; }
private:
uint32_t m_value;
BinaryTree* m_left;
BinaryTree* m_right;
};
#endif // BINARYTREE_H
```
binarytree.cpp
```c
#include <iomanip>
#include <iostream>
#include <string>
#include "binarytree.h"
BinaryTree::BinaryTree(uint32_t value, BinaryTree *left, BinaryTree *right)
: m_value(value), m_left(left), m_right(right) {}
void BinaryTree::print()
{
// Skip nodes without children
if (!m_left && !m_right) {
return;
}
// Value
std::cout << std::setw(2) << m_value << " -> ";
// Left
std::cout << std::setw(2) << (m_left ? std::to_string(m_left->value()) : "") << " | ";
// Right
std::cout << (m_right ? std::to_string(m_right->value()) : "") << std::endl;
// Recursion
if (m_left) {
m_left->print();
}
if (m_right) {
m_right->print();
}
}
void BinaryTree::invert()
{
// Swap
BinaryTree* tmp = m_left;
m_left = m_right;
m_right = tmp;
// Recursion
if (m_left) {
m_left->invert();
}
if (m_right) {
m_right->invert();
}
}
```
main.cpp
```cpp
#include <iostream>
#include "binarytree.h"
int main(int argc, char* argv[])
{
(void) argc;
(void) argv;
BinaryTree n3(3);
BinaryTree n9(9);
BinaryTree n13(13);
BinaryTree n4(4, &n3);
BinaryTree n6(6);
BinaryTree n8(8, nullptr, &n9);
BinaryTree n12(12, nullptr, &n13);
BinaryTree n5(5, &n4, &n6);
BinaryTree n10(10, &n8, &n12);
BinaryTree n7(7, &n5, &n10);
std::cout << std::endl << "Binary Tree:" << std::endl;
n7.print();
n7.invert();
std::cout << std::endl << "Binary Tree Inverted:" << std::endl;
n7.print();
return 0;
}
```
Output:
```
Binary Tree:
7 -> 5 | 10
5 -> 4 | 6
4 -> 3 |
10 -> 8 | 12
8 -> | 9
12 -> | 13
Binary Tree Inverted:
7 -> 10 | 5
10 -> 12 | 8
12 -> 13 |
8 -> 9 |
5 -> 6 | 4
4 -> | 3
```
Source:<br>
[Binary Search Trees in Java (YouTube)](https://www.youtube.com/watch?v=Qa5r8Wsda70){ target=_blank }
+63
View File
@@ -0,0 +1,63 @@
---
title: "Manafiles"
description: "Config file and package tracking utility."
navigation: false
date: "2021-09-04"
img: "/img/manafiles-unit-test.png"
tags:
- C++20
- CMake
- Software
---
<small>Config file and package tracking utility.<br>
Repository at
[GitHub](https://github.com/riyyi/manafiles){target="_blank"},
[GitLab](https://gitlab.com/riyyi/manafiles){target="_blank"} or
[Gitea](https://git.riyyi.com/riyyi/manafiles){target="_blank"}.
</small>
Written in C++20, using the build tool CMake.
The goal of this project is simple reproducibility of a Linux system, without
using symlinks. This is achieved by managing configuration files and keeping
track of what packages were installed.
In order to use the same bundle of files for multiple systems, the program
allows to specify variables inside of the configuration files. These
configuration lines will then get commented or uncommented when pushing the
configuration to the system, depending on the value of the variables. The
variables that are supported are the `distribution` the `hostname`, the
`username`, and the display `session`, which is either X.Org or Wayland.
Below an example of a variable block, where I set the amount of jobs the
compiler will use, depending on the hostname, because my desktop has more cores
than my laptop.
```
# >>> hostname=arch-desktop
MAKEFLAGS="-j8"
# <<<
# >>> hostname=arch-laptop
# MAKEFLAGS="-j4"
# <<<
```
List of features:
- Manage dotfiles and system config files.
- Selectively comment and uncomment depending on machine configuration.
- Store a list of all installed packages.
- Install packages from a stored list.
- Pattern matching in the config file and cli arguments.
- Test suite with unit tests, using my own macros.
Pictured below is the output of running the test suite:
<div class="row">
<div class="col-6">
![manafiles unit test output](/img/manafiles-unit-test.png "manafiles unit test output")
</div>
</div>
+36
View File
@@ -0,0 +1,36 @@
---
title: "OpenGL Test"
description: "OpenGL test."
navigation: false
date: "2021-02-20"
img: "/img/opengl-test-preview.png"
tags:
- C++14
- GLSL
- CMake
- Software
---
<small>Config file and package tracking utility.<br>
Repository at
[GitHub](https://github.com/riyyi/opengl-test){target="_blank"},
[GitLab](https://gitlab.com/riyyi/opengl-test){target="_blank"} or
[Gitea](https://git.riyyi.com/riyyi/opengl-test){target="_blank"}.
</small>
Written in C++ with glad, glfw, glm and stb, using the build tool CMake.
Created for the Computer Graphics course at Hogeschool Rotterdam.
Design structure.
![opengl test design](/img/opengl-test-design.png "opengl test design")
Preview.
![opengl test preview](/img/opengl-test-preview.png "opengl test preview")
Demo showcasing translation, rotation, scaling and textures working. You can
also fly around, which is not shown in the video.
::VideoLazy{:src="/img/opengl-test-demo.webm"}
::
+127
View File
@@ -0,0 +1,127 @@
---
title: "Personal Website"
description: "An open-source content management system, used for this website."
navigation: false
date: "2021-03-03"
img: "/img/personal-website/admin-menu.png"
tags:
- PHP 7
- MySQL
- jQuery
- Software
---
<small>Open-source content management system.<br>
Repository at
[GitHub](https://github.com/riyyi/website){target="_blank"},
[GitLab](https://gitlab.com/riyyi/website){target="_blank"} or
[Gitea](https://git.riyyi.com/riyyi/website){target="_blank"}.
</small>
This is the CMS that is used for this website! It's written in PHP 7, MySQL and
jQuery, with the libraries Klein.php and Mailer.
Features:
- PHP 7
- Composer
- [Klein.php](https://github.com/klein/klein.php)
- [Mailer](https://github.com/txthinking/Mailer)
- MVC design pattern
- MySQL database for storing data
- CMS with CRUD functions for managing data
- ORM for mapping between PHP classes and data
- Login system
- Stay logged in using cookies
- Forget password with a generated link send using mail
- Security mitigations
- Password hashing using BCrypt
- Per-user cryptographically secure generated salt
- SQL injection protection using prepared statements
- XSS
- Cookies set to 'HttpOnly'
- Escape rendered user input using: `htmlentities(ENT_QUOTES | ENT_HTML5, 'UTF-8');`
- CSRF
- Cookies 'SameSite' set to 'Strict'
- Token for each session used in POST/PUT/DELETE requests
- Bootstrap
- jQuery
Directory structure:
```
.
├── app
│ ├── classes
│ │ └── <classes>
│ ├── controllers
│ │ └── <controllers>
│ ├── helper.php
│ ├── model
│ │ └── <models>
│ ├── seed.php
│ ├── traits
│ │ └── Log.php
│ └── views
│ └── <views>
├── composer.json
├── config.php
├── config.php.example
├── public
│ ├── index.php
│ └── <files>
├── route.php
├── syncconfig.sh
├── syncconfig.sh.example
└── sync.sh
```
<div class="row">
<div class="col-12 col-lg-6">
Pictured below is the EER (Enhanced entity-relationship) diagram of the MySQL database:
![website database design](/img/personal-website/database-design.png "website database design")
</div>
</div>
Some of the pages of the CMS.
<div class="row">
<div class="col-12 col-lg-6">
Admin menu.
![admin menu](/img/personal-website/admin-menu.png "admin menu")
</div>
<div class="col-12 col-lg-6">
CRUD index page, displaying all the entries of this table, including pagination.
![crud index page](/img/personal-website/crud-index.png "crud index page")
</div>
<div class="col-12 col-lg-6">
CRUD edit page, editing an entry.
![crud edit page](/img/personal-website/crud-edit.png "crud edit page")
</div>
<div class="col-12 col-lg-6">
CRUD show page, show all values of an entry.
![crud show page](/img/personal-website/crud-show.png "crud show page")
</div>
<div class="col-12 col-lg-6">
Login page.
![login page](/img/personal-website/login.png "login page")
</div>
<div class="col-12 col-lg-6">
Password reset page, showing a flash message.
![password reset page](/img/personal-website/reset-password.png "password reset page")
</div>
</div>
-8
View File
@@ -1,8 +0,0 @@
---
title: "My Second Blog Post"
description: "This is another article."
---
# Test!
This is another article.
+67
View File
@@ -0,0 +1,67 @@
---
title: "Sieve of Eratosthenes"
description: "Sieve of Eratosthenes."
navigation: false
date: "2021-03-02 02:31:16"
img: "/img/algorithms/sieve-of-eratosthenes.gif"
tags:
- Algorithm
---
Implementation of the ancient sieve of Eratosthenes math algorithm, used for
finding prime numbers up to a given limit.
![sieve of eratosthenes](/img/algorithms/sieve-of-eratosthenes.gif "sieve of eratosthenes")
```c
#include <stdbool.h> // bool
#include <stdio.h> // printf
#include <string.h> // memset
int main(int argc, char* argv[]) {
int limit = 10000;
int calculate = limit * 20;
// Create an array and set all to true
bool numbers[calculate];
memset(&numbers, true, sizeof(bool) * calculate);
// Loop through all the numbers
for (int i = 2; i < calculate; i++) {
// Already crossed out numbers don't have to be checked
if (numbers[i]) {
// Cross out all the numbers that can't be a prime, which are multiples of itself
for (int j = i * i; j < calculate; j += i) {
numbers[j] = false;
}
// Once you exceed the calculate range, you can exit the loop
if (i * i > calculate) {
break;
}
}
}
int sum = 0;
int counter = 1;
// Get the sum of the first 10000 primes
for (int i = 2; i < calculate; i++) {
if (numbers[i]) {
sum += i;
if (counter >= limit) {
break;
}
counter++;
}
}
printf("sum of first %d primes is: %d\n", counter, sum);
return 0;
}
```
Source:<br>
[https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes){ target=_blank }
+28
View File
@@ -0,0 +1,28 @@
---
title: "Simple 2D RPG"
description: "Simple 2D RPG."
navigation: false
date: "2015-03-09"
img: "/img/simple-2d-rpg.png"
tags:
- C++11
- CMake
- Software
---
<small>Simple 2D RPG.<br>
Repository at
[GitHub](https://github.com/riyyi/rpg){target="_blank"},
[GitLab](https://gitlab.com/riyyi/rpg){target="_blank"} or
[Gitea](https://git.riyyi.com/riyyi/rpg){target="_blank"}.
</small>
Written in C++ with SFML and RapidJSON, using the build tool CMake.
This is a very old project, the only things that are implemented are walking and
tile collision. Mainly used for exploring the SFML library and Tiled tool for
level design.
Preview.
![preview](/img/simple-2d-rpg.png "preview")
+80
View File
@@ -0,0 +1,80 @@
---
title: "Smart Outlet"
description: "Home automation smart outlet."
navigation: false
date: "2018-10-31"
img: "/img/smart-outlet/result.jpg"
tags:
- Hardware
---
This page describes my version of a smart socket for home automation.
The device can be toggled via the button on the enclosure or a wireless signal.
## Build
Pictured below is a scetch of the concept of the project.<br>
As well as an electrical circuit.
![](/img/smart-outlet/concept-drawing.jpg "")
The MDF enclosure is cut out with a laser cutter. The base of the design is
generated via
[boxes.py](https://www.festi.info/boxes.py/ClosedBox).
Holes have been added to the base so that the cable,
socket, LED and button can be mounted.
![](/img/smart-outlet/box-design.png "")
The button is printed with a 3D printer, the bottom part of the button is
extended by 2mm on each side (4mm in total), so that it stays in place.
![](/img/smart-outlet/button-3d-model.png "")
This is the final electrical circuit that was used, which is made in Fritzing.
All hardware components (minus the power supply) are incorporated in this
circuit. A red LED is used as an example appliance that can be toggled.
![](/img/smart-outlet/fritzing.png "")
Pictured below is a model of the design in Tinkercad, the button will be printed
in 3D and the housing will be cut out with the laser cutter. The black cylinder
represents the power cable.
Front view.
![](/img/smart-outlet/concept-3d-front.png "")
Side view.
![](/img/smart-outlet/concept-3d-side.png "")
Back view.
![](/img/smart-outlet/concept-3d-back.png "")
Box cut out.
![](/img/smart-outlet/box-cut-out.jpg "")
3D printed button.
![](/img/smart-outlet/button-3d-print.jpg "")
Overview of all the electronics.
![](/img/smart-outlet/electronics-overview.jpg "")
The electronics mounted in the box.
![](/img/smart-outlet/electronics-in-box.jpg "")
Completed assembly.
![](/img/smart-outlet/result.jpg "")
Domoticz is used as a controller, which completes the smart outlet project. View
the demo below.
::VideoLazy{:src="/img/smart-outlet/demo.webm"}
::
+65
View File
@@ -0,0 +1,65 @@
---
title: "Space Walk"
description: "Space Walk board game."
navigation: false
date: "2021-02-18"
img: "/img/space-walk/phase-1.png"
tags:
- C++11
- Software
---
<small>Board game.<br>
Repository at
[GitHub](https://github.com/riyyi/space-walk){target="_blank"},
[GitLab](https://gitlab.com/riyyi/space-walk){target="_blank"} or
[Gitea](https://git.riyyi.com/riyyi/space-walk){target="_blank"}.
</small>
This is an implementation of the [Space
Walk](https://mancala.fandom.com/wiki/Space_Walk){target="_blank"} board game,
written in C++ with ncurses for the UI, built using GNU Make. Created for the
C++ course at Hogeschool Rotterdam.
UML design.
![UML design](/img/space-walk/uml.png "UML design")
<div class="row">
<div class="col-12 col-lg-6">
Title screen
![title screen](/img/space-walk/title-screen.png "title screen")
</div>
<div class="col-12 col-lg-6">
Setting player names.
![setting up players](/img/space-walk/player-name.png "setting up players")
</div>
<div class="col-12 col-lg-6">
Message box informing the players of the current phase.
![message box](/img/space-walk/phase-1.png "message box")
</div>
<div class="col-12 col-lg-6">
Players take turns putting their space ships onto the planets.
![player turn langing space ship](/img/space-walk/place-ship.png "player turn langing space ship")
</div>
<div class="col-12 col-lg-6">
Message box informing the players of the current phase.
![message box](/img/space-walk/phase-2.png "message box")
</div>
<div class="col-12 col-lg-6">
Players take turns evacuating the planets, where ships that pass the edges of
the board will get sucked into black holes (top and bottom square).
![player turn for evacuating](/img/space-walk/evacuate.png "player turn for evacuating")
</div>
</div>
+83
View File
@@ -0,0 +1,83 @@
---
title: "Xbox 360 Controller Type-C Mod"
description: "Mod to change the Xbox 360 wired controller to Type-C."
navigation: false
date: "2022-11-28"
img: "/img/type-c-mod/controller-after.jpg"
tags:
- Hardware
---
<small>Mod to change the Xbox 360 wired controller to Type-C.<br>
## Result
![](/img/type-c-mod/controller-before.jpg "")
![](/img/type-c-mod/controller-after.jpg "")
## Tools
::GridWithImage
---
rows:
- title: Soldering iron
- title: Wire stripper
src: "/img/type-c-mod/wire-stripper.jpg"
- title: Round file
src: "/img/type-c-mod/round-file.jpg"
- title: Nail file
src: "/img/type-c-mod/nail-file.jpg"
---
::
## Shopping list
::GridWithImage
---
rows:
- title: Solder
- title: 3D printed bezel
src: "/img/type-c-mod/printed-bezel.jpg"
- title: USB Type-C female socket
src: "/img/type-c-mod/port.jpg"
---
::
Unfortunately, the original design was a little fragile at the round part that
would slide into the housing and it would snap off. I tweaked the design by
moving the USB port up by 1mm and reinforcing it by attaching it on the sides,
as there was space to do so. The 3D bezel design was taken from here:
[thingiverse.com/thing:3066354](https://thingiverse.com/thing:3066354).<br>
The new edited design:
[thingiverse.com/thing:5664077](https://thingiverse.com/thing:5664077).<br>
This is the USB-C port I used:
[aliexpress.com/item/1005002328403673.html](https://aliexpress.com/item/1005002328403673.html)
## Build
Unmodified, the original cable hole is a little too narrow to fit a Type-C port.
![](/img/type-c-mod/before-filing.jpg "")
I used the nail file to slightly widen the hole, this is what it looks like afterwards.
![](/img/type-c-mod/after-filing.jpg "")
Initially, my plan was to replace the original connector with a male pin header
and dupont connectors, but my soldering iron couldn't get hot enough to melt the
solder of the thick pins. So instead, I repurposed the original wires.
![](/img/type-c-mod/failed-idea.jpg "")
I used a third hand to help keep the port in place while soldering.
![](/img/type-c-mod/soldering-port.jpg "")
Finished soldering.
![](/img/type-c-mod/soldering-port-finished.jpg "")
Test fitting everything together.
![](/img/type-c-mod/test-fit.jpg "")
+317
View File
@@ -0,0 +1,317 @@
---
title: "Utility Library"
description: "Utility library."
navigation: true
date: "2022-08-17"
img: "/img/ruc-example-unit-test.png"
tags:
- C++20
- CMake
- Software
---
<small>Utility library.<br>
Repository at
[GitHub](https://github.com/riyyi/ruc){target="_blank"},
[GitLab](https://gitlab.com/riyyi/ruc){target="_blank"} or
[Gitea](https://git.riyyi.com/riyyi/ruc){target="_blank"}.
</small>
C++20 utility library without any dependencies, using build tool CMake.
This is an attempt at deduplicating all the commonly used functionality across
my projects and create one cohesive style.
## Argument parsing
This is a simple argument parsing feature, with the attempt of making it simpler
to use than getopt. All you have to do is specify the variables you want to use,
then add the options and arguments on the `ArgParser` object and finally call
the `parse` function.
```cpp
#include <string>
#include <vector>
#include "ruc/argparser.h"
int main(int argc, const char* argv[])
{
bool verbose = false;
std::vector<std::string> targets {};
ruc::ArgParser argParser;
argParser.addOption(verbose, 'v', "verbose", nullptr, nullptr);
argParser.addArgument(targets, "targets", nullptr, nullptr, ruc::ArgParser::Required::No);
argParser.parse(argc, argv);
// Do some work here..
return 0;
}
```
The `nullptr`'s in the above example were going to be used for automatic help
string generation, but that isn't implemented yet.
## Formatting library
This is basically a partial copy of the `fmt` library, from before that became
part of the C++20 standard and I didn't want to use an additional dependency in
my projects, because that is less fun!
The part of `fmt` that is implement is the
"[mini-language](https://fmt.dev/11.1/syntax/#format-specification-mini-language)",
which is a very convenient API.
```cpp
#include <string>
#include "ruc/format/format.h"
#include "ruc/format/print.h"
int main(int argc, const char* argv[])
{
std::string fmt = format(
R"(
number {}
string {}
bool {}j
double {}
double {:.2} with 2 precision
)", 123, "this is a string", true, 456.789, 3.14159265359);
print("{}\n", fmt);
return 0;
}
```
Which results the the output.
```
number 123
string this is a string
bool true
double 456.789000
double 3.14 with 2 precision
```
The implementation consists of 2 major parts, the parsing of the format string
and the `format` and `print` functions. The parsing part isn't that complex, so
wont be discussed here. The other secion is interesting, however.
The functions are implemented with variadic arguments using type erasure, this
improves both compile time and binary size significantly. What it also does, is
allow the library to work on types that are specified by the user and are
therefor not part of the library.
The formatter implements all the primitives and some of the STL types, it can be
extended by the user. The basic use-case is to specify to just the `format`
function, but the `parser` function can also be overwritten.
```cpp
// In the header we are extending the existing formatter for vectors,
// so we take advantage of their nice formatting automatically
template<>
struct ruc::format::Formatter<glm::vec4> : Formatter<std::vector<float>> {
void format(Builder& builder, glm::vec4 value) const;
};
// Then in the implementation, we implement the format functon
void ruc::format::Formatter<glm::vec4>::format(Builder& builder, glm::vec4 value) const
{
return Formatter<std::vector<float>>::format(builder, { value.x, value.y, value.z, value.w });
}
```
Users can even extend formatters based on their own formats. In this example we
are extending the vector (`glm::vec4`) formatter, so we can also print matrices
(`glm::mat4`).
```cpp
template<>
struct ruc::format::Formatter<glm::mat4> : Formatter<glm::vec4> {
void format(Builder& builder, glm::mat4 value) const;
};
void ruc::format::Formatter<glm::mat4>::format(Builder& builder, glm::mat4 value) const
{
builder.putString("mat4 ");
Formatter<glm::vec4>::format(builder, value[0]);
builder.putString("\n ");
Formatter<glm::vec4>::format(builder, value[1]);
builder.putString("\n ");
Formatter<glm::vec4>::format(builder, value[2]);
builder.putString("\n ");
return Formatter<glm::vec4>::format(builder, value[3]);
}
```
## JSON parsing
This is a full implementation of the JSON
[RFC 7159](https://datatracker.ietf.org/doc/html/rfc7159)
specification. Created mostly
for fun, but also for the convenient API.
First, lets specify some JSON that we want to parse.
```js
{
"window": {
"title": "Inferno",
"width": 1280
"height": 720,
"fullscreen": "windowed",
"vsync": false,
}
}
```
Then, define the structs the JSON will get serialized into.
```cpp
struct WindowProperties {
std::string title { "Inferno" };
uint32_t width { 1280 };
uint32_t height { 720 };
std::string fullscreen { "borderless" };
bool vsync { true };
};
struct SettingsProperties {
WindowProperties window;
};
```
To deserialize the JSON into the struct defined above, we first need to read the
contents of the JSON file. Then we call the parse function on it, which will
return an instance of the base class of the JSON library. Calling the get
function on this instance will try to convert it to the specified type, using
user-declared conversion functions, more on this later.
```cpp
ruc::Json object = ruc::Json::parse(ruc::File("assets/settings.json").data());
if (object.type() != ruc::Json::Type::Object) {
ruc::warn("Settings invalid formatting");
return false;
}
SettingsProperties properties = object.get<SettingsProperties>();
```
Serializing back into JSON is simple: just create an instance of the JSON base
class, whose constructor takes in any type. After, call the dump function on it.
```cpp
ruc::Json object = properties;
auto file = ruc::File("assets/settings.json");
file.clear();
file.append(object.dump(1, '\t'));
file.append("\n");
file.flush();
```
So how does this work, how are the JSON objects mapped to the C++ instances? The
library is using a [clever
trick](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html){target="_blank"}
with Argument-Dependent Lookup (ADL). "Lookup" refers to name lookup, which is
the process a C++ compiler uses to resolve identifiers to their declarations. We
let the compiler search for a function of a specific shape, anywhere in the
project, that allows the library to find functions declared by the user of that
library! What this effectively means is that all a user has to do is declare a
`to` and `from` conversion function anywhere in his project and these will get
used automatically by the library.
Below the implementation of the window settings.
```cpp
void fromJson(const ruc::Json& object, WindowProperties& window)
{
VERIFY(object.type() == ruc::Json::Type::Object);
if (object.exists("title"))
object.at("title").getTo(window.title);
if (object.exists("width"))
object.at("width").getTo(window.width);
if (object.exists("height"))
object.at("height").getTo(window.height);
if (object.exists("fullscreen"))
object.at("fullscreen").getTo(window.fullscreen);
if (object.exists("vsync"))
object.at("vsync").getTo(window.vsync);
}
void toJson(ruc::Json& object, const WindowProperties& window)
{
object = ruc::Json {
{ "title", window.title },
{ "width", window.width },
{ "height", window.height },
{ "fullscreen", window.fullscreen },
{ "vsync", window.vsync },
};
}
void fromJson(const ruc::Json& object, SettingsProperties& settings)
{
VERIFY(object.type() == ruc::Json::Type::Object);
if (object.exists("window"))
object.at("window").getTo(settings.window);
}
void toJson(ruc::Json& object, const SettingsProperties& settings)
{
object = ruc::Json {
{ "window", settings.window }
};
}
```
## Unit test macros
These are some macros to quickly setup a unit test. The usage is made very
simple, as there is no need to setup a main function entrypoint.
To accomplish this, the macro has a trick to declare the unit test inside of a
struct and that also has a static variable of that struct type. Because its
static it will get allocated on program startup and in the constructor of the
struct it will register the unit test function in the `TestSuite` class.
What this effectively means, is that after the CMake configuration is setup, all
a user has to do is create a `.cpp` file and put a test inside of it with the
`TEST_CASE` macro.
```cpp
#include "macro.h"
#include "testcase.h"
#include "testsuite.h"
TEST_CASE(ExampleUnitTest)
{
// Test a boolean value
EXPECT(true);
}
TEST_CASE(ExampleUnitTestTrue)
{
// Test 2 values, true
int leftside = 3;
int rightside = 3;
EXPECT_EQ(leftside, rightside);
}
TEST_CASE(ExampleUnitTestFalse)
{
// Test 2 values, false
int leftside = 3;
int rightside = 5;
EXPECT_EQ(leftside, rightside);
}
```
Output of the testcases above:
![example unit test](/img/ruc-example-unit-test.png "example unit test")
+9 -3
View File
@@ -15,7 +15,7 @@ export default defineNuxtConfig({
},
langs: [
// https://github.com/shikijs/shiki/blob/main/packages/langs/package.json
"c", "cpp", "css", "html", "js", "json", "lua", "md", "mdc", "php", "shell", "ts", "vue", "yaml"
"c", "cpp", "css", "elisp", "html", "js", "json", "lua", "md", "mdc", "php", "python", "shell", "ts", "vue", "yaml"
]
},
toc: {
@@ -36,7 +36,9 @@ export default defineNuxtConfig({
public: "../public"
},
modules: [
"@nuxt/content",
"@nuxtjs/robots",
"@nuxtjs/sitemap",
"@nuxt/content", // NOTE: @nuxt/content after robots and sitemap
"@nuxt/eslint",
"@pinia/nuxt",
"pinia-plugin-persistedstate/nuxt",
@@ -47,12 +49,16 @@ export default defineNuxtConfig({
},
piniaPluginPersistedstate: {
debug: process.env.NODE_ENV === "development", // log error to console
storage: "cookies",
cookieOptions: {
sameSite: "lax", // prevent CSRF
secure: process.env.NODE_ENV !== "development" // only send over HTTPS
}
},
routeRules: {
// Dont add to the sitemap.xml
"/__nuxt_content/**": { robots: false },
},
site: { url: "https://riyyi.com", name: "Personal Website" },
srcDir: "src/",
ssr: false,
typescript: {
+273 -31
View File
@@ -22,9 +22,9 @@
"@iconify-json/fa-solid": "^1.2.1",
"@nuxt/content": "^3.4.0",
"@nuxt/eslint": "^1.2.0",
"@nuxtjs/robots": "^5.2.9",
"@nuxtjs/sitemap": "^7.2.10",
"@types/bootstrap": "^5.2.10",
"@types/bun": "latest",
"@vue/compiler-sfc": "^3.5.13",
"@vue/language-server": "^2.1.10",
"@vue/typescript-plugin": "^2.1.10",
"nuxt": "^3.14.1592",
@@ -1644,6 +1644,136 @@
"vfile": "^6.0.3"
}
},
"node_modules/@nuxtjs/robots": {
"version": "5.2.9",
"resolved": "https://registry.npmjs.org/@nuxtjs/robots/-/robots-5.2.9.tgz",
"integrity": "sha512-JJykE1llIByw8vqhrn4abSPyAcEFnfVOkIaD2/XLwkhNeZHzAQT2/p+ZhqwtaSMyWq8pnW1y0ei7gQYdCqOu+g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nuxt/kit": "^3.16.2",
"consola": "^3.4.2",
"defu": "^6.1.4",
"nuxt-site-config": "^3.1.9",
"pathe": "^2.0.3",
"pkg-types": "^2.1.0",
"sirv": "^3.0.1",
"std-env": "^3.9.0",
"ufo": "^1.5.4"
},
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
}
},
"node_modules/@nuxtjs/robots/node_modules/@nuxt/kit": {
"version": "3.16.2",
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.16.2.tgz",
"integrity": "sha512-K1SAUo2vweTfudKZzjKsZ5YJoxPLTspR5qz5+G61xtZreLpsdpDYfBseqsIAl5VFLJuszeRpWQ01jP9LfQ6Ksw==",
"dev": true,
"license": "MIT",
"dependencies": {
"c12": "^3.0.2",
"consola": "^3.4.2",
"defu": "^6.1.4",
"destr": "^2.0.3",
"errx": "^0.1.0",
"exsolve": "^1.0.4",
"globby": "^14.1.0",
"ignore": "^7.0.3",
"jiti": "^2.4.2",
"klona": "^2.0.6",
"knitwork": "^1.2.0",
"mlly": "^1.7.4",
"ohash": "^2.0.11",
"pathe": "^2.0.3",
"pkg-types": "^2.1.0",
"scule": "^1.3.0",
"semver": "^7.7.1",
"std-env": "^3.8.1",
"ufo": "^1.5.4",
"unctx": "^2.4.1",
"unimport": "^4.1.3",
"untyped": "^2.0.0"
},
"engines": {
"node": ">=18.12.0"
}
},
"node_modules/@nuxtjs/sitemap": {
"version": "7.2.10",
"resolved": "https://registry.npmjs.org/@nuxtjs/sitemap/-/sitemap-7.2.10.tgz",
"integrity": "sha512-7w1Ys2XIE/QVTJn5dbt2p/hrmDoGO9Ay1S3o6LI5M/MDRzKPvnXi5ByRUcc7Sfa3LUpLad5/w4IOQ4lyxhq9Hw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nuxt/devtools-kit": "^2.3.2",
"@nuxt/kit": "^3.16.2",
"chalk": "^5.4.1",
"defu": "^6.1.4",
"h3-compression": "^0.3.2",
"nuxt-site-config": "^3.1.9",
"ofetch": "^1.4.1",
"pathe": "^2.0.3",
"pkg-types": "^2.1.0",
"radix3": "^1.1.2",
"semver": "^7.7.1",
"sirv": "^3.0.1",
"ufo": "^1.5.4"
},
"engines": {
"node": ">=18.0.0"
},
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
}
},
"node_modules/@nuxtjs/sitemap/node_modules/@nuxt/kit": {
"version": "3.16.2",
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.16.2.tgz",
"integrity": "sha512-K1SAUo2vweTfudKZzjKsZ5YJoxPLTspR5qz5+G61xtZreLpsdpDYfBseqsIAl5VFLJuszeRpWQ01jP9LfQ6Ksw==",
"dev": true,
"license": "MIT",
"dependencies": {
"c12": "^3.0.2",
"consola": "^3.4.2",
"defu": "^6.1.4",
"destr": "^2.0.3",
"errx": "^0.1.0",
"exsolve": "^1.0.4",
"globby": "^14.1.0",
"ignore": "^7.0.3",
"jiti": "^2.4.2",
"klona": "^2.0.6",
"knitwork": "^1.2.0",
"mlly": "^1.7.4",
"ohash": "^2.0.11",
"pathe": "^2.0.3",
"pkg-types": "^2.1.0",
"scule": "^1.3.0",
"semver": "^7.7.1",
"std-env": "^3.8.1",
"ufo": "^1.5.4",
"unctx": "^2.4.1",
"unimport": "^4.1.3",
"untyped": "^2.0.0"
},
"engines": {
"node": ">=18.12.0"
}
},
"node_modules/@nuxtjs/sitemap/node_modules/chalk": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/@oxc-parser/binding-linux-x64-gnu": {
"version": "0.56.5",
"cpu": [
@@ -2258,16 +2388,6 @@
"@popperjs/core": "^2.9.2"
}
},
"node_modules/@types/bun": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.2.8.tgz",
"integrity": "sha512-t8L1RvJVUghW5V+M/fL3Thbxcs0HwNsXsnTEBEfEVqGteiJToOlZ/fyOEaR1kZsNqnu+3XA4RI/qmnX4w6+S+w==",
"dev": true,
"license": "MIT",
"dependencies": {
"bun-types": "1.2.7"
}
},
"node_modules/@types/debug": {
"version": "4.1.12",
"dev": true,
@@ -2315,6 +2435,8 @@
"version": "22.13.14",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"undici-types": "~6.20.0"
}
@@ -2339,14 +2461,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/ws": {
"version": "8.18.0",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.28.0",
"dev": true,
@@ -3552,15 +3666,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/bun-types": {
"version": "1.2.7",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*",
"@types/ws": "*"
}
},
"node_modules/bundle-name": {
"version": "4.1.0",
"dev": true,
@@ -5900,6 +6005,19 @@
"uncrypto": "^0.1.3"
}
},
"node_modules/h3-compression": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/h3-compression/-/h3-compression-0.3.2.tgz",
"integrity": "sha512-B+yCKyDRnO0BXSfjAP4tCXJgJwmnKp3GyH5Yh66mY9KuOCrrGQSPk/gBFG2TgH7OyB/6mvqNZ1X0XNVuy0qRsw==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/codedredd"
},
"peerDependencies": {
"h3": "^1.6.0"
}
},
"node_modules/h3/node_modules/cookie-es": {
"version": "1.2.2",
"dev": true,
@@ -8452,6 +8570,110 @@
"nuxt-component-meta": "bin/nuxt-component-meta.mjs"
}
},
"node_modules/nuxt-site-config": {
"version": "3.1.9",
"resolved": "https://registry.npmjs.org/nuxt-site-config/-/nuxt-site-config-3.1.9.tgz",
"integrity": "sha512-YB69GX0st8drv1d5xypweseiEWeR22tfGdyVH3U4R+mpUSz8paBx48ArKC6MgV22DKItoQm51LVoapF5pl5bEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nuxt/kit": "^3.16.2",
"nuxt-site-config-kit": "3.1.9",
"pathe": "^2.0.3",
"pkg-types": "^2.1.0",
"sirv": "^3.0.1",
"site-config-stack": "3.1.9",
"ufo": "^1.5.4"
},
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
}
},
"node_modules/nuxt-site-config-kit": {
"version": "3.1.9",
"resolved": "https://registry.npmjs.org/nuxt-site-config-kit/-/nuxt-site-config-kit-3.1.9.tgz",
"integrity": "sha512-bcmpajYJgkNzA0jTq6CmmhKF2wHZUUKeVx/CIGI8lwWuAD81EBUZN0T4iKvVDo54g9UBrUUl8/5GhD65YBBG0A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nuxt/kit": "^3.16.2",
"pkg-types": "^2.1.0",
"site-config-stack": "3.1.9",
"std-env": "^3.9.0",
"ufo": "^1.5.4"
},
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
}
},
"node_modules/nuxt-site-config-kit/node_modules/@nuxt/kit": {
"version": "3.16.2",
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.16.2.tgz",
"integrity": "sha512-K1SAUo2vweTfudKZzjKsZ5YJoxPLTspR5qz5+G61xtZreLpsdpDYfBseqsIAl5VFLJuszeRpWQ01jP9LfQ6Ksw==",
"dev": true,
"license": "MIT",
"dependencies": {
"c12": "^3.0.2",
"consola": "^3.4.2",
"defu": "^6.1.4",
"destr": "^2.0.3",
"errx": "^0.1.0",
"exsolve": "^1.0.4",
"globby": "^14.1.0",
"ignore": "^7.0.3",
"jiti": "^2.4.2",
"klona": "^2.0.6",
"knitwork": "^1.2.0",
"mlly": "^1.7.4",
"ohash": "^2.0.11",
"pathe": "^2.0.3",
"pkg-types": "^2.1.0",
"scule": "^1.3.0",
"semver": "^7.7.1",
"std-env": "^3.8.1",
"ufo": "^1.5.4",
"unctx": "^2.4.1",
"unimport": "^4.1.3",
"untyped": "^2.0.0"
},
"engines": {
"node": ">=18.12.0"
}
},
"node_modules/nuxt-site-config/node_modules/@nuxt/kit": {
"version": "3.16.2",
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.16.2.tgz",
"integrity": "sha512-K1SAUo2vweTfudKZzjKsZ5YJoxPLTspR5qz5+G61xtZreLpsdpDYfBseqsIAl5VFLJuszeRpWQ01jP9LfQ6Ksw==",
"dev": true,
"license": "MIT",
"dependencies": {
"c12": "^3.0.2",
"consola": "^3.4.2",
"defu": "^6.1.4",
"destr": "^2.0.3",
"errx": "^0.1.0",
"exsolve": "^1.0.4",
"globby": "^14.1.0",
"ignore": "^7.0.3",
"jiti": "^2.4.2",
"klona": "^2.0.6",
"knitwork": "^1.2.0",
"mlly": "^1.7.4",
"ohash": "^2.0.11",
"pathe": "^2.0.3",
"pkg-types": "^2.1.0",
"scule": "^1.3.0",
"semver": "^7.7.1",
"std-env": "^3.8.1",
"ufo": "^1.5.4",
"unctx": "^2.4.1",
"unimport": "^4.1.3",
"untyped": "^2.0.0"
},
"engines": {
"node": ">=18.12.0"
}
},
"node_modules/nuxt/node_modules/estree-walker": {
"version": "3.0.3",
"dev": true,
@@ -10446,6 +10668,22 @@
"dev": true,
"license": "MIT"
},
"node_modules/site-config-stack": {
"version": "3.1.9",
"resolved": "https://registry.npmjs.org/site-config-stack/-/site-config-stack-3.1.9.tgz",
"integrity": "sha512-ed53+wLi+36SGqidU+YUpl7f1OHClPLmvUJ/aYZny1dCBnXvOsuFottrMkXDIK2N5UaMED9mz8KrRZTk94ARCg==",
"dev": true,
"license": "MIT",
"dependencies": {
"ufo": "^1.5.4"
},
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
},
"peerDependencies": {
"vue": "^3"
}
},
"node_modules/skin-tone": {
"version": "2.0.0",
"dev": true,
@@ -10647,7 +10885,9 @@
}
},
"node_modules/std-env": {
"version": "3.8.1",
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz",
"integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
"license": "MIT"
},
"node_modules/streamx": {
@@ -11233,7 +11473,9 @@
"node_modules/undici-types": {
"version": "6.20.0",
"dev": true,
"license": "MIT"
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/unenv": {
"version": "2.0.0-rc.15",
+2 -2
View File
@@ -25,9 +25,9 @@
"@iconify-json/fa-solid": "^1.2.1",
"@nuxt/content": "^3.4.0",
"@nuxt/eslint": "^1.2.0",
"@nuxtjs/robots": "^5.2.9",
"@nuxtjs/sitemap": "^7.2.10",
"@types/bootstrap": "^5.2.10",
"@types/bun": "latest",
"@vue/compiler-sfc": "^3.5.13",
"@vue/language-server": "^2.1.10",
"@vue/typescript-plugin": "^2.1.10",
"nuxt": "^3.14.1592",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 776 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

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