refactor: Replace Google Fonts with system fonts for improved performance and consistency

This commit is contained in:
2026-05-27 13:31:30 +02:00
parent 296c25192d
commit f68906f3cb
5 changed files with 157 additions and 48 deletions
+2 -1
View File
@@ -82,7 +82,8 @@ def set_security_headers(response):
response.headers["X-XSS-Protection"] = "1; mode=block" response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains" response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin" response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Content-Security-Policy"] = "default-src 'self'; script-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com https://fonts.googleapis.com; img-src 'self' data:; connect-src 'self'; frame-src 'self' https://www.youtube.com https://youtube.com https://www.youtube-nocookie.com https://youtube-nocookie.com; media-src 'self' https:;" # Fonts no longer loaded from Google Fonts; remove font hosts
response.headers["Content-Security-Policy"] = "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data:; connect-src 'self'; frame-src 'self' https://www.youtube.com https://youtube.com https://www.youtube-nocookie.com https://youtube-nocookie.com; media-src 'self' https:;"
return response return response
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) BASE_DIR = os.path.dirname(os.path.abspath(__file__))
+2 -2
View File
@@ -232,7 +232,7 @@ td,
button, button,
.action-link, .action-link,
.inline-admin-form input { .inline-admin-form input {
font-family: "JetBrains Mono", "Fira Code", "Consolas", monospace; font-family: Arial, Helvetica, sans-serif;
} }
.head h1 { .head h1 {
@@ -385,7 +385,7 @@ button,
background: #0b1114; background: #0b1114;
color: var(--term-text); color: var(--term-text);
padding: 0.36rem 0.5rem; padding: 0.36rem 0.5rem;
font-family: "JetBrains Mono", "Fira Code", "Consolas", monospace; font-family: Arial, Helvetica, sans-serif;
} }
.log-output { .log-output {
margin: 0; margin: 0;
+149 -40
View File
@@ -24,9 +24,7 @@
<meta name="twitter:description" content="{{ meta_description|default('Invario — Lehrmittel-, Inventar- und Ausleihverwaltung für Schulen. Sicher, einfach, nachvollziehbar.') }}"> <meta name="twitter:description" content="{{ meta_description|default('Invario — Lehrmittel-, Inventar- und Ausleihverwaltung für Schulen. Sicher, einfach, nachvollziehbar.') }}">
<meta name="twitter:image" content="{{ url_for('static', filename='images/logo-1.jpeg') }}"> <meta name="twitter:image" content="{{ url_for('static', filename='images/logo-1.jpeg') }}">
{% block head %} {% block head %}
<link rel="preconnect" href="https://fonts.googleapis.com"> <!-- Using system Arial font instead of Google Fonts -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Merriweather:wght@700;900&family=Public+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<style> <style>
:root { :root {
--bg-main: #f3f1ea; --bg-main: #f3f1ea;
@@ -47,7 +45,7 @@
/* Verhinderung von iOS Auto-Zoom bei Inputs */ /* Verhinderung von iOS Auto-Zoom bei Inputs */
input, textarea, select, button { input, textarea, select, button {
font-family: inherit; font-family: Arial, Helvetica, sans-serif;
font-size: 16px; font-size: 16px;
} }
@@ -59,7 +57,7 @@
padding-left: env(safe-area-inset-left); padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right); padding-right: env(safe-area-inset-right);
min-height: 100%; min-height: 100%;
font-family: "Public Sans", system-ui, sans-serif; font-family: Arial, Helvetica, sans-serif;
color: var(--text-main); color: var(--text-main);
background: radial-gradient(circle at 88% -8%, #dbe4d9 0%, transparent 34%), background: radial-gradient(circle at 88% -8%, #dbe4d9 0%, transparent 34%),
radial-gradient(circle at -5% 58%, #ece4d2 0%, transparent 40%), radial-gradient(circle at -5% 58%, #ece4d2 0%, transparent 40%),
@@ -72,33 +70,16 @@
flex-direction: column; flex-direction: column;
} }
h1 { h1, h2, h3, h4 {
margin: 0; margin: 0;
font-family: "Merriweather", Georgia, serif; font-family: Arial, Helvetica, sans-serif;
line-height: 1.16; line-height: 1.16;
font-size: clamp(1.85rem, 4vw, 3.5rem);
} }
h2 { h1 { font-size: clamp(1.85rem, 4vw, 3.5rem); }
margin: 0; h2 { font-size: clamp(1.5rem, 3vw, 2.5rem); }
font-family: "Merriweather", Georgia, serif; h3 { font-size: clamp(1.2rem, 2vw, 1.8rem); }
line-height: 1.2; h4 { font-size: clamp(1rem, 1.8vw, 1.5rem); }
font-size: clamp(1.5rem, 3vw, 2.5rem);
}
h3 {
margin: 0;
font-family: "Merriweather", Georgia, serif;
line-height: 1.24;
font-size: clamp(1.2rem, 2vw, 1.8rem);
}
h4 {
margin: 0;
font-family: "Merriweather", Georgia, serif;
line-height: 1.24;
font-size: clamp(1rem, 1.8vw, 1.5rem);
}
p { p {
margin: 0; margin: 0;
@@ -702,10 +683,45 @@
</style> </style>
</footer> </footer>
<div class="cookie-banner hidden" id="cookieBanner" role="dialog" aria-live="polite" aria-label="Cookie Hinweis"> <div class="cookie-banner hidden" id="cookieBanner" role="dialog" aria-live="polite" aria-label="Cookie Hinweis">
<p>Wir verwenden Cookies, um die Website sicher und benutzerfreundlich zu betreiben. Mit "Cookies akzeptieren" wird die Nutzung bestätigt. Details in <a href="{{ url_for('datenschutz') }}">Datenschutz</a>.</p> <p>Wir verwenden Cookies, um die Website sicher und benutzerfreundlich zu betreiben. Du kannst auswählen, welche Cookies du erlaubst. Details in <a href="{{ url_for('datenschutz') }}">Datenschutz</a>.</p>
<div class="cookie-actions"> <div class="cookie-actions">
<button type="button" class="cookie-btn" id="cookieAcceptBtn">Cookies akzeptieren</button> <button type="button" class="cookie-btn" id="cookieAcceptBtn">Alle Cookies akzeptieren</button>
<button type="button" class="cookie-btn secondary" id="cookieDeclineBtn">Ablehnen und zu Ecosia</button> <button type="button" class="cookie-btn secondary" id="cookiePrefsBtn">Einstellungen</button>
</div>
</div>
<div id="cookiePrefsModal" class="cookie-banner hidden" role="dialog" aria-live="polite" aria-label="Cookie Einstellungen" style="max-width:640px; left:50%; transform:translateX(-50%); bottom:2rem;">
<div style="flex-direction:column; display:flex; gap:0.6rem;">
<h3 style="margin:0 0 0.3rem 0;">Cookie-Einstellungen</h3>
<p style="margin:0 0 0.6rem 0; color:#315055;">Wähle, welche Cookies du erlauben möchtest. Technisch notwendige Cookies sind für die Anmeldung und Sicherheit erforderlich.</p>
<div style="display:flex; flex-direction:column; gap:0.6rem;">
<label style="display:flex; justify-content:space-between; align-items:center; gap:1rem;">
<div>
<strong>Notwendige Cookies</strong>
<div style="font-size:0.9rem; color:#52686a;">Erforderlich für Login, Sitzungsverwaltung und Sicherheit (nicht deaktivierbar).</div>
</div>
<input type="checkbox" id="cookie_necessary" disabled checked />
</label>
<label style="display:flex; justify-content:space-between; align-items:center; gap:1rem;">
<div>
<strong>Analytik</strong>
<div style="font-size:0.9rem; color:#52686a;">Anonyme Nutzungsauswertung zur Verbesserung der Website (optional).</div>
</div>
<input type="checkbox" id="cookie_analytics" />
</label>
<label style="display:flex; justify-content:space-between; align-items:center; gap:1rem;">
<div>
<strong>Marketing</strong>
<div style="font-size:0.9rem; color:#52686a;">Personalisierte Inhalte und externe Dienste (optional).</div>
</div>
<input type="checkbox" id="cookie_marketing" />
</label>
</div>
<div style="display:flex; gap:0.6rem; margin-top:0.6rem;">
<button type="button" class="cookie-btn" id="cookiePrefsAcceptAll">Alle akzeptieren</button>
<button type="button" class="cookie-btn secondary" id="cookiePrefsSave">Speichern</button>
<button type="button" class="cookie-btn secondary" id="cookiePrefsOnlyNecessary">Nur notwendige Cookies</button>
</div>
</div> </div>
</div> </div>
<!-- Mobile compatibility scripts --> <!-- Mobile compatibility scripts -->
@@ -769,27 +785,120 @@
var consentKey = "cookie_consent_v1"; var consentKey = "cookie_consent_v1";
function hasConsent() { function getConsent() {
return window.localStorage.getItem(consentKey) === "accepted" || document.cookie.indexOf("cookie_consent=accepted") !== -1; try {
var raw = window.localStorage.getItem(consentKey);
if (!raw) return null;
return JSON.parse(raw);
} catch (e) {
return null;
}
} }
function setConsent() { function saveConsent(obj) {
window.localStorage.setItem(consentKey, "accepted"); try {
document.cookie = "cookie_consent=accepted; Max-Age=" + (60 * 60 * 24 * 365) + "; Path=/; SameSite=Strict"; window.localStorage.setItem(consentKey, JSON.stringify(obj));
var cookieVal = obj.status || (obj.accepted ? 'accepted' : 'custom');
document.cookie = "cookie_consent=" + encodeURIComponent(JSON.stringify(obj)) + "; Max-Age=" + (60 * 60 * 24 * 365) + "; Path=/; SameSite=Strict";
// Apply consent immediately: enable any scripts gated by consent
applyConsent();
} catch (e) {
// ignore
}
} }
if (!hasConsent()) { function showBannerIfNeeded() {
var c = getConsent();
if (!c) {
banner.classList.remove("hidden"); banner.classList.remove("hidden");
} }
}
showBannerIfNeeded();
acceptBtn.addEventListener("click", function () { acceptBtn.addEventListener("click", function () {
setConsent(); var obj = { status: 'accepted', necessary: true, analytics: true, marketing: true, saved_at: Date.now() };
saveConsent(obj);
banner.classList.add("hidden"); banner.classList.add("hidden");
}); });
declineBtn.addEventListener("click", function () { // Preferences modal handling
window.location.href = "https://www.ecosia.org"; var prefsBtn = document.getElementById('cookiePrefsBtn');
var prefsModal = document.getElementById('cookiePrefsModal');
var analyticsCheckbox = document.getElementById('cookie_analytics');
var marketingCheckbox = document.getElementById('cookie_marketing');
var prefsSave = document.getElementById('cookiePrefsSave');
var prefsAcceptAll = document.getElementById('cookiePrefsAcceptAll');
var prefsOnlyNecessary = document.getElementById('cookiePrefsOnlyNecessary');
function openPrefs() {
// initialize from existing consent if present
var c = getConsent() || {};
analyticsCheckbox.checked = !!c.analytics;
marketingCheckbox.checked = !!c.marketing;
prefsModal.classList.remove('hidden');
}
function closePrefs() {
prefsModal.classList.add('hidden');
banner.classList.add('hidden');
}
if (prefsBtn) prefsBtn.addEventListener('click', openPrefs);
prefsSave.addEventListener('click', function () {
var obj = { status: 'custom', necessary: true, analytics: !!analyticsCheckbox.checked, marketing: !!marketingCheckbox.checked, saved_at: Date.now() };
saveConsent(obj);
closePrefs();
}); });
prefsAcceptAll.addEventListener('click', function () {
var obj = { status: 'accepted', necessary: true, analytics: true, marketing: true, saved_at: Date.now() };
saveConsent(obj);
closePrefs();
});
prefsOnlyNecessary.addEventListener('click', function () {
var obj = { status: 'necessary', necessary: true, analytics: false, marketing: false, saved_at: Date.now() };
saveConsent(obj);
closePrefs();
});
// --- Consent-based script loader -------------------------------------------------
// Usage: include scripts that require consent like this in templates:
// <script type="text/plain" data-consent="analytics" src="https://www.googletagmanager.com/gtag/js?id=XXX"></script>
// or for inline scripts:
// <script type="text/plain" data-consent="marketing"> /* fbq or other init code */ </script>
function applyConsent() {
try {
var c = getConsent() || {};
var analyticsAllowed = !!c.analytics;
var marketingAllowed = !!c.marketing;
// Find all scripts that were intentionally disabled by using type="text/plain"
document.querySelectorAll('script[type="text/plain"]').forEach(function (node) {
var required = node.getAttribute('data-consent') || 'analytics';
if ((required === 'analytics' && analyticsAllowed) || (required === 'marketing' && marketingAllowed) || required === 'necessary') {
var s = document.createElement('script');
// copy src or inline content
if (node.src) s.src = node.src;
else s.text = node.textContent || node.innerText || '';
// copy attributes that are safe/needed
var type = node.getAttribute('data-script-type');
if (type) s.type = type;
// append to same parent
node.parentNode.insertBefore(s, node);
// mark original as applied to avoid double-inserting
node.setAttribute('data-consent-applied', '1');
}
});
} catch (e) {
console.error('applyConsent error', e);
}
}
// Run once on load in case user already saved consent
applyConsent();
})(); })();
</script> </script>
</body> </body>
+1 -2
View File
@@ -717,7 +717,6 @@
</script> </script>
<header class="hero" id="top" aria-label="Seitenstart"> <header class="hero" id="top" aria-label="Seitenstart">
<div> <div>
<span class="eyebrow">Lehrmittelverwaltungssoftware für Schulen</span>
<h1>Die Software, die iPads genauso effizient verwaltet wie das Schulbuch.</h1> <h1>Die Software, die iPads genauso effizient verwaltet wie das Schulbuch.</h1>
<p> <p>
Radikale Einfachheit für Ihren Schulalltag. Starten Sie in nur 5 Minuten mit der modernen Inventarsoftware für alle Schularten. Hybrid, intuitiv, sofort einsatzbereit. Radikale Einfachheit für Ihren Schulalltag. Starten Sie in nur 5 Minuten mit der modernen Inventarsoftware für alle Schularten. Hybrid, intuitiv, sofort einsatzbereit.
@@ -796,7 +795,7 @@
</article> </article>
<article class="card"> <article class="card">
<h3>Zero Maintenance</h3> <h3>Zero Maintenance</h3>
<p>Reine WebApp: automatische Updates, keine lokalen Server, nahezu kein Betriebsaufwand für Ihre IT.</p> <p>Reine WebApp: automatische Updates, keine lokalen Server, nahezu kein Betriebsaufwand für Ihre IT. Deswegen benötigen sie keine App oder irgend etwas anderes als einen Internetzugang!</p>
</article> </article>
<article class="card"> <article class="card">
<h3>Datenschutz &amp; DSGVO</h3> <h3>Datenschutz &amp; DSGVO</h3>
+2 -2
View File
@@ -72,7 +72,7 @@
font-size: 1.8rem; font-size: 1.8rem;
color: var(--text-main); color: var(--text-main);
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
font-family: "Merriweather", Georgia, serif; font-family: Arial, Helvetica, sans-serif;
} }
.video-meta { .video-meta {
@@ -188,7 +188,7 @@
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
font-weight: 600; font-weight: 600;
line-height: 1.4; line-height: 1.4;
font-family: "Merriweather", Georgia, serif; font-family: Arial, Helvetica, sans-serif;
} }
.tutorial-card-meta { .tutorial-card-meta {