diff --git a/.pages.yml b/.pages.yml
index cc28a9a..13f940c 100644
--- a/.pages.yml
+++ b/.pages.yml
@@ -78,6 +78,10 @@ content:
- name: websiteId
label: Umami Website ID
type: string
+ - name: factsUrl
+ label: Facts JSON URL
+ description: URL to fetch facts data from
+ type: string
- name: socialmedia
label: Social Media
diff --git a/content/de/facts.md b/content/de/facts.md
new file mode 100644
index 0000000..53c0580
--- /dev/null
+++ b/content/de/facts.md
@@ -0,0 +1,6 @@
+---
+title: "Fakten"
+slug: "facts"
+---
+
+Einige Fakten über mich...
\ No newline at end of file
diff --git a/content/en/facts.md b/content/en/facts.md
new file mode 100644
index 0000000..c3e2523
--- /dev/null
+++ b/content/en/facts.md
@@ -0,0 +1,6 @@
+---
+title: "Facts"
+slug: "facts"
+---
+
+Some facts about me...
\ No newline at end of file
diff --git a/hugo.toml b/hugo.toml
index bd06716..58237ae 100644
--- a/hugo.toml
+++ b/hugo.toml
@@ -99,3 +99,4 @@ defaultContentLanguage = 'de'
twitterUsername = "@michivonah"
umamiUrl = "https://data.mchvnh.ch/script.js"
websiteId = "9b188ed8-77b0-4238-aef5-c1b3d48106e4"
+ factsUrl = "https://cdn.michivonah.ch/facts.json"
diff --git a/i18n/de.toml b/i18n/de.toml
index c99a695..8aa026d 100644
--- a/i18n/de.toml
+++ b/i18n/de.toml
@@ -15,4 +15,5 @@ no-projects-found = 'Keine Projekte gefunden'
no-more-projects = 'Keine weiteren Projekte verfügbar'
faq = 'Häufige Fragen'
faq-short = 'FAQ'
-close = 'Schliessen'
\ No newline at end of file
+close = 'Schliessen'
+facts = 'Fakten'
\ No newline at end of file
diff --git a/i18n/en.toml b/i18n/en.toml
index 20fcbf9..87d099e 100644
--- a/i18n/en.toml
+++ b/i18n/en.toml
@@ -15,4 +15,5 @@ no-projects-found = 'No projects found'
no-more-projects = 'No more projects available'
faq = 'Frequently Asked Questions'
faq-short = 'FAQ'
-close = 'Close'
\ No newline at end of file
+close = 'Close'
+facts = 'Facts'
\ No newline at end of file
diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html
index 973c1b6..7423ebe 100644
--- a/layouts/_default/baseof.html
+++ b/layouts/_default/baseof.html
@@ -13,6 +13,8 @@
{{ partial "projects.html" . }}
+ {{ partial "facts.html" . }}
+
{{ partial "faq.html" . }}
diff --git a/layouts/partials/facts.html b/layouts/partials/facts.html
new file mode 100644
index 0000000..dd4ec40
--- /dev/null
+++ b/layouts/partials/facts.html
@@ -0,0 +1,34 @@
+
+
+ {{ with .Site.GetPage "facts" }}
+
{{ .Title }}
+
{{ .Content }}
+ {{ end }}
+
+
+ {{ $url := .Site.Params.factsUrl }}
+ {{ with try (resources.GetRemote $url) }}
+ {{ with .Err }}
+ {{ errorf "%s" . }}
+ {{ else with .Value }}
+ {{ $facts := . | transform.Unmarshal }}
+ {{ if $facts }}
+ {{ range $fact := $facts }}
+ {{ $count := 0 }}
+ {{ if isset $fact "total" }}
+ {{ $count = $fact.total }}
+ {{ else if isset $fact "items" }}
+ {{ $count = len $fact.items }}
+ {{ end }}
+
+
+
{{ index $fact.titles $.Lang }}
+
+ {{ end }}
+ {{ else }}
+ {{ errorf "Unable to get remote resource %q" $url }}
+ {{ end }}
+ {{ end }}
+ {{ end }}
+
+
\ No newline at end of file
diff --git a/layouts/partials/navbar.html b/layouts/partials/navbar.html
index fa4234c..26f6e3c 100644
--- a/layouts/partials/navbar.html
+++ b/layouts/partials/navbar.html
@@ -5,6 +5,7 @@
{{ T "home" }}
{{ T "about" }}
{{ T "projects" }}
+ {{ T "facts" }}
{{ T "faq-short" }}
{{ T "blog" }}
{{ T "contact" }}
diff --git a/static/main.js b/static/main.js
index 9b8343b..58426db 100644
--- a/static/main.js
+++ b/static/main.js
@@ -100,29 +100,65 @@ function updateNavStyle(){
document.querySelector("nav").classList.toggle("small", window.scrollY > 20);
}
-// intersection observer for animations
-// credits: https://coolcssanimation.com/how-to-trigger-a-css-animation-on-scroll/
+/**
+ * Template for easily creating a IntersectionObserver
+ * @param {*} triggerElement Element which triggers the IntersectionObserver
+ * @param {*} callback Function which gets interesction object back
+ * @param {*} rootMargin Parameter rootMargin of the IntersectionObserver
+ * @param {*} threshold Parameter threshold of the IntersectionObserver
+ * @returns IntersectionObserver
+ */
+function createIntersectionObserver(triggerElement, callback, rootMargin = '0px 0px 5% 0px', threshold = 0.1){
+ if(triggerElement){
+ const observer = new IntersectionObserver(entries => {
+ entries.forEach(entry => {
+ callback(entry);
+ });
+ }, { rootMargin, threshold });
+ return observer.observe(triggerElement);
+ }
+}
+
+/**
+ * Function which applies an IntersectionObserver
+ * for running animations on scroll/visibility of a container.
+ *
+ * Original inspiration: https://coolcssanimation.com/how-to-trigger-a-css-animation-on-scroll/
+ * @param {*} triggerSelector Query selector of the element, which should trigger the animation
+ * @param {*} animationClass Class to append when animation is triggered
+ * @param {*} targetElement Element to apply the animation (css class) to. Will applied to triggerSelector if not defined.
+ * @returns IntersectionObserver
+ */
function animationOnScroll(triggerSelector, animationClass, targetElement){
const trigger = document.querySelector(triggerSelector);
const target = ((targetElement) ? document.querySelector(targetElement) : trigger);
- if(trigger){
- const observer = new IntersectionObserver(entries => {
- entries.forEach(entry => {
- if (entry.isIntersecting) {
- target.classList.add(animationClass);
- }
- else{
- target.classList.remove(animationClass);
- }
- });
- }, { rootMargin: '0px 0px 5% 0px', threshold: 0.1 });
-
- return observer.observe(trigger);
- }
+ return createIntersectionObserver(trigger, (entry) => {
+ entry.isIntersecting ? target.classList.add(animationClass) : target.classList.remove(animationClass);
+ });
}
+/**
+ * Creates IntersectionObserver for triggering counter animation
+ * @param {*} elementSelector Selector of the element(s) to apply the counter effect to
+ * @param {*} cssPropertyName CSS Property with counter's value
+ * @param {*} valueAttribute HTML data attribute with the counter's final value
+ */
+function countOnScroll(elementSelector, cssPropertyName, valueAttribute){
+ const items = document.querySelectorAll(elementSelector);
+
+ items.forEach(element => {
+ const value = parseInt(element.getAttribute(valueAttribute));
+
+ createIntersectionObserver(element, (entry) => {
+ element.style.setProperty(cssPropertyName, entry.isIntersecting ? value : 0);
+ });
+ });
+}
+
+// apply on scroll effects
animationOnScroll('.contact-title-wrapper', 'typewriter-animation', '.contact-title');
+countOnScroll('.fact-counter', '--factCounter', 'data-count');
// calculate age
function calculateAge(selector){
diff --git a/static/style.css b/static/style.css
index 1af1fc9..22a905b 100644
--- a/static/style.css
+++ b/static/style.css
@@ -777,6 +777,71 @@ nav.small .nav-links a:last-child:focus{
}
}
+/* FACTS */
+.facts{
+ margin-top: 30px;
+ padding-bottom: 20px;
+ text-align: center;
+ scroll-margin-top: calc(var(--navSmallHeight) + 10px);
+}
+
+.fact-container{
+ display: flex;
+ flex-direction: row;
+ justify-content: space-around;
+}
+
+.fact{
+ display: flex;
+ flex-direction: column;
+ font-size: 1.2rem;
+ font-weight: 600;
+ width: 100%;
+ padding-block: 40px 20px;
+}
+
+.fact p{
+ margin: 5px 0;
+ padding: 0;
+}
+
+@media screen and (max-width:690px){
+ .fact-container{
+ flex-direction: column;
+ }
+
+ .fact{
+ padding-block: 10px;
+ }
+}
+
+@property --factCounter{
+ syntax: '';
+ initial-value: 0;
+ inherits: false;
+}
+
+.fact-counter{
+ font-size: 3.8rem;
+ font-weight: 700;
+ color: var(--primary);
+ transition: --factCounter 1s, var(--baseTransition);
+ counter-reset: factCounter var(--factCounter);
+}
+
+@media (pointer: fine){
+ .fact-counter:hover,
+ .fact-counter:focus{
+ transform: scale(1.1);
+ transition: var(--baseTransition);
+ filter: drop-shadow(0 0 0.6rem var(--primary));
+ }
+}
+
+.fact-counter::after{
+ content: counter(factCounter);
+}
+
/* FAQ */
.faq{
padding-block: 40px;