diff --git a/README.md b/README.md
index 0a3b5a6..5d33e53 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,47 @@
# douwco.be
The main landing page for all my projects.
-You can find it at [www.douwco.be](https://www.douwco.be)
\ No newline at end of file
+You can find it at [www.douwco.be](https://www.douwco.be)
+
+Pure static front-end site served by nginx. No build step required.
+
+## Structure
+
+```
+public/ Site root (served by nginx)
+├── index.html Single page: home, about, apps, games
+├── favicon.ico
+└── assets/
+ ├── css/ douwco.css (branding), style.css (layout)
+ ├── js/ index.js (nav, copyright), project-card.js (web component)
+ ├── img/ Logos, screenshots, icons
+ ├── fonts/ Righteous + Montserrat (self-hosted)
+ └── vid/ Demo videos
+```
+
+## Development
+
+Any static file server works, e.g.:
+
+```bash
+docker compose up # site on http://localhost:8080
+```
+
+or natively:
+
+```bash
+python3 -m http.server -d public 8080
+```
+
+## Adding a project card
+
+Use the `project-card` component in `public/index.html`:
+
+```html
+
+
+```
diff --git a/dockerfile b/dockerfile
index 6fb5b3a..a92a073 100644
--- a/dockerfile
+++ b/dockerfile
@@ -4,7 +4,7 @@ FROM nginx:alpine
RUN rm /usr/share/nginx/html/index.html
# Copy static website files
-COPY wwwroot/ /usr/share/nginx/html/
+COPY public/ /usr/share/nginx/html/
# Expose port 80
EXPOSE 80
diff --git a/public/assets/css/douwco.css b/public/assets/css/douwco.css
new file mode 100644
index 0000000..541e05a
--- /dev/null
+++ b/public/assets/css/douwco.css
@@ -0,0 +1,36 @@
+@font-face {
+ font-family: 'Righteous';
+ src: url('/assets/fonts/Righteous-Regular.ttf') format('truetype');
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'Montserrat';
+ src: url('/assets/fonts/Montserrat-Regular.ttf') format('truetype');
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'Montserrat';
+ src: url('/assets/fonts/Montserrat-Bold.ttf') format('truetype');
+ font-weight: bold;
+ font-display: swap;
+}
+
+:root {
+ --background-clr: #283e3e;
+ --background-accent-clr: #324f4f;
+ --blue-clr: #47bcdf;
+ --green-clr: #6ede9a;
+ --white-clr: #ffffff;
+}
+
+body {
+ font-family: 'Montserrat', sans-serif;
+ background-color: var(--background-clr);
+ color: var(--white-clr);
+}
+
+.title {
+ font-family: 'Righteous', sans-serif;
+}
diff --git a/wwwroot/css/style.css b/public/assets/css/style.css
similarity index 88%
rename from wwwroot/css/style.css
rename to public/assets/css/style.css
index 0be196e..2e19977 100644
--- a/wwwroot/css/style.css
+++ b/public/assets/css/style.css
@@ -1,18 +1,17 @@
/* ===== Base Styles ===== */
-
-html, body{
+html, body {
height: 100%;
margin: 0;
padding: 0;
font-size: 25px;
}
-body{
+body {
display: flex;
flex-direction: column;
}
-main {
+main {
flex: 1;
margin: 20px;
overflow-y: auto;
@@ -21,7 +20,7 @@ main {
}
a {
- color: var(--blue-clr)
+ color: var(--blue-clr)
}
footer {
@@ -30,17 +29,21 @@ footer {
width: 100%;
}
-footer > Div {
+footer > div {
justify-content: space-between
}
-footer > Div > p {
+footer > div > p {
font-size: 16px;
margin: 10px 25px 10px 25px;
}
-/* ===== Container Layouts ===== */
+.flag {
+ height: 16px;
+ vertical-align: middle;
+}
+/* ===== Container Layouts ===== */
.vert_container {
display: flex;
}
@@ -51,12 +54,12 @@ footer > Div > p {
}
}
-.vert_container.centered{
+.vert_container.centered {
align-items: center;
justify-content: space-around;
}
-.vert_container.start{
+.vert_container.start {
align-items: flex-start;
justify-content: space-around;
}
@@ -74,7 +77,8 @@ footer > Div > p {
/* ===== Navigationbar ===== */
.nav_option {
- font-family: monsterrat_bold;
+ font-family: 'Montserrat', sans-serif;
+ font-weight: bold;
color: var(--white-clr);
text-decoration: none;
font-size: 36px;
@@ -89,7 +93,7 @@ footer > Div > p {
padding-right: 10px;
}
-.nav_option:hover{
+.nav_option:hover {
background-color: var(--background-accent-clr);
}
@@ -139,6 +143,11 @@ footer > Div > p {
.title {
font-size: 100px;
}
+
+ .tagline {
+ font-size: 26px;
+ width: 400px;
+ }
}
.tagline {
@@ -156,17 +165,6 @@ footer > Div > p {
margin-left: 7px;
}
-@media (max-width: 1200px) {
- .title {
- font-size: 100px;
- }
-
- .tagline {
- font-size: 26px;
- width: 400px;
- }
-}
-
.content-box {
background-color: var(--background-accent-clr);
margin: 50px 20px 20px;
@@ -189,40 +187,44 @@ footer > Div > p {
height: 150px;
object-fit: cover;
margin-right: 15px;
- border-radius: 75x;
+ border-radius: 75px;
float: left;
}
-
/* ===== Images ===== */
.icon {
vertical-align: middle;
padding: 10px;
}
+
.icon.small {
height: 45px;
}
+
.icon.medium {
height: 100px;
}
+
.icon.large {
height: 250px;
}
+
@media (max-width: 1200px) {
.icon.large.toggle {
height: 150px;
}
}
-
-.image{
+.image {
margin: 30px;
}
-.image.large{
+
+.image.large {
width: 40%;
}
-.image.small{
+
+.image.small {
width: 25%;
}
@@ -230,14 +232,13 @@ footer > Div > p {
.image.large {
width: 80%;
}
- .image.small{
+
+ .image.small {
width: 50%;
}
}
.image.stick {
- position: sticky;
+ position: sticky;
top: 20px
}
-
-
\ No newline at end of file
diff --git a/wwwroot/fonts/Montserrat-Bold.ttf b/public/assets/fonts/Montserrat-Bold.ttf
similarity index 100%
rename from wwwroot/fonts/Montserrat-Bold.ttf
rename to public/assets/fonts/Montserrat-Bold.ttf
diff --git a/wwwroot/fonts/Montserrat-Regular.ttf b/public/assets/fonts/Montserrat-Regular.ttf
similarity index 100%
rename from wwwroot/fonts/Montserrat-Regular.ttf
rename to public/assets/fonts/Montserrat-Regular.ttf
diff --git a/wwwroot/fonts/Righteous-Regular.ttf b/public/assets/fonts/Righteous-Regular.ttf
similarity index 100%
rename from wwwroot/fonts/Righteous-Regular.ttf
rename to public/assets/fonts/Righteous-Regular.ttf
diff --git a/wwwroot/img/alcove.png b/public/assets/img/alcove.png
similarity index 100%
rename from wwwroot/img/alcove.png
rename to public/assets/img/alcove.png
diff --git a/wwwroot/img/douwco_logo.png b/public/assets/img/douwco_logo.png
similarity index 100%
rename from wwwroot/img/douwco_logo.png
rename to public/assets/img/douwco_logo.png
diff --git a/wwwroot/img/godot_addons.png b/public/assets/img/godot_addons.png
similarity index 100%
rename from wwwroot/img/godot_addons.png
rename to public/assets/img/godot_addons.png
diff --git a/wwwroot/img/icons/devicegamepad2.png b/public/assets/img/icons/devicegamepad2.png
similarity index 100%
rename from wwwroot/img/icons/devicegamepad2.png
rename to public/assets/img/icons/devicegamepad2.png
diff --git a/wwwroot/img/icons/europe.png b/public/assets/img/icons/europe.png
similarity index 100%
rename from wwwroot/img/icons/europe.png
rename to public/assets/img/icons/europe.png
diff --git a/wwwroot/img/icons/projects_icon.png b/public/assets/img/icons/projects_icon.png
similarity index 100%
rename from wwwroot/img/icons/projects_icon.png
rename to public/assets/img/icons/projects_icon.png
diff --git a/wwwroot/img/icons/user.png b/public/assets/img/icons/user.png
similarity index 100%
rename from wwwroot/img/icons/user.png
rename to public/assets/img/icons/user.png
diff --git a/wwwroot/img/kingfisher.png b/public/assets/img/kingfisher.png
similarity index 100%
rename from wwwroot/img/kingfisher.png
rename to public/assets/img/kingfisher.png
diff --git a/wwwroot/img/ludum_dare.png b/public/assets/img/ludum_dare.png
similarity index 100%
rename from wwwroot/img/ludum_dare.png
rename to public/assets/img/ludum_dare.png
diff --git a/wwwroot/img/me_img.png b/public/assets/img/me_img.png
similarity index 100%
rename from wwwroot/img/me_img.png
rename to public/assets/img/me_img.png
diff --git a/wwwroot/img/rollability.png b/public/assets/img/rollability.png
similarity index 100%
rename from wwwroot/img/rollability.png
rename to public/assets/img/rollability.png
diff --git a/wwwroot/img/server_image.png b/public/assets/img/server_image.png
similarity index 100%
rename from wwwroot/img/server_image.png
rename to public/assets/img/server_image.png
diff --git a/wwwroot/img/thesis_tool.png b/public/assets/img/thesis_tool.png
similarity index 100%
rename from wwwroot/img/thesis_tool.png
rename to public/assets/img/thesis_tool.png
diff --git a/wwwroot/img/wizard_gloves.png b/public/assets/img/wizard_gloves.png
similarity index 100%
rename from wwwroot/img/wizard_gloves.png
rename to public/assets/img/wizard_gloves.png
diff --git a/wwwroot/js/index.js b/public/assets/js/index.js
similarity index 50%
rename from wwwroot/js/index.js
rename to public/assets/js/index.js
index 2526fb6..2515b3c 100644
--- a/wwwroot/js/index.js
+++ b/public/assets/js/index.js
@@ -1,30 +1,31 @@
-// Setup the navigation bar
-function setupNavigationHighlight(){
+document.addEventListener('DOMContentLoaded', () => {
+ setupNavigationHighlight();
+ setupNavigationClicks();
+ setupCopyright();
+});
+
+function setupNavigationHighlight() {
const sections = document.querySelectorAll('.scroll_section');
const navOptions = document.querySelectorAll('.nav_option');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const sectionId = entry.target.id;
- window.history.pushState(null, null, `#${sectionId}`);
+ window.history.replaceState(null, null, `#${sectionId}`);
navOptions.forEach(link => {
- link.classList.remove('active');
- if (link.getAttribute('href') === `#${sectionId}`) {
- link.classList.add('active');
- }
+ link.classList.toggle('active', link.getAttribute('href') === `#${sectionId}`);
});
}
- }), {threshold: 0.5};
- })
-
- sections.forEach(section => {
- observer.observe(section);
+ });
+ }, {
+ threshold: 0.5
});
+
+ sections.forEach(section => observer.observe(section));
}
-function setupNavigationClickObserver() {
- const navLinks = document.querySelectorAll('.nav_option');
- navLinks.forEach(link => {
+function setupNavigationClicks() {
+ document.querySelectorAll('.nav_option').forEach(link => {
link.addEventListener('click', (event) => {
event.preventDefault();
@@ -39,9 +40,6 @@ function setupNavigationClickObserver() {
});
}
-setupNavigationHighlight();
-setupNavigationClickObserver();
-
-// Set copyright year
-const currentYear = new Date().getFullYear();
-document.getElementById('copyright').textContent = `© ${currentYear} - douwco.be`;
+function setupCopyright() {
+ document.getElementById('copyright').textContent = `© ${new Date().getFullYear()} - douwco.be`;
+}
diff --git a/public/assets/js/project-card.js b/public/assets/js/project-card.js
new file mode 100644
index 0000000..eaf6653
--- /dev/null
+++ b/public/assets/js/project-card.js
@@ -0,0 +1,101 @@
+class ProjectCard extends HTMLElement {
+ static get observedAttributes() {
+ return ['image', 'image-alt', 'title', 'description'];
+ }
+
+ constructor() {
+ super();
+ this.attachShadow({ mode: 'open' });
+ this._rendered = false;
+ }
+
+ connectedCallback() {
+ this._render();
+ }
+
+ attributeChangedCallback() {
+ if (this._rendered) {
+ this._render();
+ }
+ }
+
+ _render() {
+ this._rendered = true;
+ const alt = this.getAttribute('image-alt') || '';
+ const title = this.getAttribute('title') || '';
+ const description = this.getAttribute('description') || '';
+
+ this.shadowRoot.innerHTML = `
+
+
+
+
+
`;
+ this.shadowRoot.querySelector('.intro-text').textContent = title;
+ this._renderDescription(description);
+ }
+
+ _renderDescription(description) {
+ const slot = this.shadowRoot.querySelector('slot');
+ slot.textContent = '';
+ const linkPattern = /\[\[([^|\]]+)\|([^\]]+)\]\]/g;
+ let lastIndex = 0;
+ let match;
+ while ((match = linkPattern.exec(description)) !== null) {
+ if (match.index > lastIndex) {
+ slot.appendChild(document.createTextNode(description.slice(lastIndex, match.index)));
+ }
+ const link = document.createElement('a');
+ link.href = match[2];
+ link.textContent = match[1];
+ if (match[2].startsWith('http')) {
+ link.target = '_blank';
+ link.rel = 'noopener';
+ }
+ slot.appendChild(link);
+ lastIndex = match.index + match[0].length;
+ if (linkPattern.lastIndex === match.index) {
+ linkPattern.lastIndex++;
+ }
+ }
+ if (lastIndex < description.length) {
+ slot.appendChild(document.createTextNode(description.slice(lastIndex)));
+ }
+ }
+
+ _escapeAttribute(value) {
+ return value
+ .replace(/&/g, '&')
+ .replace(/"/g, '"')
+ .replace(/
+
+
+
+
+
+ Douwco
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Made and hosted in
+
+
+
+
diff --git a/wwwroot/css/douwco.css b/wwwroot/css/douwco.css
deleted file mode 100644
index 12fd5fd..0000000
--- a/wwwroot/css/douwco.css
+++ /dev/null
@@ -1,37 +0,0 @@
-@font-face {
- font-family: righteous;
- src: url(/fonts/Righteous-Regular.ttf);
- format('truetype');
-}
-
-@font-face {
- font-family: monsterrat_regular;
- src: url(/fonts/Montserrat-Regular.ttf);
- format('truetype');
-}
-
-@font-face {
- font-family: monsterrat_bold;
- src: url(/fonts/Montserrat-Bold.ttf);
- format('truetype');
-}
-
-:root {
- --background-clr: #283e3e;
- --background-accent-clr: #324f4f;
- --blue-clr: #47bcdf;
- --green-clr: #6ede9a;
- --purple-clr: #a48da;
- --orange-clr: #e2a661;
- --white-clr: #ffffff;
-}
-
-body {
- font-family: monsterrat_regular;
- background-color: var(--background-clr);
- color: var(--white-clr);;
-}
-
-.title{
- font-family: righteous;
-}
\ No newline at end of file
diff --git a/wwwroot/includes/about_me.html b/wwwroot/includes/about_me.html
deleted file mode 100644
index b6596f6..0000000
--- a/wwwroot/includes/about_me.html
+++ /dev/null
@@ -1,82 +0,0 @@
-
-
-
-
Hi, I'm Douwe
-
- A PhD student at KU Leuven with a passion for game development and software independence.
-
- Currently I'm working on:
-
-
-
-
- RollAbility
- For my PhD research at KU Leuven, I'm developing RollAbility a therapeutic exergame that bridges player autonomy with clinical guidelines to enhance wheelchair training.
- Developed in collaboration with therapists, RollAbility is a game build onto a clinical protocol
- and controlled by an actual powered wheelchair. The game connects to the RollAbility App which allows therapists
- to control the training session in real-time.
- Read the paper or Watch the video .
-
-
-
-
-
- Alcove
- Alcove is a interreg project between Flemish, Walloon and French universities and hospitals that aims to build a device that detects lung cancer through a breath test.
- My role is to create a user interface for the device. This UI has to work on both android phones as on embedded linux devices.
- Read more about Alcove .
-
-
-
-
However in the past I also worked on:
-
-
-
-
- Godot Addons
- I published two small addons to the godot asset library.
- One addon called Godot Visual Studio Debugger which allows you to dynammicaly run the current scene open in the Godot editor directly in Visual Studio Debugger.
- The second addons called Godot Test Scenes extends the Godot editor with an additional run mode, this mode runs a associated test scene to the currently open one in the editor.
- They are both open source:
- Godot Test Scenes and Godot Visual Studio Debugger
-
-
-
-
-
- Kingfisher
- The project Kingfisher is a game build by PossilbyPixels that rises awareness about Autism. It is a storybased action / puzzle game for PC and mobile.
- My role in the project was to build minigames that occur throughout the story and also to develop tools to help development.
- One of the tools I made for this project was a suburban neigborhood generator. This tool allowed us to draw streets in the editor which then were automaticly filled with procedural generated houses and gardens. You can see a demo in this video .
- You can wishlist the game on Steam !
-
-
-
-
-
- Ludum Dare
- Between 2019 and 2024, I participated regulary into the Ludum Dare Event. This event was all about creating games under tight deadlines of 48 hours and with no external resources such as audio or 3D art.
- After each event I published my game on Itch.io where they are still availble for play. In the future I want to migrate them to this website.
- I want to especially highlight Cobor goes farming and Windy the Indy
-
-
-
-
-
- Masterthesis: Planet Tool
- For my Masterthesis at Group T KU Leuven I made a tool for Unity that creates a procedural planet terrain.
- The main challenge of this project was to optimize the algorithm to work as fast as possible. For this I leveraged GPU compute shaders to process large chunks of data at once.
- You can find the tool and my thesis paper here .
-
-
-
-
-
- Masterproject: Wizard Gloves
- In another project during my masters we explored gesture-based interactions in virtual environments.
- For this project we developed a prototype using Unity and custom wearable hardware to enable magic-like hand gestures for interfacing with a story game.
- See the game here or you can find the game and paper here
-
-
-
-
diff --git a/wwwroot/includes/apps.html b/wwwroot/includes/apps.html
deleted file mode 100644
index b978f62..0000000
--- a/wwwroot/includes/apps.html
+++ /dev/null
@@ -1,6 +0,0 @@
-Douwco Apps
-
-
- Under construction...
-
-
\ No newline at end of file
diff --git a/wwwroot/includes/games.html b/wwwroot/includes/games.html
deleted file mode 100644
index 80266e9..0000000
--- a/wwwroot/includes/games.html
+++ /dev/null
@@ -1,6 +0,0 @@
-My Games
-
-
- Under construction...
-
-
\ No newline at end of file
diff --git a/wwwroot/includes/home.html b/wwwroot/includes/home.html
deleted file mode 100644
index eb4c996..0000000
--- a/wwwroot/includes/home.html
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
Douwco
-
-
Because
-
nerds build their own homeserver.
-
-
-
-
-
- Welcome to the Douwco website.
- Ever wondered what machine the website you are visiting is run on? Well this time you know, just look to the image.
- This small homeserver hosts several open-source services like Nextcloud, Gitea, Overleaf community and ofcourse this
- website.
- Here I showcase my portfolio and all the games I've created over the years. If you are
- a family member you can also find the different services in the Douwco Apps tab.
-
-
-
-
-
diff --git a/wwwroot/index.html b/wwwroot/index.html
deleted file mode 100644
index f59d1a1..0000000
--- a/wwwroot/index.html
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
-
- Douwco
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Made and hosted in 🇪🇺
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/wwwroot/js/main.js b/wwwroot/js/main.js
deleted file mode 100644
index c7d5f62..0000000
--- a/wwwroot/js/main.js
+++ /dev/null
@@ -1,32 +0,0 @@
-// JIT file loading
-function include_file(filename, id) {
- fetch(`includes/${filename}.html`)
- .then(response => response.text())
- .then(html => { document.getElementById(id).innerHTML = html; })
- .catch(err => console.error('Error loading include:', err));
-}
-
-function setupIntersectionObserver() {
- const observer = new IntersectionObserver((entries) => {
- entries.forEach(entry => {
- if (entry.isIntersecting) {
- const element = entry.target;
- const name = element.getAttribute('data-section-name');
- include_file(name, element.id);
- observer.unobserve(element);
- }
- });
- }, {
- threshold: 0.05,
- rootMargin: "0px 0px -50px 0px"
- });
-
- const observedSections = document.querySelectorAll('.observed');
- if (observedSections.length === 0) {
- console.error('No elements with class "observed" found!');
- } else {
- observedSections.forEach(section => { observer.observe(section); });
- }
-}
-
-setupIntersectionObserver();
\ No newline at end of file