From a7df408049093b6e2529dc2c81bbe7f44ba7f68f Mon Sep 17 00:00:00 2001 From: michivonah Date: Wed, 15 Oct 2025 21:32:26 +0200 Subject: [PATCH] create facts section & by the way reengineer the IntersectionObserver function --- .pages.yml | 4 +++ content/de/facts.md | 6 ++++ content/en/facts.md | 6 ++++ hugo.toml | 1 + i18n/de.toml | 3 +- i18n/en.toml | 3 +- layouts/_default/baseof.html | 2 ++ layouts/partials/facts.html | 34 ++++++++++++++++++ layouts/partials/navbar.html | 1 + static/main.js | 68 +++++++++++++++++++++++++++--------- static/style.css | 65 ++++++++++++++++++++++++++++++++++ 11 files changed, 175 insertions(+), 18 deletions(-) create mode 100644 content/de/facts.md create mode 100644 content/en/facts.md create mode 100644 layouts/partials/facts.html 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;