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["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
|
||||
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
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
@@ -232,7 +232,7 @@ td,
|
||||
button,
|
||||
.action-link,
|
||||
.inline-admin-form input {
|
||||
font-family: "JetBrains Mono", "Fira Code", "Consolas", monospace;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.head h1 {
|
||||
@@ -385,7 +385,7 @@ button,
|
||||
background: #0b1114;
|
||||
color: var(--term-text);
|
||||
padding: 0.36rem 0.5rem;
|
||||
font-family: "JetBrains Mono", "Fira Code", "Consolas", monospace;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
.log-output {
|
||||
margin: 0;
|
||||
|
||||
+150
-41
@@ -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:image" content="{{ url_for('static', filename='images/logo-1.jpeg') }}">
|
||||
{% block head %}
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<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">
|
||||
<!-- Using system Arial font instead of Google Fonts -->
|
||||
<style>
|
||||
:root {
|
||||
--bg-main: #f3f1ea;
|
||||
@@ -47,7 +45,7 @@
|
||||
|
||||
/* Verhinderung von iOS Auto-Zoom bei Inputs */
|
||||
input, textarea, select, button {
|
||||
font-family: inherit;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
@@ -59,7 +57,7 @@
|
||||
padding-left: env(safe-area-inset-left);
|
||||
padding-right: env(safe-area-inset-right);
|
||||
min-height: 100%;
|
||||
font-family: "Public Sans", system-ui, sans-serif;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
color: var(--text-main);
|
||||
background: radial-gradient(circle at 88% -8%, #dbe4d9 0%, transparent 34%),
|
||||
radial-gradient(circle at -5% 58%, #ece4d2 0%, transparent 40%),
|
||||
@@ -72,33 +70,16 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
h1 {
|
||||
h1, h2, h3, h4 {
|
||||
margin: 0;
|
||||
font-family: "Merriweather", Georgia, serif;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
line-height: 1.16;
|
||||
font-size: clamp(1.85rem, 4vw, 3.5rem);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-family: "Merriweather", Georgia, serif;
|
||||
line-height: 1.2;
|
||||
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);
|
||||
}
|
||||
h1 { font-size: clamp(1.85rem, 4vw, 3.5rem); }
|
||||
h2 { font-size: clamp(1.5rem, 3vw, 2.5rem); }
|
||||
h3 { font-size: clamp(1.2rem, 2vw, 1.8rem); }
|
||||
h4 { font-size: clamp(1rem, 1.8vw, 1.5rem); }
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
@@ -702,10 +683,45 @@
|
||||
</style>
|
||||
</footer>
|
||||
<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">
|
||||
<button type="button" class="cookie-btn" id="cookieAcceptBtn">Cookies akzeptieren</button>
|
||||
<button type="button" class="cookie-btn secondary" id="cookieDeclineBtn">Ablehnen und zu Ecosia</button>
|
||||
<button type="button" class="cookie-btn" id="cookieAcceptBtn">Alle Cookies akzeptieren</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>
|
||||
<!-- Mobile compatibility scripts -->
|
||||
@@ -769,27 +785,120 @@
|
||||
|
||||
var consentKey = "cookie_consent_v1";
|
||||
|
||||
function hasConsent() {
|
||||
return window.localStorage.getItem(consentKey) === "accepted" || document.cookie.indexOf("cookie_consent=accepted") !== -1;
|
||||
function getConsent() {
|
||||
try {
|
||||
var raw = window.localStorage.getItem(consentKey);
|
||||
if (!raw) return null;
|
||||
return JSON.parse(raw);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function setConsent() {
|
||||
window.localStorage.setItem(consentKey, "accepted");
|
||||
document.cookie = "cookie_consent=accepted; Max-Age=" + (60 * 60 * 24 * 365) + "; Path=/; SameSite=Strict";
|
||||
function saveConsent(obj) {
|
||||
try {
|
||||
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()) {
|
||||
banner.classList.remove("hidden");
|
||||
function showBannerIfNeeded() {
|
||||
var c = getConsent();
|
||||
if (!c) {
|
||||
banner.classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
showBannerIfNeeded();
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
declineBtn.addEventListener("click", function () {
|
||||
window.location.href = "https://www.ecosia.org";
|
||||
// Preferences modal handling
|
||||
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>
|
||||
</body>
|
||||
|
||||
@@ -717,7 +717,6 @@
|
||||
</script>
|
||||
<header class="hero" id="top" aria-label="Seitenstart">
|
||||
<div>
|
||||
<span class="eyebrow">Lehrmittelverwaltungssoftware für Schulen</span>
|
||||
<h1>Die Software, die iPads genauso effizient verwaltet wie das Schulbuch.</h1>
|
||||
<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.
|
||||
@@ -796,7 +795,7 @@
|
||||
</article>
|
||||
<article class="card">
|
||||
<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 class="card">
|
||||
<h3>Datenschutz & DSGVO</h3>
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
font-size: 1.8rem;
|
||||
color: var(--text-main);
|
||||
margin-bottom: 0.5rem;
|
||||
font-family: "Merriweather", Georgia, serif;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.video-meta {
|
||||
@@ -188,7 +188,7 @@
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
font-family: "Merriweather", Georgia, serif;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.tutorial-card-meta {
|
||||
|
||||
Reference in New Issue
Block a user