Initial commit: RhetorikMaster PWA Trainer & Klausur-Spickzettel
This commit is contained in:
commit
3688695ff2
10 files changed
+2094
No files matched your search
@@ -0,0 +1,7 @@
|
|||||||
|
# OS & Editor files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
*~
|
||||||
|
*.swp
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# RhetorikMaster
|
||||||
|
|
||||||
|
Interaktiver Rhetorik-Trainer & Klausur-Spickzettel als Progressive Web App (PWA).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- **Cheat Sheet / Klausur-Spickzettel**: Tabellarische Übersicht rhetorischer Stilmittel gegliedert nach Wirkungskategorien (Anschaulichkeit, Klang/Rhythmus, Nachdruck/Struktur, Emotion/Spannung, Argumentation/Logik).
|
||||||
|
- **Kernstoff & Zusatzinfo**: Filter für Kernstoff (häufig geprüfte Stilmittel) und Zusatzinfo.
|
||||||
|
- **Interaktiver Quiz-Modus**: Multiple-Choice-Fragen, Begriffs- und Beispielfragen, Sofort-Feedback mit Erklärung, Option "Nur Kernstoff abfragen".
|
||||||
|
- **PWA & Offline-Fähig**: Service Worker mit Network-First-Strategie und Fallback auf Cache.
|
||||||
|
- **Responsives Design**: Optimiert für Desktop und mobile Endgeräte (Smartphones wie z. B. Pixel).
|
||||||
|
|
||||||
|
## Bereitstellung
|
||||||
|
- Web-Verzeichnis: `www/`
|
||||||
|
- Domain: `https://rhetorikmaster.pansi.eu/`
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 450 B |
@@ -0,0 +1,15 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#6366f1" />
|
||||||
|
<stop offset="100%" stop-color="#4338ca" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="gold" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#fbbf24" />
|
||||||
|
<stop offset="100%" stop-color="#f59e0b" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="100" height="100" rx="24" fill="url(#grad)" />
|
||||||
|
<path d="M72 22 C60 22 35 34 26 58 C24 63 26 67 31 66 C38 64 45 59 49 53 L38 53 C38 53 47 44 54 44 C58 44 55 49 52 54 L60 54 C66 45 74 33 76 25 C76 23 74 22 72 22 Z" fill="#ffffff" />
|
||||||
|
<path d="M26 62 L22 78 L34 70 Z" fill="url(#gold)" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 767 B |
Binary file not shown.
|
After Width: | Height: | Size: 450 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
rhetorik_trainer_quiz.html
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "RhetorikMaster - Stilmittel Trainer & Quiz",
|
||||||
|
"short_name": "RhetorikMaster",
|
||||||
|
"description": "Interaktiver Trainer für rhetorische Stilmittel der Textanalyse mit Karteikarten, Quiz und Leitner-System",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#0f172a",
|
||||||
|
"theme_color": "#4f46e5",
|
||||||
|
"orientation": "portrait-primary",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/favicon.svg",
|
||||||
|
"sizes": "any",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,80 @@
|
|||||||
|
const CACHE_NAME = 'rhetorikmaster-v5';
|
||||||
|
const STATIC_ASSETS = [
|
||||||
|
'/',
|
||||||
|
'/index.html',
|
||||||
|
'/rhetorik_trainer_quiz.html',
|
||||||
|
'/favicon.svg',
|
||||||
|
'/manifest.json',
|
||||||
|
'/icon-192.png',
|
||||||
|
'/icon-512.png',
|
||||||
|
'https://cdn.tailwindcss.com',
|
||||||
|
'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css',
|
||||||
|
'https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap'
|
||||||
|
];
|
||||||
|
|
||||||
|
self.addEventListener('install', (event) => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.open(CACHE_NAME).then((cache) => {
|
||||||
|
return cache.addAll(STATIC_ASSETS).catch((err) => {
|
||||||
|
console.warn('Precache partial failure:', err);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
self.skipWaiting();
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('activate', (event) => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.keys().then((cacheNames) => {
|
||||||
|
return Promise.all(
|
||||||
|
cacheNames.map((name) => {
|
||||||
|
if (name !== CACHE_NAME) {
|
||||||
|
console.log('Cleaning old cache:', name);
|
||||||
|
return caches.delete(name);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}).then(() => self.clients.claim())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('fetch', (event) => {
|
||||||
|
const request = event.request;
|
||||||
|
const isHtmlNavigation = request.mode === 'navigate' ||
|
||||||
|
(request.method === 'GET' && request.headers.get('accept') && request.headers.get('accept').includes('text/html'));
|
||||||
|
|
||||||
|
if (isHtmlNavigation) {
|
||||||
|
// Network-First for HTML/Navigation: Always fetch fresh HTML from server, fallback to cache if offline
|
||||||
|
event.respondWith(
|
||||||
|
fetch(request)
|
||||||
|
.then((networkResponse) => {
|
||||||
|
if (networkResponse && networkResponse.status === 200) {
|
||||||
|
const copy = networkResponse.clone();
|
||||||
|
caches.open(CACHE_NAME).then((cache) => cache.put(request, copy));
|
||||||
|
}
|
||||||
|
return networkResponse;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return caches.match(request).then((cached) => cached || caches.match('/rhetorik_trainer_quiz.html') || caches.match('/'));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stale-While-Revalidate for other static assets (CSS, fonts, images)
|
||||||
|
event.respondWith(
|
||||||
|
caches.match(request).then((cachedResponse) => {
|
||||||
|
const fetchPromise = fetch(request).then((networkResponse) => {
|
||||||
|
if (networkResponse && networkResponse.status === 200) {
|
||||||
|
const responseToCache = networkResponse.clone();
|
||||||
|
caches.open(CACHE_NAME).then((cache) => {
|
||||||
|
cache.put(request, responseToCache);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return networkResponse;
|
||||||
|
}).catch(() => cachedResponse);
|
||||||
|
|
||||||
|
return cachedResponse || fetchPromise;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
Reference in new issue
Block a user