diff --git a/i18n/de.toml b/i18n/de.toml
index b05d615..da7f7c6 100644
--- a/i18n/de.toml
+++ b/i18n/de.toml
@@ -9,4 +9,5 @@ logo = 'Logo'
gotop = 'Nach oben!'
godown = 'Nach unten!'
language = 'Sprache'
-select-language = 'Sprache auswählen'
\ No newline at end of file
+select-language = 'Sprache auswählen'
+load-more = 'Mehr laden'
\ No newline at end of file
diff --git a/i18n/en.toml b/i18n/en.toml
index a950ae9..e7752f6 100644
--- a/i18n/en.toml
+++ b/i18n/en.toml
@@ -9,4 +9,5 @@ logo = 'Logo'
gotop = 'Go to top'
godown = 'Scroll down!'
language = 'Language'
-select-language = 'Select language'
\ No newline at end of file
+select-language = 'Select language'
+load-more = 'Load more'
\ No newline at end of file
diff --git a/layouts/partials/projects.html b/layouts/partials/projects.html
index 6e80cfc..4988101 100644
--- a/layouts/partials/projects.html
+++ b/layouts/partials/projects.html
@@ -1,5 +1,5 @@
-
Projekte
+
{{ T "projects" }}
{{ range .Site.Data.tags }}
@@ -10,37 +10,8 @@
-
-
-
-
-
Mein Fazit: 1 Jahr mit dem Europa-Park ResortPass
-
14.01.2025
-
-
-
-
-
-
-
-
Mein Fazit: 1 Jahr mit dem Europa-Park ResortPass
-
14.01.2025
-
-
-
-
-
-
-
-
Mein Fazit: 1 Jahr mit dem Europa-Park ResortPass
-
14.01.2025
-
-
+
+
+
\ No newline at end of file
diff --git a/static/main.js b/static/main.js
index 34f02b7..6521cdf 100644
--- a/static/main.js
+++ b/static/main.js
@@ -1,5 +1,5 @@
// Add event listeners
-document.addEventListener('DOMContentLoaded', function(){
+document.addEventListener('DOMContentLoaded', async function(){
scrollTopVisibilityUpdate();
updateNavStyle();
calculateAge(".age");
@@ -9,6 +9,9 @@ document.addEventListener('DOMContentLoaded', function(){
languageSelector.addEventListener('change', function(){
window.location.href = languageSelector.value + window.location.hash;
});
+
+ // load projects
+ loadMoreContent('.project-list', 6);
});
window.addEventListener('scroll', function(){
@@ -26,6 +29,10 @@ document.querySelectorAll(".nav-links a").forEach(element => {
});
});
+document.querySelector(".project-load-btn").addEventListener('click', function(){
+ loadMoreContent('.project-list', 3);
+});
+
// Generic functions
function toggleDisplayByClass(className, displayType){
let items = document.getElementsByClassName(className);
@@ -94,4 +101,95 @@ function calculateAge(selector){
}
obj.textContent = age;
+}
+
+// load projects from api
+// content api: https://api.michivonah.ch/?limit=8&page=1
+async function loadContent(endpoint = "https://api.michivonah.ch", limit = 6, page = 1){
+ try{
+ constRequestUrl = `${endpoint}/?limit=${limit}&page=${page}`;
+ const response = await fetch(constRequestUrl);
+ if (!response.ok) {
+ throw new Error('Network response was not ok');
+ }
+ const data = await response.json();
+ return data;
+ }
+ catch(error){
+ return console.error({"message":error});
+ }
+}
+
+// add projects to page
+async function showProjects(wrapperSelector, data){
+ const wrapper = document.querySelector(wrapperSelector);
+
+ for (const project of data){
+ wrapper.append(await getProjectCard(project));
+ }
+}
+
+// create project card
+async function getProjectCard(data){
+ const title = data.title;
+ const url = data.url;
+ const image = data.image;
+ const date = data.date;
+ const icon = data.icon;
+ const category = data.category;
+
+ const dateFormatted = new Date(date).toLocaleDateString("de-DE", {
+ day: "2-digit",
+ month: "2-digit",
+ year: "numeric"
+ });
+
+ const container = document.createElement("div");
+ container.classList = "project-card";
+ container.classList.add(category);
+ //container.style.backgroundImage = `url(${image})`;
+
+ const cardFirst = document.createElement("div");
+ cardFirst.classList = "card-first";
+ const emptyText = document.createElement("p");
+ cardFirst.appendChild(emptyText);
+ const cardImage = document.createElement("img");
+ cardImage.src = image;
+ cardFirst.appendChild(cardImage);
+
+ const cardSecond = document.createElement("div");
+ cardSecond.classList = "card-second";
+ const cardIcon = document.createElement("i");
+ cardIcon.classList = icon;
+ const cardTitle = document.createElement("h2");
+ cardTitle.textContent = title;
+ const cardDate = document.createElement("p");
+ cardDate.textContent = dateFormatted;
+ cardSecond.appendChild(cardIcon);
+ cardSecond.appendChild(cardTitle);
+ cardSecond.appendChild(cardDate);
+
+ container.appendChild(cardFirst);
+ container.appendChild(cardSecond);
+
+ return container;
+}
+
+async function loadMoreContent(wrapperSelector, amount, endpoint = "https://api.michivonah.ch", btnSelector = ".project-load-btn"){
+ const wrapper = document.querySelector(wrapperSelector);
+ const page = ((wrapper.childElementCount <= 0) ? 1 : (wrapper.childElementCount / amount) + 1);
+
+ // just for debugging purposes
+ //console.log(`children: ${wrapper.childElementCount} limit: ${amount} page: ${page}`);
+
+ // error validation -> only load more content when page num is valid
+ if (page % 1 == 0){
+ await showProjects('.project-list', await loadContent(endpoint, amount, page));
+ }
+ else{
+ // hide button if no more content is available
+ //if (event.target.tagName == "BUTTON") event.target.style.display = "none";
+ document.querySelector(btnSelector).style.opacity = 0;
+ }
+
}
\ No newline at end of file
diff --git a/static/style.css b/static/style.css
index 7c4cdfd..dd61742 100644
--- a/static/style.css
+++ b/static/style.css
@@ -19,7 +19,8 @@ body{
--color: #f8f8f8;
--font: "Source Sans 3", sans-serif;
--baseRadius: 12px;
- --baseTransition: all 250ms;
+ --baseTransition: all var(--baseDuration);
+ --baseDuration: 250ms;
--navSmallHeight: 66px;
}
@@ -367,9 +368,10 @@ nav.small .nav-links a:last-child:focus{
color: var(--primary);
margin: 10px 5px;
padding: 5px 16px;
+ font-size: 1rem;
border-radius: var(--baseRadius);
transition: var(--baseTransition);
- border: 1px solid var(--primary);
+ border: 2px solid var(--primary);
}
.tag input{
@@ -381,6 +383,13 @@ nav.small .nav-links a:last-child:focus{
color: var(--color);
}
+.tag:hover{
+ padding: 5px 26px;
+ background: var(--secondary);
+ color: var(--color);
+ border: 2px solid var(--color);
+}
+
.project-list{
padding: 20px 0;
display: flex;
@@ -391,6 +400,24 @@ nav.small .nav-links a:last-child:focus{
align-items: stretch;
}
+.project-load-btn{
+ padding: 10px 20px;
+ background: var(--primary);
+ color: var(--color);
+ font-size: 1rem;
+ border: 2px solid transparent;
+ border-radius: var(--baseRadius);
+ transition: var(--baseTransition);
+ opacity: 1;
+}
+
+.project-load-btn:hover{
+ padding: 10px 40px;
+ background: var(--secondary);
+ border: 2px solid var(--color);
+ cursor: pointer;
+}
+
/* Card animation: https://uiverse.io/suleymanlaarabidev/perfect-husky-88 */
.project-card{
width: 30%;
@@ -398,7 +425,7 @@ nav.small .nav-links a:last-child:focus{
min-height: 250px;
margin: 20px 10px;
padding: 0 10px;
- background-image: url(https://blog.michivonah.ch/content/images/size/w1200/2024/12/ResortPass-Review--1-.jpg);
+ background: var(--primary);
background-size: cover;
background-repeat: no-repeat;
background-position: center;
@@ -406,6 +433,27 @@ nav.small .nav-links a:last-child:focus{
transition: var(--baseTransition);
box-shadow: 0px 0px 10px 5px rgba(0, 0, 0, 0.705);
overflow: hidden;
+ position: relative;
+ animation: fadeIn 400ms linear;
+}
+
+@keyframes fadeIn{
+ 0%{
+ opacity: 0;
+ }
+
+ 100%{
+ opacity: 1;
+ }
+}
+
+.card-first img{
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: auto;
+ height: 100%;
}
@media (pointer: fine){
@@ -430,7 +478,7 @@ nav.small .nav-links a:last-child:focus{
transition: var(--baseTransition);
opacity: 1;
}
-
+
.project-card:hover .card-first{
height: 0;
opacity: 0;
@@ -480,8 +528,9 @@ nav.small .nav-links a:last-child:focus{
box-shadow: 0px 0px 10px 5px rgba(0, 0, 0, 0.705);
}
- .card-first{
- display: none;
+ .card-first img{
+ height: calc(100% + 60px);
+ filter: blur(6px);
}
.card-second{