Blog/2025 05 20 test blog post (#51)

* Remove unused font-face definitions from stylesheets

The `fonts.scss` file defining `Assistant` font-face rules has been deleted as it was no longer in use. This cleanup reduces unnecessary assets and improves maintainability.

* Fix npm script command in blog preview workflow

Replaced `npm scss:build` with the correct `npm run scss:build` command in the GitHub Actions workflow. This ensures the SCSS build step executes properly during site generation.

* Update blog preview workflow to use mkdocs gh-deploy

Replaces the GitHub Pages action with `mkdocs gh-deploy` for deploying previews. Simplifies the workflow configuration and reduces dependencies.

* Update `site_url` logic and fix URL format consistency

Simplified the `site_url` handling in the workflow by removing branch-specific URL construction. Additionally, ensured the main URL in `mkdocs.yml` uses a consistent trailing slash. This enhances clarity and maintains uniformity in URL formatting.

* Set `use_directory_urls` in mkdocs.yml directly.

Removed redundant script lines for configuring `use_directory_urls` in the workflow file. This simplifies deployment logic by directly defining the configuration in the mkdocs.yml file.

* Remove unnecessary CSS source map file

Deleted `custom.css.map` as it is not required for production. Removing it helps reduce clutter and keeps the repository clean.

* Add support for 'satware.ai' as a homepage path

Updated body-classes.js to treat 'satware.ai' as a homepage path by adding the 'home' class to the body element. This ensures correct behavior for both empty paths and specific developer GitHub Pages.

* Simplify FAQ and navigation structure.

Renamed `faq.md` to `index.md` for consistency and updated navigation links in `mkdocs.yml` to use folder paths directly. Adjusted `main.html` to clean up formatting with additional line breaks.

* Update footer links to use relative paths

Replaced absolute paths with relative paths for internal footer links to ensure consistency and improve maintainability. External links remain unchanged.

* Fix relative URL for Jane Alesi's team page in authors file

Updated the URL path for Jane Alesi to ensure correct navigation to her team page. This fixes a broken link caused by an incorrect relative URL.

* Update footer links and enhance blog post on website relaunch

Updated footer links to utilize dynamic `config.site_url` for consistent URL routing. Enhanced the blog post for better readability, showcasing the MkDocs implementation, GitHub integration, and use of Mermaid diagrams.

* Update footer link and simplify satWay documentation

Replaced the "Blog" link in the footer with "satWay Prinzipien" and refined the satWay documentation by removing overly detailed content about Jane Alesi. Additionally, added the Blog section to the site navigation in `mkdocs.yml`.
This commit is contained in:
mw
2025-05-20 15:27:23 +02:00
committed by GitHub
parent 9ec744a042
commit 9fbea33697
86 changed files with 389 additions and 238 deletions
+100
View File
@@ -0,0 +1,100 @@
// Funktion zum Aktualisieren der Body-Klassen basierend auf dem aktuellen Pfad
function updateBodyClasses() {
// Aktuelle URL-Pfad ohne Domain
const path = window.location.pathname;
// Entferne führende und abschließende Schrägstriche
const cleanPath = path.replace(/^\/|\/$/g, '');
// Entferne alle vorherigen dynamischen Klassen
// (wir müssen eine Liste der zu entfernenden Klassen erstellen, da sich die classList während der Iteration ändert)
const classesToRemove = [];
for (let i = 0; i < document.body.classList.length; i++) {
const className = document.body.classList[i];
// Bewahre nur bestimmte MkDocs-spezifische Klassen
if (!className.startsWith('md-') &&
className !== 'no-js' &&
className !== 'js') {
classesToRemove.push(className);
}
}
// Entferne die Klassen
classesToRemove.forEach(className => {
document.body.classList.remove(className);
});
// Wenn der Pfad leer oder eine Entwickler Github Page ist (Startseite), füge die Klasse 'home' hinzu
if (cleanPath === '' || cleanPath === 'satware.ai') {
document.body.classList.add('home');
} else {
// Teile den Pfad in Segmente auf
const segments = cleanPath.split('/');
// Füge Klassen für jedes Segment hinzu
segments.forEach(function(segment) {
if (segment) {
document.body.classList.add(segment);
}
});
}
// Optional: Debug-Ausgabe in der Konsole
console.log('Updated body classes:', document.body.className);
}
// Initial beim Laden der Seite
document.addEventListener('DOMContentLoaded', updateBodyClasses);
// Bei Navigation innerhalb der MkDocs-Seite
// Wir müssen auf das MkDocs-spezifische Event hören
document.addEventListener('DOMContentLoaded', function() {
// Warte kurz, bis MkDocs vollständig initialisiert ist
setTimeout(function() {
// Finde alle internen Links
const internalLinks = document.querySelectorAll('a[href^="/"]:not([target]), a[href^="./"]:not([target]), a[href^="../"]:not([target])');
// Füge Event-Listener für Klicks auf interne Links hinzu
internalLinks.forEach(link => {
link.addEventListener('click', function() {
// Kurze Verzögerung, um der Navigation Zeit zu geben
setTimeout(updateBodyClasses, 100);
});
});
// Beobachte Änderungen an der URL (History API)
if (window.MutationObserver) {
// Beobachte Änderungen am Titel, was oft ein Indikator für Seitenwechsel ist
const titleObserver = new MutationObserver(updateBodyClasses);
if (document.querySelector('title')) {
titleObserver.observe(document.querySelector('title'), { childList: true });
}
// Beobachte Änderungen am Hauptinhalt
const contentObserver = new MutationObserver(function(mutations) {
for (const mutation of mutations) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
updateBodyClasses();
break;
}
}
});
if (document.querySelector('.md-content')) {
contentObserver.observe(document.querySelector('.md-content'), {
childList: true,
subtree: true
});
}
}
// Fallback: Regelmäßige Überprüfung auf URL-Änderungen
let lastPath = window.location.pathname;
setInterval(function() {
if (window.location.pathname !== lastPath) {
lastPath = window.location.pathname;
updateBodyClasses();
}
}, 200);
}, 500);
});
View File
+82
View File
@@ -0,0 +1,82 @@
document.addEventListener('DOMContentLoaded', function() {
// Funktion zum Formatieren der Zahlen mit Kommas und Plus-Zeichen
function formatNumber(num, includePlus = true) {
const formatted = num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
return includePlus ? formatted + '+' : formatted;
}
// Funktion zum Extrahieren der Zahl aus dem Text (entfernt das '+' und Kommas)
function extractNumber(text) {
return parseInt(text.replace(/,/g, '').replace('+', ''), 10);
}
// Funktion zum Animieren der Zähler
function animateCounter(counterElement, target) {
// Startpunkt
let start = 0;
// Dauer der Animation in Millisekunden (angepasst an die Größe der Zahl)
const duration = Math.min(2000 + Math.log10(target) * 300, 3000);
// Zeitpunkt des Starts
const startTime = performance.now();
// Funktion für die Animation
function updateCounter(currentTime) {
// Berechne den verstrichenen Zeitanteil (0 bis 1)
const elapsedTime = Math.min((currentTime - startTime) / duration, 1);
// Easing-Funktion für eine natürlichere Animation
const progress = easeOutQuart(elapsedTime);
// Berechne den aktuellen Wert
const currentValue = Math.floor(progress * target);
// Aktualisiere den Zähler mit dem Plus-Zeichen
counterElement.textContent = formatNumber(currentValue, true);
// Wenn die Animation noch nicht abgeschlossen ist, nächsten Frame anfordern
if (elapsedTime < 1) {
requestAnimationFrame(updateCounter);
} else {
// Stelle sicher, dass der endgültige Wert exakt dem Zielwert entspricht
counterElement.textContent = formatNumber(target, true);
}
}
// Easing-Funktion für eine natürlichere Animation
function easeOutQuart(t) {
return 1 - Math.pow(1 - t, 4);
}
// Starte die Animation
requestAnimationFrame(updateCounter);
}
// Intersection Observer zum Erkennen, wann die Zähler sichtbar werden
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
// Wenn der Zähler sichtbar wird
if (entry.isIntersecting) {
const counterElement = entry.target;
// Extrahiere die Zielnummer aus dem Text
const targetText = counterElement.textContent;
const target = extractNumber(targetText);
// Setze den Anfangswert auf 0 mit Plus-Zeichen
counterElement.textContent = '0+';
// Starte die Animation
animateCounter(counterElement, target);
// Beobachtung beenden, damit die Animation nur einmal ausgeführt wird
observer.unobserve(counterElement);
}
});
}, {
threshold: 0.1 // 10% des Elements müssen sichtbar sein
});
// Alle Zähler beobachten
document.querySelectorAll('.satag--home-counter-number').forEach(counter => {
observer.observe(counter);
});
});
+94
View File
@@ -0,0 +1,94 @@
// Funktion zur Initialisierung der FAQ-Funktionalität
function initFAQ() {
// Finde alle FAQ-Fragen
const faqQuestions = document.querySelectorAll('.custom-faq-question');
if (faqQuestions.length === 0) {
console.log('Keine benutzerdefinierten FAQ-Elemente gefunden');
return;
}
console.log('Benutzerdefinierte FAQ-Elemente gefunden:', faqQuestions.length);
// Entferne zuerst alle bestehenden Event-Listener, um Duplikate zu vermeiden
faqQuestions.forEach(question => {
// Klonen des Elements, um alle Event-Listener zu entfernen
const newQuestion = question.cloneNode(true);
question.parentNode.replaceChild(newQuestion, question);
});
// Hole die aktualisierten Elemente nach dem Klonen
const updatedFaqQuestions = document.querySelectorAll('.custom-faq-question');
// Füge Event-Listener zu jeder Frage hinzu
updatedFaqQuestions.forEach(question => {
question.addEventListener('click', function() {
// Toggle active class auf der Frage
this.classList.toggle('active');
// Toggle active class auf der Antwort
const answer = this.nextElementSibling;
answer.classList.toggle('active');
// Wenn diese Frage geöffnet wurde, schließe alle anderen
if (this.classList.contains('active')) {
updatedFaqQuestions.forEach(otherQuestion => {
if (otherQuestion !== this && otherQuestion.classList.contains('active')) {
otherQuestion.classList.remove('active');
otherQuestion.nextElementSibling.classList.remove('active');
}
});
}
});
});
console.log('Benutzerdefinierte FAQ-Funktionalität initialisiert');
}
// Führe die Initialisierung beim ersten Laden der Seite aus
document.addEventListener('DOMContentLoaded', initFAQ);
// Führe die Initialisierung aus, wenn der Inhalt der Seite durch MkDocs aktualisiert wird
document.addEventListener('DOMContentSwap', initFAQ);
// Führe die Initialisierung aus, wenn die Seite über den Material for MkDocs-Router aktualisiert wird
document.addEventListener('mdContentChanged', initFAQ);
// Führe die Initialisierung aus, wenn der Inhalt der Seite durch andere Frameworks aktualisiert wird
// MutationObserver, um Änderungen im DOM zu überwachen
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.addedNodes && mutation.addedNodes.length > 0) {
// Prüfe, ob FAQ-Elemente hinzugefügt wurden
for (let i = 0; i < mutation.addedNodes.length; i++) {
const node = mutation.addedNodes[i];
if (node.nodeType === 1 && (node.classList?.contains('custom-faq-item') ||
node.querySelector?.('.custom-faq-item'))) {
// FAQ-Elemente gefunden, initialisiere die Funktionalität
setTimeout(initFAQ, 100); // Kurze Verzögerung, um sicherzustellen, dass alles geladen ist
break;
}
}
}
});
});
// Starte die Beobachtung des Dokuments
observer.observe(document.body, {
childList: true,
subtree: true
});
// Zusätzlicher Event-Listener für dynamisch geladene Inhalte
window.addEventListener('load', function() {
// Verzögerte Initialisierung, um sicherzustellen, dass alle Inhalte geladen sind
setTimeout(initFAQ, 500);
});
// Für Material for MkDocs spezifische Ereignisse
if (typeof document$.subscribe === 'function') {
document$.subscribe(function() {
// Verzögerte Initialisierung, um sicherzustellen, dass alle Inhalte geladen sind
setTimeout(initFAQ, 100);
});
}
+201
View File
@@ -0,0 +1,201 @@
// Lightbox functionality for the homepage screenshot
document.addEventListener('DOMContentLoaded', function() {
// Get the screenshot image
const screenshotImg = document.querySelector('.screenshot-container img');
// If the image exists
if (screenshotImg) {
// Create lightbox elements
const lightbox = document.createElement('div');
lightbox.className = 'satag-lightbox';
const closeBtn = document.createElement('span');
closeBtn.className = 'satag-lightbox-close';
closeBtn.innerHTML = '&times;';
const lightboxImg = document.createElement('img');
lightboxImg.className = 'satag-lightbox-img';
lightboxImg.src = screenshotImg.src;
lightboxImg.alt = screenshotImg.alt;
// Append elements to the lightbox
lightbox.appendChild(closeBtn);
lightbox.appendChild(lightboxImg);
// Remove any existing lightbox to avoid duplicates
const existingLightbox = document.querySelector('.satag-lightbox');
if (existingLightbox) {
document.body.removeChild(existingLightbox);
}
// Append lightbox to the body instead of the screenshot container
// This allows the lightbox to be as large as the viewport, not constrained by the container
document.body.appendChild(lightbox);
// Function to position the lightbox directly over the original image
function positionLightbox() {
// Get the position and dimensions of the original image
const imgRect = screenshotImg.getBoundingClientRect();
const screenshotContainer = document.querySelector('.screenshot-container');
const containerRect = screenshotContainer.getBoundingClientRect();
// Get the current scroll position
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
// Get viewport dimensions
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
// Position the lightbox directly over the original image
lightbox.style.position = 'fixed';
lightbox.style.top = imgRect.top + 'px';
lightbox.style.left = imgRect.left + 'px';
lightbox.style.width = imgRect.width + 'px';
lightbox.style.height = imgRect.height + 'px';
lightbox.style.display = 'flex';
lightbox.style.alignItems = 'center';
lightbox.style.justifyContent = 'center';
// Set the lightbox image to fill the lightbox container
lightboxImg.style.position = 'relative';
lightboxImg.style.width = '100%';
lightboxImg.style.height = '100%';
lightboxImg.style.objectFit = 'contain';
// Position the close button in the top-right corner of the lightbox
closeBtn.style.top = '5px';
closeBtn.style.right = '5px';
}
// Function to check if lightbox should be enabled (screen width > 768px)
function shouldEnableLightbox() {
return window.innerWidth > 768;
}
// Update cursor style based on screen width
function updateCursorStyle() {
screenshotImg.style.cursor = shouldEnableLightbox() ? 'pointer' : 'default';
}
// Initial cursor style update
updateCursorStyle();
// Update cursor style when window is resized
window.addEventListener('resize', updateCursorStyle);
// Open lightbox when clicking on the screenshot (only if screen width > 768px)
screenshotImg.addEventListener('click', function(e) {
e.preventDefault(); // Prevent default behavior
// Only activate lightbox if screen width > 768px
if (!shouldEnableLightbox()) {
return;
}
// Position the lightbox over the original image
positionLightbox();
// Display the lightbox - use flex to match our CSS
lightbox.style.display = 'flex';
// Force a reflow to ensure the display change takes effect before adding the active class
void lightbox.offsetWidth;
// Apply transform scale to the lightbox to make it larger
const viewportWidth = window.innerWidth;
if (viewportWidth <= 479) { // Mobile
lightbox.style.transform = 'scale(1.2)';
} else if (viewportWidth <= 959) { // Tablet
lightbox.style.transform = 'scale(1.3)';
} else if (viewportWidth <= 1219) { // Medium screens
lightbox.style.transform = 'scale(1.4)';
} else { // Large screens
lightbox.style.transform = 'scale(1.5)';
}
// Add the active class to trigger the transition
lightbox.classList.add('active');
});
// Function to handle closing with transition
function closeLightboxWithTransition() {
lightbox.style.opacity = '0';
lightbox.style.transform = 'scale(1)'; // Reset scale to original size
// Wait for transition to complete before removing active class
setTimeout(function() {
lightbox.classList.remove('active');
// Reset display to none after the transition is complete
lightbox.style.display = 'none';
// Reset all inline styles after the lightbox is hidden
setTimeout(function() {
// Reset opacity and transform
lightbox.style.opacity = '';
lightbox.style.transform = '';
// Reset position and size styles
lightbox.style.position = '';
lightbox.style.top = '';
lightbox.style.left = '';
lightbox.style.width = '';
lightbox.style.height = '';
lightbox.style.alignItems = '';
lightbox.style.justifyContent = '';
// Reset image styles
lightboxImg.style.width = '';
lightboxImg.style.height = '';
lightboxImg.style.objectFit = '';
// Reset close button styles
closeBtn.style.top = '';
closeBtn.style.right = '';
}, 100);
}, 400); // Match the transition duration from CSS
}
// Close lightbox when clicking on the close button
closeBtn.addEventListener('click', function(e) {
e.stopPropagation(); // Prevent event from bubbling up
closeLightboxWithTransition();
});
// Close lightbox when clicking on the lightbox (outside the image)
lightbox.addEventListener('click', function(e) {
if (e.target === lightbox) {
closeLightboxWithTransition();
}
});
// Close lightbox when pressing Escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && lightbox.classList.contains('active')) {
closeLightboxWithTransition();
}
});
// Update lightbox position when window is resized
window.addEventListener('resize', function() {
if (lightbox.classList.contains('active')) {
// Reposition the lightbox based on new viewport dimensions
positionLightbox();
// Apply appropriate transform scale based on viewport width
const viewportWidth = window.innerWidth;
if (viewportWidth <= 479) { // Mobile
lightbox.style.transform = 'scale(1.2)';
} else if (viewportWidth <= 959) { // Tablet
lightbox.style.transform = 'scale(1.3)';
} else if (viewportWidth <= 1219) { // Medium screens
lightbox.style.transform = 'scale(1.4)';
} else { // Large screens
lightbox.style.transform = 'scale(1.5)';
}
}
});
}
});
+43
View File
@@ -0,0 +1,43 @@
window.addEventListener('load', function() {
// Testimonial-Wechsel-Funktionalität
const testimonialImages = document.querySelectorAll('.satag--home-testimonial-image-wrapper');
const testimonialTexts = document.querySelectorAll('.satag--home-testimonials-text');
// Funktion zum Wechseln des aktiven Testimonials
function switchTestimonial(id) {
// Entferne 'active' Klasse von allen Bildern und Texten
testimonialImages.forEach(img => img.classList.remove('active'));
testimonialTexts.forEach(text => text.classList.remove('active'));
// Füge 'active' Klasse zum ausgewählten Bild und Text hinzu
document.querySelector(`.satag--home-testimonial-image-wrapper[data-testimonial-id="${id}"]`).classList.add('active');
document.querySelector(`.satag--home-testimonials-text[data-testimonial-id="${id}"]`).classList.add('active');
}
// Event-Listener für Klicks auf die Bilder
testimonialImages.forEach(img => {
img.addEventListener('click', function() {
const id = this.getAttribute('data-testimonial-id');
switchTestimonial(id);
});
});
// Optional: Automatischer Wechsel alle 5 Sekunden
let currentId = 1;
const totalTestimonials = testimonialImages.length;
function autoSwitchTestimonial() {
currentId = currentId % totalTestimonials + 1;
switchTestimonial(currentId);
}
// Kommentiere die nächste Zeile aus, wenn du keinen automatischen Wechsel möchtest
const intervalId = setInterval(autoSwitchTestimonial, 5000);
// Optional: Stoppe den automatischen Wechsel, wenn der Benutzer mit einem Testimonial interagiert
testimonialImages.forEach(img => {
img.addEventListener('click', function() {
clearInterval(intervalId);
});
});
});
+30
View File
@@ -0,0 +1,30 @@
document.addEventListener('DOMContentLoaded', function() {
const scrollWrap = document.querySelector('.md-search__scrollwrap');
if (scrollWrap) {
scrollWrap.setAttribute('tabindex', '0');
scrollWrap.addEventListener('keydown', function(e) {
// Pfeiltasten für Scrolling
if (e.key === 'ArrowDown') {
e.preventDefault();
this.scrollTop += 30;
} else if (e.key === 'ArrowUp') {
e.preventDefault();
this.scrollTop -= 30;
} else if (e.key === 'PageDown') {
e.preventDefault();
this.scrollTop += this.clientHeight;
} else if (e.key === 'PageUp') {
e.preventDefault();
this.scrollTop -= this.clientHeight;
} else if (e.key === 'Home') {
e.preventDefault();
this.scrollTop = 0;
} else if (e.key === 'End') {
e.preventDefault();
this.scrollTop = this.scrollHeight;
}
});
}
});