refactor: Replace Google Fonts with system fonts for improved performance and consistency
This commit is contained in:
+2
-1
@@ -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__))
|
||||||
|
|||||||
@@ -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
@@ -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>
|
||||||
|
|||||||
@@ -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 Web‑App: automatische Updates, keine lokalen Server, nahezu kein Betriebsaufwand für Ihre IT.</p>
|
<p>Reine Web‑App: 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 & DSGVO</h3>
|
<h3>Datenschutz & DSGVO</h3>
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user