Compare commits
20 Commits
c5009d1718
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 063cbf944f | |||
| 0e66aff47d | |||
| 6a7e90116a | |||
| 4a9d1f4ada | |||
| dbed1ad79f | |||
| 69b15aa4df | |||
| 0b3e5d39cf | |||
| 1c46eb2421 | |||
| 955bdb7057 | |||
| 3bc6c8f75c | |||
| 9ebdb7ed3e | |||
| b1671aa29a | |||
| 1bfbd53062 | |||
| 983e57cd8d | |||
| f8f068150e | |||
| 501ecc0c1e | |||
| 3a1bd759c7 | |||
| 0b3286af4c | |||
| e853ca8e93 | |||
| e52cada445 |
@@ -34,7 +34,15 @@ jobs:
|
||||
ALTER USER mindflux WITH PASSWORD :'new_db_password';
|
||||
SQL
|
||||
docker compose -f docker-compose.yml pull
|
||||
<<<<<<< HEAD
|
||||
docker compose -f docker-compose.yml up -d --wait --wait-timeout 90 || {
|
||||
docker logs --tail 200 mindflux >&2
|
||||
exit 1
|
||||
}
|
||||
=======
|
||||
docker compose -f docker-compose.yml up -d
|
||||
- name: Cleanup
|
||||
run: |
|
||||
docker image prune -f
|
||||
docker builder prune -f --filter "until=168h"
|
||||
>>>>>>> f79df17 (CI: dangling images und alten build cache aufraeumen)
|
||||
|
||||
@@ -4,6 +4,103 @@ Alle nennenswerten Änderungen an **SysnaptiK Neural** werden hier dokumentiert.
|
||||
Format angelehnt an [Keep a Changelog](https://keepachangelog.com/de/1.0.0/),
|
||||
Versionierung [semantisch](https://semver.org/lang/de/).
|
||||
|
||||
## [Unreleased] – 2026-07-21 · Gesamt-Audit: Light Mode, Barrierefreiheit, AP2-Inhalte, DSGVO
|
||||
|
||||
### Registrierung & Kontozugang
|
||||
- **Sofort nutzbare Accounts:** Die Registrierung hängt nicht mehr an der Zustellung
|
||||
einer Bestätigungs-E-Mail. Neue Accounts können sich direkt anmelden; bereits
|
||||
wartende Konten werden mit Migration V23 aktiviert. Nicht mehr benötigte
|
||||
Bestätigungs-Token werden gelöscht. E-Mail bleibt für den Passwort-Reset erhalten.
|
||||
|
||||
### Light Mode & Design
|
||||
- **Browser-Icon:** Das Neural-Emblem wird unter `/favicon.ico` ausgeliefert und
|
||||
direkt auf der Startseite verlinkt. Die Versionierung der Bild-URL ersetzt auch
|
||||
bereits gecachte Standard-Globus-Symbole im Browser-Tab.
|
||||
- **Preismodell offen formuliert:** Die öffentliche Einstiegsseite verspricht keine
|
||||
kostenlose Nutzung mehr; Beschreibung, Registrierungs-Teaser und Account-Hinweis
|
||||
bleiben bewusst preisneutral.
|
||||
- **Produktzweck klarer:** Die Login-Seite erklärt nun Zielgruppe, Lernformen und
|
||||
den Nutzen eines Accounts; im Dashboard fasst ein kompakter Einstieg den Lernzweck
|
||||
zusammen.
|
||||
- **TechTacular-Fragen überspringbar:** Eine noch nicht beantwortete Frage kann per
|
||||
×, Klick außerhalb oder Escape folgenlos geschlossen und später wieder geöffnet
|
||||
werden.
|
||||
- **Light-Mode-Fundament:** zentrale UI-Effekte sind jetzt als Theme-Primitives
|
||||
organisiert. Dark Mode behält Neon-Emissionen; Light Mode nutzt warme Elevation,
|
||||
entfernt Text-/Icon-Halos und erhält einen sichtbaren Scrollbar-Thumb. Native
|
||||
Controls folgen über `color-scheme` dem gewählten Theme.
|
||||
- **Theme-Start & FinalProtocol:** Erstbesucher folgen der OS-Farbpräferenz, die
|
||||
Browser-Chrome erhält ein passendes `theme-color` und FinalProtocol lädt keinen
|
||||
ungenutzten Legacy-Style mehr; die statischen Lernseiten erhalten daraus nur noch
|
||||
eine minimale Reset-Basis.
|
||||
- **Kritische Light-Mode-Fälle behoben:** Login-Overlay, SyntaxSaga-Bühne,
|
||||
CodeEscape-Terminal, NetWorker-Guide und die Farbenblind-Palette sind in heller
|
||||
Darstellung wieder lesbar und kontraststark. A11y-Widget und FinalProtocol
|
||||
verwenden ebenfalls themefähige Farben.
|
||||
- **Ansichtswechsel abgesichert:** zentrale SPA-Übergänge starten oben, ohne laufendes
|
||||
Antwortfeedback zu versetzen. FinalProtocol scrollt Ergebnisse und Neustarts gezielt
|
||||
ins Sichtfeld; SyntaxSaga und DevLab fokussieren ohne Scrollsprung.
|
||||
- **Echter Light Mode statt Invertierung:** Light-Tokens auf WCAG AA nachgeschärft
|
||||
(Akzent, Muted, Sekundär, Spielakzente), neues `--warn`-Token. Die Spielkarten der
|
||||
Plattform-Hülle erhalten im Light Mode farbton-treu abgedunkelte Akzente statt
|
||||
durchschlagender Neon-Werte.
|
||||
- **ServiceClash, NetWorker und BugHunter** haben jetzt eine echte helle Variante
|
||||
(zuvor dauerhaft dunkel): alle Farben laufen über scoped CSS-Custom-Properties auf
|
||||
der Body-Klasse, Dark bleibt pixelgleich, die Light-Paletten sind handentworfen und
|
||||
vollständig WCAG-AA-geprüft; der Theme-Umschalter wurde im Header ergänzt. Der bei
|
||||
NetWorker fälschlich globale `var(--text)`/`var(--muted)`-Zugriff ist behoben.
|
||||
- **Header-Icons vereinheitlicht:** alle Icon-Buttons einheitlich 42×42 px, Radius 10,
|
||||
Glyphe 20×20, Outline-Stil (Surface-Fill, Akzent nur über Farbe/Rahmen).
|
||||
|
||||
### Barrierefreiheit
|
||||
- **Widget perfektioniert:** Fokus-Falle im Panel, `aria-modal`, Schließen per Klick
|
||||
außerhalb; der Textgrößen-Button nutzt ein dynamisches `aria-label` statt eines
|
||||
falschen `aria-pressed`, der Reset-Button ist als Aktion ausgezeichnet; Hochkontrast
|
||||
ist als „immer dunkel" kenntlich gemacht. Toter Legacy-Dock-Code entfernt.
|
||||
- **Spielanleitungen & Hints:** gemeinsames „So funktioniert's"-Overlay mit ?-Button
|
||||
und automatischem Erstaufruf für sieben Spiele (NetWorker behält seinen Inline-Guide);
|
||||
MindMatch erhält einen „Paar-Blitz"-Tipp, der kurz ein Paar aufdeckt und dafür einen
|
||||
Zug kostet (rein clientseitig, keine Backend-Änderung).
|
||||
|
||||
### FinalProtocol – Abschlussprüfung Teil 2
|
||||
- **200 neue Prüfungsfragen:** je fünf Themen für Systemintegration (Netzwerkplanung,
|
||||
IT-Sicherheit, Virtualisierung, Monitoring & Backup, Skripting) und Anwendungs-
|
||||
entwicklung (SQL, UML, OOP, Algorithmen, Testverfahren) mit je 20 Fragen, fünf
|
||||
Kategorien und ausgewogener Antwortverteilung. Titel und Navigation decken jetzt
|
||||
AP1 **und** AP2 ab.
|
||||
|
||||
### DSGVO & Recht
|
||||
- **Rechtslinks von jeder Seite erreichbar:** das bislang ungenutzte Footer-Fragment
|
||||
wird jetzt auf allen Seiten inkl. Spiel-, Fehler- und Anmeldeseiten verwendet
|
||||
(26 duplizierte Inline-Footer ersetzt, 11 fehlende ergänzt).
|
||||
- **Datenschutzerklärung** um NeuralNet-Nachrichten, E-Mail-Token (SHA-256, 24 h),
|
||||
SMTP-Versand und konkrete Speicherfristen ergänzt; **Impressum** nach § 5 DDG
|
||||
strukturiert (auffällig markierte Pflichtfeld-Platzhalter).
|
||||
- **Datenexport (Art. 20):** `/profile/export` liefert alle Kontodaten (ohne Passwort)
|
||||
als JSON-Download.
|
||||
- **Demo-Konto „NeuralTest"** wird per Migration aus produktiven Datenbanken entfernt.
|
||||
|
||||
### Technik & Qualität
|
||||
- **E-Mail-Versand diagnostizierbar:** Bestätigungs- und Reset-Mails schreiben jetzt
|
||||
datensparsame, korrelierbare Ereignislogs für Token-Erstellung, SMTP-Übergabe,
|
||||
Fehler, erneute Anforderung und Verifikation. Roh-Tokens und Bestätigungslinks
|
||||
werden nie protokolliert; eine SMTP-Annahme wird nicht als Postfachzustellung
|
||||
ausgegeben.
|
||||
- **Legacy aufgeräumt:** die nicht referenzierten Styles für Achievements, Progress,
|
||||
Settings und Soziales sowie `finalflux-check.js` entfernt; der Styleguide dokumentiert
|
||||
die neuen Effekt-Primitives.
|
||||
- **Chart.js lokal ausgeliefert** statt vom CDN – das einzige externe Asset entfällt,
|
||||
wodurch die Datenschutzerklärung wieder zur Realität passt.
|
||||
- **Security-Header** ergänzt: Content-Security-Policy (self-hosted), Referrer-Policy;
|
||||
HSTS bleibt auf HTTPS beschränkt.
|
||||
- **Umlaute** in den Spielinhalten flächendeckend korrigiert (wort-/morphemgenau,
|
||||
Slugs und Code-Bezeichner geschont), mit Reseed-Migration für bereits befüllte
|
||||
Datenbanken.
|
||||
- **AvatarService** aus dem ProfileController extrahiert (Bildverarbeitung und
|
||||
Pfad-Logik zentralisiert); **Bean Validation** eingeführt (Start mit ScoreSubmission).
|
||||
- Aufräumen: totes Scaffolding (`DummyController`, `dummy.html`, `hello.txt`) und
|
||||
obsoleter CSS-Code entfernt.
|
||||
|
||||
## [Unreleased] – 2026-07-14 · SysnaptiK Neural Rebrand
|
||||
|
||||
### TechTacular
|
||||
|
||||
+18
-11
@@ -36,17 +36,18 @@ Theme-Umschaltung: über das Attribut `<html data-theme="dark|light">` (gesetzt/
|
||||
| `--surface-2` | `#1b1610` | `#fbf4e6` | erhöhte Fläche, Hover, Felder |
|
||||
| `--border` | `#2a2117` | `#ece1cf` | Standard-Rahmen |
|
||||
| `--line` | `#221b12` | `#f1e8d8` | feine Linien, Scrollbar, Chart-Achsen |
|
||||
| `--border-accent` | `rgba(240,168,26,.45)` | `rgba(214,81,22,.40)` | akzentuierte Rahmen |
|
||||
| `--accent` | `#f0a81a` | `#d65116` | **Primärakzent**: Buttons, aktive Nav, Icons, Headline-Akzent |
|
||||
| `--accent-2` | `#f7dc11` | `#e4801d` | Sekundär-Akzent für Verläufe (Gelb-Glow) |
|
||||
| `--border-accent` | `rgba(240,168,26,.45)` | `rgba(194,65,12,.40)` | akzentuierte Rahmen |
|
||||
| `--accent` | `#f0a81a` | `#c2410c` | **Primärakzent**: Buttons, aktive Nav, Icons, Headline-Akzent |
|
||||
| `--accent-2` | `#f7dc11` | `#b45309` | Sekundär-Akzent für Verläufe (Gelb-Glow) |
|
||||
| `--accent-ink` | `#1c1100` | `#ffffff` | Textfarbe **auf** Akzentflächen |
|
||||
| `--secondary` | `#2d9cce` | `#1f86b8` | **Flux-Blau**: Links + „richtig" |
|
||||
| `--secondary-2` | `#5cc4e8` | `#2d9cce` | helleres Blau (Hover/Variante) |
|
||||
| `--secondary` | `#2d9cce` | `#146b96` | **Flux-Blau**: Links + „richtig" |
|
||||
| `--secondary-2` | `#5cc4e8` | `#0f5a80` | helleres Blau (Hover/Variante) |
|
||||
| `--text` | `#ece3d2` | `#33271c` | Fließtext |
|
||||
| `--muted` | `#8c8170` | `#8a7d6c` | gedämpfter Text, Labels, Captions |
|
||||
| `--muted` | `#8c8170` | `#6b5f4c` | gedämpfter Text, Labels, Captions |
|
||||
| `--heading` | `#ffffff` | `#241a12` | Überschriften |
|
||||
| `--success` | `#2d9cce` | `#1f86b8` | **„richtig"** (= Flux-Blau, bewusst kein Grün) |
|
||||
| `--success` | `#2d9cce` | `#146b96` | **„richtig"** (= Flux-Blau, bewusst kein Grün) |
|
||||
| `--danger` | `#ff4d6d` | `#be123c` | **„falsch"** / Fehler / Löschen (kühles Crimson) |
|
||||
| `--warn` | `#edcb4c` | `#8a6a06` | **„eingeschränkt"** / Warnstatus (Light-Wert WCAG-AA) |
|
||||
| `--glow-s` | `rgba(240,168,26,.55)` | `rgba(214,81,22,.40)` | **starker** Glow (Text-/Drop-Shadow auf Akzent) |
|
||||
| `--glow-w` | `rgba(240,168,26,.16)` | `rgba(214,81,22,.12)` | **weicher** Ambient-Glow |
|
||||
| `--g-soft` | `0 0 18px var(--glow-w)` | `0 6px 18px var(--glow-w)` | Standard-Panel-Schatten |
|
||||
@@ -94,6 +95,10 @@ Muster (siehe z. B. `syntaxsaga.css` / `techtacular.css`):
|
||||
- **Panel-Schatten:** `box-shadow: var(--g-soft)`.
|
||||
- **Radien (Konvention):** Badges/Buttons ~`7–9px`, Inputs/Icon-Buttons `10px`, Nav `11px`,
|
||||
Panels `13–14px`, Standalone-Karten `16–18px`, Pills/Bars `99px`.
|
||||
- **Header-Icon-Buttons** (`.mf-iconbtn`, `.mf-theme`, `.mf-a11y-trigger`, `.ft-trigger`):
|
||||
einheitlich **`42×42px`**, Radius `10px`, **Outline-Stil** (Surface-Fill, 1px Border,
|
||||
Akzent nur über `color`/`border-color`), Glyphe **`20×20px`**. Der `.mf-theme`-Toggle behält
|
||||
Akzentfarbe + Glow als Erkennungsmerkmal.
|
||||
- **Spielkarten** (`.mf-card`) haben eine **abgeschrägte Ecke** via `clip-path` (`--n:18px`) statt
|
||||
rundem Radius – ein bewusstes „HUD"-Detail.
|
||||
|
||||
@@ -157,16 +162,18 @@ Feedback-Konvention (Blau/Rot) einhalten.
|
||||
## Do / Don't
|
||||
|
||||
**Do**
|
||||
- Farben/Schatten/Glow ausschließlich über Tokens.
|
||||
- Farben, Schatten und Effekte ausschließlich über Tokens. Für Effekte die
|
||||
Primitives `--g-text`, `--g-icon`, `--g-strong`, `--g-amb`, `--g-card`,
|
||||
`--g-card-hover`, `--g-overlay` und `--g-focus` verwenden; keine direkten
|
||||
Glow-Geometrien in Komponenten schreiben.
|
||||
- Auf Akzentflächen Text in `--accent-ink`.
|
||||
- Erfolg = Blau, Fehler = Rot (nie Grün/Orange für „richtig/falsch").
|
||||
- Neue Komponenten an den `.mf-*`-Bausteinen orientieren.
|
||||
|
||||
**Don't**
|
||||
- Keine hart kodierten Hex-/RGBA-Werte in Templates oder Spiele-CSS.
|
||||
- Nicht die **Legacy-Dateien** als Stil-Referenz nehmen: `achievements.css`, `progress.css`,
|
||||
`settings.css`, `soziales.css` (ungenutzt) sowie alte Schrift-Deklarationen
|
||||
(`'Segoe UI'`/`'Poppins'`) entsprechen **nicht** dem aktuellen System. Maßgeblich ist `theme.css`.
|
||||
- Keine direkten `0 0 … var(--glow-*)`-, `text-shadow`- oder `drop-shadow`-Werte
|
||||
in Komponenten. Maßgeblich ist `theme.css`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -18,6 +18,21 @@ Basis-Pfad: `RES` = `src\main\resources`
|
||||
|
||||
---
|
||||
|
||||
## Umsetzungsstatus (Stand 2026-07-21, auf `main`)
|
||||
|
||||
- ✅ **AP 0** Quick Wins — vollständig (inkl. V21-Reseed-Migration für Alt-DBs)
|
||||
- ✅ **AP 1** Light Mode — vollständig, alle Kontraste WCAG-AA rechnerisch geprüft. ⚠️ **Nachtrag (2026-07-21):** Glow-Effekte wirken im Light Mode störend → Alternative nötig, s. AP 1e.
|
||||
- ✅ **AP 2** Header-Icons — vollständig
|
||||
- ✅ **AP 3** Barrierefreiheits-Widget — vollständig
|
||||
- ✅ **AP 4** Spielanleitungen & Hints — vollständig (AP 4c TechTacular-Joker war bereits erledigt)
|
||||
- ✅ **AP 5** AP2-Ausbau — vollständig (10 Themen × 20 = 200 Fragen)
|
||||
- ✅ **AP 6** DSGVO — vollständig (Footer auf 35 Seiten, Datenschutz/Impressum, Export, V22)
|
||||
- 🟡 **AP 7** Best Practices — **teilweise**: Security-Header/CSP, AvatarService-Extraktion und Bean-Validation-Start erledigt. **Offen:** Validierung auf alle Request-DTOs + NeuralNet-`Map`→DTO, Spring-Boot-Patch-Bump, Cookie-basiertes CSRF, Web-Layer-Test für `/profile/export`.
|
||||
|
||||
Automatisiert verifiziert: `mvn test` 54/54 grün, Build grün, Umlaut-QA sauber, alle 200 AP2-Fragen strukturell validiert. **Noch nicht durchgeführt (braucht laufende App + DB + Browser):** visuelle Light-Mode-Prüfung aller Spiele hell/dunkel, A11y-Widget-Handtest, Guide-Auto-Open, Export-Download- und DSGVO-Netzwerk-Check; die 8 Vollbild-Spielseiten haben nun ebenfalls einen Footer (ggf. schlanke CSS-Anpassung). Vor Launch offen: echte Impressumsdaten, Support-/Feedback-Zieladresse.
|
||||
|
||||
---
|
||||
|
||||
## AP 0 — Quick Wins (zuerst, alles klein)
|
||||
|
||||
1. **Chart.js lokal vendoren**: nach `RES\static\js\vendor\chart.umd.min.js` laden, `RES\templates\progress.html:9` auf lokalen Pfad umstellen. Schließt das einzige DSGVO-Datenleck.
|
||||
@@ -65,6 +80,10 @@ Vorgehen je Spiel: alle Roh-Hexwerte in scoped Custom Properties überführen (`
|
||||
|
||||
**Check:** jeder Spiel-Header, dessen Spiel jetzt theme-fähig ist, braucht den `.mf-theme`-Toggle (theme.js verdrahtet ihn automatisch); mindestens serviceclash.html fehlt er.
|
||||
|
||||
### 1e. Nachtrag: Glow im Light Mode überarbeiten (offen, 2026-07-21)
|
||||
**Befund:** Die aus dem Dark-Mode-Neon-Look übernommenen Glow-Effekte wirken im Light Mode störend (auf fast-weißem Grund lesen sie sich als Schmutz-/Haze-Rand statt als Akzent). Betrifft die durchgereichten Glow-Tokens der Kartenakzente (`--glow-s: rgba(<hex>,.22)`, s. 1b), die spielspezifischen `*-glow`-rgba (s. 1a) und den Glow am `.mf-theme`-Toggle (s. AP 2, `theme.css:120–121`).
|
||||
**Wir brauchen eine Alternative** für den hellen Modus — noch zu entscheiden, z. B. Glow-Alpha im Light-Block deutlich reduzieren/auf 0 setzen, oder den Neon-Glow durch ein dezentes Mittel ersetzen (weicher Schatten / abgesetzter Border / leichter Farbverlauf), das die Erkennungsfunktion behält, ohne den hellen Grund zu trüben. Dark Mode bleibt unverändert.
|
||||
|
||||
---
|
||||
|
||||
## AP 2 — Header-Icons vereinheitlichen
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
# Mindflux — Refine 2: Light-Mode-Konzept, Scroll-Verhalten, Restpunkte
|
||||
|
||||
## Kontext
|
||||
|
||||
Folgeplan zu `mindfluxrefine.md`. Die Code-Verifikation (2026-07-21) bestätigt: **alle „vollständig"-Claims aus Refine 1 halten der Prüfung stand**; offen sind AP 1e (Glow im Light Mode), der AP-7-Rest sowie einige Zusatzfunde (s. AP R5). Dieser Plan liefert das, was Refine 1 offen ließ: ein **echtes Light-Mode-Konzept**, das alle Probleme an der Wurzel behebt (statt Glow-Alphas weiter zu dimmen), plus das neue Thema **Scroll-Verhalten** (Ergebnisanzeigen u. ä. müssen oben starten, nicht unten).
|
||||
|
||||
**Audit-Kurzbefunde (2026-07-21, drei unabhängige Durchgänge):**
|
||||
|
||||
- **Light-Mode-Strukturfehler:** Der Light-Block in `theme.css` tauscht nur *Farbwerte/Alphas* der Glow-Tokens. Die *Geometrie* der Effekte (`box-shadow:0 0 20px …`, `text-shadow:0 0 12px …`, `drop-shadow(0 0 8px …)`) steht hart in ~70 Komponentenregeln quer durch `theme.css`, alle Spiele-CSS und Template-`<style>`-Blöcke. Ein Halo mit reduziertem Alpha bleibt ein Halo — auf Fast-Weiß liest er sich als Schmutz/Haze. Einzige strukturell adaptierte Stelle heute: `--g-soft` (`0 0 18px` dark → `0 6px 18px` light, `theme.css:31/53`). Genau **eine** Regel neutralisiert einen Glow explizit im Light Mode (`auth.css:127–128`).
|
||||
- **5 im Light Mode kaputte Stellen:** Login-Overlay weiß-auf-weiß (auth.css:123–127 × platform-neural.css:94), SyntaxSaga-Bühne bleibt schwarz (`syntaxsaga.css:250–251`), CodeEscape-Eingaben dunkel-auf-dunkel (`codeescape.css` `.esc-input` `#080604`), NetWorker-Guide-Panel dunkelnavy mit Light-`--muted` (`networker-guide.css`), Farbenblind-Palette × Light = Hellgelb/Hellblau auf Weiß (`accessibility.css:15–23`, `!important` schlägt die Light-Kartenakzente).
|
||||
- **Theme-Bootstrap-Lücken:** `color-scheme` fehlt komplett (native Scrollbars/Form-Controls folgen dem UA-Default), kein `theme-color`-Meta, kein `prefers-color-scheme`-Fallback (Erstbesucher mit OS-Light starten dunkel), alle Templates hardcoden `data-theme="dark"`.
|
||||
- **Scroll:** Im gesamten Projekt existiert **kein einziger** `scrollTo`/`scrollIntoView`/`scrollRestoration`-Aufruf (außer Chat-Autoscroll `neuralnet.js:84`, der korrekt ist). Die vier SPA-Spiele (BugHunter, CodeEscape, NetWorker, ServiceClash) ersetzen Views per `innerHTML` ohne Reset; der FinalProtocol-Quiz kollabiert am Ende einer langen Lernseite (Browser clampt ans neue Seitenende); SyntaxSaga/DevLab fokussieren Inputs ohne `preventScroll`.
|
||||
- **CSS-Leichen:** `achievements.css`, `progress.css`, `settings.css`, `soziales.css` werden nirgends mehr referenziert (repo-weiter Grep leer); `style.css` wird nur noch von `finalflux.html` geladen, ~90 % davon ist toter Dark-Code.
|
||||
|
||||
Basis-Pfad: `RES` = `src\main\resources`
|
||||
|
||||
---
|
||||
|
||||
## Das Konzept: „Licht statt Leuchten"
|
||||
|
||||
**Prinzip:** Dark Mode und Light Mode sind Geschwister mit derselben Farbidentität, aber unterschiedlicher *Physik*:
|
||||
|
||||
| | Dark Mode | Light Mode |
|
||||
|---|---|---|
|
||||
| Metapher | Neon in der Nacht — Elemente **emittieren** Licht | Druck/Material am Tag — Elemente **werfen Schatten** |
|
||||
| Betonung | farbiger Glow (Halo) | **Elevation** (weicher, warm getönter neutraler Schatten) |
|
||||
| Identität/Wiedererkennung | Glow-Farbe | **Akzentfarbe direkt am Element** (Border, Text, Icon) — ist durch `--border-accent`, Icon-Farben und Kartenakzente bereits überall vorhanden |
|
||||
| Text-Halos | ja (`text-shadow`) | **nie** (liest sich als Unschärfe) |
|
||||
| Fokus-Ringe | farbig, weich | farbig, **crisp** (Ring statt Blur — funktional, bleibt farbig) |
|
||||
| Scrims/Overlays | dunkel | dunkel (funktional), aber leicht entschärft |
|
||||
|
||||
**Mechanik:** Effekte werden zu **Primitives** — komplette `box-shadow`/`filter`/`text-shadow`-*Werte* als Tokens, nicht nur Farben. Komponenten schreiben nie mehr `0 0 20px var(--glow-s)`, sondern `var(--g-strong)`. Der Light-Block tauscht dann die *Kategorie* des Effekts (Glow → Elevation), nicht nur die Farbe. Dark bleibt visuell gleich (Blur-Radien werden auf die Primitive normalisiert; ±4 px sind nicht unterscheidbar). `--glow-s`/`--glow-w` bleiben als Farb-Tokens bestehen — die Spiele-CSS remappen sie bereits per `color-mix` auf ihre Akzente, und die Primitives referenzieren sie per `var()`, sodass Spiel-Glows im Dark Mode automatisch die Spielfarbe behalten.
|
||||
|
||||
---
|
||||
|
||||
## AP R0 — Aufräumen (erledigt 2026-07-22)
|
||||
|
||||
1. **Erledigt:** Verwaiste CSS gelöscht: `achievements.css`, `progress.css`, `settings.css`, `soziales.css`. Die Referenzprüfung ergab 0 Treffer; STYLEGUIDE bereinigt.
|
||||
2. **Erledigt:** `style.css` auf eine vierzeilige Basis für die 40 statischen
|
||||
FinalProtocol-Lernseiten reduziert; FinalProtocol-Hub lädt sie nicht mehr.
|
||||
3. **Erledigt:** Nicht eingebundenes `finalflux-check.js` entfernt. `fluxtimer.css` bleibt separat offen, da die Datei in diesem Paket nicht geändert wurde.
|
||||
|
||||
---
|
||||
|
||||
## AP R1 — Effekt-Primitives (Kern des Light-Konzepts)
|
||||
|
||||
### R1a. Tokens in `RES\static\css\theme.css` — erledigt 2026-07-22
|
||||
|
||||
In den Dark-Block (Z. 11–32) und Light-Block (Z. 33–54) ergänzen. Schattenfarbe Light ist **warmes Braun** `rgb(67,48,26)` (zur Creme-Palette passend, kein totes Grau/Schwarz):
|
||||
|
||||
```css
|
||||
:root, [data-theme="dark"] {
|
||||
/* … bestehende Tokens … */
|
||||
color-scheme:dark;
|
||||
--g-text:0 0 12px var(--glow-s); /* Text-Halo: Logo, Headline-Akzent, *-brand b */
|
||||
--g-icon:drop-shadow(0 0 8px var(--glow-s)); /* leuchtende Glyphen/Embleme */
|
||||
--g-strong:0 0 20px var(--glow-s); /* starke Betonung: aktive Nav/Tabs, Primär-Btn */
|
||||
--g-amb:0 0 14px var(--glow-w); /* dezenter Schein: Icon-Hover, Toggle, Burger */
|
||||
--g-card:drop-shadow(0 0 7px var(--glow-w)); /* Spielkarten-Grundzustand */
|
||||
--g-card-hover:drop-shadow(0 0 16px var(--glow-s));
|
||||
--g-overlay:0 24px 60px rgba(0,0,0,.5); /* schwebende Panels: Guide, Drawer, A11y */
|
||||
--g-focus:0 0 0 3px var(--glow-w); /* Fokus-Ring (bleibt in beiden Themes farbig) */
|
||||
--scroll-thumb:var(--line);
|
||||
}
|
||||
[data-theme="light"] {
|
||||
/* … bestehende Tokens … */
|
||||
color-scheme:light;
|
||||
--bg-glow: radial-gradient(900px 420px at 60% -10%, rgba(214,81,22,.05), transparent 60%); /* statt .10 — Haze halbieren */
|
||||
--g-text:none;
|
||||
--g-icon:none;
|
||||
--g-strong:0 2px 10px rgba(67,48,26,.18), 0 1px 3px rgba(67,48,26,.10);
|
||||
--g-amb:0 1px 5px rgba(67,48,26,.10);
|
||||
--g-card:drop-shadow(0 2px 5px rgba(67,48,26,.10));
|
||||
--g-card-hover:drop-shadow(0 8px 18px rgba(67,48,26,.16));
|
||||
--g-overlay:0 18px 44px rgba(67,48,26,.20);
|
||||
--g-focus:0 0 0 3px color-mix(in srgb, var(--accent) 28%, transparent);
|
||||
--scroll-thumb:#d9cbb2; /* --line #f1e8d8 ist auf #fbf6ec unsichtbar */
|
||||
}
|
||||
```
|
||||
|
||||
Scrollbar-Regel `theme.css:64` auf `background:var(--scroll-thumb)` umstellen.
|
||||
|
||||
### R1b. Mechanische Migration in `theme.css`
|
||||
|
||||
| Zeile | Selektor | Alt | Neu |
|
||||
|---|---|---|---|
|
||||
| 79 | `.mf-logo .mark` | `drop-shadow(0 0 8px var(--glow-s))` | `filter:var(--g-icon)` |
|
||||
| 80 | `.mf-logo .txt b` | `text-shadow:0 0 12px var(--glow-s)` | `text-shadow:var(--g-text)` |
|
||||
| 87 | `.mf-nav a.active` | `0 0 20px var(--glow-s)` | `box-shadow:var(--g-strong)` |
|
||||
| 94 | `.mf-avatar` | `0 0 14px var(--glow-s)` | `box-shadow:var(--g-strong)` (gleiche Farbstärke-Familie wie heute) |
|
||||
| 102/104 | `.mf-streak .fire` / `.mf-brain` | drop-shadows | `filter:var(--g-icon)` |
|
||||
| 109 | `.mf-hello h1 .nm` | `text-shadow:0 0 16px var(--glow-s)` | `text-shadow:var(--g-text)` |
|
||||
| 113/120/202 | iconbtn-Hover / `.mf-theme` / `.mf-burger` | `0 0 14px var(--glow-w)` | `box-shadow:var(--g-amb)` |
|
||||
| 128/130 | `.mf-card` / `:hover` | drop-shadow 7/16px | `filter:var(--g-card)` / `var(--g-card-hover)` |
|
||||
| 131 | `.mf-card .ico` | drop-shadow | `filter:var(--g-icon)` |
|
||||
| 136 | `.mf-card:hover .mf-play` | `0 0 16px var(--glow-s)` | `box-shadow:var(--g-strong)` |
|
||||
| 146 | `.mf-panel.accent` | `0 0 20px var(--glow-w)` | `box-shadow:var(--g-amb)` |
|
||||
| 151 | `.mf-bar > i` | `0 0 10px var(--glow-s)` | eigenes Primitive `--g-bar` (dark `0 0 10px var(--glow-s)` / light `none`) — der Balken trägt seine Farbe selbst, im Light braucht er keinen Hof |
|
||||
| 154 | `.mf-btn` | `0 0 18px var(--glow-s)` | `box-shadow:var(--g-strong)` |
|
||||
| 168 | Input-Fokus | `0 0 0 3px var(--glow-w)` | `box-shadow:var(--g-focus)` |
|
||||
| 182 | `.mf-cardbox` | `0 0 30px var(--glow-w)` | Hero-Aura: Zweizeiler — Basis `box-shadow:var(--g-strong)`, plus `[data-theme="dark"] .mf-cardbox{box-shadow:0 0 30px var(--glow-w)}` |
|
||||
| 223 | Mobile `.mf-sidebar` | `0 18px 50px rgba(0,0,0,.55)` | `box-shadow:var(--g-overlay)` |
|
||||
| 238 | `.mf-tab.active` | `0 0 18px var(--glow-s)` | `box-shadow:var(--g-strong)` |
|
||||
|
||||
### R1c. `platform-neural.css`
|
||||
|
||||
Hat als einzige Datei schon gute Struktur-Overrides (Z. 91 ersetzt Schwarz- durch Grünschatten — Vorbild). Nachziehen: Z. 87 Logo-Drop-Shadow im Light auf `filter:none`; Z. 90 äußeren `0 0 13px`-Glow der aktiven Nav im Light streichen (Inset-Ring reicht). Die `--glow-s`-Kartenwerte (Z. 41–49/99–107) bleiben — sie speisen nach R1b nur noch Dark-Effekte.
|
||||
|
||||
### R1d. Spiele-CSS + Guide/Widget-CSS migrieren
|
||||
|
||||
Gleiches Muster in allen Dateien; die Spiel-`--glow-s`-Remaps (z. B. `mindmatch.css:364`) bleiben unverändert und speisen die Primitives automatisch mit der Spielfarbe. Betroffene Stellen (Audit-Inventar):
|
||||
|
||||
- **Text-Halos** → `text-shadow:var(--g-text)`: `syntaxsaga.css:40`, `mindmatch.css:53`, `techtacular.css:48`, `devlab.css` (`.dl-tube span`), `devlabgame.css` (Header-Titel), `codeescape.css` (`.esc-brand b`, `.esc-result .score`), `bughunter.css` (`.bh-brand b`, Stamp), `networker.css` (`.nw-brand b`, `.nw-final-score`), `serviceclash.css` (`.sc-brand b`, `.sc-score`), `auth.css:74` (`.auth-head b`), `style.css:89` (falls nach R0 noch vorhanden), Template-`<style>`: `progress.html:14` (Prozentzahl).
|
||||
- **Box-Glows** → `var(--g-strong)`/`var(--g-amb)` nach Gewicht: `syntaxsaga.css:37/70/100/125/136/200/229/233`, `mindmatch.css:40/106/226/237/280/287/299/324`, `techtacular.css:39/75/174/207/299`, `devlab.css:102/158/173`, `devlabgame.css:10/12/23/49/61/70/78/106/108`, `codeescape.css` (Buttons/Dialog/Hero — Hero-`0 0 50px` als Dark-only-Zweizeiler wie `.mf-cardbox`), `codeescape-guide.css` (`.esc-how .active`, `.esc-door.ready`, `.esc-input.bad`), `bughunter.css`, `networker.css`, `serviceclash.css` (`.sc-meter i`), `fluxtimer.css:6/7`, `accessibility.css:29`, `accessibility-placement.css:2/16`, `auth.css:14/34/88/98` (`.neon-btn` inkl.: den `::before`-Conic-Blur-Rotor Z. 95/99 im Light per `display:none`).
|
||||
- **Fokus-Ringe** → `var(--g-focus)`: `syntaxsaga.css:160`, `devlabgame.css:35/39`, CodeEscape-Inputs.
|
||||
- **Schwarz-Schatten** → `var(--g-overlay)` bzw. warm getönt: `game-guide.css:72`, `accessibility.css:30`, `accessibility-placement.css:17`, `fluxtimer.css:8` (+ Z. 14 `inset`-Schwarz auf hellem Display im Light entfernen), `serviceclash.css` (`.sc-card`), `bughunter.css` (Drawer, dort auch die im Light unsichtbaren `rgba(255,255,255,.09–.16)`-Inset-Highlights auf Light-Varianten umstellen).
|
||||
- **Scrims** (funktional, bleiben dunkel, aber vereinheitlichen): `mindmatch.css:309`, `techtacular.css:192`, `syntaxsaga.css:216`, `devlabgame.css:104` — im Light auf das Muster `game-guide.css:138` (`rgba(15,23,42,.5)`) wechseln, damit sich nicht zusätzlich der warme `--bg-glow`-Radial über den Scrim legt (`var(--bg-glow)` aus den Scrim-Deklarationen entfernen).
|
||||
- **Hint-Glow** `game-guide.css:155/160–161` (`#ffd54a`): Light-Override `[data-theme="light"] .mm-card.hinted { box-shadow:0 0 0 2px #8a6a06, 0 4px 12px rgba(67,48,26,.18); filter:none; }` (Warn-Ton aus dem Light-Block).
|
||||
|
||||
### R1e. Chart/Dashboard-`<style>`-Blöcke
|
||||
|
||||
`dashboard.html:14/16/18/20/29/31/43/46/171`, `achievements.html:12–16`, `admin/dashboard.html:17–19`, `impressum.html:12`, `profile.html:11`: alle `text-shadow`/`drop-shadow`/`0 0 Npx`-Vorkommen auf die Primitives umstellen (Suchmuster: `grep -n "glow\|text-shadow\|drop-shadow" RES\templates -r`).
|
||||
|
||||
---
|
||||
|
||||
## AP R2 — Kaputte Light-Stellen (kritische Punkte erledigt 2026-07-22)
|
||||
|
||||
### R2a. Login/Registrierung: Overlay weiß-auf-weiß (kritisch)
|
||||
|
||||
Ursache: `auth.css:123–127` setzt für Light `#fff`-Text/weiße Ghost-Border (Annahme: kräftiges Orange-Panel), aber auf der Plattform übersteuert `platform-neural.css:94` den Overlay-Grund mit Fast-Weiß. Fix in `platform-neural.css` (Light-Sektion, höhere Spezifität durch `.sn-platform` gewinnt gegen auth.css):
|
||||
|
||||
```css
|
||||
html[data-theme="light"].sn-platform body .overlay-panel h3 { color:#10291f; text-shadow:none; }
|
||||
html[data-theme="light"].sn-platform body .overlay-panel p { color:#3c554a; }
|
||||
html[data-theme="light"].sn-platform body .ghost-btn { color:#167a52; border-color:rgba(22,122,82,.55); }
|
||||
html[data-theme="light"].sn-platform body .ghost-btn:hover { background:rgba(22,122,82,.10); }
|
||||
```
|
||||
|
||||
### R2b. SyntaxSaga: Bühne bleibt schwarz (kritisch)
|
||||
|
||||
`syntaxsaga.css:250–251` hardcodet `#07100b` + grüne Scanlines. Light-Block ergänzen (Palette: helles Bühnen-Grün zur Light-`--ss-accent` `#277d49`):
|
||||
|
||||
```css
|
||||
[data-theme="light"] body.ss-body {
|
||||
background:radial-gradient(1100px 500px at 50% -10%, rgba(39,125,73,.08), transparent 60%), #eef6f0;
|
||||
}
|
||||
[data-theme="light"] body.ss-body::before {
|
||||
background:repeating-linear-gradient(0deg, rgba(16,64,40,.05) 0 1px, transparent 1px 4px);
|
||||
}
|
||||
```
|
||||
|
||||
### R2c. CodeEscape: Terminal-Eingaben & Dark-Inks (kritisch)
|
||||
|
||||
- `.esc-input, .esc-select` (`#080604`): Light-Override `[data-theme="light"] .esc-input, [data-theme="light"] .esc-select { background:#fffaf0; color:var(--text); border-color:var(--border); }` — Dark behält den Terminal-Look.
|
||||
- Hardcodierte Dark-Inks auf Akzentflächen durch das Token ersetzen (funktioniert in beiden Themes): `.esc-btn:hover`/`.primary` `#160d00` → `var(--accent-ink)`; `codeescape-guide.css` `.esc-fragment small` `#160d00` → `var(--accent-ink)`; `.esc-how .done b` `#06120a` → `var(--accent-ink)`.
|
||||
- Tote Scanlines `rgba(255,255,255,.018)` (`codeescape.css`, `theme.css:138`): im Light-Override auf `rgba(67,48,26,.04)` drehen oder streichen.
|
||||
|
||||
### R2d. NetWorker-Guide: dunkles Panel (kritisch)
|
||||
|
||||
`networker-guide.css` (`.nw-guide` `color-mix(#0b2540 74%, var(--surface))`): Light-Block ergänzen:
|
||||
|
||||
```css
|
||||
[data-theme="light"] .nw-guide { background:var(--surface); border-color:#d3e2f0; box-shadow:var(--g-soft); }
|
||||
```
|
||||
|
||||
(Textfarben laufen dort über `var(--muted)`/`var(--text)` und stimmen dann automatisch.)
|
||||
|
||||
### R2e. Farbenblind-Palette × Light (kritisch)
|
||||
|
||||
`accessibility.css:15–23` erzwingt helle Okabe-Ito-Akzente per `!important` — auf weißen Karten < 1.5:1. Neuen Satz `html[data-theme="light"][data-mf-color="safe"]` mit **abgedunkelten** Okabe-Ito-Entsprechungen ergänzen (gleiche Zuordnung Spiel→Farbfamilie wie im Dark-Satz, Werte rechnerisch ≥ 4.5:1 auf Weiß, beim Umsetzen mit Kontrast-Tool gegenprüfen):
|
||||
|
||||
| Okabe-Ito (dark) | Light-safe |
|
||||
|---|---|
|
||||
| `#e69f00` Orange | `#8a5f00` |
|
||||
| `#56b4e9` Himmelblau | `#19607f` |
|
||||
| `#009e73` Grün | `#007054` |
|
||||
| `#f0e442` Gelb | `#7a6a00` |
|
||||
| `#0072b2` Blau | `#0072b2` (bleibt) |
|
||||
| `#d55e00` Vermillion | `#a94500` |
|
||||
| `#cc79a7` Purpur | `#9c4f79` |
|
||||
|
||||
Die zugehörigen Glow-Alphas entfallen im Light automatisch über die Primitives (R1).
|
||||
|
||||
### R2f. Kleinere Light-Fixes
|
||||
|
||||
1. **Cancel-Hover** `mindmatch.css:128–129`, `techtacular.css:94` (`#fff` auf Blassrosa): Light-Override `[data-theme="light"] .mm-cancel:hover, …` → `background:var(--danger); color:#fff;` (Light-Danger `#be123c`, 7:1). Dark bleibt unverändert.
|
||||
2. **A11y-Widget-Inks:** `accessibility-placement.css:7–15` hardcodet dunkle `--mf-a11y-ink`-Werte — im Light sind die Akzente selbst dunkel. Eine Regel ergänzt: `html[data-theme="light"] body { --mf-a11y-ink:#fff; }` (gewinnt per Spezifität gegen die Skin-Zuweisungen). Panel-Kopf optional angleichen: `[data-theme="light"] .mf-a11y-head { background:var(--surface-2); color:var(--heading); }`.
|
||||
3. **finalflux-Altlast:** `finalflux.css:345` (`#0b0d1b`-Hardcode) funktioniert nur über die fragile `background:transparent`-Kaskade aus `platform-neural.css:61`. Hardcode durch `var(--bg)`-basierte Deklaration ersetzen, damit die Seite auch ohne `sn-platform` beide Themes überlebt.
|
||||
|
||||
**Erledigt:** Login-Overlay, SyntaxSaga-Bühne, CodeEscape-Eingaben und Akzent-Inks,
|
||||
NetWorker-Guide, Farbenblind-Palette für Light Mode, A11y-Widget-Inks sowie der
|
||||
FinalProtocol-Hintergrund. Die noch offenen kleineren Cancel-Hover- und Detaileffekte
|
||||
werden zusammen mit der vollständigen CSS-Migration aus R1 abgeschlossen.
|
||||
|
||||
---
|
||||
|
||||
## AP R3 — Theme-Bootstrap (erledigt 2026-07-22)
|
||||
|
||||
In `RES\static\js\theme.js`:
|
||||
|
||||
1. **Erledigt:** `prefers-color-scheme` wird verwendet, wenn noch keine lokale Auswahl gespeichert ist.
|
||||
2. **Erledigt:** `theme-color` wird beim initialen Anwenden und Umschalten auf `#0a0806` bzw. `#fbf6ec` aktualisiert.
|
||||
3. **Erledigt:** `color-scheme` wird im Dark-/Light-Tokenblock gesetzt.
|
||||
4. **Teilweise erledigt:** geänderte FinalProtocol-Assets tragen neue Versionen; die übrigen Template-Referenzen werden bei ihrer jeweiligen Migration angehoben.
|
||||
|
||||
---
|
||||
|
||||
## AP R4 — Scroll: Ergebnisanzeigen & Ansichten starten oben (weitgehend erledigt 2026-07-22)
|
||||
|
||||
Es gibt projektweit keinen Scroll-Reset. Drei Schichten (zentraler Helper + gezielte Aufrufe), Chat-Autoscroll `neuralnet.js:84` bleibt unangetastet:
|
||||
|
||||
### R4a. Helper in `theme.js` (läuft auf jeder Seite)
|
||||
|
||||
```js
|
||||
window.MindfluxUX = {
|
||||
toTop: function () { window.scrollTo(0, 0); },
|
||||
focusQuiet: function (el) { if (el) el.focus({ preventScroll: true }); }
|
||||
};
|
||||
```
|
||||
|
||||
**Umsetzungshinweis:** Der zentrale Helper ist vorhanden. Die vier SPA-Spiele setzen
|
||||
`history.scrollRestoration` nur auf Spielrouten auf `manual`; ein zentraler,
|
||||
ereignisbasierter Übergangs-Reset deckt Ansichtswechsel ab und lässt Antwort- bzw.
|
||||
Zeilenfeedback unverändert. FinalProtocol, SyntaxSaga, DevLab, MindMatch und
|
||||
TechTacular haben die zusätzlich beschriebenen gezielten Resets/Fokusoptionen.
|
||||
**Offen für die visuelle E2E-Prüfung:** die asynchron gerenderten Ergebniswechsel von
|
||||
CodeEscape, NetWorker und BugHunter müssen im Browser gegen die Scroll-Checkliste
|
||||
validiert und bei Bedarf direkt in den jeweiligen Render-Funktionen präzisiert werden.
|
||||
|
||||
### R4b. SPA-Spiele — Reset bei jedem Ansichtswechsel
|
||||
|
||||
Muster pro Spiel: Mini-Wrapper `function setView(html){ view.innerHTML = html; window.scrollTo(0,0); }`, alle Render-Funktionen darüber. Zusätzlich **nur in diesen vier Skripten** beim Init: `if ('scrollRestoration' in history) history.scrollRestoration = 'manual'; window.scrollTo(0,0);` — verhindert, dass der Browser nach Reload/Back die alte, untere Position auf den neu aufgebauten Katalog anwendet. (Nicht global in `theme.js`: auf Lernseiten/Dashboard ist die Back-Position erwünscht.)
|
||||
|
||||
| Spiel | Funktionen |
|
||||
|---|---|
|
||||
| BugHunter (`bughunter.js`) | `renderBriefing` :29, `renderHunt` :36 (nur bei Fallwechsel, nicht bei Zeilen-Re-Render), `showDebrief` :82, `renderResult` :88 |
|
||||
| CodeEscape (`codeescape.js`) | `renderLobby` :6, `renderRoom` :8, `renderResult` :17 |
|
||||
| NetWorker (`networker.js`) | `renderCatalog` :3, `renderIntro` :5, `renderLevel` :6, `finish` :11 |
|
||||
| ServiceClash (`serviceclash.js`) | `renderCatalog` :2, `renderRun` :4 (**nur bei Ticket-/Indexwechsel**, nicht beim Feedback-Re-Render), `finish` :9 |
|
||||
|
||||
### R4c. FinalProtocol-Quiz (`finalflux-test.js`)
|
||||
|
||||
- `finishIfDone()` Z. 101–104: nach `testResult.hidden = false` → `elements.testResult.scrollIntoView({ block: 'start' });` (Auswertung beginnt oben im Blick, statt dass der Viewport ans geclampte Seitenende fällt).
|
||||
- `restartTest()` Z. 151–158: nach dem Wiedereinblenden → `elements.testRoot.scrollIntoView({ block: 'start' });`
|
||||
|
||||
### R4d. Fokus ohne Scroll-Sprung
|
||||
|
||||
- `syntaxsaga.js`: `renderMission()` — `window.scrollTo(0,0)` vor Z. 209, dann Z. 210 `first.focus({preventScroll:true})`. (`nextBtn.focus()` Z. 297 bleibt — Nutzeraktion am selben Ort.)
|
||||
- `devlabgame.js`: `renderTask()` Z. 243 — `window.scrollTo(0,0)` am Ende; Z. 83 und Z. 233 `focus({preventScroll:true})`.
|
||||
- `codeescape.js:8/15` bleiben (Fokus am Türterminal ist dort korrekt).
|
||||
|
||||
### R4e. Niedrige Prio (nur Mobile relevant)
|
||||
|
||||
`mindmatch.js` `newGame()` :332 und `techtacular.js` `newGame()` :265: `window.scrollTo(0,0)` — Desktop-Bodys sind `overflow:hidden`, mobil scrollt die Seite.
|
||||
|
||||
---
|
||||
|
||||
## AP R5 — Carry-over: Rest aus Refine 1 + Zusatzfunde der Verifikation
|
||||
|
||||
Aus Refine 1 (dort AP 7 / offen):
|
||||
1. **Bean Validation:** `@Valid` + Constraints auf alle Request-DTOs — `BugHunterApiController:17–18`, `DevLabApiController:37/43`, `SyntaxSagaApiController:32/38`, `TechtacularApiController:50/75`, `CodeEscapeApiController` (4 DTOs), `NetworkerApiController` (2), `ServiceClashApiController` (2); `NeuralNetApiController:14/19` `Map<String,String>` → typisierte DTOs.
|
||||
2. **Spring Boot** 3.4.4 → aktuelles 3.4.x-Patch (`pom.xml:11`), Testlauf.
|
||||
3. **CSRF:** Blanket-Exemption `/api/**` (`SecurityConfig:46`) durch Cookie-basierte Tokens ersetzen.
|
||||
4. **Web-Layer-Test** für `GET /profile/export`.
|
||||
|
||||
Zusatzfunde (neu):
|
||||
5. **`UserController:25/45`** bindet die **Entity `User`** direkt als `@RequestBody` ohne Validierung → auf DTO + `@Valid` umstellen (gehört in die Runde von Punkt 1).
|
||||
6. **5 Admin-Templates ohne Footer:** `admin\bughunter.html`, `admin\bughunter-hunt.html`, `admin\codeescape.html`, `admin\codeescape-room.html`, `admin\codeescape-scenario.html` → `th:replace="~{fragments/layout :: footer}"` ergänzen.
|
||||
7. **HSTS** explizit für Prod konfigurieren statt Spring-Default (`SecurityConfig:56–57`).
|
||||
8. Optional (unverändert Phase 2): BugHunter progressive Hinweise (Refine 1, AP 4d).
|
||||
|
||||
---
|
||||
|
||||
## Reihenfolge
|
||||
|
||||
1. **AP R0** Aufräumen → 2. **AP R1** Primitives + Migration (theme.css zuerst, dann platform-neural, dann Spiele-CSS, dann Templates) → 3. **AP R2** kaputte Light-Stellen → 4. **AP R3** Bootstrap → 5. **AP R4** Scroll (unabhängig von 1–4, parallelisierbar) → 6. **AP R5** Carry-over → 7. Gesamtverifikation. STYLEGUIDE.md (Abschnitt „Glow, Schatten & Radien" → Primitives-Tabelle) und CHANGELOG nach R1–R3 nachziehen.
|
||||
|
||||
## Verifikation
|
||||
|
||||
- **Build:** `mvn test` bleibt grün (54/54); App-Boot gegen Wegwerf-Postgres wie gehabt.
|
||||
- **Grep-Gates nach R1:** `grep -rnE "0 0 [0-9]+px var\(--" RES\static\css` → Treffer nur noch in den Primitive-Definitionen von `theme.css`; `grep -rn "text-shadow:0 0" RES\static\css RES\templates` → nur `--g-text`-Definition; `grep -rn "rgba(0,0,0,.5" RES\static\css` → nur Scrims/`--g-overlay`-Definition.
|
||||
- **Visuell (hell UND dunkel):** Dashboard (Karten + Hover), Login/Registrierung (insb. Overlay-Panel!), Settings, Progress, Soziales, NeuralNet, alle 9 Spiele (insb. SyntaxSaga-Bühne, CodeEscape-Eingaben, NetWorker-Guide), FinalProtocol-Lernseite + Quiz, A11y-Panel. Dark Mode muss vor/nach R1 visuell deckungsgleich sein (Stichprobe per Screenshot-Vergleich).
|
||||
- **A11y-Kombinationen:** Light × Hochkontrast (bleibt bewusst dunkel), Light × Farbenblind (neue dunkle Okabe-Ito-Werte sichtbar, Kontrast-Tool ≥ 4.5:1), Light × Mono.
|
||||
- **Native Controls:** Light Mode → helle Scrollbars und helle `<select>`-Dropdowns (Chrome + Firefox); mobile Adressleiste folgt `theme-color`.
|
||||
- **Erstbesuch:** localStorage leeren, OS auf Light stellen → Seite startet hell; OS Dark → dunkel; Umschalter persistiert weiter.
|
||||
- **Scroll:** je SPA-Spiel: langes Board nach unten scrollen → Aktion abschließen → Ergebnis-/Folge-View beginnt **oben**; ServiceClash-Antwort-Feedback und BugHunter-Zeilenauswahl dürfen **nicht** nach oben springen; FinalProtocol: langen Lernartikel bis zum Quiz scrollen, Quiz abschließen → Auswertung oben im Viewport; Reload mitten im Spiel → Katalog startet oben; SyntaxSaga/DevLab: Missions-/Taskwechsel startet oben, Fokus springt nicht; NeuralNet-Chat scrollt weiterhin ans Ende.
|
||||
|
||||
## Offene Punkte (unverändert aus Refine 1, vor Launch)
|
||||
|
||||
- Echte Impressumsdaten (`impressum.html:44–52`) + §1 Datenschutzerklärung.
|
||||
- Support-/Feedback-Adressen bestätigen (`fragments/layout.html:88–89`).
|
||||
|
||||
## Nachtrag 2026-07-22 — E-Mail-Diagnose (erledigt)
|
||||
|
||||
- Der Versand von Bestätigungs- und Reset-Links protokolliert nun korrelierbare
|
||||
Ereignisse: Token-Ausstellung, SMTP-Übergabe begonnen/angenommen/fehlgeschlagen,
|
||||
erneute Anforderung und erfolgreiche Verifikation. Logs enthalten User-/Token-IDs
|
||||
sowie einen gekürzten E-Mail-Fingerprint, niemals den Link oder das Roh-Token.
|
||||
- Wichtig für die Analyse: `SMTP_SUBMISSION_ACCEPTED` bedeutet nur, dass der
|
||||
konfigurierte SMTP-Server die Nachricht angenommen hat. Spamfilter, Bounces und
|
||||
die finale Postfachzustellung müssen zusätzlich im Mailserver bzw. über DSN geprüft
|
||||
werden.
|
||||
- Der Registrierungsabschluss meldet keinen Versand-Erfolg mehr, falls der gerade
|
||||
angelegte Benutzer beim Versand nicht geladen werden kann oder bereits als bestätigt
|
||||
vorliegt; der Grund erscheint als `VERIFICATION_SKIPPED` im Betriebslog.
|
||||
|
||||
## Nachtrag 2026-07-22 — Nutzerfeedback (erledigt)
|
||||
|
||||
- **TechTacular:** Unbeantwortete Fragen lassen sich per ×, Klick auf den Hintergrund
|
||||
oder Escape schließen und später erneut öffnen; sie werden dabei weder gewertet noch
|
||||
als beantwortet markiert.
|
||||
- **Produktverständnis:** Vor dem Login erklärt eine sichtbare Einordnung Zielgruppe,
|
||||
Lernformen und Nutzen der Plattform. Das Dashboard wiederholt den Zweck mit einem
|
||||
kompakten „Was du hier machst“-Abschnitt.
|
||||
@@ -12,6 +12,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.core.userdetails.*;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter;
|
||||
|
||||
@Configuration
|
||||
public class SecurityConfig {
|
||||
@@ -20,7 +21,7 @@ public class SecurityConfig {
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/", "/login", "/register", "/forgot-password", "/reset-password", "/verify-email", "/verification/resend", "/impressum", "/nutzungsbedingungen", "/datenschutz", "/richtlinien", "/css/**", "/js/**", "/img/**").permitAll()
|
||||
.requestMatchers("/", "/login", "/register", "/forgot-password", "/reset-password", "/verify-email", "/verification/resend", "/impressum", "/nutzungsbedingungen", "/datenschutz", "/richtlinien", "/favicon.ico", "/css/**", "/js/**", "/img/**").permitAll()
|
||||
.requestMatchers("/actuator/health").permitAll() // Container-Healthcheck
|
||||
.requestMatchers("/admin/**").hasRole("ADMIN")
|
||||
.requestMatchers("/api/users/**").hasRole("ADMIN")
|
||||
@@ -43,6 +44,17 @@ public class SecurityConfig {
|
||||
// Spiele-APIs werden per fetch (application/json) ohne Token aufgerufen -> ausnehmen.
|
||||
// Reine JSON-Endpunkte sind nicht über klassische HTML-Formulare CSRF-ausnutzbar.
|
||||
.ignoringRequestMatchers("/api/games/**", "/api/techtacular/**", "/api/mindmatch/**", "/api/syntaxsaga/**", "/api/devlab/**", "/api/codeescape/**", "/api/bughunter/**", "/api/serviceclash/**", "/api/networker/**", "/api/neuralnet/**")
|
||||
)
|
||||
.headers(headers -> headers
|
||||
// CSP: pragmatischer Start – alle Assets sind self-hosted (Chart.js lokal
|
||||
// gevendort). 'unsafe-inline' bleibt vorerst nötig, weil Templates Inline-
|
||||
// Styles und einzelne Inline-Skripte enthalten (später auf Nonces umstellbar).
|
||||
.contentSecurityPolicy(csp -> csp.policyDirectives(
|
||||
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; "
|
||||
+ "img-src 'self' data:; font-src 'self'; connect-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'"))
|
||||
.referrerPolicy(referrer -> referrer.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN))
|
||||
// HSTS bleibt auf Spring-Default: wird nur über HTTPS gesendet, greift also
|
||||
// faktisch nur in Produktion (Dev läuft über HTTP).
|
||||
);
|
||||
|
||||
return http.build();
|
||||
@@ -63,7 +75,6 @@ public class SecurityConfig {
|
||||
.withUsername(user.getUsername())
|
||||
.password(user.getPassword())
|
||||
.roles(user.getRole()) // Achtung: Nur ein String, z.B. "USER" oder "ADMIN"
|
||||
.disabled(!user.isEmailVerified())
|
||||
.build();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.example.mindflux.controller;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
public class DummyController {
|
||||
|
||||
@GetMapping("/comingsoon")
|
||||
public String comingSoon() {
|
||||
return "dummy"; // verweist auf dummy.html in src/main/resources/templates
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.example.mindflux.controller;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.CacheControl;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class FaviconController {
|
||||
|
||||
@GetMapping(value = "/favicon.ico", produces = MediaType.IMAGE_PNG_VALUE)
|
||||
public ResponseEntity<Resource> favicon() {
|
||||
Resource emblem = new ClassPathResource("static/img/mindflux-emblem.png");
|
||||
return ResponseEntity.ok()
|
||||
.cacheControl(CacheControl.maxAge(Duration.ofDays(30)).cachePublic())
|
||||
.body(emblem);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import com.example.mindflux.dto.GameStats;
|
||||
import com.example.mindflux.dto.ScoreResult;
|
||||
import com.example.mindflux.dto.ScoreSubmission;
|
||||
import com.example.mindflux.service.GameScoreService;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@@ -32,7 +33,7 @@ public class GameScoreController {
|
||||
|
||||
@PostMapping("/{game}/scores")
|
||||
public ResponseEntity<ScoreResult> submitScore(@PathVariable String game,
|
||||
@RequestBody ScoreSubmission submission,
|
||||
@Valid @RequestBody ScoreSubmission submission,
|
||||
Principal principal) {
|
||||
if (!KNOWN_GAMES.contains(game)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
|
||||
@@ -17,19 +17,15 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.Principal;
|
||||
import java.util.UUID;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.example.mindflux.dto.WeeklyActivity;
|
||||
import com.example.mindflux.service.AvatarService;
|
||||
import com.example.mindflux.service.ProfileExportService;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
@Controller
|
||||
public class ProfileController {
|
||||
@@ -45,7 +41,11 @@ public class ProfileController {
|
||||
@Autowired
|
||||
private ProgressService progressService;
|
||||
|
||||
private static final String UPLOAD_DIR = System.getProperty("user.dir") + "/uploads/profiles/";
|
||||
@Autowired
|
||||
private AvatarService avatarService;
|
||||
|
||||
@Autowired
|
||||
private ProfileExportService profileExportService;
|
||||
|
||||
@GetMapping("/profile")
|
||||
public String profile(Model model, Principal principal, @RequestParam(value = "success", required = false) Boolean success) {
|
||||
@@ -67,24 +67,8 @@ public class ProfileController {
|
||||
User existingUser = userService.findByUsername(username);
|
||||
|
||||
if (existingUser != null) {
|
||||
String profilePicture = existingUser.getProfilePicture();
|
||||
if (profilePicture != null && !profilePicture.trim().isEmpty()) {
|
||||
try {
|
||||
Path picturePath = Paths.get(System.getProperty("user.dir"), "uploads", "profiles", profilePicture);
|
||||
if (Files.exists(picturePath)) {
|
||||
Files.delete(picturePath);
|
||||
log.info("Profilbild gelöscht: {}", picturePath.getFileName());
|
||||
} else {
|
||||
log.warn("Profilbild-Datei existiert nicht: {}", picturePath.toAbsolutePath());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Fehler beim Löschen der Profilbild-Datei", e);
|
||||
}
|
||||
} else {
|
||||
log.debug("Kein Profilbild gesetzt, nichts zu löschen.");
|
||||
}
|
||||
|
||||
existingUser.setProfilePicture(null); // Wichtig: auch wenn Datei nicht existiert
|
||||
avatarService.delete(existingUser.getProfilePicture());
|
||||
existingUser.setProfilePicture(null); // auch wenn keine Datei existierte
|
||||
userService.save(existingUser);
|
||||
}
|
||||
|
||||
@@ -97,6 +81,16 @@ public class ProfileController {
|
||||
return "settings";
|
||||
}
|
||||
|
||||
/** DSGVO-Datenexport (Art. 20): alle Kontodaten als JSON-Download. */
|
||||
@GetMapping("/profile/export")
|
||||
public ResponseEntity<Map<String, Object>> exportData(Principal principal) {
|
||||
Map<String, Object> data = profileExportService.export(principal.getName());
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"mindflux-datenexport.json\"")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(data);
|
||||
}
|
||||
|
||||
@GetMapping("/profile/knowledge")
|
||||
public String knowledgeCenter(Model model, Principal principal) {
|
||||
String username = principal.getName();
|
||||
@@ -122,47 +116,9 @@ public class ProfileController {
|
||||
if (existingUser != null) {
|
||||
existingUser.setEmail(email); // Email aktualisieren
|
||||
|
||||
if (file != null && !file.isEmpty()) {
|
||||
try {
|
||||
BufferedImage original = ImageIO.read(file.getInputStream());
|
||||
if (original == null) {
|
||||
// Keine lesbare Bilddatei -> Upload ignorieren, restliches Profil trotzdem speichern.
|
||||
log.warn("Profilbild-Upload ignoriert: keine lesbare Bilddatei.");
|
||||
} else {
|
||||
// Auf max. Kantenlänge herunterskalieren (kein Hochskalieren).
|
||||
final int MAX_DIM = 400;
|
||||
int w = original.getWidth();
|
||||
int h = original.getHeight();
|
||||
double scale = Math.min(1.0, (double) MAX_DIM / Math.max(w, h));
|
||||
int nw = Math.max(1, (int) Math.round(w * scale));
|
||||
int nh = Math.max(1, (int) Math.round(h * scale));
|
||||
|
||||
BufferedImage scaled = new BufferedImage(nw, nh, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g = scaled.createGraphics();
|
||||
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g.setColor(Color.WHITE); // JPEG kennt kein Alpha -> Transparenz auf weißem Grund
|
||||
g.fillRect(0, 0, nw, nh);
|
||||
g.drawImage(original, 0, 0, nw, nh, null);
|
||||
g.dispose();
|
||||
|
||||
// Altes Profilbild löschen (falls vorhanden)
|
||||
if (existingUser.getProfilePicture() != null && !existingUser.getProfilePicture().isEmpty()) {
|
||||
Path oldFilePath = Paths.get(System.getProperty("user.dir"), "uploads", "profiles", existingUser.getProfilePicture());
|
||||
Files.deleteIfExists(oldFilePath);
|
||||
}
|
||||
|
||||
// Normalisiert als JPEG speichern
|
||||
String fileName = UUID.randomUUID() + ".jpg";
|
||||
Path uploadPath = Paths.get(UPLOAD_DIR);
|
||||
Files.createDirectories(uploadPath);
|
||||
ImageIO.write(scaled, "jpg", uploadPath.resolve(fileName).toFile());
|
||||
|
||||
existingUser.setProfilePicture(fileName);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Fehler beim Speichern des Profilbilds", e);
|
||||
}
|
||||
String newAvatar = avatarService.store(file, existingUser.getProfilePicture());
|
||||
if (newAvatar != null) {
|
||||
existingUser.setProfilePicture(newAvatar);
|
||||
}
|
||||
|
||||
userService.save(existingUser);
|
||||
@@ -203,15 +159,7 @@ public class ProfileController {
|
||||
// Profilbild-Datei von der Platte entfernen (DB-Daten löscht der Service in einer Transaktion).
|
||||
User user = userService.findByUsername(username);
|
||||
if (user != null) {
|
||||
String profilePicture = user.getProfilePicture();
|
||||
if (profilePicture != null && !profilePicture.trim().isEmpty()) {
|
||||
try {
|
||||
Path picturePath = Paths.get(System.getProperty("user.dir"), "uploads", "profiles", profilePicture);
|
||||
Files.deleteIfExists(picturePath);
|
||||
} catch (IOException e) {
|
||||
log.error("Fehler beim Löschen des Profilbilds", e);
|
||||
}
|
||||
}
|
||||
avatarService.delete(user.getProfilePicture());
|
||||
}
|
||||
|
||||
userService.deleteByUsername(username);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.example.mindflux.controller;
|
||||
|
||||
import com.example.mindflux.service.RegistrationResult;
|
||||
import com.example.mindflux.service.AccountMailService;
|
||||
import com.example.mindflux.service.UserService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -12,11 +11,9 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
public class RegisterController {
|
||||
|
||||
private final UserService userService;
|
||||
private final AccountMailService accountMailService;
|
||||
|
||||
public RegisterController(UserService userService, AccountMailService accountMailService) {
|
||||
public RegisterController(UserService userService) {
|
||||
this.userService = userService;
|
||||
this.accountMailService = accountMailService;
|
||||
}
|
||||
|
||||
@GetMapping("/register")
|
||||
@@ -32,14 +29,9 @@ public class RegisterController {
|
||||
@RequestParam(required = false) String confirmPassword) {
|
||||
RegistrationResult result = userService.register(username, email, password, confirmPassword);
|
||||
return switch (result) {
|
||||
case SUCCESS -> {
|
||||
try {
|
||||
accountMailService.sendVerificationFor(userService.findByUsernameIgnoreCase(username.trim()));
|
||||
yield "redirect:/?registerSuccess";
|
||||
} catch (RuntimeException e) {
|
||||
yield "redirect:/?registerMailError";
|
||||
}
|
||||
}
|
||||
// Ein SMTP-Problem darf die Nutzung eines neu angelegten Accounts nicht blockieren.
|
||||
// Die E-Mail-Adresse wird weiterhin für den Passwort-Reset gespeichert.
|
||||
case SUCCESS -> "redirect:/?registerSuccess";
|
||||
case PASSWORD_MISMATCH -> "redirect:/?passwordMismatch";
|
||||
case PASSWORD_TOO_SHORT -> "redirect:/?passwordPolicy";
|
||||
case EMAIL_INVALID -> "redirect:/?emailInvalid";
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
package com.example.mindflux.dto;
|
||||
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
/**
|
||||
* Was der Client nach einem abgeschlossenen Spiel sendet. Der Score wird
|
||||
* bewusst NICHT mitgeschickt, sondern serverseitig aus diesen Rohwerten
|
||||
* berechnet, damit ein manipulierter Client keinen Fantasie-Score speichern kann.
|
||||
* Die Constraints fangen offensichtlich unsinnige Eingaben schon vor der Logik ab.
|
||||
*/
|
||||
public class ScoreSubmission {
|
||||
@NotBlank @Size(max = 100)
|
||||
private String deck;
|
||||
@Min(0) @Max(1000)
|
||||
private int pairs;
|
||||
@Min(0) @Max(100000)
|
||||
private int moves;
|
||||
@Min(0) @Max(86400)
|
||||
private int durationSeconds;
|
||||
|
||||
public String getDeck() { return deck; }
|
||||
|
||||
@@ -27,6 +27,9 @@ public interface GameScoreRepository extends JpaRepository<GameScore, Long> {
|
||||
/** Alle Spielstände eines Spielers löschen (z. B. beim Löschen des Kontos). */
|
||||
void deleteByUsername(String username);
|
||||
|
||||
/** Alle Spielstände eines Spielers (für den DSGVO-Datenexport). */
|
||||
List<GameScore> findByUsernameOrderByPlayedAtDesc(String username);
|
||||
|
||||
/** Gesamtsumme aller Scores eines Spielers (XP-Basis). */
|
||||
@Query("select coalesce(sum(g.score), 0) from GameScore g where g.username = :username")
|
||||
long sumScoreByUsername(@Param("username") String username);
|
||||
|
||||
@@ -11,4 +11,6 @@ public interface NeuralNetConnectionRepository extends JpaRepository<NeuralNetCo
|
||||
List<NeuralNetConnection> acceptedFor(@Param("username") String username);
|
||||
@Query("select c from NeuralNetConnection c where c.status='ACCEPTED' and ((c.requester=:a and c.addressee=:b) or (c.requester=:b and c.addressee=:a))")
|
||||
Optional<NeuralNetConnection> acceptedBetween(@Param("a") String a, @Param("b") String b);
|
||||
@Query("select c from NeuralNetConnection c where c.requester=:u or c.addressee=:u order by c.createdAt asc")
|
||||
List<NeuralNetConnection> findAllInvolving(@Param("u") String u);
|
||||
}
|
||||
|
||||
@@ -7,4 +7,6 @@ public interface NeuralNetMessageRepository extends JpaRepository<NeuralNetMessa
|
||||
@Query("select m from NeuralNetMessage m where (m.sender=:a and m.recipient=:b) or (m.sender=:b and m.recipient=:a) order by m.sentAt asc")
|
||||
List<NeuralNetMessage> conversation(@Param("a") String a, @Param("b") String b);
|
||||
long countByRecipientAndReadAtIsNull(String recipient);
|
||||
@Query("select m from NeuralNetMessage m where m.sender=:u or m.recipient=:u order by m.sentAt asc")
|
||||
List<NeuralNetMessage> findAllInvolving(@Param("u") String u);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
@@ -25,6 +27,7 @@ import java.util.Optional;
|
||||
public class AccountMailService {
|
||||
|
||||
private static final Duration TOKEN_LIFETIME = Duration.ofHours(24);
|
||||
private static final Logger log = LoggerFactory.getLogger(AccountMailService.class);
|
||||
private final AccountEmailTokenRepository tokenRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final JavaMailSender mailSender;
|
||||
@@ -48,32 +51,48 @@ public class AccountMailService {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void sendVerificationFor(User user) {
|
||||
if (user == null || user.isEmailVerified()) {
|
||||
return;
|
||||
public boolean sendVerificationFor(User user) {
|
||||
if (user == null) {
|
||||
log.error("mail_event=VERIFICATION_SKIPPED reason=USER_NOT_FOUND_AFTER_REGISTRATION");
|
||||
return false;
|
||||
}
|
||||
String token = issueToken(user, AccountEmailToken.Purpose.EMAIL_VERIFICATION);
|
||||
send(user.getEmail(), "Bestätige deinen Mindflux-Account",
|
||||
if (user.isEmailVerified()) {
|
||||
log.info("mail_event=VERIFICATION_SKIPPED reason=ALREADY_VERIFIED userId={}", user.getId());
|
||||
return false;
|
||||
}
|
||||
IssuedToken issued = issueToken(user, AccountEmailToken.Purpose.EMAIL_VERIFICATION);
|
||||
send(user, issued, "Bestätige deinen Mindflux-Account",
|
||||
"Hallo " + user.getUsername() + ",\n\n"
|
||||
+ "bitte bestätige deine E-Mail-Adresse, um deinen Account freizuschalten:\n"
|
||||
+ publicUrl + "/verify-email?token=" + token + "\n\n"
|
||||
+ publicUrl + "/verify-email?token=" + issued.rawToken() + "\n\n"
|
||||
+ "Der Link ist 24 Stunden gültig und kann nur einmal verwendet werden.\n\n"
|
||||
+ "Falls du dich nicht registriert hast, kannst du diese E-Mail ignorieren.");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void resendVerification(String email) {
|
||||
uniqueUserForEmail(email).filter(user -> !user.isEmailVerified()).ifPresent(this::sendVerificationFor);
|
||||
Optional<User> user = uniqueUserForEmail(email);
|
||||
if (user.isEmpty()) {
|
||||
log.info("mail_event=VERIFICATION_RESEND_IGNORED reason=NO_UNIQUE_USER recipientFingerprint={}", recipientFingerprint(email));
|
||||
return;
|
||||
}
|
||||
if (user.get().isEmailVerified()) {
|
||||
log.info("mail_event=VERIFICATION_RESEND_IGNORED reason=ALREADY_VERIFIED userId={}", user.get().getId());
|
||||
return;
|
||||
}
|
||||
log.info("mail_event=VERIFICATION_RESEND_REQUESTED userId={}", user.get().getId());
|
||||
sendVerificationFor(user.get());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void requestPasswordReset(String email) {
|
||||
uniqueUserForEmail(email).filter(User::isEmailVerified).ifPresent(user -> {
|
||||
String token = issueToken(user, AccountEmailToken.Purpose.PASSWORD_RESET);
|
||||
send(user.getEmail(), "Passwort für deinen Mindflux-Account zurücksetzen",
|
||||
IssuedToken issued = issueToken(user, AccountEmailToken.Purpose.PASSWORD_RESET);
|
||||
send(user, issued, "Passwort für deinen Mindflux-Account zurücksetzen",
|
||||
"Hallo " + user.getUsername() + ",\n\n"
|
||||
+ "über diesen Link kannst du ein neues Passwort vergeben:\n"
|
||||
+ publicUrl + "/reset-password?token=" + token + "\n\n"
|
||||
+ publicUrl + "/reset-password?token=" + issued.rawToken() + "\n\n"
|
||||
+ "Der Link ist 24 Stunden gültig und kann nur einmal verwendet werden.\n\n"
|
||||
+ "Falls du diese Anfrage nicht gestellt hast, kannst du diese E-Mail ignorieren.");
|
||||
});
|
||||
@@ -91,6 +110,7 @@ public class AccountMailService {
|
||||
value.setConsumedAt(Instant.now());
|
||||
userRepository.save(user);
|
||||
tokenRepository.save(value);
|
||||
log.info("mail_event=VERIFICATION_CONFIRMED userId={} tokenId={}", user.getId(), value.getId());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -124,7 +144,7 @@ public class AccountMailService {
|
||||
return users.size() == 1 ? Optional.of(users.getFirst()) : Optional.empty();
|
||||
}
|
||||
|
||||
private String issueToken(User user, AccountEmailToken.Purpose purpose) {
|
||||
private IssuedToken issueToken(User user, AccountEmailToken.Purpose purpose) {
|
||||
tokenRepository.deleteByUserAndPurpose(user, purpose);
|
||||
byte[] bytes = new byte[32];
|
||||
secureRandom.nextBytes(bytes);
|
||||
@@ -135,7 +155,9 @@ public class AccountMailService {
|
||||
token.setTokenHash(hash(rawToken));
|
||||
token.setExpiresAt(Instant.now().plus(TOKEN_LIFETIME));
|
||||
tokenRepository.save(token);
|
||||
return rawToken;
|
||||
log.info("mail_event=TOKEN_ISSUED purpose={} userId={} tokenId={} expiresAt={}",
|
||||
purpose, user.getId(), token.getId(), token.getExpiresAt());
|
||||
return new IssuedToken(rawToken, token.getId(), purpose);
|
||||
}
|
||||
|
||||
private Optional<AccountEmailToken> validToken(String rawToken, AccountEmailToken.Purpose purpose) {
|
||||
@@ -146,15 +168,43 @@ public class AccountMailService {
|
||||
.filter(token -> token.getExpiresAt().isAfter(Instant.now()));
|
||||
}
|
||||
|
||||
private void send(String recipient, String subject, String body) {
|
||||
private void send(User user, IssuedToken issued, String subject, String body) {
|
||||
SimpleMailMessage message = new SimpleMailMessage();
|
||||
message.setFrom(fromAddress);
|
||||
message.setTo(recipient);
|
||||
message.setTo(user.getEmail());
|
||||
message.setSubject(subject);
|
||||
message.setText(body);
|
||||
mailSender.send(message);
|
||||
log.info("mail_event=SMTP_SUBMISSION_STARTED purpose={} userId={} tokenId={} recipientFingerprint={}",
|
||||
issued.purpose(), user.getId(), issued.tokenId(), recipientFingerprint(user.getEmail()));
|
||||
try {
|
||||
mailSender.send(message);
|
||||
// JavaMail bestätigt hier nur die Annahme durch den konfigurierten SMTP-Server,
|
||||
// nicht die spätere Zustellung im Postfach. Für Bounces sind dessen Logs/DSN nötig.
|
||||
log.info("mail_event=SMTP_SUBMISSION_ACCEPTED purpose={} userId={} tokenId={}",
|
||||
issued.purpose(), user.getId(), issued.tokenId());
|
||||
} catch (RuntimeException exception) {
|
||||
log.error("mail_event=SMTP_SUBMISSION_FAILED purpose={} userId={} tokenId={} errorType={} detail={}",
|
||||
issued.purpose(), user.getId(), issued.tokenId(), exception.getClass().getSimpleName(),
|
||||
safeFailureDetail(exception.getMessage()));
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
private String recipientFingerprint(String email) {
|
||||
return hash(email == null ? "" : email.trim().toLowerCase()).substring(0, 12);
|
||||
}
|
||||
|
||||
private String safeFailureDetail(String detail) {
|
||||
if (detail == null || detail.isBlank()) {
|
||||
return "none";
|
||||
}
|
||||
String sanitized = detail.replaceAll("(?i)[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}", "[redacted-email]")
|
||||
.replaceAll("[\\r\\n]+", " ");
|
||||
return sanitized.substring(0, Math.min(sanitized.length(), 240));
|
||||
}
|
||||
|
||||
private record IssuedToken(String rawToken, Long tokenId, AccountEmailToken.Purpose purpose) { }
|
||||
|
||||
private String hash(String value) {
|
||||
try {
|
||||
byte[] digest = MessageDigest.getInstance("SHA-256")
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.example.mindflux.service;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
/**
|
||||
* Kapselt die gesamte Profilbild-Logik (Dekodierung, Skalierung, Datei-I/O, Pfad-Auflösung),
|
||||
* die zuvor mehrfach im ProfileController dupliziert war. Der Controller bleibt so schlank
|
||||
* und die Bildverarbeitung ist an einer Stelle testbar/änderbar.
|
||||
*/
|
||||
@Service
|
||||
public class AvatarService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AvatarService.class);
|
||||
private static final int MAX_DIM = 400;
|
||||
|
||||
/** Basisverzeichnis der Profilbilder – eine einzige Quelle der Wahrheit für den Pfad. */
|
||||
private Path baseDir() {
|
||||
return Paths.get(System.getProperty("user.dir"), "uploads", "profiles");
|
||||
}
|
||||
|
||||
/** Voller Pfad zu einem gespeicherten Profilbild. */
|
||||
public Path resolve(String fileName) {
|
||||
return baseDir().resolve(fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verarbeitet einen Bild-Upload: liest das Bild, skaliert auf max. Kantenlänge herunter,
|
||||
* löscht ein evtl. vorhandenes altes Bild und speichert normalisiert als JPEG.
|
||||
*
|
||||
* @return der neue Dateiname, oder {@code null}, wenn kein/kein lesbares Bild geliefert wurde.
|
||||
*/
|
||||
public String store(MultipartFile file, String oldFileName) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
BufferedImage original = ImageIO.read(file.getInputStream());
|
||||
if (original == null) {
|
||||
log.warn("Profilbild-Upload ignoriert: keine lesbare Bilddatei.");
|
||||
return null;
|
||||
}
|
||||
int w = original.getWidth();
|
||||
int h = original.getHeight();
|
||||
double scale = Math.min(1.0, (double) MAX_DIM / Math.max(w, h));
|
||||
int nw = Math.max(1, (int) Math.round(w * scale));
|
||||
int nh = Math.max(1, (int) Math.round(h * scale));
|
||||
|
||||
BufferedImage scaled = new BufferedImage(nw, nh, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g = scaled.createGraphics();
|
||||
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g.setColor(Color.WHITE); // JPEG kennt kein Alpha -> Transparenz auf weißem Grund
|
||||
g.fillRect(0, 0, nw, nh);
|
||||
g.drawImage(original, 0, 0, nw, nh, null);
|
||||
g.dispose();
|
||||
|
||||
delete(oldFileName); // altes Bild aufräumen
|
||||
|
||||
String fileName = UUID.randomUUID() + ".jpg";
|
||||
Path dir = baseDir();
|
||||
Files.createDirectories(dir);
|
||||
ImageIO.write(scaled, "jpg", dir.resolve(fileName).toFile());
|
||||
return fileName;
|
||||
} catch (IOException e) {
|
||||
log.error("Fehler beim Speichern des Profilbilds", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Löscht ein Profilbild, falls Name gesetzt und Datei vorhanden. Fehler werden nur geloggt. */
|
||||
public void delete(String fileName) {
|
||||
if (fileName == null || fileName.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Path path = resolve(fileName);
|
||||
if (Files.deleteIfExists(path)) {
|
||||
log.info("Profilbild gelöscht: {}", path.getFileName());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Fehler beim Löschen der Profilbild-Datei", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.example.mindflux.service;
|
||||
|
||||
import com.example.mindflux.model.Achievement;
|
||||
import com.example.mindflux.model.GameScore;
|
||||
import com.example.mindflux.model.NeuralNetConnection;
|
||||
import com.example.mindflux.model.NeuralNetMessage;
|
||||
import com.example.mindflux.model.User;
|
||||
import com.example.mindflux.repository.AchievementRepository;
|
||||
import com.example.mindflux.repository.GameScoreRepository;
|
||||
import com.example.mindflux.repository.NeuralNetConnectionRepository;
|
||||
import com.example.mindflux.repository.NeuralNetMessageRepository;
|
||||
import com.example.mindflux.repository.NeuralNetPrivacyRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Sammelt alle personenbezogenen Daten eines Kontos für den DSGVO-Datenexport
|
||||
* (Art. 20 – Datenübertragbarkeit). Bewusst OHNE das Passwort-Hash; nur Daten,
|
||||
* die die betroffene Person selbst erzeugt hat.
|
||||
*/
|
||||
@Service
|
||||
public class ProfileExportService {
|
||||
|
||||
private final UserService userService;
|
||||
private final GameScoreRepository scores;
|
||||
private final AchievementRepository achievements;
|
||||
private final NeuralNetPrivacyRepository privacy;
|
||||
private final NeuralNetConnectionRepository connections;
|
||||
private final NeuralNetMessageRepository messages;
|
||||
|
||||
public ProfileExportService(UserService userService, GameScoreRepository scores,
|
||||
AchievementRepository achievements, NeuralNetPrivacyRepository privacy,
|
||||
NeuralNetConnectionRepository connections, NeuralNetMessageRepository messages) {
|
||||
this.userService = userService;
|
||||
this.scores = scores;
|
||||
this.achievements = achievements;
|
||||
this.privacy = privacy;
|
||||
this.connections = connections;
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
public Map<String, Object> export(String username) {
|
||||
Map<String, Object> out = new LinkedHashMap<>();
|
||||
out.put("exportiertAm", LocalDateTime.now().toString());
|
||||
out.put("hinweis", "DSGVO-Datenexport (Art. 20). Enthält alle zu diesem Konto gespeicherten "
|
||||
+ "personenbezogenen Daten außer dem Passwort-Hash.");
|
||||
|
||||
User user = userService.findByUsername(username);
|
||||
Map<String, Object> account = new LinkedHashMap<>();
|
||||
if (user != null) {
|
||||
account.put("benutzername", user.getUsername());
|
||||
account.put("email", user.getEmail());
|
||||
account.put("rolle", user.getRole());
|
||||
account.put("emailBestaetigt", user.isEmailVerified());
|
||||
account.put("hatProfilbild", user.getProfilePicture() != null && !user.getProfilePicture().isBlank());
|
||||
}
|
||||
out.put("konto", account);
|
||||
|
||||
List<Map<String, Object>> scoreList = new ArrayList<>();
|
||||
for (GameScore s : scores.findByUsernameOrderByPlayedAtDesc(username)) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("spiel", s.getGame());
|
||||
m.put("deck", s.getDeck());
|
||||
m.put("punkte", s.getScore());
|
||||
m.put("zuege", s.getMoves());
|
||||
m.put("dauerSekunden", s.getDurationSeconds());
|
||||
m.put("gespieltAm", s.getPlayedAt() != null ? s.getPlayedAt().toString() : null);
|
||||
scoreList.add(m);
|
||||
}
|
||||
out.put("spielstaende", scoreList);
|
||||
|
||||
List<Map<String, Object>> achievementList = new ArrayList<>();
|
||||
for (Achievement a : achievements.findByUsername(username)) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("titel", a.getTitle());
|
||||
m.put("beschreibung", a.getDescription());
|
||||
m.put("erreichtAm", a.getDateAchieved() != null ? a.getDateAchieved().toString() : null);
|
||||
achievementList.add(m);
|
||||
}
|
||||
out.put("erfolge", achievementList);
|
||||
|
||||
Map<String, Object> neural = new LinkedHashMap<>();
|
||||
neural.put("nachrichtenAktiviert", privacy.findById(username).map(p -> p.isEnabled()).orElse(false));
|
||||
List<Map<String, Object>> connList = new ArrayList<>();
|
||||
for (NeuralNetConnection c : connections.findAllInvolving(username)) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("anfragender", c.getRequester());
|
||||
m.put("empfaenger", c.getAddressee());
|
||||
m.put("status", c.getStatus());
|
||||
m.put("erstelltAm", c.getCreatedAt() != null ? c.getCreatedAt().toString() : null);
|
||||
connList.add(m);
|
||||
}
|
||||
neural.put("verbindungen", connList);
|
||||
List<Map<String, Object>> msgList = new ArrayList<>();
|
||||
for (NeuralNetMessage msg : messages.findAllInvolving(username)) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("von", msg.getSender());
|
||||
m.put("an", msg.getRecipient());
|
||||
m.put("text", msg.getBody());
|
||||
m.put("gesendetAm", msg.getSentAt() != null ? msg.getSentAt().toString() : null);
|
||||
m.put("gelesen", msg.getReadAt() != null);
|
||||
msgList.add(m);
|
||||
}
|
||||
neural.put("nachrichten", msgList);
|
||||
out.put("neuralNet", neural);
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,9 @@ public class UserServiceImpl implements UserService {
|
||||
return RegistrationResult.EMAIL_TAKEN;
|
||||
}
|
||||
User user = new User(name, passwordEncoder.encode(password), email.trim(), "USER");
|
||||
user.setEmailVerified(false);
|
||||
// Die Registrierung ist bewusst nicht vom Versand oder Empfang einer E-Mail abhängig.
|
||||
// Damit bleibt der Login auch bei verzögerter bzw. abgewiesener SMTP-Zustellung möglich.
|
||||
user.setEmailVerified(true);
|
||||
try {
|
||||
userRepository.save(user);
|
||||
} catch (DataIntegrityViolationException e) {
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
{
|
||||
"slug": "menue-fallt-durch", "title": "Fall #3: Das fallende Menü", "language": "Java", "difficulty": "junior", "examScope": "AP1",
|
||||
"description": "Der Punkt ‚Status‘ öffnet unerwartet den Abmelde-Dialog.", "briefing": "Ein Azubi meldet einen Fehler im Konsolenmenü. Prüfe die Verzweigungen.", "consoleOutput": "Eingabe: 2\nStatus: OK\nAbmeldung gestartet",
|
||||
"files": [{"filename":"Menue.java","content":"switch (auswahl) {\n case 1:\n System.out.println(\"Start\");\n break;\n case 2:\n System.out.println(\"Status: OK\");\n case 3:\n System.out.println(\"Abmeldung gestartet\");\n break;\n default:\n System.out.println(\"Ungültig\");\n}"}],
|
||||
"bugs": [{"points":100,"file":"Menue.java","line":6,"category":"logik","evidence":"Nach dem Status-Fall wird der nächste case ebenfalls ausgeführt.","fixOptions":[" break;"," continue;"," return false;"," case 2: break;"],"fixIndex":0,"fixedLine":" break;","explanation":"Ohne break fällt ein switch in den nächsten Fall durch (fall-through)."}]
|
||||
"files": [{"filename":"Menü.java","content":"switch (auswahl) {\n case 1:\n System.out.println(\"Start\");\n break;\n case 2:\n System.out.println(\"Status: OK\");\n case 3:\n System.out.println(\"Abmeldung gestartet\");\n break;\n default:\n System.out.println(\"Ungültig\");\n}"}],
|
||||
"bugs": [{"points":100,"file":"Menü.java","line":6,"category":"logik","evidence":"Nach dem Status-Fall wird der nächste case ebenfalls ausgeführt.","fixOptions":[" break;"," continue;"," return false;"," case 2: break;"],"fixIndex":0,"fixedLine":" break;","explanation":"Ohne break fällt ein switch in den nächsten Fall durch (fall-through)."}]
|
||||
},
|
||||
{
|
||||
"slug": "absturz-um-mitternacht", "title": "Fall #4: Der Absturz um Mitternacht", "language": "Java", "difficulty": "senior", "examScope": "AP2_FIAE",
|
||||
@@ -43,11 +43,11 @@
|
||||
"slug": "archiv-unter-druck", "title": "Fall #7: Archiv unter Druck", "language": "Java", "difficulty": "senior", "examScope": "AP2_FIAE",
|
||||
"description": "Der tägliche Audit-Export bricht bei einem leeren Archiv ab und die CSV-Route stürzt nur in der Nachtverarbeitung ab.", "briefing": "Das Compliance-Team benötigt den Export vor 06:00 Uhr. Zwei Fehler liegen in getrennten Dateien; der Stacktrace nennt nur den Controller.", "consoleOutput": "java.lang.NullPointerException: Cannot invoke \"String.equals(Object)\"\n at ExportController.ausliefern(ExportController.java:4)\n[warn] Audit-Archiv enthält heute keine Einträge",
|
||||
"files": [
|
||||
{"filename":"AuditExport.java","content":"import java.util.List;\n\npublic class AuditExport {\n public String export(List<String> eintraege) {\n if (eintraege == null && eintraege.isEmpty()) {\n return \"Keine Einträge\";\n }\n return String.join(\"\\n\", eintraege);\n }\n}"},
|
||||
{"filename":"ExportController.java","content":"public class ExportController {\n public String ausliefern(String format, AuditExport export, java.util.List<String> eintraege) {\n if (format.equals(\"csv\")) {\n return export.export(eintraege);\n }\n return \"Format nicht unterstützt\";\n }\n}"}
|
||||
{"filename":"AuditExport.java","content":"import java.util.List;\n\npublic class AuditExport {\n public String export(List<String> einträge) {\n if (einträge == null && einträge.isEmpty()) {\n return \"Keine Einträge\";\n }\n return String.join(\"\\n\", einträge);\n }\n}"},
|
||||
{"filename":"ExportController.java","content":"public class ExportController {\n public String ausliefern(String format, AuditExport export, java.util.List<String> einträge) {\n if (format.equals(\"csv\")) {\n return export.export(einträge);\n }\n return \"Format nicht unterstützt\";\n }\n}"}
|
||||
],
|
||||
"bugs": [
|
||||
{"points":150,"file":"AuditExport.java","line":5,"category":"laufzeit","evidence":"Bei null wird rechts von && trotzdem ein Methodenaufruf ausgeführt.","fixOptions":[" if (eintraege == null || eintraege.isEmpty()) {"," if (eintraege != null && eintraege.isEmpty()) {"," if (eintraege.isEmpty() || eintraege == null) {"," if (eintraege == null & eintraege.isEmpty()) {"],"fixIndex":0,"fixedLine":" if (eintraege == null || eintraege.isEmpty()) {","explanation":"Die ODER-Verknüpfung beendet die Auswertung sofort, wenn die Liste null ist. Danach kann isEmpty sicher geprüft werden."},
|
||||
{"points":150,"file":"AuditExport.java","line":5,"category":"laufzeit","evidence":"Bei null wird rechts von && trotzdem ein Methodenaufruf ausgeführt.","fixOptions":[" if (einträge == null || einträge.isEmpty()) {"," if (einträge != null && einträge.isEmpty()) {"," if (einträge.isEmpty() || einträge == null) {"," if (einträge == null & einträge.isEmpty()) {"],"fixIndex":0,"fixedLine":" if (einträge == null || einträge.isEmpty()) {","explanation":"Die ODER-Verknüpfung beendet die Auswertung sofort, wenn die Liste null ist. Danach kann isEmpty sicher geprüft werden."},
|
||||
{"points":150,"file":"ExportController.java","line":3,"category":"laufzeit","evidence":"Der Stacktrace zeigt auf den Methodenaufruf equals; format kann aus einem optionalen Jobparameter stammen.","fixOptions":[" if (\"csv\".equals(format)) {"," if (format == \"csv\") {"," if (format.equalsIgnoreCase == \"csv\") {"," if (format != \"csv\") {"],"fixIndex":0,"fixedLine":" if (\"csv\".equals(format)) {","explanation":"Wird die feste Zeichenkette links verwendet, liefert equals bei einem null-Wert einfach false statt eine NullPointerException auszulösen."}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
-- V21: Umlaut-Korrektur fuer bereits geseedete Spiel-Inhalte (bestehende Datenbanken).
|
||||
--
|
||||
-- Die Inhalte von DevLab, TechTacular und BugHunter wurden einmalig aus den
|
||||
-- content.json-Dateien geseedet (Seeder laufen nur bei leerer Tabelle). Die JSON-
|
||||
-- Quellen sind jetzt umlautkorrekt; frische Installationen sind dadurch bereits
|
||||
-- korrekt. Fuer schon befuellte DBs leeren wir die Content-Tabellen, woraufhin die
|
||||
-- CommandLineRunner-Seeder sie beim naechsten Start aus dem korrigierten JSON neu
|
||||
-- aufbauen (gleiche Vorgehensweise wie V6 fuer TechTacular).
|
||||
--
|
||||
-- CASCADE raeumt nur die abhaengigen Content-Tabellen (Tasks/Questions/Answers/
|
||||
-- Files/Bugs). Spielstaende (game_scores speichert das Spiel als String, kein FK)
|
||||
-- und der Fortschritt (bughunter_hunt_progress hat keinen FK auf die Hunts) bleiben
|
||||
-- erhalten; durch RESTART IDENTITY und die deterministische Seed-Reihenfolge sind
|
||||
-- die neuen IDs identisch zu vorher.
|
||||
--
|
||||
-- Networker ist NICHT betroffen: dort war ausschliesslich der (bewusst stabile)
|
||||
-- Slug transliteriert, die sichtbaren Welt-Titel/-Beschreibungen sind bereits korrekt.
|
||||
|
||||
TRUNCATE TABLE devlab_games RESTART IDENTITY CASCADE;
|
||||
TRUNCATE TABLE techtacular_categories RESTART IDENTITY CASCADE;
|
||||
TRUNCATE TABLE bughunter_hunts RESTART IDENTITY CASCADE;
|
||||
@@ -0,0 +1,14 @@
|
||||
-- V22: Entfernt das Demo-/Testkonto "NeuralTest" (per V17 fuer den NeuralNet-Flow
|
||||
-- geseedet) aus produktiven Datenbanken. Ein klar erkennbares Testkonto gehoert
|
||||
-- nicht in Produktion.
|
||||
--
|
||||
-- Die NeuralNet-Tabellen (neuralnet_privacy, neuralnet_connections, neuralnet_messages)
|
||||
-- referenzieren users(username) mit ON DELETE CASCADE und werden beim Loeschen des
|
||||
-- Kontos automatisch mitgeraeumt. game_scores hat keinen Fremdschluessel, daher wird
|
||||
-- ein evtl. vorhandener Spielstand des Demo-Kontos defensiv vorab entfernt.
|
||||
--
|
||||
-- Wird das Konto fuer die lokale Entwicklung gebraucht, sollte es ueber einen
|
||||
-- @Profile("dev")-gebundenen Runner neu angelegt werden, nicht per Migration.
|
||||
|
||||
DELETE FROM game_scores WHERE username = 'NeuralTest';
|
||||
DELETE FROM users WHERE username = 'NeuralTest';
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Die Anmeldung ist nicht mehr an die Zustellung einer E-Mail gebunden.
|
||||
-- Bereits registrierte, bisher noch wartende Accounts werden dadurch nutzbar.
|
||||
UPDATE users
|
||||
SET email_verified = TRUE
|
||||
WHERE email_verified = FALSE;
|
||||
|
||||
-- Verifikations-Links werden nicht mehr benötigt; gespeicherte Hashes entfernen.
|
||||
DELETE FROM account_email_tokens
|
||||
WHERE purpose = 'EMAIL_VERIFICATION';
|
||||
@@ -1253,7 +1253,7 @@
|
||||
"type": "cloze",
|
||||
"prompt": "Kopfbereich auszeichnen",
|
||||
"points": 100,
|
||||
"narrative": "Oben auf der Seite stehen Logo und Titel. Welches Element umschliesst diesen Kopfbereich?",
|
||||
"narrative": "Oben auf der Seite stehen Logo und Titel. Welches Element umschließt diesen Kopfbereich?",
|
||||
"codeTemplate": "<{{1}}>\n <h1>Mindflux</h1>\n</{{2}}>",
|
||||
"gaps": [
|
||||
{
|
||||
@@ -1268,7 +1268,7 @@
|
||||
{
|
||||
"position": 2,
|
||||
"placeholder": "Tag",
|
||||
"hint": "Gleiches Tag wie das oeffnende.",
|
||||
"hint": "Gleiches Tag wie das öffnende.",
|
||||
"caseSensitive": false,
|
||||
"solutions": [
|
||||
"header"
|
||||
@@ -1290,19 +1290,19 @@
|
||||
],
|
||||
"correctIndex": 0,
|
||||
"hint": "Es darf pro Seite nur einmal sichtbar vorkommen.",
|
||||
"successText": "Genau, <main> umschliesst den Hauptinhalt."
|
||||
"successText": "Genau, <main> umschließt den Hauptinhalt."
|
||||
},
|
||||
{
|
||||
"type": "cloze",
|
||||
"prompt": "Navigation",
|
||||
"points": 100,
|
||||
"narrative": "Die Linkliste fuehrt durch die Seite.",
|
||||
"codeTemplate": "<{{1}} aria-label=\"Hauptmenue\">\n <a href=\"/\">Start</a>\n</{{2}}>",
|
||||
"narrative": "Die Linkliste führt durch die Seite.",
|
||||
"codeTemplate": "<{{1}} aria-label=\"Hauptmenü\">\n <a href=\"/\">Start</a>\n</{{2}}>",
|
||||
"gaps": [
|
||||
{
|
||||
"position": 1,
|
||||
"placeholder": "Tag",
|
||||
"hint": "Steht fuer Navigation.",
|
||||
"hint": "Steht für Navigation.",
|
||||
"caseSensitive": false,
|
||||
"solutions": [
|
||||
"nav"
|
||||
@@ -1311,32 +1311,32 @@
|
||||
{
|
||||
"position": 2,
|
||||
"placeholder": "Tag",
|
||||
"hint": "Gleiches Tag schliessen.",
|
||||
"hint": "Gleiches Tag schließen.",
|
||||
"caseSensitive": false,
|
||||
"solutions": [
|
||||
"nav"
|
||||
]
|
||||
}
|
||||
],
|
||||
"successText": "<nav> ist die richtige Wahl fuer Navigationsbloecke."
|
||||
"successText": "<nav> ist die richtige Wahl für Navigationsblöcke."
|
||||
},
|
||||
{
|
||||
"type": "order",
|
||||
"prompt": "Seitengeruest ordnen",
|
||||
"prompt": "Seitengerüst ordnen",
|
||||
"points": 100,
|
||||
"instruction": "Bringe die Grundbausteine einer Seite in die uebliche Reihenfolge von oben nach unten.",
|
||||
"instruction": "Bringe die Grundbausteine einer Seite in die übliche Reihenfolge von oben nach unten.",
|
||||
"items": [
|
||||
"<header>",
|
||||
"<nav>",
|
||||
"<main>",
|
||||
"<footer>"
|
||||
],
|
||||
"hint": "Kopf, dann Menue, dann Inhalt, dann Fuss.",
|
||||
"hint": "Kopf, dann Menü, dann Inhalt, dann Fuss.",
|
||||
"successText": "Sauberes Dokument-Outline!"
|
||||
},
|
||||
{
|
||||
"type": "cloze",
|
||||
"prompt": "Eigenstaendiger Beitrag",
|
||||
"prompt": "Eigenständiger Beitrag",
|
||||
"points": 100,
|
||||
"narrative": "Ein Blogpost soll auch einzeln (z. B. im RSS-Feed) sinnvoll sein.",
|
||||
"codeTemplate": "<{{1}}>\n <h2>Blogpost</h2>\n <p>Text</p>\n</{{2}}>",
|
||||
@@ -1344,7 +1344,7 @@
|
||||
{
|
||||
"position": 1,
|
||||
"placeholder": "Tag",
|
||||
"hint": "Eigenstaendiger, wiederverwendbarer Inhalt.",
|
||||
"hint": "Eigenständiger, wiederverwendbarer Inhalt.",
|
||||
"caseSensitive": false,
|
||||
"solutions": [
|
||||
"article"
|
||||
@@ -1353,7 +1353,7 @@
|
||||
{
|
||||
"position": 2,
|
||||
"placeholder": "Tag",
|
||||
"hint": "Gleiches Tag schliessen.",
|
||||
"hint": "Gleiches Tag schließen.",
|
||||
"caseSensitive": false,
|
||||
"solutions": [
|
||||
"article"
|
||||
@@ -1374,14 +1374,14 @@
|
||||
"<menu role=\"nav\">"
|
||||
],
|
||||
"correctIndex": 0,
|
||||
"hint": "Es gibt ein dediziertes HTML-Element dafuer.",
|
||||
"hint": "Es gibt ein dediziertes HTML-Element dafür.",
|
||||
"successText": "Richtig, <nav> ist standardisiert und barrierearm."
|
||||
},
|
||||
{
|
||||
"type": "spot",
|
||||
"prompt": "Falsches schliessendes Tag",
|
||||
"prompt": "Falsches schließendes Tag",
|
||||
"points": 100,
|
||||
"narrative": "Ein schliessendes Tag passt nicht zum oeffnenden Element.",
|
||||
"narrative": "Ein schließendes Tag passt nicht zum öffnenden Element.",
|
||||
"instruction": "Klick den fehlerhaften Token an.",
|
||||
"tokens": [
|
||||
"<section>",
|
||||
@@ -1390,20 +1390,20 @@
|
||||
],
|
||||
"bugIndex": 2,
|
||||
"fix": "</section>",
|
||||
"hint": "Welches Tag wurde geoeffnet?",
|
||||
"successText": "Korrekt, oeffnendes und schliessendes Tag muessen uebereinstimmen."
|
||||
"hint": "Welches Tag wurde geöffnet?",
|
||||
"successText": "Korrekt, öffnendes und schließendes Tag müssen übereinstimmen."
|
||||
},
|
||||
{
|
||||
"type": "cloze",
|
||||
"prompt": "Thematischer Abschnitt",
|
||||
"points": 100,
|
||||
"narrative": "Ein klar abgegrenztes Kapitel mit eigener Ueberschrift.",
|
||||
"narrative": "Ein klar abgegrenztes Kapitel mit eigener Überschrift.",
|
||||
"codeTemplate": "<{{1}}>\n <h2>Kapitel 1</h2>\n</{{2}}>",
|
||||
"gaps": [
|
||||
{
|
||||
"position": 1,
|
||||
"placeholder": "Tag",
|
||||
"hint": "Thematischer Block mit Ueberschrift.",
|
||||
"hint": "Thematischer Block mit Überschrift.",
|
||||
"caseSensitive": false,
|
||||
"solutions": [
|
||||
"section"
|
||||
@@ -1412,20 +1412,20 @@
|
||||
{
|
||||
"position": 2,
|
||||
"placeholder": "Tag",
|
||||
"hint": "Gleiches Tag schliessen.",
|
||||
"hint": "Gleiches Tag schließen.",
|
||||
"caseSensitive": false,
|
||||
"solutions": [
|
||||
"section"
|
||||
]
|
||||
}
|
||||
],
|
||||
"successText": "<section> gruppiert thematisch zusammengehoerigen Inhalt."
|
||||
"successText": "<section> gruppiert thematisch zusammengehörigen Inhalt."
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"prompt": "Randinhalt",
|
||||
"points": 100,
|
||||
"question": "Welches Element eignet sich fuer ergaenzende Randinhalte wie eine Sidebar?",
|
||||
"question": "Welches Element eignet sich für ergänzende Randinhalte wie eine Sidebar?",
|
||||
"options": [
|
||||
"<aside>",
|
||||
"<side>",
|
||||
@@ -1433,12 +1433,12 @@
|
||||
"<div class=\"sidebar\">"
|
||||
],
|
||||
"correctIndex": 0,
|
||||
"hint": "Inhalt, der nur indirekt zum Hauptinhalt gehoert.",
|
||||
"successText": "Genau, <aside> ist fuer nebensaechliche Inhalte gedacht."
|
||||
"hint": "Inhalt, der nur indirekt zum Hauptinhalt gehört.",
|
||||
"successText": "Genau, <aside> ist für nebensächliche Inhalte gedacht."
|
||||
},
|
||||
{
|
||||
"type": "cloze",
|
||||
"prompt": "Label verknuepfen",
|
||||
"prompt": "Label verknüpfen",
|
||||
"points": 100,
|
||||
"narrative": "Das Label soll beim Klick das passende Eingabefeld fokussieren.",
|
||||
"codeTemplate": "<label {{1}}=\"mail\">E-Mail</label>\n<input id=\"mail\" {{2}}=\"email\">",
|
||||
@@ -1462,7 +1462,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"successText": "for verknuepft Label und Feld, type setzt die Eingabeart."
|
||||
"successText": "for verknüpft Label und Feld, type setzt die Eingabeart."
|
||||
},
|
||||
{
|
||||
"type": "spot",
|
||||
@@ -1483,9 +1483,9 @@
|
||||
},
|
||||
{
|
||||
"type": "order",
|
||||
"prompt": "Ueberschriften-Hierarchie",
|
||||
"prompt": "Überschriften-Hierarchie",
|
||||
"points": 100,
|
||||
"instruction": "Ordne die Ueberschriften von der wichtigsten zur untergeordneten Ebene.",
|
||||
"instruction": "Ordne die Überschriften von der wichtigsten zur untergeordneten Ebene.",
|
||||
"items": [
|
||||
"<h1>",
|
||||
"<h2>",
|
||||
@@ -1505,7 +1505,7 @@
|
||||
{
|
||||
"position": 1,
|
||||
"placeholder": "Tag",
|
||||
"hint": "Abschliessender Bereich.",
|
||||
"hint": "Abschließender Bereich.",
|
||||
"caseSensitive": false,
|
||||
"solutions": [
|
||||
"footer"
|
||||
@@ -1514,14 +1514,14 @@
|
||||
{
|
||||
"position": 2,
|
||||
"placeholder": "Tag",
|
||||
"hint": "Gleiches Tag schliessen.",
|
||||
"hint": "Gleiches Tag schließen.",
|
||||
"caseSensitive": false,
|
||||
"solutions": [
|
||||
"footer"
|
||||
]
|
||||
}
|
||||
],
|
||||
"successText": "<footer> markiert den abschliessenden Bereich."
|
||||
"successText": "<footer> markiert den abschließenden Bereich."
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
@@ -1536,11 +1536,11 @@
|
||||
],
|
||||
"correctIndex": 0,
|
||||
"hint": "Eines kapselt das Bild, das andere die Beschriftung.",
|
||||
"successText": "Richtig, <figcaption> gehoert in eine <figure>."
|
||||
"successText": "Richtig, <figcaption> gehört in eine <figure>."
|
||||
},
|
||||
{
|
||||
"type": "cloze",
|
||||
"prompt": "Schaltflaeche",
|
||||
"prompt": "Schaltfläche",
|
||||
"points": 100,
|
||||
"narrative": "Der Button soll ein Formular absenden.",
|
||||
"codeTemplate": "<button {{1}}=\"submit\">Senden</button>",
|
||||
@@ -1555,7 +1555,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"successText": "type=\"submit\" loest das Absenden aus."
|
||||
"successText": "type=\"submit\" löst das Absenden aus."
|
||||
},
|
||||
{
|
||||
"type": "spot",
|
||||
@@ -1571,14 +1571,14 @@
|
||||
],
|
||||
"bugIndex": 0,
|
||||
"fix": "<nav",
|
||||
"hint": "Welches dedizierte Element gibt es fuer Navigation?",
|
||||
"successText": "Mit <nav> brauchst du keine Hilfsklasse fuer die Bedeutung."
|
||||
"hint": "Welches dedizierte Element gibt es für Navigation?",
|
||||
"successText": "Mit <nav> brauchst du keine Hilfsklasse für die Bedeutung."
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"prompt": "Ueberschriften-Ebene",
|
||||
"prompt": "Überschriften-Ebene",
|
||||
"points": 100,
|
||||
"question": "Wie viele <h1>-Hauptueberschriften sind pro Seite ueblicherweise empfohlen?",
|
||||
"question": "Wie viele <h1>-Hauptüberschriften sind pro Seite üblicherweise empfohlen?",
|
||||
"options": [
|
||||
"Genau eine",
|
||||
"Beliebig viele",
|
||||
@@ -1586,12 +1586,12 @@
|
||||
"Keine"
|
||||
],
|
||||
"correctIndex": 0,
|
||||
"hint": "Eine klare Hauptueberschrift pro Dokument.",
|
||||
"successText": "Eine einzige h1 haelt die Struktur eindeutig."
|
||||
"hint": "Eine klare Hauptüberschrift pro Dokument.",
|
||||
"successText": "Eine einzige h1 hält die Struktur eindeutig."
|
||||
},
|
||||
{
|
||||
"type": "cloze",
|
||||
"prompt": "Seiteninhalt umschliessen",
|
||||
"prompt": "Seiteninhalt umschließen",
|
||||
"points": 100,
|
||||
"narrative": "Zwischen Header und Footer steht der zentrale Inhaltsbereich.",
|
||||
"codeTemplate": "<{{1}}>\n <h1>Willkommen</h1>\n</{{2}}>",
|
||||
@@ -1608,14 +1608,14 @@
|
||||
{
|
||||
"position": 2,
|
||||
"placeholder": "Tag",
|
||||
"hint": "Gleiches Tag schliessen.",
|
||||
"hint": "Gleiches Tag schließen.",
|
||||
"caseSensitive": false,
|
||||
"solutions": [
|
||||
"main"
|
||||
]
|
||||
}
|
||||
],
|
||||
"successText": "<main> umschliesst den dominanten Inhalt der Seite."
|
||||
"successText": "<main> umschließt den dominanten Inhalt der Seite."
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1683,7 +1683,7 @@
|
||||
],
|
||||
"bugIndex": 2,
|
||||
"fix": "flex",
|
||||
"hint": "Der CSS-Wert heisst nicht wie das Konzept.",
|
||||
"hint": "Der CSS-Wert heißt nicht wie das Konzept.",
|
||||
"successText": "Korrekt, der Wert lautet flex, nicht flexbox."
|
||||
},
|
||||
{
|
||||
@@ -1716,7 +1716,7 @@
|
||||
"type": "cloze",
|
||||
"prompt": "Abstand zwischen Items",
|
||||
"points": 100,
|
||||
"narrative": "Zwischen den Kacheln soll ein gleichmaessiger Abstand entstehen.",
|
||||
"narrative": "Zwischen den Kacheln soll ein gleichmäßiger Abstand entstehen.",
|
||||
"codeTemplate": ".grid {\n display: grid;\n {{1}}: 16px;\n}",
|
||||
"gaps": [
|
||||
{
|
||||
@@ -1729,7 +1729,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"successText": "gap erzeugt Abstaende ohne Rand-Tricks."
|
||||
"successText": "gap erzeugt Abstände ohne Rand-Tricks."
|
||||
},
|
||||
{
|
||||
"type": "cloze",
|
||||
@@ -1748,13 +1748,13 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"successText": "center buendelt die Items in der Mitte."
|
||||
"successText": "center bündelt die Items in der Mitte."
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"prompt": "Zweidimensionales Layout",
|
||||
"points": 100,
|
||||
"question": "Welches Modell eignet sich am besten fuer ein Raster aus Zeilen UND Spalten?",
|
||||
"question": "Welches Modell eignet sich am besten für ein Raster aus Zeilen UND Spalten?",
|
||||
"options": [
|
||||
"display: grid",
|
||||
"display: flex",
|
||||
@@ -1763,7 +1763,7 @@
|
||||
],
|
||||
"correctIndex": 0,
|
||||
"hint": "Flex ist eindimensional, das gesuchte ist zweidimensional.",
|
||||
"successText": "Grid ist fuer zweidimensionale Layouts gemacht."
|
||||
"successText": "Grid ist für zweidimensionale Layouts gemacht."
|
||||
},
|
||||
{
|
||||
"type": "spot",
|
||||
@@ -1780,7 +1780,7 @@
|
||||
"bugIndex": 2,
|
||||
"fix": "space-between",
|
||||
"hint": "Da fehlt ein Buchstabe.",
|
||||
"successText": "Richtig, der Wert heisst space-between."
|
||||
"successText": "Richtig, der Wert heißt space-between."
|
||||
},
|
||||
{
|
||||
"type": "cloze",
|
||||
@@ -1865,7 +1865,7 @@
|
||||
"type": "cloze",
|
||||
"prompt": "Eine flexible Spalte",
|
||||
"points": 100,
|
||||
"narrative": "Auf schmalen Schirmen soll alles in einer flexiblen Spalte ueber die volle Breite stehen.",
|
||||
"narrative": "Auf schmalen Schirmen soll alles in einer flexiblen Spalte über die volle Breite stehen.",
|
||||
"codeTemplate": "@media (max-width: 600px) {\n .grid { grid-template-columns: {{1}}; }\n}",
|
||||
"gaps": [
|
||||
{
|
||||
@@ -1878,7 +1878,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"successText": "1fr fuellt den verfuegbaren Raum mit einer Spalte."
|
||||
"successText": "1fr füllt den verfügbaren Raum mit einer Spalte."
|
||||
},
|
||||
{
|
||||
"type": "spot",
|
||||
@@ -1895,13 +1895,13 @@
|
||||
"bugIndex": 0,
|
||||
"fix": "grid-template-columns",
|
||||
"hint": "Lies das Wort 'columns' genau.",
|
||||
"successText": "Richtig geschrieben heisst es grid-template-columns."
|
||||
"successText": "Richtig geschrieben heißt es grid-template-columns."
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"prompt": "Luecke zwischen Zellen",
|
||||
"prompt": "Lücke zwischen Zellen",
|
||||
"points": 100,
|
||||
"question": "Welche Property erzeugt gleichmaessige Abstaende zwischen Grid- oder Flex-Items?",
|
||||
"question": "Welche Property erzeugt gleichmäßige Abstände zwischen Grid- oder Flex-Items?",
|
||||
"options": [
|
||||
"gap",
|
||||
"margin-collapse",
|
||||
@@ -1910,7 +1910,7 @@
|
||||
],
|
||||
"correctIndex": 0,
|
||||
"hint": "Funktioniert ohne Rand am letzten Element.",
|
||||
"successText": "gap ist die moderne Loesung fuer Item-Abstaende."
|
||||
"successText": "gap ist die moderne Lösung für Item-Abstände."
|
||||
},
|
||||
{
|
||||
"type": "cloze",
|
||||
@@ -1938,7 +1938,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"successText": "display: grid eroeffnet das Raster-Layout."
|
||||
"successText": "display: grid eröffnet das Raster-Layout."
|
||||
},
|
||||
{
|
||||
"type": "match",
|
||||
@@ -1990,7 +1990,7 @@
|
||||
"title": "CSS Cascade",
|
||||
"language": "CSS",
|
||||
"path": "visual",
|
||||
"description": "Verstehe Spezifitaet, Reihenfolge, Vererbung und !important - und welche Regel am Ende gewinnt.",
|
||||
"description": "Verstehe Spezifität, Reihenfolge, Vererbung und !important - und welche Regel am Ende gewinnt.",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "choice",
|
||||
@@ -2005,13 +2005,13 @@
|
||||
],
|
||||
"correctIndex": 0,
|
||||
"hint": "ID-Selektoren wiegen schwerer als Klassen.",
|
||||
"successText": "Richtig, eine ID schlaegt eine Klasse."
|
||||
"successText": "Richtig, eine ID schlägt eine Klasse."
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"prompt": "Inline gegen ID",
|
||||
"points": 100,
|
||||
"question": "Welche Deklaration hat die hoechste Spezifitaet (ohne !important)?",
|
||||
"question": "Welche Deklaration hat die höchste Spezifität (ohne !important)?",
|
||||
"options": [
|
||||
"Ein Inline-Style (style-Attribut)",
|
||||
"Ein ID-Selektor",
|
||||
@@ -2019,14 +2019,14 @@
|
||||
"Ein Element-Selektor"
|
||||
],
|
||||
"correctIndex": 0,
|
||||
"hint": "Denke an die Spezifitaets-Stufen a,b,c,d.",
|
||||
"successText": "Inline-Styles haben die hoechste normale Spezifitaet."
|
||||
"hint": "Denke an die Spezifitäts-Stufen a,b,c,d.",
|
||||
"successText": "Inline-Styles haben die höchste normale Spezifität."
|
||||
},
|
||||
{
|
||||
"type": "match",
|
||||
"prompt": "Spezifitaet zuordnen",
|
||||
"prompt": "Spezifität zuordnen",
|
||||
"points": 100,
|
||||
"instruction": "Ordne jeden Selektor seiner Spezifitaet (IDs, Klassen, Elemente) zu.",
|
||||
"instruction": "Ordne jeden Selektor seiner Spezifität (IDs, Klassen, Elemente) zu.",
|
||||
"pairs": [
|
||||
[
|
||||
"#header",
|
||||
@@ -2045,14 +2045,14 @@
|
||||
"1,1,1"
|
||||
]
|
||||
],
|
||||
"hint": "Zaehle IDs, dann Klassen, dann Element-Namen.",
|
||||
"successText": "Perfekt, du zaehlst Spezifitaet richtig!"
|
||||
"hint": "Zähle IDs, dann Klassen, dann Element-Namen.",
|
||||
"successText": "Perfekt, du zählst Spezifität richtig!"
|
||||
},
|
||||
{
|
||||
"type": "cloze",
|
||||
"prompt": "Spezifischer werden",
|
||||
"points": 100,
|
||||
"narrative": "Die Klasse allein verliert gegen eine andere Regel. Erhoehe die Spezifitaet, indem du die ID #card des Containers voranstellst.",
|
||||
"narrative": "Die Klasse allein verliert gegen eine andere Regel. Erhöhe die Spezifität, indem du die ID #card des Containers voranstellst.",
|
||||
"codeTemplate": "{{1}} .title {\n color: var(--accent);\n}",
|
||||
"gaps": [
|
||||
{
|
||||
@@ -2071,37 +2071,37 @@
|
||||
"type": "choice",
|
||||
"prompt": "Notbremse !important",
|
||||
"points": 100,
|
||||
"question": "Welche Aussage ueber !important ist korrekt?",
|
||||
"question": "Welche Aussage über !important ist korrekt?",
|
||||
"options": [
|
||||
"Es ueberschreibt normale Deklarationen unabhaengig von der Spezifitaet",
|
||||
"Es erhoeht die Spezifitaet um genau eine ID",
|
||||
"Es überschreibt normale Deklarationen unabhängig von der Spezifität",
|
||||
"Es erhöht die Spezifität um genau eine ID",
|
||||
"Es wirkt nur bei Inline-Styles",
|
||||
"Es deaktiviert die Vererbung"
|
||||
],
|
||||
"correctIndex": 0,
|
||||
"hint": "!important steht ausserhalb der normalen Spezifitaets-Rechnung.",
|
||||
"hint": "!important steht ausserhalb der normalen Spezifitäts-Rechnung.",
|
||||
"successText": "Genau, !important sticht normale Regeln - sparsam einsetzen."
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"prompt": "Gleiche Spezifitaet",
|
||||
"prompt": "Gleiche Spezifität",
|
||||
"points": 100,
|
||||
"question": "Zwei Regeln gleicher Spezifitaet treffen dasselbe Element:\n.btn { color: red; }\n.btn { color: green; }\nWelche gewinnt?",
|
||||
"question": "Zwei Regeln gleicher Spezifität treffen dasselbe Element:\n.btn { color: red; }\n.btn { color: green; }\nWelche gewinnt?",
|
||||
"options": [
|
||||
"Die spaeter im Stylesheet stehende (green)",
|
||||
"Die später im Stylesheet stehende (green)",
|
||||
"Die zuerst stehende (red)",
|
||||
"Beide gemischt",
|
||||
"Die kuerzere Regel"
|
||||
"Die kürzere Regel"
|
||||
],
|
||||
"correctIndex": 0,
|
||||
"hint": "Bei Gleichstand entscheidet die Quellreihenfolge.",
|
||||
"successText": "Bei gleicher Spezifitaet gewinnt die letzte Regel."
|
||||
"successText": "Bei gleicher Spezifität gewinnt die letzte Regel."
|
||||
},
|
||||
{
|
||||
"type": "cloze",
|
||||
"prompt": "Farbe als Token",
|
||||
"points": 100,
|
||||
"narrative": "Statt einer hartkodierten Hex-Farbe nutzt du eine CSS-Variable, damit Themes durchgreifen koennen.",
|
||||
"narrative": "Statt einer hartkodierten Hex-Farbe nutzt du eine CSS-Variable, damit Themes durchgreifen können.",
|
||||
"codeTemplate": ".badge {\n color: {{1}};\n}",
|
||||
"gaps": [
|
||||
{
|
||||
@@ -2114,7 +2114,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"successText": "var(--accent) bleibt theme-faehig statt fest verdrahtet."
|
||||
"successText": "var(--accent) bleibt theme-fähig statt fest verdrahtet."
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
@@ -2122,8 +2122,8 @@
|
||||
"points": 100,
|
||||
"question": "p { color: black; } und .lead { color: gray; } treffen dasselbe Element. Welche gewinnt?",
|
||||
"options": [
|
||||
".lead (Klasse schlaegt Element)",
|
||||
"p (Element schlaegt Klasse)",
|
||||
".lead (Klasse schlägt Element)",
|
||||
"p (Element schlägt Klasse)",
|
||||
"Die zuletzt geladene Datei",
|
||||
"Keine"
|
||||
],
|
||||
@@ -2138,12 +2138,12 @@
|
||||
"instruction": "Ordne jeden Faktor seiner Rolle in der Kaskade zu.",
|
||||
"pairs": [
|
||||
[
|
||||
"Spezifitaet",
|
||||
"Spezifität",
|
||||
"Gewicht des Selektors"
|
||||
],
|
||||
[
|
||||
"Reihenfolge",
|
||||
"Spaeteres ueberschreibt Gleichwertiges"
|
||||
"Späteres überschreibt Gleichwertiges"
|
||||
],
|
||||
[
|
||||
"!important",
|
||||
@@ -2154,14 +2154,14 @@
|
||||
"Werte fliessen an Kindelemente"
|
||||
]
|
||||
],
|
||||
"hint": "Vier Saeulen entscheiden, welcher Wert greift.",
|
||||
"hint": "Vier Säulen entscheiden, welcher Wert greift.",
|
||||
"successText": "Du kennst die Hebel der Kaskade!"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"prompt": "Spezifitaet zaehlen",
|
||||
"prompt": "Spezifität zählen",
|
||||
"points": 100,
|
||||
"question": "Welche Spezifitaet (IDs, Klassen, Elemente) hat der Selektor ul li a.active?",
|
||||
"question": "Welche Spezifität (IDs, Klassen, Elemente) hat der Selektor ul li a.active?",
|
||||
"options": [
|
||||
"0,1,3",
|
||||
"0,2,2",
|
||||
@@ -2176,7 +2176,7 @@
|
||||
"type": "cloze",
|
||||
"prompt": "Variable definieren",
|
||||
"points": 100,
|
||||
"narrative": "Lege das Theme-Token zentral an, damit alle Komponenten darauf zugreifen koennen.",
|
||||
"narrative": "Lege das Theme-Token zentral an, damit alle Komponenten darauf zugreifen können.",
|
||||
"codeTemplate": ":root {\n {{1}}: #4f46e5;\n}",
|
||||
"gaps": [
|
||||
{
|
||||
@@ -2189,7 +2189,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"successText": "--accent ist nun als wiederverwendbares Token verfuegbar."
|
||||
"successText": "--accent ist nun als wiederverwendbares Token verfügbar."
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
@@ -2204,7 +2204,7 @@
|
||||
],
|
||||
"correctIndex": 0,
|
||||
"hint": "Text-bezogene Eigenschaften vererben sich oft.",
|
||||
"successText": "Richtig, color wird standardmaessig vererbt."
|
||||
"successText": "Richtig, color wird standardmäßig vererbt."
|
||||
},
|
||||
{
|
||||
"type": "cloze",
|
||||
@@ -2223,13 +2223,13 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"successText": "#main spricht das Element ueber seine ID an."
|
||||
"successText": "#main spricht das Element über seine ID an."
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"prompt": "Universalselektor",
|
||||
"points": 100,
|
||||
"question": "Welche Spezifitaet (IDs, Klassen, Elemente) traegt der Universalselektor * ?",
|
||||
"question": "Welche Spezifität (IDs, Klassen, Elemente) trägt der Universalselektor * ?",
|
||||
"options": [
|
||||
"0,0,0",
|
||||
"0,0,1",
|
||||
@@ -2238,13 +2238,13 @@
|
||||
],
|
||||
"correctIndex": 0,
|
||||
"hint": "Der Stern hat kein eigenes Gewicht.",
|
||||
"successText": "Korrekt, * traegt die Spezifitaet 0,0,0."
|
||||
"successText": "Korrekt, * trägt die Spezifität 0,0,0."
|
||||
},
|
||||
{
|
||||
"type": "match",
|
||||
"prompt": "Selektoren vergleichen",
|
||||
"points": 100,
|
||||
"instruction": "Ordne jeden Selektor seiner Spezifitaet zu.",
|
||||
"instruction": "Ordne jeden Selektor seiner Spezifität zu.",
|
||||
"pairs": [
|
||||
[
|
||||
"*",
|
||||
@@ -2264,28 +2264,28 @@
|
||||
]
|
||||
],
|
||||
"hint": "Von kein Gewicht bis ID aufsteigend.",
|
||||
"successText": "Du sortierst Spezifitaet sicher!"
|
||||
"successText": "Du sortierst Spezifität sicher!"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"prompt": "Wer schlaegt wen?",
|
||||
"prompt": "Wer schlägt wen?",
|
||||
"points": 100,
|
||||
"question": "Eine Stylesheet-Regel hat !important, ein Inline-Style hat es nicht. Welche gewinnt?",
|
||||
"options": [
|
||||
"Die Regel mit !important",
|
||||
"Der Inline-Style",
|
||||
"Die mit der hoeheren Spezifitaet",
|
||||
"Die mit der höheren Spezifität",
|
||||
"Beide gleich"
|
||||
],
|
||||
"correctIndex": 0,
|
||||
"hint": "!important steht ueber normalen Inline-Styles.",
|
||||
"successText": "Richtig, !important schlaegt einen normalen Inline-Style."
|
||||
"hint": "!important steht über normalen Inline-Styles.",
|
||||
"successText": "Richtig, !important schlägt einen normalen Inline-Style."
|
||||
},
|
||||
{
|
||||
"type": "cloze",
|
||||
"prompt": "Klasse an Element binden",
|
||||
"points": 100,
|
||||
"narrative": "Erhoehe die Spezifitaet, indem du die Klasse active direkt an das Element a bindest.",
|
||||
"narrative": "Erhöhe die Spezifität, indem du die Klasse active direkt an das Element a bindest.",
|
||||
"codeTemplate": "a{{1}} {\n color: var(--accent);\n}",
|
||||
"gaps": [
|
||||
{
|
||||
@@ -2307,7 +2307,7 @@
|
||||
"title": "Flexbox Factory",
|
||||
"language": "CSS",
|
||||
"path": "visual",
|
||||
"description": "Baue Flexbox-Layouts Schritt fuer Schritt: Richtung, Ausrichtung, Umbruch und flexible Items.",
|
||||
"description": "Baue Flexbox-Layouts Schritt für Schritt: Richtung, Ausrichtung, Umbruch und flexible Items.",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "cloze",
|
||||
@@ -2356,7 +2356,7 @@
|
||||
"type": "cloze",
|
||||
"prompt": "Richtung festlegen",
|
||||
"points": 100,
|
||||
"narrative": "Das Menue soll seine Eintraege untereinander anzeigen.",
|
||||
"narrative": "Das Menü soll seine Einträge untereinander anzeigen.",
|
||||
"codeTemplate": ".menu {\n display: flex;\n {{1}}: column;\n}",
|
||||
"gaps": [
|
||||
{
|
||||
@@ -2401,7 +2401,7 @@
|
||||
"type": "cloze",
|
||||
"prompt": "Auf der Hauptachse verteilen",
|
||||
"points": 100,
|
||||
"narrative": "Logo links, Menue rechts - mit maximalem Abstand dazwischen.",
|
||||
"narrative": "Logo links, Menü rechts - mit maximalem Abstand dazwischen.",
|
||||
"codeTemplate": ".row {\n display: flex;\n {{1}}: space-between;\n}",
|
||||
"gaps": [
|
||||
{
|
||||
@@ -2420,7 +2420,7 @@
|
||||
"type": "choice",
|
||||
"prompt": "Hauptachse bei row",
|
||||
"points": 100,
|
||||
"question": "Bei flex-direction: row - in welche Richtung verlaeuft die Hauptachse?",
|
||||
"question": "Bei flex-direction: row - in welche Richtung verläuft die Hauptachse?",
|
||||
"options": [
|
||||
"Horizontal (links nach rechts)",
|
||||
"Vertikal (oben nach unten)",
|
||||
@@ -2429,7 +2429,7 @@
|
||||
],
|
||||
"correctIndex": 0,
|
||||
"hint": "row bedeutet Reihe.",
|
||||
"successText": "Genau, bei row laeuft die Hauptachse horizontal."
|
||||
"successText": "Genau, bei row läuft die Hauptachse horizontal."
|
||||
},
|
||||
{
|
||||
"type": "order",
|
||||
@@ -2466,9 +2466,9 @@
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"prompt": "Gleiche Hoehe",
|
||||
"prompt": "Gleiche Höhe",
|
||||
"points": 100,
|
||||
"question": "Welcher align-items-Wert streckt alle Items auf die volle Hoehe des Containers?",
|
||||
"question": "Welcher align-items-Wert streckt alle Items auf die volle Höhe des Containers?",
|
||||
"options": [
|
||||
"stretch",
|
||||
"center",
|
||||
@@ -2477,7 +2477,7 @@
|
||||
],
|
||||
"correctIndex": 0,
|
||||
"hint": "Es ist auch der Standardwert.",
|
||||
"successText": "stretch dehnt die Items ueber die Querachse."
|
||||
"successText": "stretch dehnt die Items über die Querachse."
|
||||
},
|
||||
{
|
||||
"type": "cloze",
|
||||
@@ -2510,7 +2510,7 @@
|
||||
],
|
||||
[
|
||||
"space-between",
|
||||
"Aussen buendig, Rest verteilt"
|
||||
"Aussen bündig, Rest verteilt"
|
||||
],
|
||||
[
|
||||
"flex-end",
|
||||
@@ -2518,17 +2518,17 @@
|
||||
],
|
||||
[
|
||||
"space-evenly",
|
||||
"Ueberall gleicher Abstand"
|
||||
"Überall gleicher Abstand"
|
||||
]
|
||||
],
|
||||
"hint": "Achte auf die Raender.",
|
||||
"hint": "Achte auf die Ränder.",
|
||||
"successText": "Du beherrschst die Verteilungs-Werte!"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"prompt": "Verfuegbaren Platz fuellen",
|
||||
"prompt": "Verfügbaren Platz füllen",
|
||||
"points": 100,
|
||||
"question": "Womit laesst du ein einzelnes Flex-Item den verbleibenden freien Platz einnehmen?",
|
||||
"question": "Womit lässt du ein einzelnes Flex-Item den verbleibenden freien Platz einnehmen?",
|
||||
"options": [
|
||||
"flex-grow: 1",
|
||||
"flex-shrink: 0",
|
||||
@@ -2537,7 +2537,7 @@
|
||||
],
|
||||
"correctIndex": 0,
|
||||
"hint": "Es geht ums Wachsen, nicht ums Schrumpfen.",
|
||||
"successText": "flex-grow: 1 laesst das Item expandieren."
|
||||
"successText": "flex-grow: 1 lässt das Item expandieren."
|
||||
},
|
||||
{
|
||||
"type": "cloze",
|
||||
@@ -2556,7 +2556,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"successText": "gap haelt die Items gleichmaessig auf Abstand."
|
||||
"successText": "gap hält die Items gleichmäßig auf Abstand."
|
||||
},
|
||||
{
|
||||
"type": "order",
|
||||
@@ -2569,7 +2569,7 @@
|
||||
"align-items: center;"
|
||||
],
|
||||
"hint": "Erst Flex, dann beide Achsen zentrieren.",
|
||||
"successText": "Der Klassiker fuer perfekte Zentrierung!"
|
||||
"successText": "Der Klassiker für perfekte Zentrierung!"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
@@ -2635,7 +2635,7 @@
|
||||
"type": "choice",
|
||||
"prompt": "Querachse benennen",
|
||||
"points": 100,
|
||||
"question": "Bei flex-direction: row verlaeuft die Querachse ...",
|
||||
"question": "Bei flex-direction: row verläuft die Querachse ...",
|
||||
"options": [
|
||||
"vertikal (oben nach unten)",
|
||||
"horizontal (links nach rechts)",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
body{padding-left:0!important;padding-bottom:0!important}
|
||||
.mf-a11y-trigger{position:static!important;width:38px;height:38px;z-index:auto;flex:0 0 38px;box-shadow:0 0 14px var(--glow-w);border-radius:10px}
|
||||
.mf-a11y-trigger svg{width:18px;height:18px}
|
||||
/* (Legacy-Dock-Padding-Reset entfallen – es gibt kein body-padding-Dock mehr.) */
|
||||
.mf-a11y-trigger{position:static!important;width:42px;height:42px;z-index:auto;flex:0 0 42px;box-shadow:0 0 14px var(--glow-w);border-radius:10px}
|
||||
.mf-a11y-trigger svg{width:20px;height:20px}
|
||||
.mf-a11y-panel{inset:76px 16px auto auto!important;width:min(450px,calc(100vw - 32px));max-height:calc(100dvh - 92px)}
|
||||
.mf-a11y-utility{position:sticky;top:0;z-index:1700;min-height:60px;display:flex;align-items:center;justify-content:flex-end;padding:.5rem 1rem;background:var(--panel);border-bottom:1px solid var(--border)}
|
||||
.mf-theme-float{display:flex;align-items:center;gap:.55rem}
|
||||
@@ -13,12 +13,10 @@ body.mf-a11y-skin-finalflux{--mf-a11y-accent:var(--ff-accent);--mf-a11y-glow:var
|
||||
body.mf-a11y-skin-bughunter{--mf-a11y-accent:var(--bh-accent);--mf-a11y-glow:var(--bh-glow);--mf-a11y-ink:#210c12}
|
||||
body.mf-a11y-skin-networker{--mf-a11y-accent:var(--nw-accent);--mf-a11y-glow:var(--nw-glow);--mf-a11y-ink:#061321}
|
||||
body.mf-a11y-skin-serviceclash{--mf-a11y-accent:var(--sc-accent);--mf-a11y-glow:var(--sc-glow);--mf-a11y-ink:#06171c}
|
||||
body[class*="mf-a11y-skin-"] .mf-a11y-trigger{border-color:color-mix(in srgb,var(--mf-a11y-accent) 65%,var(--border));background:linear-gradient(135deg,var(--mf-a11y-accent),color-mix(in srgb,var(--mf-a11y-accent) 72%,#fff));color:var(--mf-a11y-ink);box-shadow:0 0 16px var(--mf-a11y-glow)}
|
||||
body[class*="mf-a11y-skin-"] .mf-a11y-trigger{border-color:color-mix(in srgb,var(--mf-a11y-accent) 65%,var(--border));background:var(--surface);color:var(--mf-a11y-accent);box-shadow:0 0 16px var(--mf-a11y-glow)}
|
||||
body[class*="mf-a11y-skin-"] .mf-a11y-panel{border-color:color-mix(in srgb,var(--mf-a11y-accent) 56%,var(--border));box-shadow:0 12px 45px rgba(0,0,0,.55),0 0 22px var(--mf-a11y-glow)}
|
||||
body[class*="mf-a11y-skin-"] .mf-a11y-head{background:color-mix(in srgb,var(--mf-a11y-accent) 28%,#07121f)}
|
||||
body[class*="mf-a11y-skin-"] .mf-a11y-close{background:var(--mf-a11y-accent);color:var(--mf-a11y-ink)}
|
||||
body[class*="mf-a11y-skin-"] .mf-a11y-option[aria-pressed="true"]{border-color:var(--mf-a11y-accent);background:color-mix(in srgb,var(--mf-a11y-accent) 18%,var(--surface));box-shadow:inset 0 0 0 2px var(--mf-a11y-accent)}
|
||||
.bh-cabinet-level{grid-column:1/-1;margin:.5rem 0 -.15rem;padding-bottom:.35rem;border-bottom:1px solid #60473d;color:var(--bh-accent);font:700 .66rem "Courier New",monospace;letter-spacing:.12em}
|
||||
.bh-drawer.solved{overflow:hidden}.bh-solved-stamp{display:inline-block;border:3px solid #79bf91;border-radius:4px;padding:.24rem .48rem;color:#79bf91;font:700 .78rem/1 "Courier New",monospace;letter-spacing:.12em;transform:rotate(-12deg);text-shadow:none;box-shadow:0 0 12px color-mix(in srgb,#79bf91 26%,transparent);pointer-events:none}.bh-drawer .bh-solved-stamp{position:absolute;right:3.1rem;top:.75rem}.bh-solved-stamp-detail{position:absolute;right:1.4rem;top:1.25rem;font-size:1rem}.bh-solved-stamp-result{margin:0 0 .8rem;font-size:1.15rem}
|
||||
.nw-app{background:repeating-linear-gradient(0deg,rgba(103,187,255,.12) 0 1px,transparent 1px 30px),repeating-linear-gradient(90deg,rgba(103,187,255,.12) 0 1px,transparent 1px 30px),radial-gradient(circle at 50% -20%,rgba(27,89,148,.38),transparent 48%),#071527!important}.nw-board{background:repeating-linear-gradient(0deg,rgba(103,187,255,.12) 0 1px,transparent 1px 30px),repeating-linear-gradient(90deg,rgba(103,187,255,.12) 0 1px,transparent 1px 30px),rgba(5,18,33,.9)!important}
|
||||
body[class*="mf-a11y-skin-"] .mf-a11y-option[aria-pressed="true"],body[class*="mf-a11y-skin-"] .mf-a11y-option.is-active{border-color:var(--mf-a11y-accent);background:color-mix(in srgb,var(--mf-a11y-accent) 18%,var(--surface));box-shadow:inset 0 0 0 2px var(--mf-a11y-accent)}
|
||||
@media(max-width:700px){.mf-a11y-panel{inset:70px 6px auto 6px!important;width:auto;max-height:calc(100dvh - 82px)}}
|
||||
html[data-theme="light"] body{--mf-a11y-ink:#fff}html[data-theme="light"] .mf-a11y-head{background:var(--surface-2);color:var(--heading)}
|
||||
|
||||
@@ -3,6 +3,8 @@ html[data-mf-readable="true"] body,html[data-mf-readable="true"] button,html[dat
|
||||
/* Die Tokens liegen je nach Spiel auf body bzw. der jeweiligen Spielwurzel.
|
||||
Deshalb müssen die Overrides auf der ganzen Oberfläche gelten – ein Override
|
||||
nur auf html wurde von den lokalen Themes bislang wieder überschrieben. */
|
||||
/* Hochkontrast ist BEWUSST immer dunkel (maximaler, deterministischer Kontrast) –
|
||||
auch im Light Mode. Im Button-Untertitel als "immer dunkel" gekennzeichnet. */
|
||||
html[data-mf-contrast="true"] body,html[data-mf-contrast="true"] body *{--bg:#000!important;--panel:#050505!important;--panel-2:#080808!important;--surface:#0b0b0b!important;--surface-2:#151515!important;--text:#fff!important;--heading:#fff!important;--muted:#f1f1f1!important;--border:#fff!important;--line:#fff!important;--border-accent:#6fffb5!important;--accent:#6fffb5!important;--accent-2:#fff!important;--secondary:#83dbff!important;--secondary-2:#fff!important;--success:#83dbff!important;--danger:#ff9caf!important;--esc-accent:#6fffb5!important;--bh-accent:#6fffb5!important;--sc-accent:#83dbff!important;--nw-accent:#83dbff!important;--mm-accent:#6fffb5!important;--mm-secondary:#83dbff!important;--tt-accent:#6fffb5!important;--tt-secondary:#83dbff!important;--dl-accent:#6fffb5!important;--dl-secondary:#83dbff!important;--ss-accent:#6fffb5!important;--ss-secondary:#83dbff!important;--ff-accent:#6fffb5!important;--ff-secondary:#83dbff!important;--glow-s:transparent!important;--glow-w:transparent!important;--esc-glow:transparent!important;--bh-glow:transparent!important;--sc-glow:transparent!important;--nw-glow:transparent!important;--mm-glow:transparent!important;--tt-glow:transparent!important;--dl-glow:transparent!important;--ss-glow:transparent!important;--ff-glow:transparent!important}
|
||||
html[data-mf-contrast="true"] body{background:#000!important;color:#fff!important;color-scheme:dark}
|
||||
html[data-mf-contrast="true"] body *,html[data-mf-contrast="true"] body *::before,html[data-mf-contrast="true"] body *::after{text-shadow:none!important;box-shadow:none!important}
|
||||
@@ -22,6 +24,10 @@ html[data-mf-color="safe"].sn-platform body .mf-card-finalprotocol{--accent:#999
|
||||
html[data-mf-mono="true"] body{filter:grayscale(1)}html[data-mf-links="true"] a{text-decoration:underline!important;text-underline-offset:.18em;text-decoration-thickness:.12em}
|
||||
html[data-mf-motion="false"] *,html[data-mf-motion="false"] *::before,html[data-mf-motion="false"] *::after{animation-duration:.001ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-duration:.001ms!important}
|
||||
html[data-mf-cursor="true"],html[data-mf-cursor="true"] *{cursor:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cpath d='M2 1v25l7-7 5 11 5-2-5-10h10z' fill='white' stroke='black' stroke-width='2'/%3E%3C/svg%3E") 2 1,auto!important}
|
||||
body{padding-left:72px}.mf-a11y-dock{position:fixed;inset:0 auto 0 0;z-index:1799;width:72px;display:flex;align-items:flex-end;justify-content:center;padding:0 0 18px;background:color-mix(in srgb,var(--panel) 94%,transparent);border-right:1px solid var(--border);box-shadow:5px 0 18px rgba(0,0,0,.16)}
|
||||
.mf-a11y-trigger{position:fixed;left:8px;bottom:18px;z-index:1800;width:56px;height:56px;flex:0 0 auto;border:1px solid var(--border-accent);border-radius:50%;display:grid;place-items:center;background:linear-gradient(135deg,var(--accent),var(--accent-2));color:var(--accent-ink);box-shadow:0 0 22px var(--glow-s),0 7px 24px rgba(0,0,0,.38);cursor:pointer}.mf-a11y-trigger svg{width:31px;height:31px}.mf-a11y-trigger:focus-visible,.mf-a11y-panel button:focus-visible{outline:3px solid var(--accent);outline-offset:3px}
|
||||
.mf-a11y-panel{position:fixed;left:82px;bottom:18px;z-index:1801;width:min(450px,calc(100vw - 100px));max-height:calc(100dvh - 36px);overflow:auto;border:1px solid var(--border);border-radius:10px;background:var(--panel);color:var(--text);box-shadow:0 12px 45px rgba(0,0,0,.55)}.mf-a11y-panel[hidden]{display:none}.mf-a11y-head{position:sticky;top:0;display:flex;align-items:center;gap:1rem;padding:.65rem;background:#07121f;color:#fff;z-index:1}.mf-a11y-head h2{font:800 1.05rem Arial,sans-serif;margin:0 auto}.mf-a11y-close{width:44px;height:44px;border:0;border-radius:6px;background:#fff;color:#07121f;font-size:1.8rem;cursor:pointer}.mf-a11y-grid{display:grid;grid-template-columns:1fr 1fr;gap:4px;padding:10px;background:var(--surface)}.mf-a11y-option{min-height:88px;border:1px solid var(--border);border-radius:6px;background:var(--surface-2);color:var(--text);padding:.65rem;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.35rem;font:inherit;cursor:pointer}.mf-a11y-option strong{font-size:1.35rem}.mf-a11y-option[aria-pressed="true"]{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 18%,var(--surface));box-shadow:inset 0 0 0 2px var(--accent)}.mf-a11y-option small{color:var(--muted)}@media(max-width:700px){body{padding-left:0;padding-bottom:68px}.mf-a11y-dock{inset:auto 0 0 0;width:auto;height:68px;padding:0 10px;justify-content:flex-start;align-items:center;border-right:0;border-top:1px solid var(--border);box-shadow:0 -5px 18px rgba(0,0,0,.18)}.mf-a11y-trigger{width:52px;height:52px}.mf-a11y-panel{left:6px;bottom:74px;width:calc(100vw - 12px);max-height:calc(100dvh - 86px)}.mf-a11y-grid{grid-template-columns:1fr 1fr}.mf-a11y-option{min-height:78px;font-size:.82rem}}
|
||||
/* Legacy-Dock entfernt: der A11y-Trigger sitzt jetzt als 42px-Icon-Button im Header
|
||||
(Platzierung in accessibility-placement.css). Kein body-padding / Fixed-Dock mehr. */
|
||||
.mf-a11y-trigger{width:42px;height:42px;border:1px solid var(--border);border-radius:10px;display:inline-flex;align-items:center;justify-content:center;background:var(--surface);color:var(--muted);cursor:pointer;transition:all .18s}.mf-a11y-trigger:hover{color:var(--accent);border-color:var(--border-accent);box-shadow:0 0 14px var(--glow-w)}.mf-a11y-trigger svg{width:20px;height:20px}.mf-a11y-trigger:focus-visible,.mf-a11y-panel button:focus-visible{outline:3px solid var(--accent);outline-offset:3px}
|
||||
.mf-a11y-panel{position:fixed;left:82px;bottom:18px;z-index:1801;width:min(450px,calc(100vw - 100px));max-height:calc(100dvh - 36px);overflow:auto;border:1px solid var(--border);border-radius:10px;background:var(--panel);color:var(--text);box-shadow:0 12px 45px rgba(0,0,0,.55)}.mf-a11y-panel[hidden]{display:none}.mf-a11y-head{position:sticky;top:0;display:flex;align-items:center;gap:1rem;padding:.65rem;background:#07121f;color:#fff;z-index:1}.mf-a11y-head h2{font:800 1.05rem Arial,sans-serif;margin:0 auto}.mf-a11y-close{width:44px;height:44px;border:0;border-radius:6px;background:#fff;color:#07121f;font-size:1.8rem;cursor:pointer}.mf-a11y-grid{display:grid;grid-template-columns:1fr 1fr;gap:4px;padding:10px;background:var(--surface)}.mf-a11y-option{min-height:88px;border:1px solid var(--border);border-radius:6px;background:var(--surface-2);color:var(--text);padding:.65rem;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.35rem;font:inherit;cursor:pointer}.mf-a11y-option strong{font-size:1.35rem}.mf-a11y-option[aria-pressed="true"],.mf-a11y-option.is-active{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 18%,var(--surface));box-shadow:inset 0 0 0 2px var(--accent)}.mf-a11y-option small{color:var(--muted)}@media(max-width:700px){.mf-a11y-panel{left:6px;bottom:74px;width:calc(100vw - 12px);max-height:calc(100dvh - 86px)}.mf-a11y-grid{grid-template-columns:1fr 1fr}.mf-a11y-option{min-height:78px;font-size:.82rem}}
|
||||
/* Okabe-Ito accents need darker values against the light page surface. */
|
||||
html[data-theme="light"][data-mf-color="safe"] body,html[data-theme="light"][data-mf-color="safe"] body *{--success:#0072b2!important;--danger:#8a5f00!important;--secondary:#9c4f79!important;--secondary-2:#19607f!important;--esc-accent:#8a5f00!important;--bh-accent:#a94500!important;--sc-accent:#19607f!important;--nw-accent:#0072b2!important;--mm-accent:#9c4f79!important;--mm-secondary:#19607f!important;--tt-accent:#7a6a00!important;--tt-secondary:#0072b2!important;--dl-accent:#007054!important;--dl-secondary:#19607f!important;--ss-accent:#0072b2!important;--ss-secondary:#8a5f00!important;--ff-accent:#a94500!important;--ff-secondary:#9c4f79!important}
|
||||
.mf-a11y-trigger:hover{box-shadow:var(--g-amb)}
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(145deg, #1f1f2e, #2c2c3d);
|
||||
color: #ffffff;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.achievements-container {
|
||||
max-width: 1400px;
|
||||
margin: 2rem auto;
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
animation: fadeInUp 0.8s ease-out;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
color: #00e5ff;
|
||||
text-shadow: 0 0 5px rgba(0, 229, 255, 0.2);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.slogan {
|
||||
text-align: center;
|
||||
color: #ccc;
|
||||
margin-bottom: 2rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
||||
/* === Zurück-Button === */
|
||||
.back-button {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
background-color: #00d9ff;
|
||||
color: #0d1117;
|
||||
padding: 10px 18px;
|
||||
border-radius: 10px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 0 10px rgba(0, 217, 255, 0.5);
|
||||
transition: background-color 0.3s ease, transform 0.3s ease;
|
||||
font-size: 1rem;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background-color: #00aacc;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* Responsives Verhalten */
|
||||
@media (max-width: 768px) {
|
||||
.back-button {
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
padding: 8px 14px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
.achievements-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.achievement-card {
|
||||
background-color: rgba(10, 15, 25, 0.95);
|
||||
padding: 2rem;
|
||||
border-radius: 20px;
|
||||
text-align: center;
|
||||
box-shadow:
|
||||
0 0 15px rgba(0, 229, 255, 0.07),
|
||||
0 4px 10px rgba(0, 0, 0, 0.3);
|
||||
backdrop-filter: blur(4px);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.achievement-card:hover {
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 0 20px rgba(0, 229, 255, 0.2);
|
||||
}
|
||||
|
||||
.trophy-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.achievement-card h2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.achievement-card p {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.status {
|
||||
margin-top: 1rem;
|
||||
font-weight: bold;
|
||||
color: #00e5ff;
|
||||
}
|
||||
|
||||
.locked {
|
||||
opacity: 0.6;
|
||||
filter: grayscale(70%);
|
||||
}
|
||||
|
||||
/* === Unlock-Effekt für freigeschaltete Achievements === */
|
||||
.achievement-card.unlocked {
|
||||
animation: unlock-glow 1s ease-out;
|
||||
}
|
||||
|
||||
/* Animation für Unlock-Glow */
|
||||
@keyframes unlock-glow {
|
||||
0% {
|
||||
box-shadow: 0 0 0 rgba(0, 229, 255, 0);
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 20px rgba(0, 229, 255, 0.7);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 10px rgba(0, 229, 255, 0.2);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Sanftes Pulsieren beim Hover über freigeschaltete Achievements */
|
||||
.achievement-card.unlocked:hover {
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 10px rgba(0, 229, 255, 0.2);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 20px rgba(0, 229, 255, 0.5);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 10px rgba(0, 229, 255, 0.2);
|
||||
}
|
||||
}
|
||||
/* === Footer === */
|
||||
footer {
|
||||
background-color: #111122;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: #66d9ef;
|
||||
text-decoration: none;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
footer a:hover {
|
||||
text-decoration: underline;
|
||||
@@ -6,6 +6,12 @@
|
||||
@property --mf-ang { syntax: '<angle>'; initial-value: 0deg; inherits: false; }
|
||||
|
||||
.auth-stage { width:100%; display:flex; flex-direction:column; align-items:center; gap:1.4rem; }
|
||||
.auth-intro { width:100%; max-width:840px; padding:1.25rem 1.4rem; border:1px solid var(--border); border-radius:16px; background:var(--surface); box-shadow:var(--g-soft); }
|
||||
.auth-intro h1 { color:var(--heading); font-size:clamp(1.35rem,3vw,1.85rem); line-height:1.18; margin:.35rem 0 .6rem; }
|
||||
.auth-intro p { color:var(--muted); line-height:1.6; max-width:72ch; }
|
||||
.auth-intro ul { display:grid; grid-template-columns:repeat(3,1fr); gap:.7rem 1rem; list-style:none; margin-top:.9rem; padding:0; }
|
||||
.auth-intro li { color:var(--muted); font-size:.82rem; line-height:1.45; }
|
||||
.auth-intro li b { color:var(--accent); }
|
||||
|
||||
/* ---- Dual-Panel-Container (Bild 2) ---- */
|
||||
.auth-container {
|
||||
@@ -149,6 +155,8 @@
|
||||
|
||||
/* ---- Responsiv: gestapelt, ohne Overlay ---- */
|
||||
@media (max-width:760px) {
|
||||
.auth-intro { padding:1rem; }
|
||||
.auth-intro ul { grid-template-columns:1fr; gap:.45rem; }
|
||||
.auth-container { max-width:420px; min-height:auto; }
|
||||
.overlay-container { display:none; }
|
||||
.form-box {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1,2 @@
|
||||
.esc-how{display:grid;grid-template-columns:repeat(3,1fr);gap:.65rem;margin:0 0 1rem}.esc-how>div{display:flex;align-items:center;gap:.65rem;padding:.7rem;background:var(--surface);border:1px solid var(--border);border-radius:9px;color:var(--muted)}.esc-how b{display:grid;place-items:center;min-width:1.7rem;height:1.7rem;border:1px solid var(--border);border-radius:50%;color:var(--muted)}.esc-how strong,.esc-how small{display:block}.esc-how strong{color:var(--heading);font-size:.78rem}.esc-how small{margin-top:.15rem;font-size:.65rem}.esc-how .done,.esc-how .active{border-color:var(--esc-accent)}.esc-how .done b{background:var(--success);border-color:var(--success);color:#06120a}.esc-how .active{box-shadow:0 0 18px var(--esc-glow)}.esc-puzzle:not(.done) .status{color:var(--esc-accent)}.esc-puzzle:not(.done):hover{border-color:var(--esc-accent);transform:translateY(-2px)}.esc-fragment{display:flex;align-items:center;gap:.45rem}.esc-fragment small{display:grid;place-items:center;width:1.25rem;height:1.25rem;border-radius:50%;background:var(--esc-accent);color:#160d00;font-size:.6rem}.esc-inventory.complete{border-style:solid;border-color:var(--success)}.esc-ready{margin:.7rem 0 0;color:var(--success);font-size:.75rem}.esc-door{flex-wrap:wrap}.esc-door.ready{border-color:var(--esc-accent);box-shadow:0 0 22px color-mix(in srgb,var(--esc-accent) 14%,transparent)}.esc-door-title{flex-basis:100%;display:flex;justify-content:space-between;align-items:center}.esc-door-title strong{color:var(--heading);font-size:.8rem}.esc-door-feedback{flex-basis:100%;min-height:1.2rem;font-size:.75rem}.esc-door-feedback.ok{color:var(--success)}.esc-door-feedback.bad{color:var(--danger)}.esc-door-feedback.wait{color:var(--esc-accent)}.esc-input.bad{border-color:var(--danger);box-shadow:0 0 0 2px color-mix(in srgb,var(--danger) 28%,transparent)}.esc-btn:disabled{opacity:.45;cursor:not-allowed;box-shadow:none}@media(max-width:800px){.esc-how{grid-template-columns:1fr}.esc-door-title{align-items:flex-start}}
|
||||
.esc-how .done b,.esc-fragment small{color:var(--accent-ink)}.esc-how .active{box-shadow:var(--g-strong)}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -344,3 +344,4 @@ body.finalflux-page {
|
||||
/* Prüfungszentrale: ruhige Gold-/Indigo-Konstellation statt Spielhallen-Optik. */
|
||||
body.finalflux-shell,body.finalflux-page { --accent:var(--ff-accent); --accent-2:var(--ff-secondary); --glow-s:var(--ff-glow); --glow-w:color-mix(in srgb,var(--ff-accent) 14%,transparent); background:radial-gradient(circle at 17% 13%,color-mix(in srgb,var(--ff-secondary) 17%,transparent),transparent 30%),radial-gradient(circle at 82% 8%,color-mix(in srgb,var(--ff-accent) 13%,transparent),transparent 28%),#0b0d1b; }
|
||||
body.finalflux-shell::before,body.finalflux-page::before { content:""; position:fixed; inset:0; pointer-events:none; opacity:.28; background-image:radial-gradient(circle,var(--ff-accent) 0 1px,transparent 1.5px),radial-gradient(circle,var(--ff-secondary) 0 1px,transparent 1.5px); background-size:91px 91px,137px 137px; background-position:14px 22px,47px 65px; }.finalflux-sidebar,.finalflux-main,.finalflux-page { position:relative; z-index:1; }.finalflux-hero,.finalflux-panel,.finalflux-page section { border-left-color:var(--ff-secondary); box-shadow:0 10px 28px color-mix(in srgb,var(--ff-secondary) 9%,transparent); }
|
||||
body.finalflux-shell,body.finalflux-page{background:radial-gradient(circle at 17% 13%,color-mix(in srgb,var(--ff-secondary) 17%,transparent),transparent 30%),radial-gradient(circle at 82% 8%,color-mix(in srgb,var(--ff-accent) 13%,transparent),transparent 28%),var(--bg)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* FluxTimer – Zeitschaltkreis-Optik, rein clientseitig. */
|
||||
.ft-trigger { position:relative; color:var(--fluxtimer-secondary); }
|
||||
.ft-trigger-icon { width:22px!important; height:22px!important; fill:none; stroke:currentColor; stroke-width:1.35; }
|
||||
.ft-trigger-icon { width:20px; height:20px; fill:none; stroke:currentColor; stroke-width:1.5; }
|
||||
.ft-trigger-bolt { fill:color-mix(in srgb,currentColor 24%,transparent); stroke-width:1.5; stroke-linejoin:round; }
|
||||
.ft-trigger-dot { position:absolute; right:4px; top:4px; width:6px; height:6px; border-radius:50%; background:var(--muted); border:1px solid var(--surface); opacity:.5; }
|
||||
.ft-trigger.ft-active { color:var(--fluxtimer-primary); border-color:color-mix(in srgb,var(--fluxtimer-primary) 65%,var(--border)); box-shadow:0 0 14px color-mix(in srgb,var(--fluxtimer-primary) 35%,transparent); }
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
/* Mindflux – Gemeinsames Guide-Modul „So funktioniert's".
|
||||
Styles für den „?"-Trigger, den Tipp-Button, das Overlay und den MindMatch-Hint-Glow.
|
||||
Theme-bewusst: dunkel als Standard, helle Überschreibungen für data-theme="light". */
|
||||
|
||||
/* ---------- Buttons in der Header-Tools-Leiste ---------- */
|
||||
.mf-guide-trigger,
|
||||
.mf-guide-hint {
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
border: 1px solid rgba(255, 255, 255, 0.28);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border-radius: 999px;
|
||||
transition: background-color .2s ease, border-color .2s ease, transform .12s ease;
|
||||
}
|
||||
.mf-guide-trigger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.1rem;
|
||||
height: 2.1rem;
|
||||
font-size: 1.05rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
.mf-guide-hint {
|
||||
padding: .34rem .8rem;
|
||||
font-size: .85rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mf-guide-trigger:hover,
|
||||
.mf-guide-hint:hover:not(:disabled) {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.mf-guide-trigger:focus-visible,
|
||||
.mf-guide-hint:focus-visible {
|
||||
outline: 2px solid currentColor;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.mf-guide-hint:disabled {
|
||||
opacity: .45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* ---------- Overlay ---------- */
|
||||
.mf-guide-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 99999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1.25rem;
|
||||
background: rgba(5, 8, 16, 0.72);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
.mf-guide-backdrop[hidden] { display: none; }
|
||||
|
||||
.mf-guide-panel {
|
||||
width: min(34rem, 100%);
|
||||
max-height: min(82vh, 640px);
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
padding: 1.4rem 1.5rem 1.6rem;
|
||||
border-radius: 18px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
background: #141a26;
|
||||
color: #eef2f8;
|
||||
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
|
||||
animation: mf-guide-in .22s ease;
|
||||
}
|
||||
@keyframes mf-guide-in {
|
||||
from { opacity: 0; transform: translateY(12px) scale(.98); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
}
|
||||
.mf-guide-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
margin-bottom: .4rem;
|
||||
}
|
||||
.mf-guide-kicker {
|
||||
font-size: .72rem;
|
||||
letter-spacing: .16em;
|
||||
text-transform: uppercase;
|
||||
opacity: .7;
|
||||
}
|
||||
.mf-guide-close {
|
||||
flex: none;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
font-size: 1.3rem;
|
||||
line-height: 1;
|
||||
color: inherit;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
.mf-guide-close:hover { background: rgba(255, 255, 255, 0.18); }
|
||||
.mf-guide-title {
|
||||
margin: 0 0 .9rem;
|
||||
font-size: 1.3rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.mf-guide-steps {
|
||||
margin: 0 0 1rem;
|
||||
padding-left: 1.2rem;
|
||||
display: grid;
|
||||
gap: .5rem;
|
||||
}
|
||||
.mf-guide-steps li { line-height: 1.45; }
|
||||
.mf-guide-tip {
|
||||
margin: 0;
|
||||
padding: .75rem .9rem;
|
||||
border-radius: 12px;
|
||||
line-height: 1.45;
|
||||
background: rgba(120, 170, 255, 0.14);
|
||||
border: 1px solid rgba(120, 170, 255, 0.3);
|
||||
}
|
||||
body.mf-guide-open { overflow: hidden; }
|
||||
|
||||
/* ---------- Helles Theme ---------- */
|
||||
:root[data-theme="light"] .mf-guide-trigger,
|
||||
:root[data-theme="light"] .mf-guide-hint {
|
||||
border-color: rgba(20, 30, 50, 0.28);
|
||||
background: rgba(20, 30, 50, 0.05);
|
||||
}
|
||||
:root[data-theme="light"] .mf-guide-trigger:hover,
|
||||
:root[data-theme="light"] .mf-guide-hint:hover:not(:disabled) {
|
||||
background: rgba(20, 30, 50, 0.12);
|
||||
border-color: rgba(20, 30, 50, 0.45);
|
||||
}
|
||||
:root[data-theme="light"] .mf-guide-backdrop { background: rgba(15, 23, 42, 0.5); }
|
||||
:root[data-theme="light"] .mf-guide-panel {
|
||||
background: #ffffff;
|
||||
color: #16202e;
|
||||
border-color: rgba(20, 30, 50, 0.12);
|
||||
}
|
||||
:root[data-theme="light"] .mf-guide-close { background: rgba(20, 30, 50, 0.08); }
|
||||
:root[data-theme="light"] .mf-guide-close:hover { background: rgba(20, 30, 50, 0.16); }
|
||||
:root[data-theme="light"] .mf-guide-tip {
|
||||
background: rgba(60, 110, 220, 0.12);
|
||||
border-color: rgba(60, 110, 220, 0.28);
|
||||
}
|
||||
|
||||
/* ---------- MindMatch – kurz aufgedecktes Tipp-Paar ---------- */
|
||||
.mm-card.hinted {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
box-shadow: 0 0 0 2px #ffd54a, 0 0 22px 5px rgba(255, 213, 74, 0.6);
|
||||
border-radius: 14px;
|
||||
animation: mf-hint-pulse 1s ease;
|
||||
}
|
||||
@keyframes mf-hint-pulse {
|
||||
0%, 100% { filter: none; }
|
||||
50% { filter: brightness(1.12); }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.mf-guide-panel { animation: none; }
|
||||
.mm-card.hinted { animation: none; }
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
.nw-guide{margin:1rem 0;border:1px solid color-mix(in srgb,var(--nw-accent) 62%,var(--border));border-radius:10px;padding:1rem 1.1rem;background:color-mix(in srgb,#0b2540 74%,var(--surface));box-shadow:0 0 18px color-mix(in srgb,var(--nw-accent) 12%,transparent)}.nw-guide h2{margin:.35rem 0 .5rem;color:var(--heading);font-size:1rem}.nw-guide p,.nw-guide li{color:var(--muted);font-size:.82rem;line-height:1.5}.nw-guide ol{margin:.45rem 0;padding-left:1.25rem}.nw-guide strong{color:var(--text)}.nw-guide code{color:var(--nw-accent);font:700 .78rem "Courier New",monospace}.nw-guide details{margin-top:.7rem;border-top:1px solid var(--border);padding-top:.6rem}.nw-guide summary{color:var(--nw-accent);cursor:pointer;font:700 .72rem "Courier New",monospace;letter-spacing:.05em}.nw-guide dl{display:grid;grid-template-columns:max-content 1fr;gap:.35rem .75rem;margin:.65rem 0 0}.nw-guide dt{color:var(--nw-accent);font-weight:700}.nw-guide dd{margin:0;color:var(--muted);font-size:.8rem}
|
||||
[data-theme="light"] .nw-guide{background:var(--surface);border-color:#d3e2f0;box-shadow:var(--g-soft)}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -93,5 +93,28 @@ html[data-theme="light"].sn-platform body .mf-footer { background:rgba(244,251,2
|
||||
html[data-theme="light"].sn-platform body .auth-container { background:linear-gradient(145deg,rgba(255,255,255,.98),rgba(239,249,243,.98)); border-color:rgba(22,122,82,.38); }
|
||||
html[data-theme="light"].sn-platform body .overlay { background:radial-gradient(520px 320px at 62% 18%,rgba(22,122,82,.15),transparent 62%),linear-gradient(150deg,#e8f6ef,#f7fcf9); }
|
||||
html[data-theme="light"].sn-platform body .auth-head b { color:#167a52; }
|
||||
/* Light-Kartenakzente: farbton-treu abgedunkelt, damit die Neon-Werte aus Z.41-49
|
||||
nicht auf die fast-weissen Karten leaken. Alle Akzente >=4.6:1 auf Kartengrund,
|
||||
weisse Ink darauf >=4.9:1; --accent-2 als hellerer Deko-Ton. */
|
||||
html[data-theme="light"].sn-platform body .mf-card-mindmatch { --accent:#a62c74; --accent-2:#c87ca9; --glow-s:rgba(166,44,116,.22); }
|
||||
html[data-theme="light"].sn-platform body .mf-card-techtacular { --accent:#67700a; --accent-2:#a1a667; --glow-s:rgba(103,112,10,.22); }
|
||||
html[data-theme="light"].sn-platform body .mf-card-devlab { --accent:#167a52; --accent-2:#6fad94; --glow-s:rgba(22,122,82,.22); }
|
||||
html[data-theme="light"].sn-platform body .mf-card-syntaxsaga { --accent:#6742d6; --accent-2:#a18ae6; --glow-s:rgba(103,66,214,.22); }
|
||||
html[data-theme="light"].sn-platform body .mf-card-codeescape { --accent:#8a6a06; --accent-2:#b6a365; --glow-s:rgba(138,106,6,.22); }
|
||||
html[data-theme="light"].sn-platform body .mf-card-serviceclash { --accent:#0e7c86; --accent-2:#6aaeb4; --glow-s:rgba(14,124,134,.22); }
|
||||
html[data-theme="light"].sn-platform body .mf-card-bughunter { --accent:#c22736; --accent-2:#d97982; --glow-s:rgba(194,39,54,.22); }
|
||||
html[data-theme="light"].sn-platform body .mf-card-networker { --accent:#1668a8; --accent-2:#6fa1c9; --glow-s:rgba(22,104,168,.22); }
|
||||
html[data-theme="light"].sn-platform body .mf-card-finalprotocol{ --accent:#b5490f; --accent-2:#d18e6a; --glow-s:rgba(181,73,15,.22); }
|
||||
/* Light-Variante des finalflux-Shell-Overrides (dunkel: Z.57-60). */
|
||||
html[data-theme="light"].sn-platform body.finalflux-shell, html[data-theme="light"].sn-platform body.finalflux-page {
|
||||
--accent:#b5490f; --accent-2:#d18e6a; --accent-ink:#fff;
|
||||
--glow-s:rgba(181,73,15,.22); --glow-w:rgba(181,73,15,.12);
|
||||
}
|
||||
@media (max-width:820px) { html.sn-platform body .mf-sidebar { background:#061114; } }
|
||||
@media (max-width:820px) { html[data-theme="light"].sn-platform body .mf-sidebar { background:#f5fbf7; } }
|
||||
/* Light-mode text must not inherit the white-on-color auth overlay treatment. */
|
||||
html[data-theme="light"].sn-platform body .mf-logo .mark,html[data-theme="light"].sn-platform body .auth-head .mark,html[data-theme="light"].sn-platform body .overlay-panel .o-emblem{filter:none}
|
||||
html[data-theme="light"].sn-platform body .overlay-panel h3{color:#10291f;text-shadow:none}
|
||||
html[data-theme="light"].sn-platform body .overlay-panel p{color:#3c554a}
|
||||
html[data-theme="light"].sn-platform body .ghost-btn{color:#167a52;border-color:rgba(22,122,82,.55)}
|
||||
html[data-theme="light"].sn-platform body .ghost-btn:hover{background:rgba(22,122,82,.10)}
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
|
||||
/* === Zurück-Button === */
|
||||
.back-button {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
background-color: #00d9ff;
|
||||
color: #0d1117;
|
||||
padding: 10px 18px;
|
||||
border-radius: 10px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 0 10px rgba(0, 217, 255, 0.5);
|
||||
transition: background-color 0.3s ease, transform 0.3s ease;
|
||||
font-size: 1rem;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background-color: #00aacc;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* Responsives Verhalten */
|
||||
@media (max-width: 768px) {
|
||||
.back-button {
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
padding: 8px 14px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(145deg, #1f1f2e, #2c2c3d);
|
||||
color: #ffffff;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
max-width: 1400px;
|
||||
margin: 2rem auto;
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
animation: fadeInUp 0.8s ease-out;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
color: #00e5ff;
|
||||
text-shadow: 0 0 5px rgba(0, 229, 255, 0.2);
|
||||
}
|
||||
|
||||
/* === Gesamtfortschritt === */
|
||||
.section-overall-progress {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
flex-wrap: wrap;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.progress-summary {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: 2rem;
|
||||
background: rgba(0, 9, 22, 0.95);
|
||||
padding: 2rem;
|
||||
border-radius: 20px;
|
||||
box-shadow:
|
||||
0 0 15px rgba(0, 229, 255, 0.05),
|
||||
0 4px 10px rgba(0, 0, 0, 0.3);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.circular-progress {
|
||||
position: relative;
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
.percentage-label {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 1.6rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* XP-Leiste */
|
||||
.xp-bar {
|
||||
background-color: #1a1a2e;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
height: 20px;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.xp-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #00e5ff, #4cc3dd);
|
||||
}
|
||||
|
||||
/* === Lernzeitbereich === */
|
||||
.section-learn-time {
|
||||
background: rgba(0, 9, 22, 0.9);
|
||||
padding: 1.5rem;
|
||||
border-radius: 20px;
|
||||
box-shadow:
|
||||
0 0 10px rgba(0, 229, 255, 0.05),
|
||||
0 4px 10px rgba(0, 0, 0, 0.3);
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#learnTimeChart {
|
||||
max-width: 100%;
|
||||
height: 180px !important;
|
||||
}
|
||||
|
||||
/* === Spielkarten === */
|
||||
.section-game-progress {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.game-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.game-card {
|
||||
background-color: rgba(10, 15, 25, 0.95);
|
||||
padding: 1.5rem;
|
||||
border-radius: 16px;
|
||||
box-shadow:
|
||||
0 0 15px rgba(0, 229, 255, 0.07),
|
||||
0 4px 10px rgba(0, 0, 0, 0.3);
|
||||
backdrop-filter: blur(4px);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.game-card:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.game-progress-bar {
|
||||
background-color: #1a1a2e;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
height: 10px;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.game-progress-bar .fill {
|
||||
height: 100%;
|
||||
background-color: #00e5ff;
|
||||
}
|
||||
|
||||
/* === Footer === */
|
||||
footer {
|
||||
background-color: #111122;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: #66d9ef;
|
||||
text-decoration: none;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
footer a:hover {
|
||||
text-decoration: underline;
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,207 +0,0 @@
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(145deg, #1f1f2e, #2c2c3d);
|
||||
color: #ffffff;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
header {
|
||||
position: relative; /* wichtig für .back-button */
|
||||
text-align: center;
|
||||
padding: 2rem 1rem 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2.5rem;
|
||||
color: #00e5ff;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
header .slogan {
|
||||
font-size: 1.2rem;
|
||||
color: #ccc;
|
||||
text-shadow: 0 0 5px rgba(0, 229, 255, 0.2);
|
||||
}
|
||||
|
||||
/* === Zurück-Button === */
|
||||
.back-button {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
background-color: #00d9ff;
|
||||
color: #0d1117;
|
||||
padding: 10px 18px;
|
||||
border-radius: 10px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 0 10px rgba(0, 217, 255, 0.5);
|
||||
transition: background-color 0.3s ease, transform 0.3s ease;
|
||||
font-size: 1rem;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background-color: #00aacc;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* Responsives Verhalten */
|
||||
@media (max-width: 768px) {
|
||||
.back-button {
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
padding: 8px 14px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.back-button {
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
padding: 6px 10px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-container {
|
||||
max-width: 1000px;
|
||||
margin: 2rem auto;
|
||||
padding: 2rem;
|
||||
background: rgba(0, 9, 22, 0.95);
|
||||
border-radius: 20px;
|
||||
box-shadow:
|
||||
0 0 15px rgba(0, 229, 255, 0.05),
|
||||
0 4px 10px rgba(0, 0, 0, 0.3);
|
||||
backdrop-filter: blur(4px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.section {
|
||||
background-color: rgba(10, 15, 25, 0.95);
|
||||
padding: 1.5rem;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 0 10px rgba(0, 229, 255, 0.1);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.section:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 6px 20px rgba(0, 229, 255, 0.2);
|
||||
}
|
||||
|
||||
.section h2 {
|
||||
margin-bottom: 1rem;
|
||||
color: #00e5ff;
|
||||
}
|
||||
|
||||
.switch-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background-color: #888;
|
||||
transition: 0.4s;
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
transition: 0.4s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #00e5ff;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(24px);
|
||||
}
|
||||
|
||||
.settings-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.settings-form input {
|
||||
padding: 0.8rem;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
background: #1a1a2e;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.settings-form button {
|
||||
padding: 0.8rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background-color: #00e5ff;
|
||||
color: #00172e;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.settings-form button:hover {
|
||||
background-color: #4cc3dd;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.danger-zone button {
|
||||
background-color: #ff4d4d;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.danger-zone button:hover {
|
||||
background-color: #cc0000;
|
||||
}
|
||||
|
||||
/* === Footer === */
|
||||
footer {
|
||||
background-color: #111122;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: #66d9ef;
|
||||
text-decoration: none;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
body {
|
||||
background-color: #0d1117;
|
||||
color: #00d9ff;
|
||||
font-family: 'Poppins', sans-serif; /* oder deine Mindflux-Schrift */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 40px;
|
||||
border: 1px solid #00d9ff; /* feiner als dashed */
|
||||
border-radius: 20px;
|
||||
background-color: #121826;
|
||||
box-shadow: 0 0 15px rgba(0, 217, 255, 0.5);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.2rem;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.time-machine {
|
||||
font-size: 2.5rem;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.small-hint {
|
||||
font-size: 0.9rem;
|
||||
margin-top: 20px;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
.back-button {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
background-color: #00d9ff;
|
||||
color: #0d1117;
|
||||
padding: 10px 18px;
|
||||
border-radius: 10px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 0 10px rgba(0, 217, 255, 0.5);
|
||||
transition: background-color 0.3s ease, transform 0.3s ease;
|
||||
font-size: 1rem;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background-color: #00aacc;
|
||||
transform: scale(1.05); /* leichter "Hover-Zoom" */
|
||||
}
|
||||
|
||||
/* Responsives Verhalten */
|
||||
@media (max-width: 768px) {
|
||||
.back-button {
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
padding: 8px 14px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.back-button {
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
padding: 6px 10px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
@@ -1,551 +1,4 @@
|
||||
/* === Reset === */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* === Animationen === */
|
||||
@keyframes fadeInUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
/* Glanz-Effekt für Hover */
|
||||
@keyframes shine {
|
||||
0% {
|
||||
background-position: -100%;
|
||||
}
|
||||
100% {
|
||||
background-position: 200%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fade-In für Container beim Laden */
|
||||
@keyframes fadeInUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* === Body Hintergrund === */
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(145deg, var(--surface), var(--surface-2));
|
||||
color: var(--heading);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* === Header === */
|
||||
header {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
margin: 2rem auto 1rem;
|
||||
width: 90%;
|
||||
max-width: 1200px;
|
||||
background-color: var(--panel);
|
||||
border-radius: 20px;
|
||||
box-shadow:
|
||||
0 8px 20px var(--glow-w),
|
||||
0 4px 12px rgba(0, 0, 0, 0.4);
|
||||
transition: all 0.3s ease-in-out;
|
||||
animation: fadeInUp 0.8s ease-out;
|
||||
}
|
||||
|
||||
header:hover {
|
||||
box-shadow:
|
||||
0 10px 30px var(--glow-s),
|
||||
0 6px 16px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.logo {
|
||||
max-height: 350px;
|
||||
width: auto;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
header img {
|
||||
max-height: 80px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
header p.slogan {
|
||||
font-style: italic;
|
||||
color: var(--accent);
|
||||
font-size: 1.1rem;
|
||||
letter-spacing: 0.5px;
|
||||
text-shadow: 0 0 5px var(--glow-s);
|
||||
}
|
||||
|
||||
/* === Main Bereich === */
|
||||
main {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 2rem;
|
||||
padding: 2rem;
|
||||
flex: 1;
|
||||
width: 90%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
animation: fadeInUp 0.8s ease-out;
|
||||
}
|
||||
|
||||
/* === Info-Textfeld === */
|
||||
.info-box {
|
||||
flex: 1 1 400px;
|
||||
max-width: 600px;
|
||||
background: rgba(0, 9, 22, 0.9);
|
||||
padding: 2rem;
|
||||
border-radius: 16px;
|
||||
box-shadow:
|
||||
0 0 15px var(--glow-w),
|
||||
0 4px 10px rgba(0, 0, 0, 0.3);
|
||||
backdrop-filter: blur(4px);
|
||||
animation: fadeInUp 0.9s ease-out;
|
||||
}
|
||||
|
||||
/* === Login/Register Box === */
|
||||
.login-box {
|
||||
flex: 1 1 300px;
|
||||
max-width: 400px;
|
||||
background: rgba(10, 15, 25, 0.95);
|
||||
padding: 2rem;
|
||||
border-radius: 16px;
|
||||
box-shadow:
|
||||
0 0 15px var(--glow-w),
|
||||
0 4px 10px rgba(0, 0, 0, 0.3);
|
||||
backdrop-filter: blur(4px);
|
||||
animation: fadeInUp 1s ease-out;
|
||||
}
|
||||
|
||||
.login-box h2 {
|
||||
margin-bottom: 1rem;
|
||||
color: var(--accent-2);
|
||||
}
|
||||
|
||||
.login-box form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.login-box input,
|
||||
.login-box button {
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* === Input === */
|
||||
.login-box input {
|
||||
background-color: var(--heading);
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* === Button mit Glanz- und Licht-Effekt === */
|
||||
.login-box button {
|
||||
background-color: var(--accent);
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: background-color 0.3s ease-in-out, transform 0.2s ease;
|
||||
box-shadow: 0 0 10px var(--glow-s);
|
||||
}
|
||||
|
||||
.login-box button::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -75%;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
120deg,
|
||||
rgba(255, 255, 255, 0.2) 0%,
|
||||
rgba(255, 255, 255, 0.5) 50%,
|
||||
rgba(255, 255, 255, 0.2) 100%
|
||||
);
|
||||
transform: skewX(-20deg);
|
||||
animation: shine 2s infinite;
|
||||
}
|
||||
|
||||
.login-box button:hover {
|
||||
background-color: var(--accent-2);
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 0 15px var(--glow-s), 0 0 30px var(--glow-w) inset;
|
||||
}
|
||||
|
||||
/* === Footer === */
|
||||
footer {
|
||||
background-color: var(--panel);
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: var(--accent-2);
|
||||
text-decoration: none;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Kleinere Switch-Größe */
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 42px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background-color: var(--muted);
|
||||
transition: 0.4s;
|
||||
border-radius: 30px;
|
||||
box-shadow: 0 0 6px var(--glow-s);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 6px;
|
||||
font-size: 0.8rem;
|
||||
color: silver;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background-color: var(--heading);
|
||||
transition: 0.4s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* Bei aktiviertem Switch */
|
||||
input:checked + .slider {
|
||||
background-color: var(--accent);
|
||||
color: var(--heading);
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
.icon {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.dashboard-tile {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(0, 9, 22, 0.95);
|
||||
border-radius: 20px;
|
||||
padding: 2rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-decoration: none;
|
||||
color: var(--accent);
|
||||
font-weight: bold;
|
||||
font-size: 1.2rem;
|
||||
box-shadow:
|
||||
0 0 10px var(--glow-w),
|
||||
0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.dashboard-tile:hover {
|
||||
background-color: var(--glow-w);
|
||||
transform: translateY(-3px);
|
||||
box-shadow:
|
||||
0 6px 16px var(--glow-s),
|
||||
0 0 20px var(--glow-w) inset;
|
||||
}
|
||||
|
||||
.dashboard-tile span {
|
||||
color: var(--accent);
|
||||
text-shadow: 0 0 5px var(--glow-s);
|
||||
}
|
||||
|
||||
/* === Profilbereich === */
|
||||
.profile-container {
|
||||
max-width: 800px;
|
||||
margin: 2rem auto;
|
||||
background: rgba(0, 9, 22, 0.95);
|
||||
padding: 2rem;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 0 20px var(--glow-w);
|
||||
animation: fadeInUp 0.8s ease-out;
|
||||
}
|
||||
|
||||
.profile-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.profile-form label {
|
||||
font-weight: bold;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.profile-form input {
|
||||
padding: 0.75rem;
|
||||
background-color: var(--surface-2);
|
||||
color: var(--heading);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: 0.3s ease;
|
||||
}
|
||||
|
||||
.btn.save {
|
||||
background-color: var(--accent);
|
||||
color: var(--surface-2);
|
||||
}
|
||||
|
||||
.btn.save:hover {
|
||||
background-color: var(--accent-2);
|
||||
}
|
||||
|
||||
.btn.danger {
|
||||
background-color: var(--danger);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn.danger:hover {
|
||||
background-color: var(--danger);
|
||||
}
|
||||
|
||||
/* Profilbild */
|
||||
.profile-picture img {
|
||||
max-height: 150px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
/* === Tab-Navigation oben === */
|
||||
.tab-nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
margin: 1rem auto 2rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tab-nav .tab {
|
||||
padding: 0.6rem 1.2rem;
|
||||
background-color: var(--glow-w);
|
||||
color: var(--accent);
|
||||
border-radius: 12px;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s ease-in-out;
|
||||
box-shadow: 0 2px 6px var(--glow-w);
|
||||
}
|
||||
|
||||
.tab-nav .tab:hover {
|
||||
background-color: var(--glow-s);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 10px var(--glow-s);
|
||||
}
|
||||
|
||||
.tab-nav .tab.active {
|
||||
background-color: var(--accent);
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
background-color: var(--accent);
|
||||
color: var(--accent-ink);
|
||||
padding: 1rem;
|
||||
margin: 1rem auto;
|
||||
max-width: 600px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 0 10px var(--glow-s);
|
||||
}
|
||||
|
||||
.success-message {
|
||||
background-color: var(--accent);
|
||||
color: var(--accent-ink);
|
||||
padding: 1rem;
|
||||
margin: 1rem auto;
|
||||
max-width: 600px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 0 10px var(--glow-s);
|
||||
opacity: 1;
|
||||
transition: opacity 1.5s ease-out;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
background-color: var(--accent);
|
||||
color: var(--accent-ink);
|
||||
padding: 10px 18px;
|
||||
border-radius: 10px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 0 10px var(--glow-s);
|
||||
transition: background-color 0.3s ease, transform 0.3s ease;
|
||||
font-size: 1rem;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
background-color: var(--accent);
|
||||
color: var(--accent-ink);
|
||||
padding: 10px 18px;
|
||||
border-radius: 10px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 0 10px var(--glow-s);
|
||||
transition: background-color 0.3s ease, transform 0.3s ease;
|
||||
font-size: 1rem;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background-color: var(--accent-2);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.back-button {
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
padding: 8px 14px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
<style>
|
||||
/* Admin-Button Stil */
|
||||
.admin-btn {
|
||||
background-color: var(--accent);
|
||||
color: var(--accent-ink);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
transition: background-color 0.3s, box-shadow 0.3s, transform 0.3s;
|
||||
box-shadow: 0 0 10px var(--glow-s);
|
||||
}
|
||||
|
||||
.admin-btn:hover {
|
||||
background-color: var(--accent-2);
|
||||
box-shadow: 0 0 15px var(--glow-s), 0 0 25px var(--glow-s);
|
||||
transform: scale(1.05);
|
||||
color: var(--surface-2);
|
||||
}
|
||||
</style>
|
||||
|
||||
.admin-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.admin-card {
|
||||
display: block;
|
||||
background-color: var(--glow-w);
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: 12px;
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 12px var(--glow-w);
|
||||
}
|
||||
|
||||
.admin-card:hover {
|
||||
background-color: var(--glow-s);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 18px var(--glow-w);
|
||||
}
|
||||
|
||||
.admin-card.disabled {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
}
|
||||
.admin-actions {
|
||||
text-align: left;
|
||||
margin: 1.5rem 2rem;
|
||||
}
|
||||
|
||||
.btn-create {
|
||||
display: inline-block;
|
||||
padding: 0.6rem 1.2rem;
|
||||
font-size: 1rem;
|
||||
background-color: var(--accent);
|
||||
color: var(--accent-ink);
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 4px 10px var(--glow-s);
|
||||
transition: background-color 0.3s, transform 0.2s;
|
||||
}
|
||||
|
||||
.btn-create:hover {
|
||||
background-color: var(--accent);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
/* Legacy entry point retained for static FinalProtocol lessons.
|
||||
Layout and components live in theme.css and finalflux.css. */
|
||||
html,body{min-height:100%}
|
||||
body{margin:0}
|
||||
|
||||
@@ -249,3 +249,6 @@ body.ss-body {
|
||||
/* Textadventure-Terminal: phosphorige Log-Atmosphäre, Bewertung bleibt blau/rot. */
|
||||
body.ss-body { --accent:var(--ss-accent); --accent-2:var(--ss-secondary); --glow-s:var(--ss-glow); --glow-w:color-mix(in srgb,var(--ss-accent) 15%,transparent); background:radial-gradient(circle at 45% -8%,color-mix(in srgb,var(--ss-accent) 16%,transparent),transparent 42%),#07100b; }
|
||||
body.ss-body::before { content:""; position:fixed; inset:0; pointer-events:none; opacity:.14; background:repeating-linear-gradient(0deg,transparent 0 3px,rgba(129,214,149,.18) 4px 5px); }.ss-shell { position:relative; z-index:1; }.ss-code { border-color:color-mix(in srgb,var(--ss-accent) 55%,var(--border)); box-shadow:inset 0 0 28px color-mix(in srgb,var(--ss-accent) 10%,transparent); }.ss-mission-no::before { content:"> "; color:var(--ss-secondary); }
|
||||
/* Daylight keeps the terminal texture without retaining a black stage. */
|
||||
[data-theme="light"] body.ss-body{background:radial-gradient(1100px 500px at 50% -10%,rgba(39,125,73,.08),transparent 60%),#eef6f0}
|
||||
[data-theme="light"] body.ss-body::before{background:repeating-linear-gradient(0deg,rgba(16,64,40,.05) 0 1px,transparent 1px 4px)}
|
||||
|
||||
@@ -216,6 +216,9 @@ body.tt-body {
|
||||
}
|
||||
.tt-q-cat { color: var(--muted); font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.85rem; }
|
||||
.tt-q-points { color: var(--accent); font-weight: 800; font-size: 1.3rem; }
|
||||
.tt-q-close { margin-left:.35rem; width:32px; height:32px; border:1px solid var(--border); border-radius:8px; background:transparent; color:var(--muted); font:400 1.5rem/1 inherit; cursor:pointer; }
|
||||
.tt-q-close:hover { color:var(--accent); border-color:var(--border-accent); background:var(--surface-2); }
|
||||
.tt-q-close:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
|
||||
|
||||
.tt-q-instruction { color: var(--muted); font-size: 0.85rem; font-weight: 700; margin-bottom: 0.35rem; }
|
||||
.tt-q-text {
|
||||
|
||||
@@ -9,13 +9,14 @@
|
||||
============================================================ */
|
||||
|
||||
:root, [data-theme="dark"] {
|
||||
color-scheme:dark;
|
||||
--bg:#0a0806; --bg-glow: radial-gradient(900px 420px at 60% -10%, rgba(240,168,26,.10), transparent 60%);
|
||||
--panel:#13100b; --panel-2:#171209; --surface:#120f0a; --surface-2:#1b1610;
|
||||
--border:#2a2117; --line:#221b12;
|
||||
--border-accent:rgba(240,168,26,.45); --accent:#f0a81a; --accent-2:#f7dc11; --accent-ink:#1c1100;
|
||||
--secondary:#2d9cce; --secondary-2:#5cc4e8;
|
||||
--text:#ece3d2; --muted:#8c8170; --heading:#ffffff;
|
||||
--success:#2d9cce; --danger:#ff4d6d;
|
||||
--success:#2d9cce; --danger:#ff4d6d; --warn:#edcb4c;
|
||||
--esc-accent:#f0a81a; --esc-glow:rgba(240,168,26,.48);
|
||||
--bh-accent:#d94b64; --bh-glow:rgba(217,75,100,.42);
|
||||
--sc-accent:#38b9d6; --sc-glow:rgba(56,185,214,.40);
|
||||
@@ -29,28 +30,49 @@
|
||||
--fluxtimer-display:#170704; --fluxtimer-segment:#ff765f;
|
||||
--glow-s:rgba(240,168,26,.55); --glow-w:rgba(240,168,26,.16);
|
||||
--g-soft:0 0 18px var(--glow-w);
|
||||
--g-text:0 0 12px var(--glow-s);
|
||||
--g-icon:drop-shadow(0 0 8px var(--glow-s));
|
||||
--g-strong:0 0 20px var(--glow-s);
|
||||
--g-amb:0 0 14px var(--glow-w);
|
||||
--g-card:drop-shadow(0 0 7px var(--glow-w));
|
||||
--g-card-hover:drop-shadow(0 0 16px var(--glow-s));
|
||||
--g-overlay:0 24px 60px rgba(0,0,0,.5);
|
||||
--g-focus:0 0 0 3px var(--glow-w);
|
||||
--g-bar:0 0 10px var(--glow-s);
|
||||
--scroll-thumb:var(--line);
|
||||
}
|
||||
[data-theme="light"] {
|
||||
--bg:#fbf6ec; --bg-glow: radial-gradient(900px 420px at 60% -10%, rgba(214,81,22,.10), transparent 60%);
|
||||
color-scheme:light;
|
||||
--bg:#fbf6ec; --bg-glow: radial-gradient(900px 420px at 60% -10%, rgba(214,81,22,.05), transparent 60%);
|
||||
--panel:#ffffff; --panel-2:#fdf7ea; --surface:#ffffff; --surface-2:#fbf4e6;
|
||||
--border:#ece1cf; --line:#f1e8d8;
|
||||
--border-accent:rgba(214,81,22,.40); --accent:#d65116; --accent-2:#e4801d; --accent-ink:#ffffff;
|
||||
--secondary:#1f86b8; --secondary-2:#2d9cce;
|
||||
--text:#33271c; --muted:#8a7d6c; --heading:#241a12;
|
||||
--success:#1f86b8; --danger:#be123c;
|
||||
--esc-accent:#c96a12; --esc-glow:rgba(201,106,18,.30);
|
||||
--border-accent:rgba(194,65,12,.40); --accent:#c2410c; --accent-2:#b45309; --accent-ink:#ffffff;
|
||||
--secondary:#146b96; --secondary-2:#0f5a80;
|
||||
--text:#33271c; --muted:#6b5f4c; --heading:#241a12;
|
||||
--success:#146b96; --danger:#be123c; --warn:#8a6a06;
|
||||
--esc-accent:#a85408; --esc-glow:rgba(168,84,8,.30);
|
||||
--bh-accent:#b42342; --bh-glow:rgba(180,35,66,.26);
|
||||
--sc-accent:#147f9e; --sc-glow:rgba(20,127,158,.26);
|
||||
--sc-accent:#0e7c86; --sc-glow:rgba(14,124,134,.26);
|
||||
--nw-accent:#2868c7; --nw-glow:rgba(40,104,199,.26);
|
||||
--mm-accent:#7d4ac5; --mm-secondary:#127b68; --mm-glow:rgba(125,74,197,.24);
|
||||
--tt-accent:#b42c76; --tt-secondary:#315bb8; --tt-glow:rgba(180,44,118,.22);
|
||||
--dl-accent:#ba5d16; --dl-secondary:#147d7a; --dl-glow:rgba(186,93,22,.22);
|
||||
--ss-accent:#277d49; --ss-secondary:#a56918; --ss-glow:rgba(39,125,73,.20);
|
||||
--ff-accent:#a8781f; --ff-secondary:#4b59ae; --ff-glow:rgba(168,120,31,.20);
|
||||
--dl-accent:#9c4d0f; --dl-secondary:#147d7a; --dl-glow:rgba(156,77,15,.22);
|
||||
--ss-accent:#277d49; --ss-secondary:#8a5710; --ss-glow:rgba(39,125,73,.20);
|
||||
--ff-accent:#8c5e10; --ff-secondary:#4b59ae; --ff-glow:rgba(140,94,16,.20);
|
||||
--fluxtimer-primary:#b42342; --fluxtimer-secondary:#c96a12;
|
||||
--fluxtimer-display:#fff5ec; --fluxtimer-segment:#a93131;
|
||||
--glow-s:rgba(214,81,22,.40); --glow-w:rgba(214,81,22,.12);
|
||||
--g-soft:0 6px 18px var(--glow-w);
|
||||
--g-text:none;
|
||||
--g-icon:none;
|
||||
--g-strong:0 2px 10px rgba(67,48,26,.18),0 1px 3px rgba(67,48,26,.10);
|
||||
--g-amb:0 1px 5px rgba(67,48,26,.10);
|
||||
--g-card:drop-shadow(0 2px 5px rgba(67,48,26,.10));
|
||||
--g-card-hover:drop-shadow(0 8px 18px rgba(67,48,26,.16));
|
||||
--g-overlay:0 18px 44px rgba(67,48,26,.20);
|
||||
--g-focus:0 0 0 3px color-mix(in srgb,var(--accent) 28%,transparent);
|
||||
--g-bar:none;
|
||||
--scroll-thumb:#d9cbb2;
|
||||
}
|
||||
|
||||
* { box-sizing:border-box; margin:0; padding:0; }
|
||||
@@ -61,7 +83,7 @@ body {
|
||||
min-height:100vh; transition:background-color .35s ease,color .35s ease;
|
||||
}
|
||||
a { color:var(--secondary); }
|
||||
::-webkit-scrollbar{width:10px;height:10px} ::-webkit-scrollbar-thumb{background:var(--line);border-radius:9px}
|
||||
::-webkit-scrollbar{width:10px;height:10px} ::-webkit-scrollbar-thumb{background:var(--scroll-thumb);border-radius:9px}
|
||||
|
||||
/* ===== App-Shell (Sidebar | Mitte | rechte Spalte) ===== */
|
||||
.mf-shell { display:grid; grid-template-columns:264px 1fr 332px; grid-template-rows:auto 1fr; min-height:100vh; }
|
||||
@@ -76,22 +98,22 @@ a { color:var(--secondary); }
|
||||
.mf-logo { display:flex; align-items:center; gap:.6rem; padding:.2rem .3rem .4rem; text-decoration:none; }
|
||||
.mf-logo .mark { width:38px; height:38px; flex:0 0 auto; background:var(--accent);
|
||||
-webkit-mask:url("/img/mindflux-emblem.png") center/contain no-repeat; mask:url("/img/mindflux-emblem.png") center/contain no-repeat;
|
||||
filter:drop-shadow(0 0 8px var(--glow-s)); }
|
||||
.mf-logo .txt b { color:var(--accent); font-weight:800; letter-spacing:.13em; font-size:1.02rem; display:block; text-shadow:0 0 12px var(--glow-s); }
|
||||
filter:var(--g-icon); }
|
||||
.mf-logo .txt b { color:var(--accent); font-weight:800; letter-spacing:.13em; font-size:1.02rem; display:block; text-shadow:var(--g-text); }
|
||||
.mf-logo .txt span { color:var(--muted); font-size:.56rem; letter-spacing:.22em; }
|
||||
|
||||
/* ===== Sidebar-Navigation ===== */
|
||||
.mf-nav { display:flex; flex-direction:column; gap:.25rem; }
|
||||
.mf-nav a { display:flex; align-items:center; gap:.75rem; padding:.7rem .85rem; border-radius:11px; color:var(--muted); text-decoration:none; font-size:.9rem; font-weight:600; border:1px solid transparent; transition:all .18s ease; }
|
||||
.mf-nav a:hover { color:var(--text); background:var(--surface-2); }
|
||||
.mf-nav a.active { color:var(--accent-ink); background:linear-gradient(100deg,var(--accent),var(--accent-2)); box-shadow:0 0 20px var(--glow-s); }
|
||||
.mf-nav a.active { color:var(--accent-ink); background:linear-gradient(100deg,var(--accent),var(--accent-2)); box-shadow:var(--g-strong); }
|
||||
.mf-nav a.active svg { color:var(--accent-ink); }
|
||||
.mf-nav svg { width:18px; height:18px; color:var(--accent); flex:0 0 auto; }
|
||||
|
||||
/* ===== Profil / Level / Streak ===== */
|
||||
.mf-profile { border:1px solid var(--border); border-radius:13px; padding:.7rem; background:var(--surface); }
|
||||
.mf-profile .row { display:flex; align-items:center; gap:.6rem; }
|
||||
.mf-avatar { width:38px; height:38px; border-radius:50%; background:linear-gradient(135deg,var(--accent),var(--accent-2)); display:inline-flex; align-items:center; justify-content:center; color:var(--accent-ink); font-weight:800; box-shadow:0 0 14px var(--glow-s); overflow:hidden; }
|
||||
.mf-avatar { width:38px; height:38px; border-radius:50%; background:linear-gradient(135deg,var(--accent),var(--accent-2)); display:inline-flex; align-items:center; justify-content:center; color:var(--accent-ink); font-weight:800; box-shadow:var(--g-strong); overflow:hidden; }
|
||||
.mf-avatar img { width:100%; height:100%; object-fit:cover; }
|
||||
.mf-profile b { color:var(--heading); font-size:.86rem; display:block; }
|
||||
.mf-profile .role { color:var(--accent); font-size:.72rem; }
|
||||
@@ -99,41 +121,41 @@ a { color:var(--secondary); }
|
||||
.mf-meta .lvl { color:var(--muted); letter-spacing:.04em; } .mf-meta .lvl b { color:var(--heading); }
|
||||
.mf-meta .xp { color:var(--muted); margin:.2rem 0 .35rem; }
|
||||
.mf-streak { display:flex; align-items:center; gap:.55rem; margin-top:.2rem; }
|
||||
.mf-streak .fire { font-size:1.1rem; filter:drop-shadow(0 0 6px var(--glow-s)); }
|
||||
.mf-streak .fire { font-size:1.1rem; filter:var(--g-icon); }
|
||||
.mf-streak b { color:var(--heading); font-size:.82rem; display:block; } .mf-streak small { color:var(--muted); }
|
||||
.mf-brain { margin-top:auto; opacity:.55; display:flex; justify-content:center; filter:drop-shadow(0 0 10px var(--glow-s)); }
|
||||
.mf-brain { margin-top:auto; opacity:.55; display:flex; justify-content:center; filter:var(--g-icon); }
|
||||
.mf-brain svg { width:150px; height:auto; }
|
||||
|
||||
/* ===== Topbar ===== */
|
||||
.mf-hello h1 { color:var(--heading); font-size:1.85rem; font-weight:800; letter-spacing:-.01em; }
|
||||
.mf-hello h1 .nm { color:var(--accent); text-shadow:0 0 16px var(--glow-s); }
|
||||
.mf-hello h1 .nm { color:var(--accent); text-shadow:var(--g-text); }
|
||||
.mf-hello p { color:var(--muted); margin-top:.35rem; font-size:.95rem; }
|
||||
.mf-tools { display:flex; align-items:center; gap:.6rem; margin-left:auto; }
|
||||
.mf-iconbtn { width:38px; height:38px; border-radius:10px; border:1px solid var(--border); background:var(--surface); color:var(--muted); display:inline-flex; align-items:center; justify-content:center; cursor:pointer; text-decoration:none; transition:all .18s; }
|
||||
.mf-iconbtn:hover { color:var(--accent); border-color:var(--border-accent); box-shadow:0 0 14px var(--glow-w); }
|
||||
.mf-iconbtn svg { width:18px; height:18px; }
|
||||
.mf-search { display:flex; align-items:center; gap:.5rem; background:var(--surface); border:1px solid var(--border); border-radius:10px; padding:.5rem .8rem; color:var(--muted); }
|
||||
.mf-iconbtn { width:42px; height:42px; border-radius:10px; border:1px solid var(--border); background:var(--surface); color:var(--muted); display:inline-flex; align-items:center; justify-content:center; cursor:pointer; text-decoration:none; transition:all .18s; }
|
||||
.mf-iconbtn:hover { color:var(--accent); border-color:var(--border-accent); box-shadow:var(--g-amb); }
|
||||
.mf-iconbtn svg { width:20px; height:20px; }
|
||||
.mf-search { display:flex; align-items:center; gap:.5rem; min-height:42px; background:var(--surface); border:1px solid var(--border); border-radius:10px; padding:.5rem .8rem; color:var(--muted); }
|
||||
.mf-search input { background:none; border:0; outline:none; color:var(--text); font-size:.86rem; width:130px; }
|
||||
.mf-search svg { width:15px; height:15px; }
|
||||
|
||||
/* ===== Theme-Umschalter ===== */
|
||||
.mf-theme { width:42px; height:38px; border-radius:50%; border:1px solid var(--border-accent); background:var(--surface); cursor:pointer; display:inline-flex; align-items:center; justify-content:center; color:var(--accent); box-shadow:0 0 14px var(--glow-w); }
|
||||
.mf-theme svg { width:18px; height:18px; }
|
||||
.mf-theme { width:42px; height:42px; border-radius:10px; border:1px solid var(--border-accent); background:var(--surface); cursor:pointer; display:inline-flex; align-items:center; justify-content:center; color:var(--accent); box-shadow:var(--g-amb); }
|
||||
.mf-theme svg { width:20px; height:20px; }
|
||||
|
||||
/* ===== HUD-Spielkarten ===== */
|
||||
.mf-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:1.1rem; }
|
||||
.mf-card { position:relative; display:flex; flex-direction:column; padding:1.15rem 1.2rem 1.1rem; background:var(--surface);
|
||||
border:1px solid var(--border-accent); --n:18px;
|
||||
clip-path:polygon(0 0, calc(100% - var(--n)) 0, 100% var(--n), 100% 100%, 0 100%);
|
||||
text-decoration:none; color:inherit; filter:drop-shadow(0 0 7px var(--glow-w)); transition:transform .16s ease, filter .25s ease, background-color .2s; }
|
||||
text-decoration:none; color:inherit; filter:var(--g-card); transition:transform .16s ease, filter .25s ease, background-color .2s; }
|
||||
.mf-card::before { content:""; position:absolute; left:10px; top:10px; width:14px; height:14px; border-left:2px solid var(--accent); border-top:2px solid var(--accent); opacity:.7; }
|
||||
.mf-card:hover { transform:translateY(-4px); background:var(--surface-2); filter:drop-shadow(0 0 16px var(--glow-s)); }
|
||||
.mf-card .ico { width:58px; height:58px; color:var(--accent); margin-bottom:.7rem; filter:drop-shadow(0 0 6px var(--glow-s)); }
|
||||
.mf-card:hover { transform:translateY(-4px); background:var(--surface-2); filter:var(--g-card-hover); }
|
||||
.mf-card .ico { width:58px; height:58px; color:var(--accent); margin-bottom:.7rem; filter:var(--g-icon); }
|
||||
.mf-card .ico svg { width:100%; height:100%; }
|
||||
.mf-card h3 { color:var(--heading); font-size:1.06rem; font-weight:700; margin-bottom:.4rem; }
|
||||
.mf-card p { color:var(--muted); font-size:.83rem; line-height:1.5; flex:1; }
|
||||
.mf-play { margin-top:.9rem; align-self:flex-start; display:inline-flex; align-items:center; gap:.45rem; font-size:.74rem; font-weight:800; letter-spacing:.08em; color:var(--accent); border:1px solid var(--border-accent); border-radius:8px; padding:.5rem .85rem; text-decoration:none; transition:all .18s; }
|
||||
.mf-card:hover .mf-play { background:var(--accent); color:var(--accent-ink); box-shadow:0 0 16px var(--glow-s); }
|
||||
.mf-card:hover .mf-play { background:var(--accent); color:var(--accent-ink); box-shadow:var(--g-strong); }
|
||||
.mf-play svg { width:14px; height:14px; }
|
||||
.mf-card-codeescape { --accent:var(--esc-accent); --glow-s:var(--esc-glow); --glow-w:color-mix(in srgb,var(--esc-accent) 16%,transparent); background:repeating-linear-gradient(0deg,rgba(255,255,255,.015) 0 1px,transparent 1px 4px),var(--surface); }
|
||||
.mf-card-codeescape::after { content:"ACCESS"; position:absolute; right:12px; top:9px; color:var(--esc-accent); font:700 .52rem/1 "Courier New",monospace; letter-spacing:.12em; opacity:.6; }
|
||||
@@ -143,15 +165,15 @@ a { color:var(--secondary); }
|
||||
|
||||
/* ===== Panels / Widgets ===== */
|
||||
.mf-panel { background:var(--surface); border:1px solid var(--border); border-radius:14px; padding:1.1rem; box-shadow:var(--g-soft); }
|
||||
.mf-panel.accent { border-color:var(--border-accent); box-shadow:0 0 20px var(--glow-w); }
|
||||
.mf-panel.accent { border-color:var(--border-accent); box-shadow:var(--g-amb); }
|
||||
.mf-panel h4 { display:flex; align-items:center; gap:.5rem; color:var(--accent); font-size:.74rem; letter-spacing:.12em; text-transform:uppercase; margin-bottom:.9rem; }
|
||||
.mf-panel h4 svg { width:15px; height:15px; }
|
||||
.mf-eyebrow { color:var(--accent); font-weight:700; text-transform:uppercase; letter-spacing:.08em; font-size:.8rem; }
|
||||
.mf-bar { height:7px; border-radius:99px; background:var(--surface-2); border:1px solid var(--border); overflow:hidden; }
|
||||
.mf-bar > i { display:block; height:100%; background:linear-gradient(90deg,var(--accent),var(--accent-2)); box-shadow:0 0 10px var(--glow-s); }
|
||||
.mf-bar > i { display:block; height:100%; background:linear-gradient(90deg,var(--accent),var(--accent-2)); box-shadow:var(--g-bar); }
|
||||
|
||||
/* ===== Buttons ===== */
|
||||
.mf-btn { display:inline-flex; align-items:center; justify-content:center; gap:.45rem; font-weight:800; font-size:.82rem; letter-spacing:.04em; border:0; border-radius:9px; padding:.6rem 1.1rem; cursor:pointer; text-decoration:none; background:var(--accent); color:var(--accent-ink); box-shadow:0 0 18px var(--glow-s); transition:all .18s; }
|
||||
.mf-btn { display:inline-flex; align-items:center; justify-content:center; gap:.45rem; font-weight:800; font-size:.82rem; letter-spacing:.04em; border:0; border-radius:9px; padding:.6rem 1.1rem; cursor:pointer; text-decoration:none; background:var(--accent); color:var(--accent-ink); box-shadow:var(--g-strong); transition:all .18s; }
|
||||
.mf-btn:hover { filter:brightness(1.05); transform:translateY(-1px); }
|
||||
.mf-btn svg { width:15px; height:15px; }
|
||||
.mf-btn.outline { background:transparent; color:var(--accent); border:1px solid var(--border-accent); box-shadow:none; }
|
||||
@@ -165,7 +187,7 @@ a { color:var(--secondary); }
|
||||
.mf-input, .mf-select, input.mf-input, select.mf-select, textarea.mf-input {
|
||||
width:100%; padding:.65rem .8rem; border-radius:10px; border:1px solid var(--border);
|
||||
background:var(--surface-2); color:var(--text); font-size:.95rem; font-family:inherit; }
|
||||
.mf-input:focus, .mf-select:focus { outline:none; border-color:var(--accent); box-shadow:0 0 0 3px var(--glow-w); }
|
||||
.mf-input:focus, .mf-select:focus { outline:none; border-color:var(--accent); box-shadow:var(--g-focus); }
|
||||
.mf-input::placeholder { color:var(--muted); }
|
||||
|
||||
/* ===== Tabellen ===== */
|
||||
@@ -179,7 +201,8 @@ a { color:var(--secondary); }
|
||||
|
||||
/* ===== Standalone-Karte (Login/Auth, einzelne Inhaltsseiten) ===== */
|
||||
.mf-center { display:flex; align-items:center; justify-content:center; min-height:100vh; padding:1.5rem; }
|
||||
.mf-cardbox { width:100%; max-width:420px; background:var(--surface); border:1px solid var(--border-accent); border-radius:18px; padding:2rem; box-shadow:0 0 30px var(--glow-w); }
|
||||
.mf-cardbox { width:100%; max-width:420px; background:var(--surface); border:1px solid var(--border-accent); border-radius:18px; padding:2rem; box-shadow:var(--g-strong); }
|
||||
[data-theme="dark"] .mf-cardbox { box-shadow:0 0 30px var(--glow-w); }
|
||||
.mf-container { max-width:1000px; margin:0 auto; padding:1.5rem; }
|
||||
.mf-page-head { display:flex; align-items:center; gap:1rem; margin-bottom:1.4rem; }
|
||||
.mf-back { color:var(--accent); text-decoration:none; font-weight:700; font-size:.88rem; }
|
||||
@@ -199,7 +222,7 @@ a { color:var(--secondary); }
|
||||
.mf-footer a + a::before { content:"·"; position:absolute; left:-.6rem; color:var(--border); }
|
||||
|
||||
/* ===== Mobile-Nav (Off-Canvas-Drawer; Button/Backdrop per theme.js injiziert) ===== */
|
||||
.mf-burger { display:none; position:fixed; top:.7rem; left:.7rem; z-index:1300; width:42px; height:42px; border-radius:10px; border:1px solid var(--border-accent); background:var(--surface); color:var(--accent); align-items:center; justify-content:center; cursor:pointer; box-shadow:0 0 14px var(--glow-w); }
|
||||
.mf-burger { display:none; position:fixed; top:.7rem; left:.7rem; z-index:1300; width:42px; height:42px; border-radius:10px; border:1px solid var(--border-accent); background:var(--surface); color:var(--accent); align-items:center; justify-content:center; cursor:pointer; box-shadow:var(--g-amb); }
|
||||
.mf-burger svg { width:20px; height:20px; }
|
||||
.mf-nav-backdrop { position:fixed; inset:0; background:rgba(0,0,0,.5); opacity:0; pointer-events:none; transition:opacity .28s ease; z-index:1100; }
|
||||
body.mf-nav-open .mf-nav-backdrop { opacity:1; pointer-events:auto; }
|
||||
@@ -220,7 +243,7 @@ body.mf-nav-open .mf-nav-backdrop { opacity:1; pointer-events:auto; }
|
||||
.mf-sidebar {
|
||||
position:fixed; top:0; left:0; height:100dvh; width:min(284px,82vw); z-index:1200;
|
||||
transform:translateX(-100%); transition:transform .28s ease; overflow-y:auto;
|
||||
box-shadow:0 18px 50px rgba(0,0,0,.55);
|
||||
box-shadow:var(--g-overlay);
|
||||
}
|
||||
body.mf-nav-open .mf-sidebar { transform:translateX(0); }
|
||||
.mf-burger { display:inline-flex; }
|
||||
@@ -235,4 +258,4 @@ body.mf-nav-open .mf-nav-backdrop { opacity:1; pointer-events:auto; }
|
||||
.mf-tab-nav { display:flex; flex-wrap:wrap; gap:.4rem; margin-bottom:1.4rem; }
|
||||
.mf-tab { padding:.55rem .95rem; border-radius:10px; border:1px solid var(--border); color:var(--muted); text-decoration:none; font-size:.85rem; font-weight:700; transition:all .18s ease; }
|
||||
.mf-tab:hover { color:var(--text); background:var(--surface-2); }
|
||||
.mf-tab.active { color:var(--accent-ink); background:linear-gradient(100deg,var(--accent),var(--accent-2)); box-shadow:0 0 18px var(--glow-s); border-color:transparent; }
|
||||
.mf-tab.active { color:var(--accent-ink); background:linear-gradient(100deg,var(--accent),var(--accent-2)); box-shadow:var(--g-strong); border-color:transparent; }
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Datenschutz - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>IP-Adressberechnung - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Projektmanagement - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Zahlensysteme & Speicher - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
@@ -75,7 +75,7 @@
|
||||
<li>1 MB = 10^6 = 1.000.000 Byte, 1 MiB = 1024^2 = 1.048.576 Byte.</li>
|
||||
<li>1 GB = 10^9 Byte, 1 GiB = 1024^3 = 1.073.741.824 Byte.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Das kleine "i" (KiB, MiB, GiB) steht fuer "binär" und damit für 1024. Ohne "i" ist es dezimal (1000).</p>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Das kleine "i" (KiB, MiB, GiB) steht für "binär" und damit für 1024. Ohne "i" ist es dezimal (1000).</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Algorithmen - FinalProtocol AP2</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
<main>
|
||||
<a href="/finalflux" class="back-link">Zurück zu FinalProtocol</a>
|
||||
<h1>Algorithmen</h1>
|
||||
|
||||
<section>
|
||||
<h2>Lernfokus</h2>
|
||||
<p>AP2 Anwendungsentwicklung: Abläufe strukturiert darstellen, Standardverfahren zum Sortieren und Suchen einordnen und ihren Aufwand mit der O-Notation bewerten.</p>
|
||||
<ul class="key-list">
|
||||
<li>Kontrollstrukturen im Struktogramm und Pseudocode sicher lesen und schreiben.</li>
|
||||
<li>Sortier- und Suchverfahren nach Prinzip und Voraussetzung unterscheiden.</li>
|
||||
<li>Laufzeitkomplexität in O-Notation einordnen und Rekursion sauber aufbauen.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Struktogramm & Pseudocode</h2>
|
||||
<p>Jeder Algorithmus lässt sich aus drei Grundstrukturen zusammensetzen, die im Struktogramm (Nassi-Shneiderman) als geschachtelte Blöcke ohne Sprünge gezeichnet werden.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Sequenz:</strong> Anweisungen werden streng nacheinander abgearbeitet – ein einfaches Rechteck.</li>
|
||||
<li><strong>Verzweigung:</strong> ein dreieckiger Bedingungskopf teilt in Ja- und Nein-Zweig (<code>if-else</code>).</li>
|
||||
<li><strong>Schleife:</strong> <em>kopfgesteuert</em> (<code>while</code>) prüft vor dem Durchlauf – ggf. null Durchläufe; <em>fußgesteuert</em> (<code>do-while</code>) prüft danach – mindestens ein Durchlauf.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Sequenz, Verzweigung, Schleife – mit diesen drei Bausteinen kommt jeder strukturierte Algorithmus ohne GOTO aus.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Sortier- & Suchverfahren</h2>
|
||||
<p>Sortier- und Suchverfahren unterscheiden sich in Vorgehen und Voraussetzung – genau das wird in der AP2 gern abgefragt.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Einfache Sortierer:</strong> Bubblesort (Nachbarn tauschen), Selectionsort (Minimum suchen), Insertionsort (einsortieren) – meist O(n²).</li>
|
||||
<li><strong>Effiziente Sortierer:</strong> Mergesort und Quicksort nach dem Prinzip „Teile und herrsche" – im Mittel O(n log n), Quicksort im schlechtesten Fall O(n²).</li>
|
||||
<li><strong>Suche:</strong> lineare Suche geht der Reihe nach durch (O(n)); die binäre Suche halbiert den Bereich (O(log n)).</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Die binäre Suche funktioniert nur auf <em>sortierten</em> Daten – ohne Sortierung bleibt nur die lineare Suche.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>O-Notation</h2>
|
||||
<p>Die O-Notation beschreibt, wie der Aufwand mit der Eingabegröße n wächst – unabhängig von Hardware und Konstanten.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>O(1):</strong> konstant, z. B. Array-Zugriff über den Index.</li>
|
||||
<li><strong>O(log n):</strong> halbierend, z. B. binäre Suche; <strong>O(n):</strong> linear, z. B. lineare Suche.</li>
|
||||
<li><strong>O(n log n):</strong> gute Sortierer (Merge-/Quicksort); <strong>O(n²):</strong> verschachtelte Schleifen, z. B. Bubblesort.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Nach steigendem Aufwand gilt O(1) < O(log n) < O(n) < O(n log n) < O(n²).</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Rekursion</h2>
|
||||
<p>Eine rekursive Funktion löst ein Problem, indem sie sich mit einem verkleinerten Teilproblem selbst aufruft.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Basisfall:</strong> Abbruchbedingung, die ein Ergebnis ohne weiteren Selbstaufruf liefert.</li>
|
||||
<li><strong>Rekursionsschritt:</strong> Selbstaufruf mit kleinerem Teilproblem, das dem Basisfall näherkommt.</li>
|
||||
<li><strong>Stack:</strong> jeder Aufruf belegt einen eigenen Stack-Frame – zu tiefe Rekursion führt zum Stack Overflow.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Ohne erreichbaren Basisfall wird jede Rekursion zur Endlosrekursion und läuft in den Stack Overflow.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Prüfungsmodus</h2>
|
||||
<p>Dieser ProtocolCheck führt dich durch die wichtigsten AP2-Aspekte dieses Themas. Du kannst Antworten prüfen, Fragen überspringen und bekommst am Ende eine Auswertung nach Kategorie.</p>
|
||||
</section>
|
||||
|
||||
<section class="finalflux-test" data-finalflux-test aria-live="polite">
|
||||
<div class="test-meta">
|
||||
<span data-question-counter>Frage 1</span>
|
||||
<span data-score-counter>0 richtig</span>
|
||||
</div>
|
||||
<div class="category-block">
|
||||
<p class="eyebrow">Kategorie</p>
|
||||
<h2 data-category-title>Algorithmen</h2>
|
||||
<p data-category-hint>AP2-Training</p>
|
||||
</div>
|
||||
<div class="question-card">
|
||||
<h3 data-question-text></h3>
|
||||
<div data-answer-options class="answer-options"></div>
|
||||
<div data-answer-feedback class="fluxcheck-result"></div>
|
||||
</div>
|
||||
<div class="test-actions">
|
||||
<button type="button" data-check-answer>Antwort prüfen</button>
|
||||
<button type="button" data-skip-question class="secondary">Überspringen</button>
|
||||
<button type="button" data-next-question class="secondary" disabled>Weiter</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section data-test-result class="finalflux-panel" hidden>
|
||||
<h2>Auswertung</h2>
|
||||
<p data-result-summary></p>
|
||||
<ul data-category-summary class="key-list"></ul>
|
||||
<div class="finalflux-actions">
|
||||
<button type="button" data-restart-test>Test neu starten</button>
|
||||
<a href="/finalflux">Zurück zur Übersicht</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="/js/finalflux-test.js" data-questions-src="/finalflux/questions/ap2_fiae_algorithmen.json" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Entwurfsmuster - FinalProtocol AP2</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>OOP - FinalProtocol AP2</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
<main>
|
||||
<a href="/finalflux" class="back-link">Zurück zu FinalProtocol</a>
|
||||
<h1>OOP</h1>
|
||||
|
||||
<section>
|
||||
<h2>Lernfokus</h2>
|
||||
<p>AP2 Anwendungsentwicklung: Objektorientierung sicher anwenden – Daten kapseln, Typen über Vererbung und Interfaces gliedern und Beziehungen bewusst als „ist-ein" oder „hat-ein" modellieren.</p>
|
||||
<ul class="key-list">
|
||||
<li>Kapselung mit Sichtbarkeiten sowie Getter/Setter richtig einsetzen.</li>
|
||||
<li>Überschreiben und Überladen samt dynamischer Bindung sicher unterscheiden.</li>
|
||||
<li>Interfaces, abstrakte Klassen und Komposition gezielt gegeneinander abwägen.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Kapselung</h2>
|
||||
<p>Kapselung (Information Hiding) verbirgt die inneren Daten einer Klasse und gibt sie nur über kontrollierte Methoden frei.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Sichtbarkeiten:</strong> <code>private</code> nur in der eigenen Klasse, <code>protected</code> zusätzlich für Unterklassen/Paket, <code>public</code> überall.</li>
|
||||
<li><strong>Getter/Setter:</strong> steuern Lese- und Schreibzugriff; der Setter kann Werte <em>validieren</em>, bevor sie gesetzt werden.</li>
|
||||
<li><strong>Nur lesbar:</strong> Attribut <code>private</code> lassen und nur einen Getter (keinen Setter) anbieten.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Attribute <code>private</code>, Zugriff über Methoden – so behält die Klasse die Kontrolle über ihre Daten.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Vererbung</h2>
|
||||
<p>Vererbung modelliert eine „ist-ein"-Beziehung: Die Unterklasse übernimmt Attribute und Methoden der Oberklasse und erweitert sie.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Ober-/Unterklasse:</strong> die Subklasse erbt und darf geerbte Methoden <em>überschreiben</em>.</li>
|
||||
<li><strong><code>super</code>:</strong> ruft Konstruktor bzw. Methode der Oberklasse auf, um deren Teil des Objekts zu initialisieren.</li>
|
||||
<li><strong>Überschreiben (override):</strong> gleiche Signatur, neue Implementierung in der Unterklasse.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> „ist-ein" heißt Vererbung – ein Hund <em>ist ein</em> Tier, ein Auto <em>ist kein</em> Motor.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Polymorphie: Überladen & Überschreiben</h2>
|
||||
<p>Polymorphie erlaubt, verschiedene Objekte einheitlich über einen gemeinsamen Obertyp zu behandeln.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Überladen (overloading):</strong> gleicher Name, andere Parameterliste, gleiche Klasse – der Compiler wählt zur <em>Compilezeit</em>.</li>
|
||||
<li><strong>Überschreiben (overriding):</strong> Unterklasse ersetzt geerbte Methode – der echte Objekttyp entscheidet zur <em>Laufzeit</em> (dynamische Bindung).</li>
|
||||
<li><strong>Nutzen:</strong> eine Liste von <code>Form</code>-Objekten zeichnen, ohne den konkreten Typ zu kennen.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Überladen entscheidet der Compiler (Parameter), Überschreiben die Laufzeit (Objekttyp).</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Interfaces, abstrakte Klassen & Komposition</h2>
|
||||
<p>Für Struktur und Wiederverwendung stehen mehrere Werkzeuge bereit, die du bewusst wählst.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Interface:</strong> reiner Vertrag; eine Klasse kann <em>mehrere</em> Interfaces implementieren (<code>implements Comparable, Serializable</code>).</li>
|
||||
<li><strong>Abstrakte Klasse:</strong> nicht instanziierbar, bringt aber Zustand und fertige Methoden mit; nur <em>eine</em> Basisklasse.</li>
|
||||
<li><strong>Komposition:</strong> „hat-ein" – ein Objekt enthält ein anderes als Attribut, lose gekoppelt und flexibel.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> „Favor composition over inheritance" – lieber ein Motor-Objekt <em>enthalten</em> als tief von einer fragilen Basisklasse erben.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Prüfungsmodus</h2>
|
||||
<p>Dieser ProtocolCheck führt dich durch die wichtigsten AP2-Aspekte dieses Themas. Du kannst Antworten prüfen, Fragen überspringen und bekommst am Ende eine Auswertung nach Kategorie.</p>
|
||||
</section>
|
||||
|
||||
<section class="finalflux-test" data-finalflux-test aria-live="polite">
|
||||
<div class="test-meta">
|
||||
<span data-question-counter>Frage 1</span>
|
||||
<span data-score-counter>0 richtig</span>
|
||||
</div>
|
||||
<div class="category-block">
|
||||
<p class="eyebrow">Kategorie</p>
|
||||
<h2 data-category-title>OOP</h2>
|
||||
<p data-category-hint>AP2-Training</p>
|
||||
</div>
|
||||
<div class="question-card">
|
||||
<h3 data-question-text></h3>
|
||||
<div data-answer-options class="answer-options"></div>
|
||||
<div data-answer-feedback class="fluxcheck-result"></div>
|
||||
</div>
|
||||
<div class="test-actions">
|
||||
<button type="button" data-check-answer>Antwort prüfen</button>
|
||||
<button type="button" data-skip-question class="secondary">Überspringen</button>
|
||||
<button type="button" data-next-question class="secondary" disabled>Weiter</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section data-test-result class="finalflux-panel" hidden>
|
||||
<h2>Auswertung</h2>
|
||||
<p data-result-summary></p>
|
||||
<ul data-category-summary class="key-list"></ul>
|
||||
<div class="finalflux-actions">
|
||||
<button type="button" data-restart-test>Test neu starten</button>
|
||||
<a href="/finalflux">Zurück zur Übersicht</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="/js/finalflux-test.js" data-questions-src="/finalflux/questions/ap2_fiae_oop.json" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,110 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>SQL - FinalProtocol AP2</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
<main>
|
||||
<a href="/finalflux" class="back-link">Zurück zu FinalProtocol</a>
|
||||
<h1>SQL</h1>
|
||||
|
||||
<section>
|
||||
<h2>Lernfokus</h2>
|
||||
<p>AP2 Anwendungsentwicklung: Daten über mehrere Tabellen verknüpfen, gruppieren, verändern und Tabellen sauber normalisieren.</p>
|
||||
<ul class="key-list">
|
||||
<li>Join-Typen sicher unterscheiden und passend wählen.</li>
|
||||
<li>Mit GROUP BY/HAVING gruppieren und aggregieren.</li>
|
||||
<li>DDL/DML einordnen und die Normalformen 1NF–3NF anwenden.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Joins</h2>
|
||||
<p>Ein Join verknüpft Zeilen mehrerer Tabellen über eine Bedingung – meist Fremdschlüssel gleich Primärschlüssel.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>INNER JOIN:</strong> nur Zeilen mit Partner in <em>beiden</em> Tabellen.</li>
|
||||
<li><strong>LEFT (OUTER) JOIN:</strong> alle Zeilen der linken Tabelle, fehlende Partner werden <code>NULL</code> – ideal, um „ohne Partner" zu finden (<code>IS NULL</code>).</li>
|
||||
<li><strong>CROSS JOIN:</strong> kartesisches Produkt, jede Zeile mit jeder (5 × 4 = 20).</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Kunden ohne Bestellung findest du mit LEFT JOIN und <code>WHERE bestellung.id IS NULL</code>.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>GROUP BY & HAVING</h2>
|
||||
<p>GROUP BY fasst Zeilen zu Gruppen zusammen, über die Aggregatfunktionen (COUNT, SUM, AVG, MIN, MAX) rechnen.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>WHERE vs. HAVING:</strong> WHERE filtert einzelne Zeilen <em>vor</em> der Gruppierung, HAVING filtert Gruppen <em>nach</em> der Gruppierung (z. B. <code>HAVING COUNT(*) > 3</code>).</li>
|
||||
<li><strong>SELECT-Regel:</strong> ohne Aggregat dürfen nur die Gruppierungsspalten in der SELECT-Liste stehen.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Aggregate (COUNT, SUM …) gehören in HAVING, nie in WHERE.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>DDL & DML</h2>
|
||||
<p>SQL-Befehle werden nach ihrer Aufgabe gruppiert.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>DDL (Definition):</strong> CREATE, ALTER, DROP – definiert und ändert die Struktur.</li>
|
||||
<li><strong>DML (Manipulation):</strong> INSERT, UPDATE, DELETE – ändert die Daten.</li>
|
||||
<li><strong>FOREIGN KEY:</strong> sichert die referenzielle Integrität – erlaubt nur Werte, die als Primärschlüssel in der Zieltabelle existieren.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Normalisierung</h2>
|
||||
<p>Normalisierung zerlegt Tabellen, um Redundanz und Anomalien zu vermeiden.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>1NF:</strong> atomare Werte, keine Mehrfachwerte oder Wiederholgruppen je Feld.</li>
|
||||
<li><strong>2NF:</strong> 1NF und jedes Nichtschlüsselattribut hängt vom <em>gesamten</em> Schlüssel ab (keine Teilschlüssel-Abhängigkeit).</li>
|
||||
<li><strong>3NF:</strong> 2NF und keine transitiven Abhängigkeiten (Nichtschlüssel → Nichtschlüssel, z. B. PLZ → Ort).</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> „Der Schlüssel, der ganze Schlüssel und nichts als der Schlüssel" – 1NF, 2NF, 3NF in einem Satz.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Prüfungsmodus</h2>
|
||||
<p>Dieser ProtocolCheck führt dich durch die wichtigsten AP2-Aspekte dieses Themas. Du kannst Antworten prüfen, Fragen überspringen und bekommst am Ende eine Auswertung nach Kategorie.</p>
|
||||
</section>
|
||||
|
||||
<section class="finalflux-test" data-finalflux-test aria-live="polite">
|
||||
<div class="test-meta">
|
||||
<span data-question-counter>Frage 1</span>
|
||||
<span data-score-counter>0 richtig</span>
|
||||
</div>
|
||||
<div class="category-block">
|
||||
<p class="eyebrow">Kategorie</p>
|
||||
<h2 data-category-title>SQL</h2>
|
||||
<p data-category-hint>AP2-Training</p>
|
||||
</div>
|
||||
<div class="question-card">
|
||||
<h3 data-question-text></h3>
|
||||
<div data-answer-options class="answer-options"></div>
|
||||
<div data-answer-feedback class="fluxcheck-result"></div>
|
||||
</div>
|
||||
<div class="test-actions">
|
||||
<button type="button" data-check-answer>Antwort prüfen</button>
|
||||
<button type="button" data-skip-question class="secondary">Überspringen</button>
|
||||
<button type="button" data-next-question class="secondary" disabled>Weiter</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section data-test-result class="finalflux-panel" hidden>
|
||||
<h2>Auswertung</h2>
|
||||
<p data-result-summary></p>
|
||||
<ul data-category-summary class="key-list"></ul>
|
||||
<div class="finalflux-actions">
|
||||
<button type="button" data-restart-test>Test neu starten</button>
|
||||
<a href="/finalflux">Zurück zur Übersicht</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="/js/finalflux-test.js" data-questions-src="/finalflux/questions/ap2_fiae_sql.json" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,114 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Testverfahren - FinalProtocol AP2</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
<main>
|
||||
<a href="/finalflux" class="back-link">Zurück zu FinalProtocol</a>
|
||||
<h1>Testverfahren</h1>
|
||||
|
||||
<section>
|
||||
<h2>Lernfokus</h2>
|
||||
<p>AP2 Anwendungsentwicklung: Testfälle systematisch entwerfen, Tests auf der richtigen Ebene einordnen und Schnittstellen mit passenden HTTP-Statuscodes und Datenformaten prüfen.</p>
|
||||
<ul class="key-list">
|
||||
<li>Blackbox- und Whitebox-Test sicher voneinander unterscheiden.</li>
|
||||
<li>Äquivalenzklassen bilden und mit der Grenzwertanalyse ergänzen.</li>
|
||||
<li>Testebenen, Testarten und REST-Grundlagen einordnen.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Blackbox & Whitebox</h2>
|
||||
<p>Beide Verfahren unterscheiden sich vor allem darin, ob der Tester den inneren Quellcode kennt.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Blackbox:</strong> ohne Kenntnis des Codes – getestet wird über Eingabe und Ausgabe gegen die Spezifikation (z. B. Äquivalenzklassen, Grenzwerte).</li>
|
||||
<li><strong>Whitebox:</strong> mit Kenntnis des Codes – gezielte Abdeckung von Anweisungen, Zweigen und Pfaden im Programmablauf.</li>
|
||||
<li><strong>Grenze:</strong> nur Whitebox findet nie durchlaufenen oder toten Code, nur Blackbox denkt konsequent aus Nutzersicht.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Blackbox schaut auf das Verhalten, Whitebox in den Code.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Äquivalenzklassen & Grenzwerte</h2>
|
||||
<p>Statt jeden Wert einzeln zu testen, fasst man Eingaben zu Klassen zusammen und prüft je einen Repräsentanten.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Gültige und ungültige Klassen:</strong> zu jedem Wertebereich gehören mindestens eine gültige Klasse und die ungültigen Bereiche darunter und darüber.</li>
|
||||
<li><strong>Grenzwertanalyse:</strong> prüft die Ränder und ihre direkten Nachbarn – bei 1 bis 100 also 0, 1, 100 und 101.</li>
|
||||
<li><strong>Warum:</strong> typische Fehler wie <code><</code> statt <code><=</code> zeigen sich genau an den Grenzen.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Ein Repräsentant je Klasse plus die Werte direkt an den Rändern.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Testebenen</h2>
|
||||
<p>Tests bauen von der kleinsten Einheit bis zum Gesamtsystem aufeinander auf.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Unit-Test:</strong> einzelne Klasse oder Methode isoliert, oft mit Mocks.</li>
|
||||
<li><strong>Integrationstest:</strong> Zusammenspiel mehrerer Module über ihre Schnittstellen.</li>
|
||||
<li><strong>Systemtest:</strong> vollständig integriertes System in produktionsnaher Umgebung.</li>
|
||||
<li><strong>Abnahmetest:</strong> Auftraggeber prüft gegen die vereinbarten Anforderungen und gibt frei.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Unit klein, Integration zwischen den Teilen, System als Ganzes, Abnahme für den Kunden.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Testarten & Schnittstellen</h2>
|
||||
<p>Neben den Ebenen zählen das Vorgehen und die geprüften Schnittstellen.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Regressionstest:</strong> stellt sicher, dass Bestehendes nach einer Änderung weiter funktioniert – ideal automatisiert für Continuous Integration.</li>
|
||||
<li><strong>TDD:</strong> Red-Green-Refactor – erst der fehlschlagende Test, dann der Code, dann das Aufräumen.</li>
|
||||
<li><strong>REST-Statuscodes:</strong> 200 OK, 201 Created (nach POST), 400 Bad Request, 404 Not Found, 500 Internal Server Error.</li>
|
||||
<li><strong>JSON vs. XML:</strong> beide textbasiert und maschinenlesbar; JSON meist kompakter, XML mit Attributen, Namespaces und Schema.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Ein bestandener Test beweist keine Fehlerfreiheit, nur die Abwesenheit der geprüften Fehler.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Prüfungsmodus</h2>
|
||||
<p>Dieser ProtocolCheck führt dich durch die wichtigsten AP2-Aspekte dieses Themas. Du kannst Antworten prüfen, Fragen überspringen und bekommst am Ende eine Auswertung nach Kategorie.</p>
|
||||
</section>
|
||||
|
||||
<section class="finalflux-test" data-finalflux-test aria-live="polite">
|
||||
<div class="test-meta">
|
||||
<span data-question-counter>Frage 1</span>
|
||||
<span data-score-counter>0 richtig</span>
|
||||
</div>
|
||||
<div class="category-block">
|
||||
<p class="eyebrow">Kategorie</p>
|
||||
<h2 data-category-title>Testverfahren</h2>
|
||||
<p data-category-hint>AP2-Training</p>
|
||||
</div>
|
||||
<div class="question-card">
|
||||
<h3 data-question-text></h3>
|
||||
<div data-answer-options class="answer-options"></div>
|
||||
<div data-answer-feedback class="fluxcheck-result"></div>
|
||||
</div>
|
||||
<div class="test-actions">
|
||||
<button type="button" data-check-answer>Antwort prüfen</button>
|
||||
<button type="button" data-skip-question class="secondary">Überspringen</button>
|
||||
<button type="button" data-next-question class="secondary" disabled>Weiter</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section data-test-result class="finalflux-panel" hidden>
|
||||
<h2>Auswertung</h2>
|
||||
<p data-result-summary></p>
|
||||
<ul data-category-summary class="key-list"></ul>
|
||||
<div class="finalflux-actions">
|
||||
<button type="button" data-restart-test>Test neu starten</button>
|
||||
<a href="/finalflux">Zurück zur Übersicht</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="/js/finalflux-test.js" data-questions-src="/finalflux/questions/ap2_fiae_testverfahren.json" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,112 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>UML - FinalProtocol AP2</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
<main>
|
||||
<a href="/finalflux" class="back-link">Zurück zu FinalProtocol</a>
|
||||
<h1>UML</h1>
|
||||
|
||||
<section>
|
||||
<h2>Lernfokus</h2>
|
||||
<p>AP2 Anwendungsentwicklung: Systeme mit UML modellieren – Struktur im Klassendiagramm, Abläufe in Sequenz- und Aktivitätsdiagrammen und die Anforderungssicht im Use-Case-Diagramm.</p>
|
||||
<ul class="key-list">
|
||||
<li>Klassen mit Attributen, Methoden und Sichtbarkeiten +/-/# lesen und notieren.</li>
|
||||
<li>Beziehungen sicher unterscheiden: Assoziation, Aggregation vs. Komposition, Vererbung samt Multiplizitäten.</li>
|
||||
<li>Dynamik im Sequenz- und Aktivitätsdiagramm sowie Akteure und include/extend im Use-Case-Diagramm einordnen.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Klassendiagramm</h2>
|
||||
<p>Eine Klasse wird als Rechteck mit drei Fächern gezeichnet: oben der Name, in der Mitte die Attribute, unten die Methoden.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Sichtbarkeiten:</strong> <code>+</code> public (überall), <code>-</code> private (nur eigene Klasse), <code>#</code> protected (auch Unterklassen).</li>
|
||||
<li><strong>Attribut-Notation:</strong> <code>name : Typ</code> – der Typ steht in UML <em>hinter</em> dem Namen, nicht davor wie in Java.</li>
|
||||
<li><strong>Methode:</strong> erkennbar an den runden Klammern hinter dem Namen, z. B. <code>berechneSumme()</code>.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Minus heißt privat, Raute heißt protected – nie verwechseln.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Beziehungen & Multiplizitäten</h2>
|
||||
<p>Linien zwischen Klassen zeigen, wie Objekte zusammenhängen; die Multiplizität sagt, wie viele Objekte je Ende beteiligt sind.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Aggregation:</strong> schwache „hat"-Beziehung, <em>leere</em> Raute am Ganzen – die Teile bestehen auch allein.</li>
|
||||
<li><strong>Komposition:</strong> starke Existenzabhängigkeit, <em>gefüllte</em> Raute am Ganzen – ohne das Ganze verschwinden die Teile.</li>
|
||||
<li><strong>Vererbung:</strong> durchgezogene Linie mit hohlem Dreieckspfeil zur Oberklasse. <strong>Multiplizität:</strong> <code>1</code> genau eins, <code>0..1</code> optional, <code>1..*</code> mindestens eins.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Gefüllte Raute = Komposition (existenzabhängig), leere Raute = Aggregation (lose „hat").</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Sequenzdiagramm</h2>
|
||||
<p>Das Sequenzdiagramm zeigt den zeitlichen Nachrichtenaustausch zwischen Objekten – die Zeit läuft von oben nach unten.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Lebenslinie:</strong> gestrichelte Senkrechte unter dem Objekt, zeigt dessen Existenz über die Zeit.</li>
|
||||
<li><strong>Aktivierungsbalken:</strong> schmales Rechteck auf der Lebenslinie – das Objekt führt gerade eine Operation aus.</li>
|
||||
<li><strong>Nachrichten:</strong> synchron = durchgezogene Linie mit gefüllter Spitze (Sender wartet); asynchron = offene Spitze (Sender läuft weiter); Antwort = gestrichelt.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Gefüllte Pfeilspitze = synchron und warten, offene Spitze = asynchron und weiterlaufen.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Aktivitäts- & Use-Case-Diagramm</h2>
|
||||
<p>Das Aktivitätsdiagramm modelliert Abläufe, das Use-Case-Diagramm die Anforderungen aus Sicht der Akteure.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Aktivität:</strong> Start = gefüllter Kreis, Ende = Kreis mit Ring, Aktion = abgerundetes Rechteck, Entscheidung = Raute, Parallelität = fork/join-Balken.</li>
|
||||
<li><strong>Use Case:</strong> Akteur = Strichmännchen außerhalb der Systemgrenze (Rahmen), Use Case = Ellipse innerhalb.</li>
|
||||
<li><strong>include vs. extend:</strong> <code><<include>></code> bindet immer verpflichtend ein, <code><<extend>></code> ergänzt nur unter einer Bedingung (optional).</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Raute = Entscheidung (ein Zweig), Balken = Parallelität (mehrere Zweige gleichzeitig).</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Prüfungsmodus</h2>
|
||||
<p>Dieser ProtocolCheck führt dich durch die wichtigsten AP2-Aspekte dieses Themas. Du kannst Antworten prüfen, Fragen überspringen und bekommst am Ende eine Auswertung nach Kategorie.</p>
|
||||
</section>
|
||||
|
||||
<section class="finalflux-test" data-finalflux-test aria-live="polite">
|
||||
<div class="test-meta">
|
||||
<span data-question-counter>Frage 1</span>
|
||||
<span data-score-counter>0 richtig</span>
|
||||
</div>
|
||||
<div class="category-block">
|
||||
<p class="eyebrow">Kategorie</p>
|
||||
<h2 data-category-title>UML</h2>
|
||||
<p data-category-hint>AP2-Training</p>
|
||||
</div>
|
||||
<div class="question-card">
|
||||
<h3 data-question-text></h3>
|
||||
<div data-answer-options class="answer-options"></div>
|
||||
<div data-answer-feedback class="fluxcheck-result"></div>
|
||||
</div>
|
||||
<div class="test-actions">
|
||||
<button type="button" data-check-answer>Antwort prüfen</button>
|
||||
<button type="button" data-skip-question class="secondary">Überspringen</button>
|
||||
<button type="button" data-next-question class="secondary" disabled>Weiter</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section data-test-result class="finalflux-panel" hidden>
|
||||
<h2>Auswertung</h2>
|
||||
<p data-result-summary></p>
|
||||
<ul data-category-summary class="key-list"></ul>
|
||||
<div class="finalflux-actions">
|
||||
<button type="button" data-restart-test>Test neu starten</button>
|
||||
<a href="/finalflux">Zurück zur Übersicht</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="/js/finalflux-test.js" data-questions-src="/finalflux/questions/ap2_fiae_uml.json" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Hochverfügbarkeit - FinalProtocol AP2</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>IT-Sicherheit - FinalProtocol AP2</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
<main>
|
||||
<a href="/finalflux" class="back-link">Zurück zu FinalProtocol</a>
|
||||
<h1>IT-Sicherheit</h1>
|
||||
|
||||
<section>
|
||||
<h2>Lernfokus</h2>
|
||||
<p>AP2 Systemintegration: Netzgrenzen mit Firewall und DMZ absichern, VPN- und Verschlüsselungsverfahren richtig auswählen sowie Zertifikate, Schutzziele und Authentisierung sauber begründen.</p>
|
||||
<ul class="key-list">
|
||||
<li>Firewall-Typen und DMZ-Konzepte unterscheiden und nach Default-Deny planen.</li>
|
||||
<li>VPN-Modi sowie symmetrische, asymmetrische und hybride Verschlüsselung einordnen.</li>
|
||||
<li>PKI, digitale Signatur, CIA-Triade und Mehr-Faktor-Authentisierung erklären.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Firewall & DMZ</h2>
|
||||
<p>Firewalls trennen Netze unterschiedlicher Vertrauensstufe. Eine DMZ (entmilitarisierte Zone) ist ein Zwischennetz für öffentlich erreichbare Dienste, das weder ganz außen noch im internen LAN liegt.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Paketfilter vs. Stateful Inspection:</strong> der zustandslose Filter prüft jedes Paket einzeln, die Stateful-Firewall verfolgt den Verbindungszustand und lässt Antwortpakete gezielt zu.</li>
|
||||
<li><strong>DMZ:</strong> öffentlich erreichbare Server (Web, Mail) werden isoliert, damit ein Einbruch dort nicht sofort das interne Netz öffnet.</li>
|
||||
<li><strong>Screened Subnet:</strong> äußere und innere Firewall spannen die DMZ dazwischen auf – ein Angreifer muss beide überwinden.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Ein gutes Regelwerk arbeitet nach Default-Deny: alles verbieten und nur den ausdrücklich benötigten Verkehr freigeben.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>VPN & Verschlüsselung</h2>
|
||||
<p>Ein VPN baut über ein unsicheres Netz einen geschützten Tunnel auf. Die dahinterliegenden Verfahren kombinieren meist Geschwindigkeit und sicheren Schlüsselaustausch.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>IPsec:</strong> ESP verschlüsselt die Nutzdaten (Vertraulichkeit), AH sichert nur Integrität/Authentizität, IKE handelt die Schlüssel aus.</li>
|
||||
<li><strong>Tunnel- vs. Transportmodus:</strong> der Tunnelmodus kapselt das ganze Original-Paket (Site-to-Site), der Transportmodus schützt nur die Nutzlast (Host-to-Host).</li>
|
||||
<li><strong>Hybridverfahren:</strong> das asymmetrische Verfahren tauscht sicher den Sitzungsschlüssel aus, das schnelle symmetrische Verfahren (z. B. AES) verschlüsselt die Massendaten.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Vertraulichkeit heißt mit dem öffentlichen Schlüssel des Empfängers verschlüsseln – nur dessen privater Schlüssel öffnet die Nachricht.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Zertifikate & PKI</h2>
|
||||
<p>Eine Public-Key-Infrastruktur schafft Vertrauen in öffentliche Schlüssel. Digitale Signaturen belegen Echtheit und Urheberschaft.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>CA & Vertrauenskette:</strong> die Zertifizierungsstelle signiert Zertifikate; das Vertrauen reicht über Zwischenzertifikate bis zu einer im System hinterlegten Root-CA.</li>
|
||||
<li><strong>Digitale Signatur:</strong> der Absender signiert mit seinem privaten Schlüssel, jeder prüft mit dem öffentlichen – das sichert Integrität und Nichtabstreitbarkeit.</li>
|
||||
<li><strong>Widerruf:</strong> zurückgezogene Zertifikate erkennt man über eine Sperrliste (CRL) oder eine OCSP-Abfrage, nicht am Ablaufdatum.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Signieren mit dem privaten Schlüssel, verschlüsseln mit dem öffentlichen Schlüssel des Empfängers – Signatur und Vertraulichkeit nie verwechseln.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Schutzziele & Authentisierung</h2>
|
||||
<p>Die CIA-Triade bündelt die drei zentralen Schutzziele der Informationssicherheit. Authentisierung und Autorisierung sind zwei getrennte Schritte der Zugriffskontrolle.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>CIA-Triade:</strong> Vertraulichkeit (kein unbefugtes Lesen), Integrität (keine unbemerkte Veränderung), Verfügbarkeit (erreichbar bei Bedarf).</li>
|
||||
<li><strong>2FA/MFA:</strong> echte Mehr-Faktor-Authentisierung kombiniert verschiedene Faktorarten – Wissen, Besitz und Inhärenz (Biometrie).</li>
|
||||
<li><strong>Authentisierung vs. Autorisierung:</strong> zuerst wird die Identität nachgewiesen, danach werden die Zugriffsrechte festgelegt.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Ein zweiter Faktor zählt nur, wenn er aus einer anderen Kategorie stammt – zwei Passwörter bleiben ein einziger Faktor Wissen.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Prüfungsmodus</h2>
|
||||
<p>Dieser ProtocolCheck führt dich durch die wichtigsten AP2-Aspekte dieses Themas. Du kannst Antworten prüfen, Fragen überspringen und bekommst am Ende eine Auswertung nach Kategorie.</p>
|
||||
</section>
|
||||
|
||||
<section class="finalflux-test" data-finalflux-test aria-live="polite">
|
||||
<div class="test-meta">
|
||||
<span data-question-counter>Frage 1</span>
|
||||
<span data-score-counter>0 richtig</span>
|
||||
</div>
|
||||
<div class="category-block">
|
||||
<p class="eyebrow">Kategorie</p>
|
||||
<h2 data-category-title>IT-Sicherheit</h2>
|
||||
<p data-category-hint>AP2-Training</p>
|
||||
</div>
|
||||
<div class="question-card">
|
||||
<h3 data-question-text></h3>
|
||||
<div data-answer-options class="answer-options"></div>
|
||||
<div data-answer-feedback class="fluxcheck-result"></div>
|
||||
</div>
|
||||
<div class="test-actions">
|
||||
<button type="button" data-check-answer>Antwort prüfen</button>
|
||||
<button type="button" data-skip-question class="secondary">Überspringen</button>
|
||||
<button type="button" data-next-question class="secondary" disabled>Weiter</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section data-test-result class="finalflux-panel" hidden>
|
||||
<h2>Auswertung</h2>
|
||||
<p data-result-summary></p>
|
||||
<ul data-category-summary class="key-list"></ul>
|
||||
<div class="finalflux-actions">
|
||||
<button type="button" data-restart-test>Test neu starten</button>
|
||||
<a href="/finalflux">Zurück zur Übersicht</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="/js/finalflux-test.js" data-questions-src="/finalflux/questions/ap2_fisi_it-sicherheit.json" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,114 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Monitoring & Backup - FinalProtocol AP2</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
<main>
|
||||
<a href="/finalflux" class="back-link">Zurück zu FinalProtocol</a>
|
||||
<h1>Monitoring & Backup</h1>
|
||||
|
||||
<section>
|
||||
<h2>Lernfokus</h2>
|
||||
<p>AP2 Systemintegration: Systeme sinnvoll überwachen, Backup-Strategien auswählen und die Wiederherstellung samt Serverkapazität belastbar planen.</p>
|
||||
<ul class="key-list">
|
||||
<li>Monitoring mit SNMP, Schwellwerten und Alarmen ohne Fehlalarm-Flut aufbauen.</li>
|
||||
<li>Backup-Arten und -Strategien (3-2-1, Voll/Inkrementell/Differenziell, Generationenprinzip) sicher unterscheiden.</li>
|
||||
<li>Wiederherstellung über RTO/RPO und Restore-Tests bewerten und Server bedarfsgerecht dimensionieren.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Monitoring & SNMP</h2>
|
||||
<p>Monitoring überwacht Systeme fortlaufend und meldet Abweichungen, bevor daraus ein Ausfall wird. SNMP ist dafür das klassische Protokoll im Netzwerk.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Rollen:</strong> der Agent läuft auf dem überwachten Gerät und hält seine Werte in der MIB bereit, der Manager (NMS) fragt sie ab und stellt sie dar.</li>
|
||||
<li><strong>Polling vs. Trap:</strong> beim Polling fragt der Manager zyklisch ab, beim Trap meldet der Agent ein Ereignis von sich aus - das verkürzt die Reaktionszeit.</li>
|
||||
<li><strong>Schwellwerte:</strong> abgestuft in Warning (frühe Warnung) und Critical (Notfall); eine Mindestdauer vermeidet Fehlalarme durch kurze Lastspitzen.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Der Agent hält die Daten, der Manager holt sie - und ein guter Schwellwert warnt früh genug, ohne bei jeder Spitze Alarm zu schlagen.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Backup-Arten & -Strategie</h2>
|
||||
<p>Die Wahl der Backup-Art entscheidet über Sicherungsdauer, Speicherbedarf und den Aufwand beim Restore. Die 3-2-1-Regel gibt den strategischen Rahmen.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>3-2-1-Regel:</strong> drei Datenkopien, auf zwei verschiedenen Medientypen, davon eine an einem externen Standort (offsite).</li>
|
||||
<li><strong>Vollbackup:</strong> sichert jedes Mal den gesamten Datenbestand - einfachster Restore, höchster Speicher- und Zeitbedarf.</li>
|
||||
<li><strong>Inkrementell:</strong> sichert nur die Änderungen seit dem <em>letzten Backup</em> (egal welcher Art) - kleine Sicherungen, aber lange Restore-Kette.</li>
|
||||
<li><strong>Differenziell:</strong> sichert alle Änderungen seit dem <em>letzten Vollbackup</em> - wachsende Sicherungen, aber Restore aus Vollbackup plus einer Sicherung.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Inkrementell misst ab dem letzten Backup, differenziell immer ab dem letzten Vollbackup - genau das entscheidet, wie viele Sicherungen du beim Restore brauchst.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Generationenprinzip</h2>
|
||||
<p>Das Großvater-Vater-Sohn-Prinzip staffelt Sicherungen zeitlich, damit man auch auf ältere Datenstände zurückgreifen kann.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Sohn:</strong> tägliche Sicherung, wird schnell wieder überschrieben (jüngster Stand).</li>
|
||||
<li><strong>Vater:</strong> wöchentliche Sicherung mit mittlerer Aufbewahrung.</li>
|
||||
<li><strong>Großvater:</strong> monatliche Sicherung, wird am längsten aufbewahrt (Langzeitstand).</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Je höher die Generation, desto länger die Aufbewahrung - so bleibt auch ein spät entdeckter Fehler (z. B. Ransomware) reparierbar.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Wiederherstellung & Serverdimensionierung</h2>
|
||||
<p>Ein Backup ist nur so gut wie sein Restore. RTO und RPO geben die Ziele vor, die Serverdimensionierung liefert die nötige Kapazität und Redundanz.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>RTO:</strong> maximal tolerierbare Zeit bis zur Wiederverfügbarkeit - wie lange darf der Ausfall dauern?</li>
|
||||
<li><strong>RPO:</strong> maximal tolerierbarer Datenverlust - er bestimmt das Sicherungsintervall (RPO 1 h → mindestens stündlich sichern).</li>
|
||||
<li><strong>Restore-Test:</strong> nur ein zurückgespieltes Backup beweist, dass Medien lesbar und Daten brauchbar sind - ein grünes Protokoll allein genügt nicht.</li>
|
||||
<li><strong>Dimensionierung:</strong> Kapazitätsreserven für Wachstum einplanen, Redundanz über RAID und N+1 (eine Einheit mehr als nötig); IOPS zählen bei Datenbanken.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> RTO fragt nach der Zeit, RPO nach der Datenmenge - und RAID ist Redundanz für den laufenden Betrieb, aber niemals ein Ersatz für ein Backup.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Prüfungsmodus</h2>
|
||||
<p>Dieser ProtocolCheck führt dich durch die wichtigsten AP2-Aspekte dieses Themas. Du kannst Antworten prüfen, Fragen überspringen und bekommst am Ende eine Auswertung nach Kategorie.</p>
|
||||
</section>
|
||||
|
||||
<section class="finalflux-test" data-finalflux-test aria-live="polite">
|
||||
<div class="test-meta">
|
||||
<span data-question-counter>Frage 1</span>
|
||||
<span data-score-counter>0 richtig</span>
|
||||
</div>
|
||||
<div class="category-block">
|
||||
<p class="eyebrow">Kategorie</p>
|
||||
<h2 data-category-title>Monitoring & Backup</h2>
|
||||
<p data-category-hint>AP2-Training</p>
|
||||
</div>
|
||||
<div class="question-card">
|
||||
<h3 data-question-text></h3>
|
||||
<div data-answer-options class="answer-options"></div>
|
||||
<div data-answer-feedback class="fluxcheck-result"></div>
|
||||
</div>
|
||||
<div class="test-actions">
|
||||
<button type="button" data-check-answer>Antwort prüfen</button>
|
||||
<button type="button" data-skip-question class="secondary">Überspringen</button>
|
||||
<button type="button" data-next-question class="secondary" disabled>Weiter</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section data-test-result class="finalflux-panel" hidden>
|
||||
<h2>Auswertung</h2>
|
||||
<p data-result-summary></p>
|
||||
<ul data-category-summary class="key-list"></ul>
|
||||
<div class="finalflux-actions">
|
||||
<button type="button" data-restart-test>Test neu starten</button>
|
||||
<a href="/finalflux">Zurück zur Übersicht</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="/js/finalflux-test.js" data-questions-src="/finalflux/questions/ap2_fisi_monitoring-backup.json" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,111 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Netzwerkplanung - FinalProtocol AP2</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
<main>
|
||||
<a href="/finalflux" class="back-link">Zurück zu FinalProtocol</a>
|
||||
<h1>Netzwerkplanung</h1>
|
||||
|
||||
<section>
|
||||
<h2>Lernfokus</h2>
|
||||
<p>AP2 Systemintegration: Adressräume berechnen, IPv6 lesen, WLAN sauber auslegen und Routing-Entscheidungen begründen.</p>
|
||||
<ul class="key-list">
|
||||
<li>Subnetze per VLSM bedarfsgerecht und sparsam dimensionieren.</li>
|
||||
<li>IPv6-Adressen kürzen sowie Adresstypen und Präfixe einordnen.</li>
|
||||
<li>WLAN-Kanäle und Sicherheitsverfahren planen, Routing-Wege nachvollziehen.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Subnetting & VLSM</h2>
|
||||
<p>Beim Subnetting teilst du einen Adressraum in kleinere Netze. VLSM (Variable Length Subnet Mask) erlaubt unterschiedlich große Subnetze im selben Netz – so verschwendest du keine Adressen.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Host-Formel:</strong> nutzbare Hosts = 2^(Host-Bits) − 2 (Netz- und Broadcast-Adresse abgezogen).</li>
|
||||
<li><strong>Blockgröße:</strong> 256 − Maskenwert des letzten Oktetts; sie legt die Netzgrenzen fest (z. B. /26 → Blöcke à 64).</li>
|
||||
<li><strong>VLSM-Reihenfolge:</strong> größte Netze zuerst vergeben, damit der Restraum lückenlos für kleinere Netze reicht.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Erst den Bedarf (Hosts) bestimmen, daraus die Host-Bits, daraus die Maske – nie umgekehrt raten.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>IPv6-Grundlagen</h2>
|
||||
<p>IPv6 nutzt 128 Bit und eine hexadezimale Schreibweise. Kürzungsregeln und Adresstypen werden in der AP2 gern abgefragt.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Kürzen:</strong> führende Nullen je Gruppe streichen, eine zusammenhängende Nullfolge einmal durch <code>::</code> ersetzen (nur einmal erlaubt).</li>
|
||||
<li><strong>Adresstypen:</strong> Global Unicast (2000::/3), Link-Local (fe80::/10, nur lokal), Multicast (ff00::/8) – Broadcast entfällt.</li>
|
||||
<li><strong>Präfixe:</strong> /64 pro Segment (SLAAC), /48 typische Standort-Zuteilung, /128 einzelne Host-Adresse.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Das <code>::</code> darf pro Adresse nur einmal vorkommen, sonst ist die Anzahl der Nullgruppen nicht mehr eindeutig.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>WLAN-Planung</h2>
|
||||
<p>Funknetze brauchen eine überlappungsfreie Kanalvergabe und ein zeitgemäßes Sicherheitsverfahren.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>2,4 GHz:</strong> nur die Kanäle 1, 6 und 11 überlappen sich nicht – ideal für benachbarte Access Points.</li>
|
||||
<li><strong>5 GHz:</strong> mehr überlappungsfreie Kanäle und weniger Störungen, dafür geringere Reichweite und Wanddurchdringung.</li>
|
||||
<li><strong>Sicherheit:</strong> WPA3-Enterprise mit RADIUS (802.1X) authentisiert jeden Nutzer einzeln; WEP und offene Netze meiden.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Routing</h2>
|
||||
<p>Router entscheiden anhand ihrer Routing-Tabelle, wohin ein Paket geht.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Longest-Prefix-Match:</strong> die spezifischste passende Route (längstes Präfix) gewinnt – unabhängig von der Reihenfolge.</li>
|
||||
<li><strong>Statisch vs. dynamisch:</strong> manuell eingetragen gegenüber selbstlernend über OSPF, RIP oder BGP.</li>
|
||||
<li><strong>Default-Route 0.0.0.0/0:</strong> Auffangbecken für alle Ziele ohne spezifischere Route (meist zum Gateway).</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> OSPF ist das Link-State-IGP im eigenen Netz, BGP das Protokoll zwischen autonomen Systemen im Internet.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Prüfungsmodus</h2>
|
||||
<p>Dieser ProtocolCheck führt dich durch die wichtigsten AP2-Aspekte dieses Themas. Du kannst Antworten prüfen, Fragen überspringen und bekommst am Ende eine Auswertung nach Kategorie.</p>
|
||||
</section>
|
||||
|
||||
<section class="finalflux-test" data-finalflux-test aria-live="polite">
|
||||
<div class="test-meta">
|
||||
<span data-question-counter>Frage 1</span>
|
||||
<span data-score-counter>0 richtig</span>
|
||||
</div>
|
||||
<div class="category-block">
|
||||
<p class="eyebrow">Kategorie</p>
|
||||
<h2 data-category-title>Netzwerkplanung</h2>
|
||||
<p data-category-hint>AP2-Training</p>
|
||||
</div>
|
||||
<div class="question-card">
|
||||
<h3 data-question-text></h3>
|
||||
<div data-answer-options class="answer-options"></div>
|
||||
<div data-answer-feedback class="fluxcheck-result"></div>
|
||||
</div>
|
||||
<div class="test-actions">
|
||||
<button type="button" data-check-answer>Antwort prüfen</button>
|
||||
<button type="button" data-skip-question class="secondary">Überspringen</button>
|
||||
<button type="button" data-next-question class="secondary" disabled>Weiter</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section data-test-result class="finalflux-panel" hidden>
|
||||
<h2>Auswertung</h2>
|
||||
<p data-result-summary></p>
|
||||
<ul data-category-summary class="key-list"></ul>
|
||||
<div class="finalflux-actions">
|
||||
<button type="button" data-restart-test>Test neu starten</button>
|
||||
<a href="/finalflux">Zurück zur Übersicht</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="/js/finalflux-test.js" data-questions-src="/finalflux/questions/ap2_fisi_netzwerkplanung.json" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,113 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Skripting - FinalProtocol AP2</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
<main>
|
||||
<a href="/finalflux" class="back-link">Zurück zu FinalProtocol</a>
|
||||
<h1>Skripting</h1>
|
||||
|
||||
<section>
|
||||
<h2>Lernfokus</h2>
|
||||
<p>AP2 Systemintegration: Aufgaben in Bash und PowerShell automatisieren, Kontrollstrukturen sauber entwerfen und Skripte über Exit-Codes zuverlässig überwachen.</p>
|
||||
<ul class="key-list">
|
||||
<li>Bash-Grundlagen beherrschen: Variablen, Pipes und Umleitungen zielsicher einsetzen.</li>
|
||||
<li>PowerShell verstehen: Cmdlets, Objekte und die objektbasierte Pipeline nutzen.</li>
|
||||
<li>Abläufe automatisieren und über Exit-Codes, <code>$?</code> und Fehlerströme absichern.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Shell-Grundlagen (Bash)</h2>
|
||||
<p>In der Bash steuerst du Programme über die Kommandozeile. Variablen, Pipes und Umleitungen sind die Bausteine jedes Skripts.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Variablen:</strong> Zuweisung ohne Leerzeichen wie <code>NAME="wert"</code>, Auslesen mit Dollarzeichen als <code>$NAME</code>.</li>
|
||||
<li><strong>Pipes:</strong> <code>|</code> reicht die Standardausgabe eines Kommandos als Standardeingabe an das nächste weiter (z. B. <code>ls -l | grep txt</code>).</li>
|
||||
<li><strong>Umleitungen:</strong> <code>></code> überschreibt eine Datei, <code>>></code> hängt an, <code><</code> liest eine Datei als Eingabe ein.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Ein einfaches <code>></code> überschreibt, der doppelte Pfeil <code>>></code> hängt an – erst prüfen, dann umleiten.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>PowerShell</h2>
|
||||
<p>PowerShell arbeitet objektorientiert: Cmdlets liefern keine reinen Textzeilen, sondern strukturierte Objekte mit Eigenschaften.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Cmdlets:</strong> folgen dem Schema Verb-Nomen, etwa <code>Get-Service</code> oder <code>Stop-Process</code>.</li>
|
||||
<li><strong>Objekt-Pipeline:</strong> zwischen den Cmdlets fließen ganze .NET-Objekte, sodass Folge-Cmdlets gezielt auf Felder zugreifen.</li>
|
||||
<li><strong>Verarbeitung:</strong> <code>Where-Object</code> filtert nach Bedingung, <code>Select-Object</code> wählt Eigenschaften, <code>Sort-Object</code> sortiert.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Bash schiebt Text durch die Pipeline, PowerShell schiebt Objekte – deshalb kannst du in PowerShell direkt nach Eigenschaften filtern.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Kontrollstrukturen & Pseudocode</h2>
|
||||
<p>Kontrollstrukturen steuern den Ablauf: Verzweigungen entscheiden, Schleifen wiederholen. Im Pseudocode planst du das sprachunabhängig.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Verzweigung:</strong> <code>if</code>-<code>elseif</code>-<code>else</code> (oder <code>switch</code>) führt je nach Bedingung genau einen Zweig aus.</li>
|
||||
<li><strong>Zählschleife:</strong> <code>for</code> nutzt du, wenn die Anzahl der Durchläufe von vornherein feststeht.</li>
|
||||
<li><strong>Bedingte Schleife:</strong> die kopfgesteuerte <code>while</code> prüft vor jedem Durchlauf; die fußgesteuerte <code>do-while</code> läuft mindestens einmal.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Feste Anzahl bekannt? Nimm <code>for</code>. Nur eine Abbruchbedingung bekannt? Nimm <code>while</code>.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Automatisierung, Exit-Codes & Fehlerbehandlung</h2>
|
||||
<p>Skripte laufen oft ohne Aufsicht. Zeitplaner starten sie, und Rückgabewerte verraten, ob sie erfolgreich waren.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Cron (Linux):</strong> fünf Felder in der Reihenfolge Minute, Stunde, Tag des Monats, Monat, Wochentag; <code>30 2 * * *</code> läuft täglich um 02:30 Uhr, <code>*/15 * * * *</code> alle 15 Minuten.</li>
|
||||
<li><strong>Aufgabenplanung (Windows):</strong> der Taskplaner bzw. <code>schtasks</code> startet Aufgaben zeitgesteuert, auch ohne angemeldeten Benutzer.</li>
|
||||
<li><strong>Exit-Codes:</strong> <code>0</code> bedeutet Erfolg, jeder Wert ungleich 0 einen Fehler; in Bash liest <code>$?</code> den letzten Rückgabewert aus.</li>
|
||||
<li><strong>Fehlerströme:</strong> <code>2></code> leitet die Standardfehlerausgabe (stderr, Deskriptor 2) um; in PowerShell fängt ein <code>try-catch</code>-Block Laufzeitfehler ab.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Exit-Code 0 heißt „alles gut", alles andere heißt „prüfen" – und stderr (Deskriptor 2) ist getrennt von stdout (Deskriptor 1).</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Prüfungsmodus</h2>
|
||||
<p>Dieser ProtocolCheck führt dich durch die wichtigsten AP2-Aspekte dieses Themas. Du kannst Antworten prüfen, Fragen überspringen und bekommst am Ende eine Auswertung nach Kategorie.</p>
|
||||
</section>
|
||||
|
||||
<section class="finalflux-test" data-finalflux-test aria-live="polite">
|
||||
<div class="test-meta">
|
||||
<span data-question-counter>Frage 1</span>
|
||||
<span data-score-counter>0 richtig</span>
|
||||
</div>
|
||||
<div class="category-block">
|
||||
<p class="eyebrow">Kategorie</p>
|
||||
<h2 data-category-title>Skripting</h2>
|
||||
<p data-category-hint>AP2-Training</p>
|
||||
</div>
|
||||
<div class="question-card">
|
||||
<h3 data-question-text></h3>
|
||||
<div data-answer-options class="answer-options"></div>
|
||||
<div data-answer-feedback class="fluxcheck-result"></div>
|
||||
</div>
|
||||
<div class="test-actions">
|
||||
<button type="button" data-check-answer>Antwort prüfen</button>
|
||||
<button type="button" data-skip-question class="secondary">Überspringen</button>
|
||||
<button type="button" data-next-question class="secondary" disabled>Weiter</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section data-test-result class="finalflux-panel" hidden>
|
||||
<h2>Auswertung</h2>
|
||||
<p data-result-summary></p>
|
||||
<ul data-category-summary class="key-list"></ul>
|
||||
<div class="finalflux-actions">
|
||||
<button type="button" data-restart-test>Test neu starten</button>
|
||||
<a href="/finalflux">Zurück zur Übersicht</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="/js/finalflux-test.js" data-questions-src="/finalflux/questions/ap2_fisi_skripting.json" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,101 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Virtualisierung - FinalProtocol AP2</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
<main>
|
||||
<a href="/finalflux" class="back-link">Zurück zu FinalProtocol</a>
|
||||
<h1>Virtualisierung</h1>
|
||||
|
||||
<section>
|
||||
<h2>Lernfokus</h2>
|
||||
<p>AP2 Systemintegration: Hypervisor-Typen unterscheiden, Container gegen VMs abgrenzen, Snapshots und Cloud-Modelle sicher einordnen sowie Speicher und Ressourcen bewusst planen.</p>
|
||||
<ul class="key-list">
|
||||
<li>Typ-1- und Typ-2-Hypervisoren sowie Container und VMs nach Aufbau und Isolation unterscheiden.</li>
|
||||
<li>Snapshots, Klone und die Grenzen zum Backup sauber einordnen.</li>
|
||||
<li>Cloud-Servicemodelle mit Verantwortungsteilung sowie Provisioning, Overcommitment und Live-Migration begründen.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Hypervisoren & Container</h2>
|
||||
<p>Virtualisierung entkoppelt Betriebssysteme von der physischen Hardware. Hypervisoren betreiben vollständige VMs, Container teilen sich hingegen den Kernel des Hosts.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Typ-1 (Bare-Metal):</strong> läuft direkt auf der Hardware (z. B. ESXi, Hyper-V) – geringer Overhead, Standard im Rechenzentrum.</li>
|
||||
<li><strong>Typ-2 (Hosted):</strong> setzt als Anwendung auf einem Host-Betriebssystem auf (z. B. VirtualBox) – praktisch für Arbeitsplätze und Tests.</li>
|
||||
<li><strong>Container:</strong> nutzen den Host-Kernel gemeinsam, starten schnell und sparen Ressourcen; ein Image ist die unveränderliche Vorlage, der Container die laufende Instanz.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Jede VM bringt ein eigenes Betriebssystem mit und ist durch den Hypervisor stark isoliert; Container teilen den Kernel und sind dafür leichtgewichtiger.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Snapshots & Klone</h2>
|
||||
<p>Snapshots und Klone helfen beim Testen und Vervielfältigen von VMs – ersetzen aber keine Datensicherung.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>Snapshot:</strong> festgehaltener Zustand zu einem Zeitpunkt; Rückkehr möglich, liegt aber meist als Delta auf demselben Storage.</li>
|
||||
<li><strong>Linked Clone:</strong> teilt die Basisplatten mit dem Original, spart Speicher, bleibt aber davon abhängig; ein Full Clone ist eigenständig und größer.</li>
|
||||
<li><strong>Langlebige Snapshots:</strong> wachsende Delta-Dateien belegen Speicher und mindern die Leistung – deshalb nur kurz halten.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Ein Snapshot ist kein Backup – fällt der Speicher aus, ist auch der Snapshot verloren.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Cloud-Modelle & Ressourcen</h2>
|
||||
<p>Cloud-Servicemodelle regeln, wie viel Betrieb der Anbieter übernimmt. Bei Speicher und Ressourcen entscheidet die Planung über Kosten und Stabilität.</p>
|
||||
<ul class="key-list">
|
||||
<li><strong>IaaS/PaaS/SaaS:</strong> von virtueller Infrastruktur über die Plattform bis zur fertigen Anwendung – je höher das Modell, desto mehr übernimmt der Anbieter.</li>
|
||||
<li><strong>Shared Responsibility:</strong> die Verantwortung für eigene Daten und Zugriffsrechte bleibt in jedem Modell beim Kunden.</li>
|
||||
<li><strong>Provisioning & Overcommitment:</strong> Thin belegt Speicher erst bei Bedarf, Thick sofort; Überbuchung spart Ressourcen, riskiert aber Engpässe bei Spitzenlast.</li>
|
||||
</ul>
|
||||
<p class="merksatz"><strong>Merksatz:</strong> Eine Live-Migration verschiebt eine laufende VM ohne Downtime – setzt aber üblicherweise gemeinsamen Speicher und kompatible CPUs voraus.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Prüfungsmodus</h2>
|
||||
<p>Dieser ProtocolCheck führt dich durch die wichtigsten AP2-Aspekte dieses Themas. Du kannst Antworten prüfen, Fragen überspringen und bekommst am Ende eine Auswertung nach Kategorie.</p>
|
||||
</section>
|
||||
|
||||
<section class="finalflux-test" data-finalflux-test aria-live="polite">
|
||||
<div class="test-meta">
|
||||
<span data-question-counter>Frage 1</span>
|
||||
<span data-score-counter>0 richtig</span>
|
||||
</div>
|
||||
<div class="category-block">
|
||||
<p class="eyebrow">Kategorie</p>
|
||||
<h2 data-category-title>Virtualisierung</h2>
|
||||
<p data-category-hint>AP2-Training</p>
|
||||
</div>
|
||||
<div class="question-card">
|
||||
<h3 data-question-text></h3>
|
||||
<div data-answer-options class="answer-options"></div>
|
||||
<div data-answer-feedback class="fluxcheck-result"></div>
|
||||
</div>
|
||||
<div class="test-actions">
|
||||
<button type="button" data-check-answer>Antwort prüfen</button>
|
||||
<button type="button" data-skip-question class="secondary">Überspringen</button>
|
||||
<button type="button" data-next-question class="secondary" disabled>Weiter</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section data-test-result class="finalflux-panel" hidden>
|
||||
<h2>Auswertung</h2>
|
||||
<p data-result-summary></p>
|
||||
<ul data-category-summary class="key-list"></ul>
|
||||
<div class="finalflux-actions">
|
||||
<button type="button" data-restart-test>Test neu starten</button>
|
||||
<a href="/finalflux">Zurück zur Übersicht</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="/js/finalflux-test.js" data-questions-src="/finalflux/questions/ap2_fisi_virtualisierung.json" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"title": "Algorithmen",
|
||||
"questions": [
|
||||
{
|
||||
"category": "Struktogramm & Pseudocode",
|
||||
"hint": "Geschachtelte Blöcke ohne Sprünge.",
|
||||
"text": "Was stellt ein Struktogramm (Nassi-Shneiderman-Diagramm) dar?",
|
||||
"answers": [
|
||||
"Den strukturierten Ablauf eines Algorithmus aus ineinander geschachtelten Blöcken ohne Sprünge.",
|
||||
"Die Beziehungen zwischen Datenbanktabellen.",
|
||||
"Den zeitlichen Ablauf von Nachrichten zwischen Objekten."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Ein Struktogramm nach Nassi-Shneiderman beschreibt den Kontrollfluss eines Algorithmus als lückenlos geschachtelte Blöcke; GOTO-Sprünge lassen sich damit gar nicht abbilden. Tabellenbeziehungen zeigt ein ER-Diagramm, den Nachrichtenaustausch von Objekten ein Sequenzdiagramm."
|
||||
},
|
||||
{
|
||||
"category": "Struktogramm & Pseudocode",
|
||||
"hint": "Bedingungskopf mit zwei Zweigen.",
|
||||
"text": "Wie wird eine zweiseitige Verzweigung (if-else) im Struktogramm dargestellt?",
|
||||
"answers": [
|
||||
"Als schlichtes Rechteck, das nur von oben nach unten durchlaufen wird.",
|
||||
"Als dreieckiger Bedingungskopf, unter dem sich der Ja- und der Nein-Zweig nebeneinander aufteilen.",
|
||||
"Als umschließender Schleifenrahmen um den Innenblock."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Die Verzweigung wird als dreieckiger Bedingungskopf gezeichnet, unter dem sich der Weg in einen Ja- und einen Nein-Zweig nebeneinander teilt. Ein reines Rechteck steht für eine Sequenz, der umschließende Rahmen für eine Schleife."
|
||||
},
|
||||
{
|
||||
"category": "Struktogramm & Pseudocode",
|
||||
"hint": "Wann wird die Bedingung geprüft?",
|
||||
"text": "Worin unterscheidet sich eine kopfgesteuerte (while) von einer fußgesteuerten (do-while) Schleife?",
|
||||
"answers": [
|
||||
"Die kopfgesteuerte Schleife läuft immer genau einmal mehr als die fußgesteuerte.",
|
||||
"Beide prüfen die Bedingung grundsätzlich erst nach dem ersten Durchlauf.",
|
||||
"Die kopfgesteuerte prüft die Bedingung vor dem ersten Durchlauf, die fußgesteuerte erst danach, weshalb die fußgesteuerte mindestens einmal läuft."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Die kopfgesteuerte while-Schleife prüft vor dem Eintritt und kann daher null Durchläufe haben; die fußgesteuerte do-while prüft am Ende und läuft mindestens einmal. Ein fester Unterschied von genau einem Durchlauf besteht nicht, und die Nachprüfung trifft nur auf die fußgesteuerte Variante zu."
|
||||
},
|
||||
{
|
||||
"category": "Struktogramm & Pseudocode",
|
||||
"hint": "Anweisungen streng nacheinander.",
|
||||
"text": "Was bezeichnet man in einem Algorithmus als Sequenz?",
|
||||
"answers": [
|
||||
"Eine Folge von Anweisungen, die streng nacheinander in der angegebenen Reihenfolge abgearbeitet werden.",
|
||||
"Eine Anweisung, die abhängig von einer Bedingung übersprungen wird.",
|
||||
"Einen Block, der so lange wiederholt wird, bis eine Bedingung erfüllt ist."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Die Sequenz ist die einfachste Grundstruktur: Anweisungen werden strikt der Reihe nach ausgeführt. Das bedingte Überspringen ist eine Verzweigung, das wiederholte Ausführen eine Schleife; beides sind eigene Kontrollstrukturen."
|
||||
},
|
||||
{
|
||||
"category": "Sortierverfahren",
|
||||
"hint": "Benachbarte Elemente tauschen.",
|
||||
"text": "Nach welchem Prinzip arbeitet Bubblesort?",
|
||||
"answers": [
|
||||
"Es sucht in jedem Durchlauf gezielt das kleinste Element und stellt es an den Anfang.",
|
||||
"Es vergleicht wiederholt benachbarte Elemente und vertauscht sie bei falscher Reihenfolge, bis kein Tausch mehr nötig ist.",
|
||||
"Es teilt die Liste an einem Pivot und sortiert die Teillisten getrennt."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Bubblesort vergleicht Nachbarpaare und vertauscht sie bei falscher Reihenfolge, sodass große Werte nach oben wandern. Das gezielte Suchen des Minimums je Durchlauf kennzeichnet Selectionsort, das Aufteilen am Pivot ist Quicksort."
|
||||
},
|
||||
{
|
||||
"category": "Sortierverfahren",
|
||||
"hint": "Wie beim Einsortieren von Spielkarten.",
|
||||
"text": "Wie geht Insertionsort (Sortieren durch Einfügen) grundsätzlich vor?",
|
||||
"answers": [
|
||||
"Es tauscht ausschließlich benachbarte Elemente in mehreren vollständigen Durchläufen.",
|
||||
"Es sucht in jeder Runde das Maximum und hängt es hinten an.",
|
||||
"Es nimmt Element für Element und fügt es an der passenden Stelle in den bereits sortierten vorderen Teil ein."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Insertionsort hält einen sortierten Anfangsbereich und schiebt jedes neue Element an die richtige Position darin, ähnlich dem Einsortieren von Spielkarten. Das Suchen des Maximums je Runde ist Selectionsort, reine Nachbartausche kennzeichnen Bubblesort."
|
||||
},
|
||||
{
|
||||
"category": "Sortierverfahren",
|
||||
"hint": "Teile und herrsche.",
|
||||
"text": "Auf welchem Prinzip beruht Mergesort?",
|
||||
"answers": [
|
||||
"Teile und herrsche: die Liste rekursiv halbieren, die Teillisten sortieren und anschließend geordnet zusammenführen (mergen).",
|
||||
"Ein einziger Durchlauf, der alle Elemente gleichzeitig an ihren Platz setzt.",
|
||||
"Wiederholtes Vertauschen benachbarter Elemente ohne jede Aufteilung."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Mergesort folgt dem Teile-und-herrsche-Prinzip: rekursiv halbieren, Teillisten sortieren und beim Verschmelzen sortiert zusammenfügen. Es braucht mehrere Ebenen statt eines einzigen Durchlaufs, und Nachbartausche ohne Aufteilung gehören zu Bubblesort."
|
||||
},
|
||||
{
|
||||
"category": "Sortierverfahren",
|
||||
"hint": "Pivot und Durchschnitts- versus Worst-Case.",
|
||||
"text": "Welche Aussage über Quicksort trifft zu?",
|
||||
"answers": [
|
||||
"Quicksort hat immer eine Laufzeit von O(n log n), auch im schlechtesten Fall.",
|
||||
"Quicksort teilt die Daten an einem Pivot und ist im Mittel O(n log n), im schlechtesten Fall aber O(n²).",
|
||||
"Quicksort benötigt zwingend bereits sortierte Eingabedaten."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Quicksort partitioniert am Pivot und erreicht im Durchschnitt O(n log n); bei ungünstiger Pivotwahl, etwa bei schon sortierten Daten, entartet es zu O(n²). Sortierte Eingabedaten setzt es nicht voraus, das gilt für die binäre Suche."
|
||||
},
|
||||
{
|
||||
"category": "Suchverfahren",
|
||||
"hint": "Der Reihe nach von vorne durchgehen.",
|
||||
"text": "Wie arbeitet die lineare Suche?",
|
||||
"answers": [
|
||||
"Sie halbiert den Suchbereich in jedem Schritt.",
|
||||
"Sie springt über den Index direkt an die Position des Werts.",
|
||||
"Sie prüft die Elemente der Reihe nach von vorne, bis der gesuchte Wert gefunden ist oder das Ende erreicht wird."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Die lineare Suche geht die Elemente einfach nacheinander durch, im schlechtesten Fall alle n. Das Halbieren des Bereichs ist die binäre Suche, der direkte Zugriff über den Index ist ein Array- oder Hashzugriff, keine Suche im eigentlichen Sinn."
|
||||
},
|
||||
{
|
||||
"category": "Suchverfahren",
|
||||
"hint": "Vergleich mit dem mittleren Element.",
|
||||
"text": "Welche Voraussetzung muss für die binäre Suche zwingend erfüllt sein?",
|
||||
"answers": [
|
||||
"Die Daten müssen sortiert vorliegen.",
|
||||
"Die Daten müssen in einer verketteten Liste gespeichert sein.",
|
||||
"Alle Werte müssen eindeutig, also ohne Duplikate, sein."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Die binäre Suche vergleicht mit dem mittleren Element und entscheidet, in welcher Hälfte es weitergeht, was nur bei sortierten Daten funktioniert. Eine verkettete Liste ist für den Mittelzugriff sogar ungünstig, und Eindeutigkeit der Werte ist nicht erforderlich."
|
||||
},
|
||||
{
|
||||
"category": "Suchverfahren",
|
||||
"hint": "Suchbereich halbieren.",
|
||||
"text": "Warum ist die binäre Suche bei großen sortierten Datenmengen deutlich schneller als die lineare Suche?",
|
||||
"answers": [
|
||||
"Weil sie alle Elemente gleichzeitig miteinander vergleicht.",
|
||||
"Weil sie den Suchbereich in jedem Schritt halbiert und so nur etwa log₂(n) Vergleiche braucht.",
|
||||
"Weil sie die Daten vorher automatisch in einen Index umwandelt."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Die binäre Suche halbiert den Suchraum bei jedem Vergleich, daher genügen rund log₂(n) Schritte, also O(log n). Sie vergleicht nicht alles gleichzeitig und baut auch keinen Index auf, sie setzt lediglich bereits sortierte Daten voraus."
|
||||
},
|
||||
{
|
||||
"category": "Suchverfahren",
|
||||
"hint": "Lohnt sich das Sortieren für eine einzige Suche?",
|
||||
"text": "Eine unsortierte Liste soll einmalig nach einem einzigen Wert durchsucht werden. Welches Vorgehen ist sinnvoll?",
|
||||
"answers": [
|
||||
"Die binäre Suche direkt auf der unsortierten Liste anwenden.",
|
||||
"Die Liste ist so grundsätzlich gar nicht durchsuchbar.",
|
||||
"Die lineare Suche, da sich das vorherige Sortieren für eine einzige Suche meist nicht lohnt."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Für eine einmalige Suche in unsortierten Daten ist die lineare Suche mit O(n) sinnvoll, weil allein das Sortieren schon O(n log n) kostet. Die binäre Suche würde auf unsortierten Daten falsche Ergebnisse liefern, und durchsuchbar ist die Liste selbstverständlich."
|
||||
},
|
||||
{
|
||||
"category": "O-Notation",
|
||||
"hint": "Aufwand unabhängig von der Datenmenge.",
|
||||
"text": "Wofür steht die Komplexität O(1)?",
|
||||
"answers": [
|
||||
"Konstante Laufzeit: der Aufwand bleibt unabhängig von der Eingabegröße gleich.",
|
||||
"Der Aufwand wächst linear mit der Eingabegröße.",
|
||||
"Der Aufwand verdoppelt sich mit jedem zusätzlichen Element."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "O(1) bedeutet konstanten Aufwand, etwa der Zugriff auf ein Array-Element über den Index, bei dem die Datenmenge keine Rolle spielt. Linear wachsender Aufwand ist O(n), eine Verdopplung je Element beschreibt exponentielles Wachstum wie O(2ⁿ)."
|
||||
},
|
||||
{
|
||||
"category": "O-Notation",
|
||||
"hint": "Effiziente vergleichsbasierte Sortierer.",
|
||||
"text": "Welche Komplexität haben effiziente vergleichsbasierte Sortierverfahren wie Mergesort im Normalfall?",
|
||||
"answers": [
|
||||
"O(n)",
|
||||
"O(n log n)",
|
||||
"O(n²)"
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Gute vergleichsbasierte Sortierverfahren wie Mergesort und Quicksort im Mittel liegen bei O(n log n). O(n) ist für vergleichsbasiertes Sortieren nicht erreichbar, und O(n²) beschreibt die einfachen Verfahren wie Bubble- oder Insertionsort im schlechten Fall."
|
||||
},
|
||||
{
|
||||
"category": "O-Notation",
|
||||
"hint": "Zwei verschachtelte Schleifen über alle Elemente.",
|
||||
"text": "Ein Algorithmus enthält eine Schleife, die für jedes Element eine weitere vollständige Schleife über alle Elemente durchläuft. Welche Komplexität liegt vor?",
|
||||
"answers": [
|
||||
"O(log n)",
|
||||
"O(n)",
|
||||
"O(n²)"
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Zwei ineinander verschachtelte Schleifen über je n Elemente ergeben n mal n, also O(n²), typisch für Bubblesort. O(n) entspricht einer einzelnen Schleife, O(log n) einem Halbieren des Bereichs wie bei der binären Suche."
|
||||
},
|
||||
{
|
||||
"category": "O-Notation",
|
||||
"hint": "Rangfolge für große n.",
|
||||
"text": "Wie ordnest du die Komplexitäten O(1), O(log n), O(n) und O(n²) nach steigendem Aufwand für große n?",
|
||||
"answers": [
|
||||
"O(1) < O(log n) < O(n) < O(n²)",
|
||||
"O(1) < O(n) < O(log n) < O(n²)",
|
||||
"O(log n) < O(1) < O(n²) < O(n)"
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Mit wachsendem n gilt O(1) < O(log n) < O(n) < O(n²). O(log n) wächst langsamer als O(n) und steht daher davor, konstantes O(1) ist immer am günstigsten. Die übrigen Reihenfolgen vertauschen diese Rangfolge."
|
||||
},
|
||||
{
|
||||
"category": "Rekursion",
|
||||
"hint": "Abbruch ohne weiteren Selbstaufruf.",
|
||||
"text": "Wozu dient der Basisfall (Abbruchbedingung) in einer rekursiven Funktion?",
|
||||
"answers": [
|
||||
"Er ruft die Funktion mit einem vergrößerten Problem erneut auf.",
|
||||
"Er beendet die Rekursion ohne weiteren Selbstaufruf und verhindert so eine Endlosrekursion.",
|
||||
"Er sorgt dafür, dass die Funktion niemals zurückkehrt."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Der Basisfall liefert ein Ergebnis ohne erneuten Selbstaufruf und stoppt damit die Rekursion. Der erneute Aufruf mit verkleinertem Problem ist der Rekursionsschritt; fehlt ein erreichbarer Basisfall, kehrt die Funktion nie zurück und es kommt zur Endlosrekursion."
|
||||
},
|
||||
{
|
||||
"category": "Rekursion",
|
||||
"hint": "Selbstaufruf mit kleinerem Teilproblem.",
|
||||
"text": "Was kennzeichnet den Rekursionsschritt?",
|
||||
"answers": [
|
||||
"Die Funktion gibt sofort einen festen Wert ohne Selbstaufruf zurück.",
|
||||
"Die Funktion iteriert mit einer Schleife über alle Elemente.",
|
||||
"Die Funktion ruft sich selbst mit einem verkleinerten Teilproblem auf, das dem Basisfall näherkommt."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Im Rekursionsschritt ruft sich die Funktion mit einem kleineren Teilproblem selbst auf und nähert sich so dem Basisfall. Das sofortige Zurückgeben eines festen Werts ist der Basisfall, das Durchlaufen mit einer Schleife wäre eine iterative statt rekursive Lösung."
|
||||
},
|
||||
{
|
||||
"category": "Rekursion",
|
||||
"hint": "Eigener Speicher je Aufruf.",
|
||||
"text": "Was passiert bei jedem rekursiven Aufruf mit dem Aufrufstapel (Call Stack)?",
|
||||
"answers": [
|
||||
"Für jeden Aufruf wird ein neuer Stack-Frame mit eigenen lokalen Daten abgelegt, der erst beim Zurückkehren wieder freigegeben wird.",
|
||||
"Der Stack bleibt unverändert, weil alle Aufrufe denselben Speicherbereich teilen.",
|
||||
"Ältere Aufrufe werden sofort gelöscht, sobald ein neuer Aufruf erfolgt."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Jeder rekursive Aufruf legt einen eigenen Stack-Frame mit seinen lokalen Variablen an, der erst beim Zurückkehren abgeräumt wird; zu tiefe Rekursion füllt den Stapel und führt zum Stack Overflow. Die Aufrufe teilen sich also nicht denselben Rahmen, und ältere Frames bleiben bis zur Rückkehr erhalten."
|
||||
},
|
||||
{
|
||||
"category": "Rekursion",
|
||||
"hint": "Basisfall fehlt oder wird nie erreicht.",
|
||||
"text": "Wodurch entsteht eine Endlosrekursion, die typischerweise mit einem Stack Overflow abbricht?",
|
||||
"answers": [
|
||||
"Durch einen zu großzügig gewählten Basisfall, der die Funktion zu früh abbrechen lässt.",
|
||||
"Durch einen fehlenden oder nie erreichbaren Basisfall, sodass sich die Funktion unbegrenzt selbst aufruft.",
|
||||
"Durch die Verwendung von zu wenigen lokalen Variablen."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Eine Endlosrekursion entsteht, wenn der Basisfall fehlt oder nie erreicht wird, sodass sich die Funktion unbegrenzt selbst aufruft, bis der Stack überläuft. Ein zu früh abbrechender Basisfall liefert höchstens ein falsches Ergebnis, und die Anzahl lokaler Variablen ist nicht die Ursache."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"title": "OOP",
|
||||
"questions": [
|
||||
{
|
||||
"category": "Kapselung",
|
||||
"hint": "Interna verbergen, Zugriff steuern.",
|
||||
"text": "Was bedeutet Kapselung (Information Hiding) in der OOP?",
|
||||
"answers": [
|
||||
"Attribute werden als private deklariert und nur über definierte öffentliche Methoden zugänglich gemacht.",
|
||||
"Alle Attribute werden public gemacht, damit jeder Programmteil möglichst schnell darauf zugreifen kann.",
|
||||
"Klassen werden lediglich in Pakete (packages) einsortiert."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Kapselung schützt die inneren Daten: Attribute sind private und werden nur über kontrollierte Methoden nach außen freigegeben. Alle Attribute public zu machen ist das Gegenteil von Information Hiding, und das Einsortieren in Pakete regelt nur die Namensräume, nicht den Zugriffsschutz."
|
||||
},
|
||||
{
|
||||
"category": "Kapselung",
|
||||
"hint": "Wozu die Methode statt des direkten Zugriffs?",
|
||||
"text": "Warum greifst du auf ein privates Attribut über Getter und Setter zu statt direkt?",
|
||||
"answers": [
|
||||
"Weil private Attribute sonst gar nicht erst im Speicher angelegt würden.",
|
||||
"Damit die Klasse die Kontrolle über Lese- und Schreibzugriffe behält und im Setter z. B. Werte prüfen (validieren) kann.",
|
||||
"Weil Getter und Setter das Attribut automatisch dauerhaft in einer Datenbank speichern."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Getter und Setter erlauben es, Zugriffe zu steuern und ungültige Werte im Setter abzufangen. Private Attribute liegen sehr wohl im Speicher, sie sind nur von außen nicht direkt erreichbar, und mit Persistenz oder Datenbanken haben einfache Zugriffsmethoden nichts zu tun."
|
||||
},
|
||||
{
|
||||
"category": "Kapselung",
|
||||
"hint": "Sichtbarkeit auf die eigene Klasse beschränkt.",
|
||||
"text": "Welcher Zugriffsmodifizierer schränkt die Sichtbarkeit am stärksten ein und lässt Zugriff nur innerhalb der eigenen Klasse zu?",
|
||||
"answers": [
|
||||
"public",
|
||||
"protected",
|
||||
"private"
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "private erlaubt Zugriff nur innerhalb der definierenden Klasse und ist damit am restriktivsten. public ist von überall sichtbar, und protected öffnet den Zugriff zusätzlich für Unterklassen und (in Java) das eigene Paket."
|
||||
},
|
||||
{
|
||||
"category": "Kapselung",
|
||||
"hint": "Von außen lesen erlaubt, schreiben nicht.",
|
||||
"text": "Wie machst du ein Attribut nach außen nur lesbar, aber nicht änderbar?",
|
||||
"answers": [
|
||||
"Du machst das Attribut public und dokumentierst nur, dass niemand es überschreiben soll.",
|
||||
"Du lässt das Attribut private und bietest nur einen Getter, aber keinen Setter an.",
|
||||
"Du deklarierst das Attribut als static."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Ohne Setter bleibt das private Attribut von außen unveränderbar, während der Getter das Lesen erlaubt. Ein public-Attribut mit bloßer Bitte, es nicht zu ändern, schützt technisch nichts, und static macht das Feld nur klassenweit gemeinsam, nicht schreibgeschützt."
|
||||
},
|
||||
{
|
||||
"category": "Vererbung",
|
||||
"hint": "Was übernimmt die Subklasse von oben?",
|
||||
"text": "Was gilt für eine Unterklasse (Subklasse) gegenüber ihrer Oberklasse?",
|
||||
"answers": [
|
||||
"Sie erbt Attribute und Methoden der Oberklasse und kann sie um eigene erweitern oder geerbte überschreiben.",
|
||||
"Sie kann nur neue Methoden hinzufügen, geerbte Methoden aber niemals verändern.",
|
||||
"Sie ist völlig unabhängig; extends übernimmt lediglich den Klassennamen."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Die Unterklasse übernimmt die sichtbaren Attribute und Methoden der Oberklasse und darf sie ergänzen oder überschreiben. Dass geerbte Methoden unveränderlich seien, widerspricht dem Überschreiben, und extends kopiert nicht nur einen Namen, sondern etabliert eine echte Typ- und Vererbungsbeziehung."
|
||||
},
|
||||
{
|
||||
"category": "Vererbung",
|
||||
"hint": "Aufruf nach oben in der Hierarchie.",
|
||||
"text": "Wozu dient der Aufruf super(...) im Konstruktor einer Unterklasse?",
|
||||
"answers": [
|
||||
"super löscht die von der Oberklasse geerbten Attribute wieder.",
|
||||
"super erzeugt ein zweites, vollständig unabhängiges Objekt der Oberklasse.",
|
||||
"super ruft den Konstruktor der Oberklasse auf, damit deren Teil des Objekts initialisiert wird."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Mit super(...) startet die Unterklasse den Konstruktor der Oberklasse und initialisiert deren Anteil desselben Objekts. Es entsteht kein zweites Objekt, und geerbte Attribute werden nicht gelöscht, sondern gerade korrekt aufgesetzt."
|
||||
},
|
||||
{
|
||||
"category": "Vererbung",
|
||||
"hint": "Gleiche Signatur, neue Implementierung in der Subklasse.",
|
||||
"text": "Was bedeutet es, eine geerbte Methode zu überschreiben (override)?",
|
||||
"answers": [
|
||||
"In derselben Klasse eine zweite Methode gleichen Namens mit anderer Parameterliste anzulegen.",
|
||||
"Die Unterklasse ersetzt eine geerbte Methode mit gleicher Signatur durch eine eigene Implementierung.",
|
||||
"Eine Methode aus einer Klasse dauerhaft zu entfernen."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Überschreiben heißt: Die Unterklasse liefert für eine geerbte Methode mit identischer Signatur eine eigene Umsetzung. Eine zweite Methode gleichen Namens mit anderen Parametern wäre Überladen, nicht Überschreiben, und ein echtes Entfernen geerbter Methoden ist nicht möglich."
|
||||
},
|
||||
{
|
||||
"category": "Vererbung",
|
||||
"hint": "Hund ist ein Tier.",
|
||||
"text": "Welche Beziehung modelliert Vererbung typischerweise?",
|
||||
"answers": [
|
||||
"Eine ist-ein-Beziehung, zum Beispiel: Ein Hund ist ein Tier.",
|
||||
"Eine hat-ein-Beziehung, zum Beispiel: Ein Auto hat einen Motor.",
|
||||
"Eine benutzt-Beziehung ganz ohne gemeinsamen Obertyp."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Vererbung drückt eine ist-ein-Beziehung aus, bei der die Unterklasse ein Spezialfall der Oberklasse ist. Die hat-ein-Beziehung (Auto hat einen Motor) gehört zur Komposition, und eine reine Benutzt-Beziehung entsteht ohne gemeinsamen Typ etwa durch Methodenaufrufe."
|
||||
},
|
||||
{
|
||||
"category": "Polymorphie",
|
||||
"hint": "Welcher Objekttyp zählt zur Laufzeit?",
|
||||
"text": "Eine Variable vom Typ Tier verweist auf ein Hund-Objekt. Welche Methode wird bei tier.laut() ausgeführt, wenn Hund laut() überschreibt?",
|
||||
"answers": [
|
||||
"Immer die Methode der Oberklasse Tier, weil die Variable den Typ Tier hat.",
|
||||
"Es entsteht ein Compilerfehler, weil die Typen nicht zusammenpassen.",
|
||||
"Die überschriebene Methode der tatsächlichen Objektklasse Hund, dank dynamischer Bindung zur Laufzeit."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Bei der dynamischen Bindung entscheidet der echte Objekttyp zur Laufzeit, also läuft die laut()-Methode von Hund. Der deklarierte Variablentyp Tier bestimmt nur, welche Methoden aufrufbar sind, nicht welche Implementierung greift, und der Aufruf ist typkonform, also kein Compilerfehler."
|
||||
},
|
||||
{
|
||||
"category": "Polymorphie",
|
||||
"hint": "Gleicher Name, andere Parameter, eine Klasse.",
|
||||
"text": "Was versteht man unter dem Überladen (overloading) einer Methode?",
|
||||
"answers": [
|
||||
"Eine Unterklasse ersetzt eine geerbte Methode und das wird erst zur Laufzeit aufgelöst.",
|
||||
"Mehrere Methoden mit gleichem Namen, aber unterschiedlicher Parameterliste innerhalb einer Klasse; die passende wählt der Compiler.",
|
||||
"Eine Methode, die sich zur Wiederholung immer wieder selbst aufruft."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Überladen bedeutet mehrere gleichnamige Methoden mit verschiedenen Parameterlisten; welche gilt, entscheidet der Compiler anhand der Argumente. Das Ersetzen einer geerbten Methode ist Überschreiben, und der wiederholte Selbstaufruf ist Rekursion."
|
||||
},
|
||||
{
|
||||
"category": "Polymorphie",
|
||||
"hint": "Compilezeit gegen Laufzeit.",
|
||||
"text": "Wann wird beim Überladen entschieden, welche Variante läuft, und wann beim Überschreiben?",
|
||||
"answers": [
|
||||
"Überladen wird zur Compilezeit anhand der Argumenttypen aufgelöst, Überschreiben zur Laufzeit anhand des tatsächlichen Objekttyps.",
|
||||
"Beides wird ausschließlich zur Laufzeit entschieden.",
|
||||
"Beides wird ausschließlich zur Compilezeit entschieden."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Überladen ist statische Bindung: der Compiler wählt die Methode nach den Argumenttypen. Überschreiben ist dynamische Bindung: erst zur Laufzeit steht der echte Objekttyp fest. Beide gemeinsam der Compilezeit oder der Laufzeit zuzuordnen vermischt gerade diesen Kernunterschied."
|
||||
},
|
||||
{
|
||||
"category": "Polymorphie",
|
||||
"hint": "Verschiedene Objekte über einen Obertyp behandeln.",
|
||||
"text": "Welchen praktischen Vorteil bietet Polymorphie beim Programmieren?",
|
||||
"answers": [
|
||||
"Sie beschleunigt jede Methode, weil der Compiler den Code grundsätzlich inlinen kann.",
|
||||
"Sie ermöglicht das Erben von Zustand aus mehreren Klassen gleichzeitig (Mehrfachvererbung).",
|
||||
"Du kannst unterschiedliche Objekte einheitlich über einen gemeinsamen Obertyp behandeln, etwa eine Liste von Formen zeichnen, ohne den konkreten Typ zu kennen."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Polymorphie erlaubt es, verschiedene Objekte über einen gemeinsamen Obertyp gleich anzusprechen; jedes reagiert mit seiner eigenen Implementierung. Mit Mehrfachvererbung von Zustand hat das nichts zu tun, und ein Geschwindigkeitsgewinn ist nicht ihr Zweck, dynamische Bindung kostet eher etwas Aufwand."
|
||||
},
|
||||
{
|
||||
"category": "Interfaces & abstrakte Klassen",
|
||||
"hint": "Mehrere Verträge gegen eine Basisklasse mit Code.",
|
||||
"text": "Worin unterscheiden sich ein Interface und eine abstrakte Klasse klassischerweise?",
|
||||
"answers": [
|
||||
"Ein Interface kann eigenen Instanzzustand (veränderliche Objektattribute) speichern, eine abstrakte Klasse nicht.",
|
||||
"Eine Klasse kann mehrere Interfaces implementieren, aber (in Java) nur von einer abstrakten Klasse erben; die abstrakte Klasse kann zudem Zustand und fertige Methoden mitbringen.",
|
||||
"Interface und abstrakte Klasse sind funktional identisch und unterscheiden sich nur im Schlüsselwort."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Interfaces lassen sich in beliebiger Zahl implementieren und legen vor allem einen Vertrag fest, während eine abstrakte Klasse als einzige Basisklasse gemeinsamen Zustand und fertigen Code vererbt. Ein Interface hält keinen veränderlichen Instanzzustand, und funktional gleich sind beide Konzepte nicht."
|
||||
},
|
||||
{
|
||||
"category": "Interfaces & abstrakte Klassen",
|
||||
"hint": "Unvollständiger Bauplan.",
|
||||
"text": "Kannst du von einer abstrakten Klasse direkt mit new ein Objekt erzeugen?",
|
||||
"answers": [
|
||||
"Nein, eine abstrakte Klasse ist nicht instanziierbar; nur von ihr abgeleitete konkrete Klassen können Objekte bilden.",
|
||||
"Ja, genauso wie von jeder gewöhnlichen Klasse.",
|
||||
"Ja, aber nur ein einziges Objekt pro Programm (erzwungenes Singleton)."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Eine abstrakte Klasse ist ein unvollständiger Bauplan und kann selbst kein Objekt erzeugen; instanziierbar sind nur ihre konkreten Unterklassen. Sie verhält sich damit nicht wie eine normale Klasse und erzwingt auch kein Singleton, dieses Muster ist etwas völlig anderes."
|
||||
},
|
||||
{
|
||||
"category": "Interfaces & abstrakte Klassen",
|
||||
"hint": "Zwei Fähigkeiten gleichzeitig zusagen.",
|
||||
"text": "Deine Klasse soll sowohl vergleichbar als auch serialisierbar sein. Wie löst du das sauber?",
|
||||
"answers": [
|
||||
"Durch Mehrfachvererbung, indem die Klasse von zwei Basisklassen gleichzeitig erbt.",
|
||||
"Gar nicht, denn eine Klasse kann immer nur eine einzige Fähigkeit besitzen.",
|
||||
"Die Klasse implementiert beide Interfaces gleichzeitig, etwa implements Comparable, Serializable."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Eine Klasse darf beliebig viele Interfaces zugleich implementieren und mehrere Verträge erfüllen. Mehrfachvererbung von Klassen ist in Java gerade nicht erlaubt, und die Annahme, eine Klasse könne nur eine Fähigkeit haben, ist falsch, dafür gibt es Interfaces."
|
||||
},
|
||||
{
|
||||
"category": "Interfaces & abstrakte Klassen",
|
||||
"hint": "Ein Vertrag ohne vorgegebene Umsetzung.",
|
||||
"text": "Wofür setzt du ein Interface typischerweise ein?",
|
||||
"answers": [
|
||||
"Um fertigen Programmcode samt gemeinsamem Zustand an alle Unterklassen zu vererben.",
|
||||
"Um einen Vertrag festzulegen, welche Methoden vorhanden sein müssen, den verschiedene Klassen unabhängig voneinander erfüllen können.",
|
||||
"Um Objekte physisch an einer bestimmten Stelle im Arbeitsspeicher anzuordnen."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Ein Interface definiert einen Vertrag aus Methodensignaturen, den unabhängige Klassen jeweils eigenständig umsetzen. Das Vererben von fertigem Code und gemeinsamem Zustand ist eher Sache einer (abstrakten) Basisklasse, und mit der Speicheranordnung von Objekten hat ein Interface nichts zu tun."
|
||||
},
|
||||
{
|
||||
"category": "Komposition vs. Vererbung",
|
||||
"hint": "Auto hat einen Motor.",
|
||||
"text": "Ein Auto besitzt einen Motor. Wie modellierst du diese Beziehung am besten?",
|
||||
"answers": [
|
||||
"Als Komposition: Die Klasse Auto enthält ein Motor-Objekt als Attribut (hat-ein).",
|
||||
"Als Vererbung: Auto erbt von Motor (ist-ein).",
|
||||
"Als Interface: Auto implementiert Motor."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Ein Auto hat einen Motor, das ist eine hat-ein-Beziehung und damit Komposition, also ein Motor-Attribut in Auto. Von Motor zu erben würde behaupten, ein Auto sei ein Motor, und ein Interface würde nur eine Fähigkeit zusagen, nicht das Enthalten eines Bauteils abbilden."
|
||||
},
|
||||
{
|
||||
"category": "Komposition vs. Vererbung",
|
||||
"hint": "Kopplung und Flexibilität.",
|
||||
"text": "Warum lautet eine bekannte Empfehlung favor composition over inheritance?",
|
||||
"answers": [
|
||||
"Weil Vererbung in modernen Programmiersprachen technisch gar nicht mehr möglich ist.",
|
||||
"Weil Komposition zur Laufzeit grundsätzlich schneller ist als Vererbung.",
|
||||
"Weil Komposition lockerer koppelt und flexibler bleibt, während tiefe Vererbungshierarchien Änderungen an der Oberklasse riskant machen (fragile Basisklasse)."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Komposition koppelt Klassen loser und lässt sich leichter umbauen oder zur Laufzeit austauschen, während Änderungen in einer Oberklasse ungewollt viele Unterklassen brechen können. Vererbung ist keineswegs abgeschafft, und ein pauschaler Geschwindigkeitsvorteil der Komposition ist nicht der Grund für die Empfehlung."
|
||||
},
|
||||
{
|
||||
"category": "Komposition vs. Vererbung",
|
||||
"hint": "Passt die ist-ein-Beziehung und die Ersetzbarkeit?",
|
||||
"text": "Woran erkennst du, dass Vererbung statt Komposition angebracht ist?",
|
||||
"answers": [
|
||||
"Immer dann, wenn du einfach Code sparen willst, unabhängig davon, wie die Objekte zusammenhängen.",
|
||||
"Wenn die ist-ein-Beziehung inhaltlich wirklich stimmt und die Unterklasse überall dort einsetzbar ist, wo der Obertyp erwartet wird (Ersetzbarkeit).",
|
||||
"Immer wenn zwei Klassen zufällig einige gleich benannte Methoden besitzen."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Vererbung ist richtig, wenn eine echte ist-ein-Beziehung besteht und die Unterklasse den Obertyp überall ersetzen kann (Ersetzbarkeitsprinzip). Vererbung nur zum Code-Sparen zu missbrauchen führt zu falschen Typbeziehungen, und gleich benannte Methoden allein begründen noch keine gemeinsame Klasse."
|
||||
},
|
||||
{
|
||||
"category": "Komposition vs. Vererbung",
|
||||
"hint": "Ungewollt Geerbtes und enge Bindung.",
|
||||
"text": "Was ist ein typischer Nachteil, wenn du Vererbung nur zur Code-Wiederverwendung missbrauchst?",
|
||||
"answers": [
|
||||
"Die Unterklasse erbt auch unpassende oder ungewollte Methoden und ist eng an die Oberklasse gekoppelt.",
|
||||
"Der Compiler verweigert danach grundsätzlich jeden Aufruf geerbter Methoden.",
|
||||
"Objekte der Unterklasse benötigen anschließend gar keinen Speicher mehr."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Wer nur zum Wiederverwenden erbt, holt sich das ganze Verhalten der Oberklasse ins Boot, auch unpassende Methoden, und bindet sich fest an deren Struktur. Der Compiler sperrt geerbte Methoden nicht, und ein Objekt der Unterklasse braucht selbstverständlich weiterhin Speicher."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"title": "SQL",
|
||||
"questions": [
|
||||
{
|
||||
"category": "Joins",
|
||||
"hint": "Nur Zeilen mit Partner in beiden Tabellen.",
|
||||
"text": "Was liefert ein INNER JOIN zwischen kunde und bestellung über die Kundennummer?",
|
||||
"answers": [
|
||||
"Nur die Kunden, zu denen es mindestens eine passende Bestellung gibt.",
|
||||
"Alle Kunden, auch solche ganz ohne Bestellung.",
|
||||
"Alle Bestellungen, auch solche ohne gültigen Kunden."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Der INNER JOIN gibt nur Zeilen zurück, für die in BEIDEN Tabellen ein Partner existiert. Kunden ohne Bestellung liefert erst ein LEFT JOIN, verwaiste Bestellungen ein RIGHT JOIN auf die Bestelltabelle."
|
||||
},
|
||||
{
|
||||
"category": "Joins",
|
||||
"hint": "Linke Tabelle vollständig behalten.",
|
||||
"text": "Du willst ALLE Kunden auflisten und dazu – falls vorhanden – ihre Bestellungen. Welcher Join passt?",
|
||||
"answers": [
|
||||
"INNER JOIN",
|
||||
"LEFT (OUTER) JOIN von kunde auf bestellung",
|
||||
"CROSS JOIN"
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Der LEFT JOIN behält alle Zeilen der linken Tabelle (kunde) und füllt fehlende Bestellungen mit NULL. Der INNER JOIN verliert Kunden ohne Bestellung, der CROSS JOIN bildet das kartesische Produkt aller Kombinationen."
|
||||
},
|
||||
{
|
||||
"category": "Joins",
|
||||
"hint": "Fehlende Partner werden zu NULL.",
|
||||
"text": "Wie findest du mit einem LEFT JOIN gezielt die Kunden OHNE jede Bestellung?",
|
||||
"answers": [
|
||||
"Mit WHERE bestellung.id = 0.",
|
||||
"Mit einem INNER JOIN und HAVING COUNT(*) = 0.",
|
||||
"Mit WHERE bestellung.id IS NULL nach dem LEFT JOIN."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Nach dem LEFT JOIN stehen bei Kunden ohne Bestellung NULL-Werte in den Bestellspalten; WHERE ... IS NULL filtert genau diese heraus. Ein INNER JOIN würde sie gar nicht erst liefern, und = 0 trifft NULL nicht."
|
||||
},
|
||||
{
|
||||
"category": "Joins",
|
||||
"hint": "Kartesisches Produkt.",
|
||||
"text": "Was ist das Ergebnis eines CROSS JOIN zweier Tabellen mit 5 und 4 Zeilen?",
|
||||
"answers": [
|
||||
"20 Zeilen – jede Zeile der einen mit jeder Zeile der anderen kombiniert.",
|
||||
"9 Zeilen – die Summe beider Zeilenzahlen.",
|
||||
"5 Zeilen – begrenzt durch die kleinere Tabelle."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Der CROSS JOIN bildet das kartesische Produkt: 5 × 4 = 20 Kombinationen, ganz ohne Join-Bedingung. Die Summe (9) oder die kleinere Zeilenzahl (5) entstehen bei keinem Join-Typ."
|
||||
},
|
||||
{
|
||||
"category": "GROUP BY & HAVING",
|
||||
"hint": "Gruppieren, dann je Gruppe rechnen.",
|
||||
"text": "Du willst pro Kunde die Anzahl seiner Bestellungen zählen. Welche Klausel gruppiert die Zeilen dafür korrekt?",
|
||||
"answers": [
|
||||
"ORDER BY kunde_id mit COUNT(*).",
|
||||
"GROUP BY kunde_id mit COUNT(*).",
|
||||
"DISTINCT kunde_id mit COUNT(*)."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "GROUP BY kunde_id fasst alle Zeilen je Kunde zu einer Gruppe zusammen, über die COUNT(*) rechnet. ORDER BY sortiert nur, DISTINCT entfernt Duplikate, aggregiert aber nicht pro Gruppe."
|
||||
},
|
||||
{
|
||||
"category": "GROUP BY & HAVING",
|
||||
"hint": "Bedingung auf das Gruppenergebnis.",
|
||||
"text": "Worin unterscheiden sich WHERE und HAVING?",
|
||||
"answers": [
|
||||
"WHERE filtert vor der Gruppierung einzelne Zeilen, HAVING filtert nach der Gruppierung anhand von Aggregaten.",
|
||||
"WHERE und HAVING sind gleichwertig und beliebig austauschbar.",
|
||||
"HAVING filtert Zeilen vor der Gruppierung, WHERE die fertigen Gruppen."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "WHERE wirkt auf einzelne Zeilen vor dem Gruppieren, HAVING auf die Gruppen danach (z. B. HAVING COUNT(*) > 3). Sie sind nicht austauschbar – die Vorstellung, HAVING filtere vor und WHERE nach der Gruppierung, vertauscht beide Rollen."
|
||||
},
|
||||
{
|
||||
"category": "GROUP BY & HAVING",
|
||||
"hint": "Was darf ohne Aggregat in die SELECT-Liste?",
|
||||
"text": "Bei GROUP BY kunde_id – welche Spalten dürfen ohne Aggregatfunktion in der SELECT-Liste stehen?",
|
||||
"answers": [
|
||||
"Beliebige Spalten der Tabelle.",
|
||||
"Nur die Gruppierungsspalten (hier kunde_id) sowie Aggregate wie COUNT oder SUM.",
|
||||
"Nur der Primärschlüssel der Tabelle."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "In der SELECT-Liste sind nur die Spalten aus GROUP BY und Aggregatfunktionen erlaubt – sonst wäre der Wert je Gruppe nicht eindeutig. Beliebige Spalten führen (in strengem SQL) zum Fehler."
|
||||
},
|
||||
{
|
||||
"category": "GROUP BY & HAVING",
|
||||
"hint": "Filter auf Summen.",
|
||||
"text": "Welche Abfrage liefert Kunden mit mehr als 3 Bestellungen?",
|
||||
"answers": [
|
||||
"SELECT kunde_id FROM bestellung WHERE COUNT(*) > 3 GROUP BY kunde_id",
|
||||
"SELECT kunde_id FROM bestellung GROUP BY kunde_id ORDER BY COUNT(*) > 3",
|
||||
"SELECT kunde_id FROM bestellung GROUP BY kunde_id HAVING COUNT(*) > 3"
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Die Bedingung über ein Aggregat (COUNT) gehört in HAVING nach dem GROUP BY. In WHERE ist COUNT nicht erlaubt, und ORDER BY sortiert nur, statt zu filtern."
|
||||
},
|
||||
{
|
||||
"category": "DDL & DML",
|
||||
"hint": "Struktur ändern vs. Daten ändern.",
|
||||
"text": "Welche Anweisung gehört zur DDL (Data Definition Language)?",
|
||||
"answers": [
|
||||
"UPDATE",
|
||||
"CREATE TABLE",
|
||||
"SELECT"
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "DDL definiert die Struktur: CREATE, ALTER, DROP. UPDATE ist DML (Datenmanipulation), SELECT ist DQL/Abfrage. Die Trennung nach Aufgabenbereich wird in der AP2 gern geprüft."
|
||||
},
|
||||
{
|
||||
"category": "DDL & DML",
|
||||
"hint": "Struktur-Löschen ist nicht rückrollbar wie ein einfaches DELETE.",
|
||||
"text": "Worin unterscheiden sich DELETE FROM t und TRUNCATE TABLE t?",
|
||||
"answers": [
|
||||
"DELETE entfernt Zeilen (mit WHERE filterbar, protokolliert, rollback-fähig); TRUNCATE leert die ganze Tabelle schnell und setzt sie zurück.",
|
||||
"DELETE und TRUNCATE sind identisch, nur unterschiedlich geschrieben.",
|
||||
"TRUNCATE löscht einzelne Spalten, DELETE ganze Zeilen."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "DELETE arbeitet zeilenweise mit WHERE und lässt sich in einer Transaktion zurückrollen; TRUNCATE leert die gesamte Tabelle sehr schnell (DDL-nah) ohne WHERE. Spalten löscht keines von beiden – das wäre ALTER TABLE."
|
||||
},
|
||||
{
|
||||
"category": "DDL & DML",
|
||||
"hint": "Neue Zeile einfügen.",
|
||||
"text": "Mit welcher DML-Anweisung fügst du einen neuen Datensatz in eine Tabelle ein?",
|
||||
"answers": [
|
||||
"INSERT INTO ... VALUES",
|
||||
"ALTER TABLE ... ADD",
|
||||
"CREATE ROW"
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "INSERT INTO ... VALUES fügt neue Zeilen ein (DML). ALTER TABLE ... ADD ändert die Struktur (fügt eine Spalte hinzu, DDL), und CREATE ROW gibt es in SQL nicht."
|
||||
},
|
||||
{
|
||||
"category": "DDL & DML",
|
||||
"hint": "Referenzielle Integrität.",
|
||||
"text": "Wozu dient ein FOREIGN KEY?",
|
||||
"answers": [
|
||||
"Er nummeriert die Zeilen automatisch fortlaufend.",
|
||||
"Er verschlüsselt den Spaltenwert in der Datenbank.",
|
||||
"Er stellt sicher, dass ein Wert auf einen existierenden Primärschlüssel einer anderen Tabelle verweist."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Der FOREIGN KEY sichert die referenzielle Integrität: er erlaubt nur Werte, die als Primärschlüssel in der Zieltabelle existieren. Das automatische Nummerieren übernimmt AUTO_INCREMENT/IDENTITY; Verschlüsselung ist keine Aufgabe eines Schlüssels."
|
||||
},
|
||||
{
|
||||
"category": "Normalisierung",
|
||||
"hint": "Atomare Werte je Feld.",
|
||||
"text": "Welche Bedingung erfüllt eine Tabelle in der ersten Normalform (1NF)?",
|
||||
"answers": [
|
||||
"Jedes Attribut enthält nur atomare (unteilbare) Werte, keine Mehrfachwerte je Feld.",
|
||||
"Jedes Nichtschlüsselattribut hängt vom gesamten Schlüssel ab.",
|
||||
"Es gibt keine transitiven Abhängigkeiten mehr."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Die 1NF verlangt atomare Werte (keine Listen/Wiederholgruppen in einem Feld). Die volle Abhängigkeit vom Gesamtschlüssel ist die 2NF, die Freiheit von transitiven Abhängigkeiten die 3NF."
|
||||
},
|
||||
{
|
||||
"category": "Normalisierung",
|
||||
"hint": "Teilschlüssel-Abhängigkeit.",
|
||||
"text": "Eine Tabelle ist in 1NF, aber ein Attribut hängt nur von einem Teil des zusammengesetzten Primärschlüssels ab. Welche Normalform ist verletzt?",
|
||||
"answers": [
|
||||
"Die 3NF.",
|
||||
"Die 2NF.",
|
||||
"Die BCNF, aber 2NF und 3NF sind erfüllt."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Die 2NF fordert, dass jedes Nichtschlüsselattribut vom GESAMTEN Schlüssel abhängt, nicht nur von einem Teil – genau das ist hier verletzt. Die 3NF betrifft transitive Abhängigkeiten und setzt die 2NF bereits voraus."
|
||||
},
|
||||
{
|
||||
"category": "Normalisierung",
|
||||
"hint": "Nichtschlüssel hängt an Nichtschlüssel.",
|
||||
"text": "Was beseitigt die dritte Normalform (3NF)?",
|
||||
"answers": [
|
||||
"Mehrfachwerte in einem einzelnen Feld.",
|
||||
"Doppelte Primärschlüssel.",
|
||||
"Transitive Abhängigkeiten, bei denen ein Nichtschlüsselattribut von einem anderen Nichtschlüsselattribut abhängt."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Die 3NF entfernt transitive Abhängigkeiten (Nichtschlüssel → Nichtschlüssel), z. B. PLZ → Ort in einer Kundentabelle. Mehrfachwerte klärt die 1NF; doppelte Primärschlüssel sind ohnehin unzulässig."
|
||||
},
|
||||
{
|
||||
"category": "Normalisierung",
|
||||
"hint": "Ziel der Normalisierung.",
|
||||
"text": "Welches Ziel verfolgt die Normalisierung vor allem?",
|
||||
"answers": [
|
||||
"Redundanz und dadurch mögliche Anomalien beim Einfügen, Ändern und Löschen zu vermeiden.",
|
||||
"Die Abfragen möglichst schnell zu machen, egal wie viele Tabellen entstehen.",
|
||||
"Alle Daten in einer einzigen großen Tabelle zusammenzufassen."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Normalisierung reduziert Redundanz und verhindert Änderungs-, Einfüge- und Löschanomalien. Reine Geschwindigkeit ist kein Ziel (Denormalisierung kann sie sogar erhöhen), und eine einzige Riesentabelle ist das Gegenteil von normalisiert."
|
||||
},
|
||||
{
|
||||
"category": "Abfragen & Constraints",
|
||||
"hint": "Fehlender Wert ist nicht gleich 0 oder ''.",
|
||||
"text": "Wie prüfst du in SQL korrekt auf einen fehlenden Wert (NULL)?",
|
||||
"answers": [
|
||||
"WHERE spalte = NULL",
|
||||
"WHERE spalte IS NULL",
|
||||
"WHERE spalte = ''"
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "NULL bedeutet „unbekannt\"; Vergleiche mit = NULL ergeben immer UNKNOWN und liefern nichts. Korrekt ist IS NULL. Der leere String '' ist ein eigener Wert und nicht dasselbe wie NULL."
|
||||
},
|
||||
{
|
||||
"category": "Abfragen & Constraints",
|
||||
"hint": "Eindeutig und nicht leer.",
|
||||
"text": "Welche Eigenschaft garantiert ein PRIMARY KEY?",
|
||||
"answers": [
|
||||
"Die Werte dürfen sich wiederholen, müssen aber sortiert sein.",
|
||||
"Die Spalte darf NULL enthalten, solange die Werte eindeutig sind.",
|
||||
"Die Werte sind eindeutig und niemals NULL."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Ein PRIMARY KEY ist eindeutig UND NOT NULL – er identifiziert jede Zeile zweifelsfrei. NULL-Werte sind ausgeschlossen; eine bloß eindeutige, aber NULL-erlaubende Spalte wäre ein UNIQUE-Constraint."
|
||||
},
|
||||
{
|
||||
"category": "Abfragen & Constraints",
|
||||
"hint": "Ergebnis begrenzen und sortieren.",
|
||||
"text": "Wie gibst du die drei teuersten Artikel aus (Standard-SQL bzw. gängige Dialekte)?",
|
||||
"answers": [
|
||||
"SELECT ... FROM artikel WHERE preis = MAX(preis) LIMIT 3",
|
||||
"SELECT ... FROM artikel ORDER BY preis DESC LIMIT 3 (bzw. FETCH FIRST 3 ROWS ONLY)",
|
||||
"SELECT TOP preis FROM artikel GROUP BY 3"
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Erst absteigend nach Preis sortieren, dann auf drei Zeilen begrenzen (LIMIT bzw. FETCH FIRST). MAX in WHERE ist nicht erlaubt, und GROUP BY 3 gruppiert nur nach der dritten Spalte statt zu begrenzen."
|
||||
},
|
||||
{
|
||||
"category": "Abfragen & Constraints",
|
||||
"hint": "Fehlende Bedingung wirkt auf alles.",
|
||||
"text": "Was passiert bei UPDATE artikel SET preis = preis * 1.1 ohne WHERE-Klausel?",
|
||||
"answers": [
|
||||
"Es passiert nichts, weil ohne WHERE keine Zeile ausgewählt ist.",
|
||||
"Nur die zuletzt eingefügte Zeile wird geändert.",
|
||||
"Der Preis wird in ALLEN Zeilen der Tabelle um 10 % erhöht."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Ohne WHERE gilt ein UPDATE für jede Zeile der Tabelle – hier steigt jeder Preis um 10 %. Ein fehlendes WHERE wählt also alles aus statt nichts und trifft nicht nur eine einzelne Zeile."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"title": "Testverfahren",
|
||||
"questions": [
|
||||
{
|
||||
"category": "Blackbox vs. Whitebox",
|
||||
"hint": "Ohne Blick in den Code.",
|
||||
"text": "Aus welcher Perspektive betrachtest du das Testobjekt beim Blackbox-Test?",
|
||||
"answers": [
|
||||
"Nur über Eingaben und erwartete Ausgaben, ohne den internen Quellcode zu kennen.",
|
||||
"Anhand des internen Quellcodes, um jeden Programmpfad gezielt abzudecken.",
|
||||
"Ausschließlich über die Analyse der Datenbankstruktur im Hintergrund."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Der Blackbox-Test prüft das Verhalten allein über die Schnittstelle: Eingabe rein, Ausgabe mit der Spezifikation vergleichen, ohne die innere Struktur zu kennen. Das gezielte Abdecken von Programmpfaden anhand des Quellcodes ist der Whitebox-Ansatz, und die Datenbankstruktur allein ist kein Kriterium des Verfahrens."
|
||||
},
|
||||
{
|
||||
"category": "Blackbox vs. Whitebox",
|
||||
"hint": "Kenntnis des Codes nutzen.",
|
||||
"text": "Welche Technik ist typisch für den Whitebox-Test?",
|
||||
"answers": [
|
||||
"Grenzwertanalyse rein anhand der fachlichen Spezifikation.",
|
||||
"Zweig- und Pfadabdeckung anhand des Programmablaufs im Quellcode.",
|
||||
"Abnahme durch den Kunden anhand der vereinbarten Anforderungen."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Whitebox nutzt die Kenntnis des Codes, um den Kontrollfluss abzudecken, etwa über Anweisungs-, Zweig- und Pfadüberdeckung. Die Grenzwertanalyse aus der Spezifikation gehört zum Blackbox-Test, und die Kundenabnahme ist eine Testebene und kein Verfahren der Codeüberdeckung."
|
||||
},
|
||||
{
|
||||
"category": "Blackbox vs. Whitebox",
|
||||
"hint": "Nicht erreichter Code.",
|
||||
"text": "Warum kann ein reiner Blackbox-Test bestimmte Fehler übersehen?",
|
||||
"answers": [
|
||||
"Weil er den kompletten Quellcode Zeile für Zeile prüft und dabei ermüdet.",
|
||||
"Weil er ausschließlich die Datenbank und nie die Programmlogik testet.",
|
||||
"Weil nicht durchlaufener oder toter Code ohne Blick in die Struktur unentdeckt bleiben kann."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Ohne Kenntnis der inneren Struktur erkennt der Blackbox-Test nicht, ob es nie erreichten oder toten Code gibt; solche Lücken deckt erst eine Codeüberdeckung per Whitebox auf. Der Blackbox-Test liest den Quellcode gerade nicht und beschränkt sich auch nicht auf die Datenbank."
|
||||
},
|
||||
{
|
||||
"category": "Blackbox vs. Whitebox",
|
||||
"hint": "Nur die Spezifikation liegt vor.",
|
||||
"text": "Ein Tester kennt nur die Anforderungsspezifikation, nicht die Implementierung, und leitet daraus die Testfälle ab. Um welches Verfahren handelt es sich?",
|
||||
"answers": [
|
||||
"Blackbox-Test.",
|
||||
"Whitebox-Test.",
|
||||
"Anweisungsüberdeckung."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Testfälle allein aus der Spezifikation ohne Blick in die Implementierung kennzeichnen den Blackbox-Test. Whitebox setzt gerade die Codekenntnis voraus, und die Anweisungsüberdeckung ist eine konkrete Whitebox-Metrik, die den Quellcode benötigt."
|
||||
},
|
||||
{
|
||||
"category": "Äquivalenzklassen & Grenzwerte",
|
||||
"hint": "Repräsentant je Klasse.",
|
||||
"text": "Was ist die Grundidee der Äquivalenzklassenbildung?",
|
||||
"answers": [
|
||||
"Für jeden einzelnen möglichen Eingabewert genau einen Testfall zu schreiben.",
|
||||
"Eingaben in Klassen zusammenzufassen, die vermutlich gleich behandelt werden, und je Klasse einen Repräsentanten zu testen.",
|
||||
"Nur ungültige Eingaben zu testen, weil gültige ohnehin funktionieren."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Äquivalenzklassen fassen Eingaben zusammen, die das Programm voraussichtlich gleich verarbeitet; getestet wird ein Repräsentant je Klasse, was die Zahl der Testfälle stark senkt. Jeden Wert einzeln zu prüfen wäre unpraktikabel, und gültige Klassen müssen ebenso getestet werden wie ungültige."
|
||||
},
|
||||
{
|
||||
"category": "Äquivalenzklassen & Grenzwerte",
|
||||
"hint": "Gültige und ungültige Bereiche.",
|
||||
"text": "Für ein Alter-Feld gilt: gültig ist 18 bis 65. Welche Aufteilung in Äquivalenzklassen ist korrekt?",
|
||||
"answers": [
|
||||
"Nur eine gültige Klasse von 18 bis 65, ungültige Werte sind irrelevant.",
|
||||
"Drei gültige Klassen und gar keine ungültige Klasse.",
|
||||
"Eine gültige Klasse 18 bis 65 sowie zwei ungültige Klassen: kleiner als 18 und größer als 65."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Neben der gültigen Klasse von 18 bis 65 gehören auch die ungültigen Bereiche darunter und darüber getestet, denn gerade dort treten Fehler auf. Die ungültigen Eingaben zu ignorieren würde das fehlerhafte Verhalten ungeprüft lassen."
|
||||
},
|
||||
{
|
||||
"category": "Äquivalenzklassen & Grenzwerte",
|
||||
"hint": "Direkt an den Rändern.",
|
||||
"text": "Gültig ist ein Wert von 1 bis 100. Welche Werte prüft die Grenzwertanalyse besonders?",
|
||||
"answers": [
|
||||
"Die Ränder und ihre direkten Nachbarn, also 0, 1, 100 und 101.",
|
||||
"Nur den Mittelwert 50, weil er die Klasse repräsentiert.",
|
||||
"Ausschließlich weit entfernte Werte wie -1000 und 1000."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Fehler treten häufig an den Klassengrenzen auf, deshalb prüft die Grenzwertanalyse die Randwerte und ihre direkten Nachbarn, hier 0, 1, 100 und 101. Ein Wert aus der Mitte deckt typische Off-by-one-Fehler nicht ab, und sehr weit entfernte Werte liegen tief in der ungültigen Klasse statt an der Grenze."
|
||||
},
|
||||
{
|
||||
"category": "Äquivalenzklassen & Grenzwerte",
|
||||
"hint": "Off-by-one an den Rändern.",
|
||||
"text": "Warum ergänzt man die Äquivalenzklassen sinnvollerweise um eine Grenzwertanalyse?",
|
||||
"answers": [
|
||||
"Weil die Grenzwertanalyse die Äquivalenzklassen vollständig ersetzt.",
|
||||
"Weil Fehler besonders oft an den Klassengrenzen entstehen, etwa durch < statt <=.",
|
||||
"Weil dadurch jeder einzelne Wert des gesamten Wertebereichs getestet wird."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Verwechslungen wie < statt <= führen zu typischen Fehlern genau an den Rändern, die ein Repräsentant aus der Klassenmitte übersieht; deshalb ergänzen Grenzwerte die Klassenbildung. Sie ersetzen die Klassen nicht und prüfen auch nicht jeden Einzelwert des Bereichs."
|
||||
},
|
||||
{
|
||||
"category": "Testebenen",
|
||||
"hint": "Kleinste Einheit isoliert.",
|
||||
"text": "Welche Testebene prüft eine einzelne Klasse oder Methode isoliert?",
|
||||
"answers": [
|
||||
"Der Systemtest.",
|
||||
"Der Abnahmetest.",
|
||||
"Der Unit-Test (Modul- bzw. Komponententest)."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Der Unit-Test prüft die kleinste Einheit, also eine Methode oder Klasse, isoliert und ersetzt Abhängigkeiten oft durch Mocks. Der Systemtest betrachtet das Gesamtsystem, und der Abnahmetest richtet sich nach der Erfüllung der Kundenanforderungen."
|
||||
},
|
||||
{
|
||||
"category": "Testebenen",
|
||||
"hint": "Zusammenspiel über Schnittstellen.",
|
||||
"text": "Worauf zielt der Integrationstest ab?",
|
||||
"answers": [
|
||||
"Auf das korrekte Zusammenspiel mehrerer Module über ihre Schnittstellen.",
|
||||
"Auf die interne Verzweigungslogik einer einzelnen Methode.",
|
||||
"Auf die formale Abnahme durch den Auftraggeber."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Der Integrationstest prüft, ob zuvor einzeln getestete Module über ihre Schnittstellen korrekt zusammenarbeiten. Die interne Logik einer einzelnen Methode ist Sache des Unit-Tests, und die formale Abnahme gehört zum Abnahmetest."
|
||||
},
|
||||
{
|
||||
"category": "Testebenen",
|
||||
"hint": "Vollständig integriert, produktionsnah.",
|
||||
"text": "In welcher Umgebung wird der Systemtest typischerweise durchgeführt?",
|
||||
"answers": [
|
||||
"In der Entwicklungsumgebung gegen einzelne Klassen.",
|
||||
"In einer produktionsnahen Umgebung gegen das vollständig integrierte Gesamtsystem.",
|
||||
"Direkt auf dem Produktivsystem der Kunden ohne jede Testdaten."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Der Systemtest prüft das vollständig integrierte System gegen die Anforderungen, möglichst in einer produktionsähnlichen Umgebung mit realistischen Testdaten. Einzelne Klassen sind Gegenstand des Unit-Tests, und ungeprüft auf dem Produktivsystem zu testen wäre riskant und untypisch."
|
||||
},
|
||||
{
|
||||
"category": "Testebenen",
|
||||
"hint": "Sicht des Auftraggebers.",
|
||||
"text": "Wer führt den Abnahmetest primär durch und wogegen wird geprüft?",
|
||||
"answers": [
|
||||
"Die Entwickler, gegen die interne Codestruktur.",
|
||||
"Ein Automatisierungsserver, gegen die erreichte Testabdeckung.",
|
||||
"Der Auftraggeber bzw. Fachbereich, gegen die vertraglich vereinbarten Anforderungen."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Der Abnahmetest wird aus Sicht des Auftraggebers gegen die vereinbarten Anforderungen durchgeführt und entscheidet über die Freigabe. Die interne Codestruktur prüfen Whitebox-Verfahren, und eine Kennzahl wie die Testabdeckung ist kein fachliches Abnahmekriterium."
|
||||
},
|
||||
{
|
||||
"category": "Testarten & Vorgehen",
|
||||
"hint": "Nach Änderung nichts kaputt?",
|
||||
"text": "Was ist das Ziel eines Regressionstests?",
|
||||
"answers": [
|
||||
"Sicherzustellen, dass bereits funktionierende Features nach einer Änderung weiterhin korrekt arbeiten.",
|
||||
"Ausschließlich neu geschriebene Funktionen zum ersten Mal zu testen.",
|
||||
"Die Ausführungsgeschwindigkeit des Systems unter hoher Last zu messen."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Der Regressionstest wiederholt bestehende Testfälle nach Änderungen, um zu erkennen, ob zuvor korrekte Funktionen ungewollt beschädigt wurden. Das erstmalige Prüfen neuer Funktionen ist nicht sein Ziel, und die Messung von Geschwindigkeit unter Last ist ein eigener Last-/Performancetest."
|
||||
},
|
||||
{
|
||||
"category": "Testarten & Vorgehen",
|
||||
"hint": "Red, Green, Refactor.",
|
||||
"text": "Welche Reihenfolge beschreibt Test-Driven Development (TDD) korrekt?",
|
||||
"answers": [
|
||||
"Erst die Funktion vollständig implementieren und ganz am Ende die Tests schreiben.",
|
||||
"Erst einen fehlschlagenden Test schreiben, dann den Code bis zum grünen Test implementieren, danach refactoren.",
|
||||
"Auf Tests verzichten und die Anwendung nur manuell durchklicken."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "TDD folgt dem Zyklus Red-Green-Refactor: zuerst ein Test, der fehlschlägt, dann minimaler Code, der ihn erfüllt, danach das Aufräumen bei weiterhin grünen Tests. Tests erst nachträglich oder gar nicht zu schreiben widerspricht dem testgetriebenen Ansatz."
|
||||
},
|
||||
{
|
||||
"category": "Testarten & Vorgehen",
|
||||
"hint": "Ohne Soll kein Urteil.",
|
||||
"text": "Was gehört zu einem vollständig beschriebenen Testfall zwingend dazu?",
|
||||
"answers": [
|
||||
"Nur eine kurze Beschreibung, was ungefähr getestet werden soll.",
|
||||
"Ausschließlich die konkreten Eingabewerte.",
|
||||
"Vorbedingung, Eingabe bzw. Aktion, erwartetes Ergebnis und der Vergleich mit dem Ist-Ergebnis."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Ein aussagekräftiger Testfall nennt die Vorbedingungen, die konkrete Eingabe oder Aktion und vor allem das erwartete Ergebnis, gegen das das tatsächliche Ergebnis geprüft wird. Ohne definiertes Soll-Ergebnis lässt sich nicht objektiv über Bestehen oder Fehlschlag entscheiden."
|
||||
},
|
||||
{
|
||||
"category": "Testarten & Vorgehen",
|
||||
"hint": "Schnell und reproduzierbar.",
|
||||
"text": "Welcher Vorteil spricht besonders für automatisierte Tests?",
|
||||
"answers": [
|
||||
"Sie lassen sich schnell und reproduzierbar beliebig oft wiederholen, ideal für Regression und Continuous Integration.",
|
||||
"Sie machen den fachlichen Testfallentwurf und das Nachdenken über Testfälle überflüssig.",
|
||||
"Sie garantieren mathematisch, dass überhaupt keine Fehler mehr vorhanden sind."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Automatisierte Tests laufen schnell und immer gleich, sodass sie sich für häufige Regressionsläufe und Continuous Integration eignen. Sie ersetzen aber nicht den fachlichen Testentwurf und können Fehlerfreiheit nicht beweisen, denn ein bestandener Test zeigt nur die Abwesenheit der geprüften Fehler."
|
||||
},
|
||||
{
|
||||
"category": "Schnittstellen & Datenformate",
|
||||
"hint": "Neue Ressource erzeugen.",
|
||||
"text": "Welche HTTP-Methode ist bei REST für das Anlegen einer neuen Ressource üblich?",
|
||||
"answers": [
|
||||
"GET",
|
||||
"POST",
|
||||
"DELETE"
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "POST legt bei REST typischerweise eine neue Ressource an und wird meist mit dem Statuscode 201 Created bestätigt. GET liest nur ohne Seiteneffekt, und DELETE entfernt eine bestehende Ressource."
|
||||
},
|
||||
{
|
||||
"category": "Schnittstellen & Datenformate",
|
||||
"hint": "Ressource existiert nicht.",
|
||||
"text": "Ein REST-Client fragt eine Ressource ab, die es nicht gibt. Welcher HTTP-Statuscode ist korrekt?",
|
||||
"answers": [
|
||||
"200 OK",
|
||||
"500 Internal Server Error",
|
||||
"404 Not Found"
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Der Code 404 Not Found signalisiert, dass die angeforderte Ressource nicht existiert. Der Code 200 OK meldet dagegen Erfolg, und 500 steht für einen unerwarteten Serverfehler und nicht für eine schlicht fehlende Ressource."
|
||||
},
|
||||
{
|
||||
"category": "Schnittstellen & Datenformate",
|
||||
"hint": "Ohne gespeicherten Sitzungszustand.",
|
||||
"text": "Welche Aussage beschreibt ein zentrales REST-Prinzip korrekt?",
|
||||
"answers": [
|
||||
"Die Kommunikation ist zustandslos: jede Anfrage enthält alle nötigen Informationen, der Server hält keinen Sitzungszustand.",
|
||||
"Der Server merkt sich zwischen den Anfragen automatisch den kompletten Zustand des Clients.",
|
||||
"Jede Operation muss zwingend über die Methode GET laufen."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "REST ist zustandslos: jede Anfrage trägt alle Informationen, die der Server zur Bearbeitung braucht, ohne gespeicherten Sitzungszustand. Serverseitig gehaltener Sitzungszustand widerspricht dem Prinzip, und je nach Operation kommen unterschiedliche Methoden wie GET, POST, PUT oder DELETE zum Einsatz."
|
||||
},
|
||||
{
|
||||
"category": "Schnittstellen & Datenformate",
|
||||
"hint": "Beide textbasiert.",
|
||||
"text": "Worin unterscheiden sich JSON und XML als Datenformat?",
|
||||
"answers": [
|
||||
"JSON ist ein Bildformat, während XML ein reines Textformat ist.",
|
||||
"Beide sind textbasiert; JSON ist meist kompakter, XML unterstützt zusätzlich Attribute, Namespaces und Schema-Validierung.",
|
||||
"Nur XML ist maschinenlesbar, JSON kann ausschließlich ein Mensch lesen."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "JSON und XML sind beide textbasiert und maschinenlesbar; JSON ist in der Regel kompakter und in Web-APIs verbreitet, während XML mit Attributen, Namespaces und Schemata wie XSD mehr Struktur bietet. JSON ist kein Bildformat, und maschinenlesbar sind beide Formate."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"title": "UML",
|
||||
"questions": [
|
||||
{
|
||||
"category": "Klassendiagramm",
|
||||
"hint": "Minus steht für die engste Sichtbarkeit.",
|
||||
"text": "Welche Sichtbarkeit bedeutet das Zeichen - vor einem Attribut im Klassendiagramm?",
|
||||
"answers": [
|
||||
"public – von überall zugreifbar.",
|
||||
"private – nur innerhalb der eigenen Klasse zugreifbar.",
|
||||
"protected – auch für erbende Unterklassen zugreifbar."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Das Minus steht für private, also Zugriff nur innerhalb der eigenen Klasse. Das Pluszeichen kennzeichnet public (von überall), die Raute # steht für protected (auch für Unterklassen). Verwechsle das Minus nicht mit protected – dessen Zeichen ist ausdrücklich die Raute."
|
||||
},
|
||||
{
|
||||
"category": "Klassendiagramm",
|
||||
"hint": "Drei Fächer von oben nach unten.",
|
||||
"text": "Wie ist eine Klasse im UML-Klassendiagramm typischerweise aufgebaut?",
|
||||
"answers": [
|
||||
"In drei Abschnitten: oben der Klassenname, in der Mitte die Attribute, unten die Methoden.",
|
||||
"In zwei Abschnitten: oben die Methoden, unten die Attribute.",
|
||||
"In einem einzigen Feld, das nur den Klassennamen enthält."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Eine Klasse wird als Rechteck mit drei Fächern gezeichnet: Name oben, Attribute in der Mitte, Methoden unten. Die Reihenfolge Attribute-über-Methoden ist fest; sie zu vertauschen oder alles in ein Feld zu packen widerspricht der UML-Notation."
|
||||
},
|
||||
{
|
||||
"category": "Klassendiagramm",
|
||||
"hint": "Achte auf die runden Klammern.",
|
||||
"text": "Woran erkennst du im Klassendiagramm eine Methode (Operation) und nicht ein Attribut?",
|
||||
"answers": [
|
||||
"Sie steht immer im obersten Abschnitt der Klasse.",
|
||||
"Sie beginnt stets mit einem Großbuchstaben.",
|
||||
"An den runden Klammern hinter dem Namen, zum Beispiel berechneSumme()."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Methoden erkennst du an den runden Klammern hinter dem Namen, die die Parameterliste aufnehmen. Der oberste Abschnitt enthält dagegen den Klassennamen, und die Groß-/Kleinschreibung ist reine Namenskonvention und kein Unterscheidungsmerkmal."
|
||||
},
|
||||
{
|
||||
"category": "Klassendiagramm",
|
||||
"hint": "Erst der Name, dann der Typ nach dem Doppelpunkt.",
|
||||
"text": "Wie notierst du im Klassendiagramm ein privates Attribut alter vom Typ Integer korrekt?",
|
||||
"answers": [
|
||||
"+ alter : Integer",
|
||||
"- alter : Integer",
|
||||
"# Integer alter"
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Privat wird durch das Minus ausgedrückt, und in UML steht der Typ hinter dem Namen nach einem Doppelpunkt (name : Typ). Das Pluszeichen wäre public, und die Raute mit vorangestelltem Typ mischt protected mit der Java-Schreibweise Typ-vor-Name, die in UML nicht gilt."
|
||||
},
|
||||
{
|
||||
"category": "Beziehungen & Multiplizitäten",
|
||||
"hint": "Ohne das Ganze verschwinden die Teile.",
|
||||
"text": "Ein Haus besteht aus Räumen; ohne das Haus existieren die Räume nicht. Welche Beziehung liegt vor und wie wird sie notiert?",
|
||||
"answers": [
|
||||
"Aggregation, dargestellt mit leerer Raute am Haus.",
|
||||
"Assoziation, dargestellt mit einer einfachen Linie.",
|
||||
"Komposition, dargestellt mit gefüllter Raute am Haus (dem Ganzen)."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Die Existenzabhängigkeit der Teile vom Ganzen ist das Kennzeichen der Komposition, notiert mit gefüllter Raute am Ganzen. Eine Aggregation (leere Raute) wäre nur eine lose hat-Beziehung ohne Existenzabhängigkeit, und eine schlichte Assoziation drückt gar kein Teil-Ganzes-Verhältnis aus."
|
||||
},
|
||||
{
|
||||
"category": "Beziehungen & Multiplizitäten",
|
||||
"hint": "Leere Raute gegen gefüllte Raute.",
|
||||
"text": "Worin unterscheidet sich eine Aggregation von einer Komposition?",
|
||||
"answers": [
|
||||
"Die Aggregation ist eine schwache hat-Beziehung mit leerer Raute; bei der Komposition ist der Teil existenzabhängig vom Ganzen und wird mit gefüllter Raute gezeichnet.",
|
||||
"Die Aggregation nutzt eine gefüllte Raute, die Komposition eine leere.",
|
||||
"Beide sind identisch und nur zwei Namen für dieselbe Beziehung."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Aggregation ist die schwache hat-Beziehung mit leerer Raute; die Teile können auch ohne das Ganze bestehen. Komposition ist die starke Form mit gefüllter Raute und Existenzabhängigkeit. Die Zuordnung der Rauten wird gern vertauscht – gefüllt gehört zur starken Komposition."
|
||||
},
|
||||
{
|
||||
"category": "Beziehungen & Multiplizitäten",
|
||||
"hint": "Der Stern steht für unbegrenzt.",
|
||||
"text": "Was bedeutet die Multiplizität 1..* an einem Assoziationsende?",
|
||||
"answers": [
|
||||
"Genau ein Objekt, nicht mehr und nicht weniger.",
|
||||
"Mindestens ein Objekt, nach oben unbegrenzt viele.",
|
||||
"Null oder ein Objekt, also optional."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "1..* meint mindestens eins und nach oben beliebig viele, weil der Stern für unbegrenzt steht. Genau eins wäre schlicht 1, und die optionale Angabe null-oder-eins schreibt man 0..1 – beide Fallen lassen die Untergrenze von mindestens eins außer Acht."
|
||||
},
|
||||
{
|
||||
"category": "Beziehungen & Multiplizitäten",
|
||||
"hint": "Ein hohles Dreieck zeigt nach oben.",
|
||||
"text": "Wie wird eine Vererbung (Generalisierung) zwischen Ober- und Unterklasse dargestellt?",
|
||||
"answers": [
|
||||
"Mit einer gestrichelten Linie ganz ohne Pfeilspitze.",
|
||||
"Mit einer gefüllten Raute an der Oberklasse.",
|
||||
"Mit einer durchgezogenen Linie und einem großen, nicht ausgefüllten Dreieckspfeil, der zur Oberklasse zeigt."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Vererbung zeichnest du mit durchgezogener Linie und hohlem Dreieckspfeil, der von der Unterklasse zur Oberklasse zeigt. Eine gefüllte Raute gehört zur Komposition, und eine gestrichelte Linie steht für Abhängigkeiten oder Realisierungen, nicht für Generalisierung."
|
||||
},
|
||||
{
|
||||
"category": "Sequenzdiagramm",
|
||||
"hint": "Sie hängt senkrecht unter dem Objekt.",
|
||||
"text": "Was stellt die senkrechte gestrichelte Linie unterhalb eines Objekts im Sequenzdiagramm dar?",
|
||||
"answers": [
|
||||
"Die Lebenslinie – sie zeigt die Existenz des Objekts über die Zeit hinweg.",
|
||||
"Eine asynchrone Nachricht des Objekts an sich selbst.",
|
||||
"Die Vererbungsbeziehung zu einer Oberklasse."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Die gestrichelte Senkrechte ist die Lebenslinie und zeigt, dass das Objekt über die dargestellte Zeit existiert. Nachrichten laufen dagegen waagerecht zwischen Lebenslinien, und Vererbung kommt im Sequenzdiagramm gar nicht vor – sie gehört ins Klassendiagramm."
|
||||
},
|
||||
{
|
||||
"category": "Sequenzdiagramm",
|
||||
"hint": "Sender wartet auf die Antwort.",
|
||||
"text": "Wie wird im Sequenzdiagramm eine synchrone Nachricht, etwa ein Methodenaufruf mit Warten auf die Antwort, dargestellt?",
|
||||
"answers": [
|
||||
"Mit einer gestrichelten Linie und offener Pfeilspitze.",
|
||||
"Mit einer durchgezogenen Linie und ausgefüllter, geschlossener Pfeilspitze.",
|
||||
"Mit einem großen, hohlen Dreieckspfeil."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Die synchrone Nachricht bekommt eine durchgezogene Linie mit gefüllter Pfeilspitze; der Sender wartet auf die Rückantwort. Die gestrichelte Linie mit offener Spitze ist die Antwort (Return) beziehungsweise die asynchrone Nachricht, und der hohle Dreieckspfeil gehört zur Vererbung im Klassendiagramm."
|
||||
},
|
||||
{
|
||||
"category": "Sequenzdiagramm",
|
||||
"hint": "Ein schmales Rechteck auf der Linie.",
|
||||
"text": "Wofür steht der schmale Balken (das schmale Rechteck) auf der Lebenslinie im Sequenzdiagramm?",
|
||||
"answers": [
|
||||
"Für das endgültige Löschen des Objekts.",
|
||||
"Für eine Bedingung oder Verzweigung im Ablauf.",
|
||||
"Für den Aktivierungsbalken – die Zeitspanne, in der das Objekt gerade eine Operation ausführt."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Der schmale Balken ist der Aktivierungsbalken und markiert, wie lange das Objekt aktiv eine Operation bearbeitet. Das Löschen wird dagegen mit einem Kreuz am Ende der Lebenslinie gezeigt, und Bedingungen fasst man in kombinierte Fragmente (etwa alt oder opt) statt in den Balken."
|
||||
},
|
||||
{
|
||||
"category": "Sequenzdiagramm",
|
||||
"hint": "Der Sender läuft sofort weiter.",
|
||||
"text": "Was kennzeichnet eine asynchrone Nachricht im Sequenzdiagramm gegenüber einer synchronen?",
|
||||
"answers": [
|
||||
"Der Sender wartet nicht auf eine Antwort und arbeitet sofort weiter; dargestellt mit offener Pfeilspitze.",
|
||||
"Der Sender blockiert immer, bis die Antwort zurückkommt.",
|
||||
"Sie wird durch eine gefüllte Raute an der Lebenslinie dargestellt."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Bei einer asynchronen Nachricht läuft der Sender ohne Warten weiter; sie erhält eine offene (strichförmige) Pfeilspitze. Das Blockieren bis zur Antwort beschreibt gerade die synchrone Nachricht, und eine gefüllte Raute gibt es im Sequenzdiagramm gar nicht – sie stammt aus dem Klassendiagramm."
|
||||
},
|
||||
{
|
||||
"category": "Aktivitätsdiagramm",
|
||||
"hint": "Ein Punkt startet, ein Punkt im Ring beendet.",
|
||||
"text": "Wie werden Start- und Endknoten im Aktivitätsdiagramm dargestellt?",
|
||||
"answers": [
|
||||
"Start und Ende jeweils als Raute.",
|
||||
"Start als gefüllter Kreis, Ende als gefüllter Kreis mit einem umgebenden Ring.",
|
||||
"Start als abgerundetes Rechteck, Ende als Dreieck."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Der Startknoten ist ein gefüllter Kreis, der Endknoten ein gefüllter Kreis mit umgebendem Ring (wie eine Zielscheibe). Rauten stehen für Entscheidungen, und abgerundete Rechtecke sind die Aktionen selbst – beides sind keine Start- oder Endmarkierungen."
|
||||
},
|
||||
{
|
||||
"category": "Aktivitätsdiagramm",
|
||||
"hint": "Ein Eingang, mehrere bedingte Ausgänge.",
|
||||
"text": "Wofür steht eine Raute mit einem eingehenden und mehreren ausgehenden Pfeilen im Aktivitätsdiagramm?",
|
||||
"answers": [
|
||||
"Für eine Aktion, die ausgeführt wird.",
|
||||
"Für den Startknoten des Ablaufs.",
|
||||
"Für eine Entscheidung (Verzweigung); die ausgehenden Kanten tragen Bedingungen (Guards)."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Die Raute ist der Entscheidungsknoten: Ein Ablauf kommt herein und verzweigt anhand der Bedingungen an den ausgehenden Kanten in genau einen Zweig. Eine Aktion wäre ein abgerundetes Rechteck, und der Startknoten ist ein gefüllter Kreis ohne eingehende Kante."
|
||||
},
|
||||
{
|
||||
"category": "Aktivitätsdiagramm",
|
||||
"hint": "Ein dicker Balken teilt und führt zusammen.",
|
||||
"text": "Wie werden im Aktivitätsdiagramm parallele, also nebenläufige Abläufe modelliert?",
|
||||
"answers": [
|
||||
"Mit einem Synchronisationsbalken (fork/join) – ein dicker Balken spaltet den Ablauf in mehrere parallele Zweige auf und führt sie später wieder zusammen.",
|
||||
"Mit einer Raute, die den Ablauf in sich ausschließende Alternativen aufteilt.",
|
||||
"Mit einer gestrichelten Lebenslinie wie im Sequenzdiagramm."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Parallelität modellierst du mit dem Synchronisationsbalken: Ein fork spaltet in mehrere gleichzeitig laufende Zweige, ein join führt sie wieder zusammen. Die Raute erzeugt dagegen sich ausschließende Alternativen (nur ein Zweig wird durchlaufen), und Lebenslinien gehören ins Sequenzdiagramm."
|
||||
},
|
||||
{
|
||||
"category": "Aktivitätsdiagramm",
|
||||
"hint": "Abgerundete Ecken für einen Arbeitsschritt.",
|
||||
"text": "Wie wird eine einzelne Aktion (ein Arbeitsschritt) im Aktivitätsdiagramm dargestellt?",
|
||||
"answers": [
|
||||
"Als Raute mit spitzen Ecken.",
|
||||
"Als Rechteck mit abgerundeten Ecken.",
|
||||
"Als gefüllter Kreis."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Eine Aktion zeichnest du als Rechteck mit abgerundeten Ecken. Die spitze Raute steht für Entscheidungen, und der gefüllte Kreis markiert den Startknoten – beide beschreiben keinen einzelnen Arbeitsschritt."
|
||||
},
|
||||
{
|
||||
"category": "Use-Case-Diagramm",
|
||||
"hint": "Er steht außerhalb des Systems.",
|
||||
"text": "Was ist ein Akteur im Use-Case-Diagramm?",
|
||||
"answers": [
|
||||
"Ein einzelner Programmschritt innerhalb des Systems.",
|
||||
"Eine Datenbanktabelle, auf die zugegriffen wird.",
|
||||
"Eine Rolle außerhalb des Systems – Mensch oder Fremdsystem –, die mit dem System interagiert; dargestellt als Strichmännchen."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Ein Akteur ist eine Rolle außerhalb der Systemgrenze, also ein Mensch oder ein anderes System, das mit dem System interagiert, und wird als Strichmännchen gezeichnet. Ein Programmschritt liegt innerhalb des Systems, und eine Datenbanktabelle ist ein technisches Detail, kein Akteur."
|
||||
},
|
||||
{
|
||||
"category": "Use-Case-Diagramm",
|
||||
"hint": "Verpflichtend eingebundenes Verhalten.",
|
||||
"text": "Was drückt die Beziehung <<include>> zwischen zwei Use Cases aus?",
|
||||
"answers": [
|
||||
"Der Basis-Use-Case bindet den eingebundenen Use Case immer verpflichtend ein – ausgelagertes, wiederverwendetes Verhalten.",
|
||||
"Der eingebundene Use Case wird nur unter einer bestimmten Bedingung optional ergänzt.",
|
||||
"Zwei Akteure erben voneinander ihre Rechte."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "include steht für immer eingebundenes, wiederverwendetes Verhalten: Läuft der Basis-Use-Case, wird der eingebundene stets mit ausgeführt. Das nur bedingte, optionale Ergänzen beschreibt dagegen extend, und eine Vererbung zwischen Akteuren ist eine eigene Generalisierungsbeziehung."
|
||||
},
|
||||
{
|
||||
"category": "Use-Case-Diagramm",
|
||||
"hint": "Nur unter einer Bedingung ergänzt.",
|
||||
"text": "Was bedeutet die Beziehung <<extend>> im Use-Case-Diagramm?",
|
||||
"answers": [
|
||||
"Der erweiternde Use Case wird immer zwingend mit ausgeführt.",
|
||||
"Der erweiternde Use Case ergänzt das Verhalten des Basis-Use-Case nur unter einer bestimmten Bedingung (optional).",
|
||||
"Es handelt sich um die Systemgrenze um alle Use Cases."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "extend beschreibt eine optionale Erweiterung: Der erweiternde Use Case greift nur unter einer bestimmten Bedingung an einem Erweiterungspunkt ein. Das immer zwingende Einbinden ist gerade include, und die Systemgrenze ist der umschließende Rahmen, keine Beziehung zwischen Use Cases."
|
||||
},
|
||||
{
|
||||
"category": "Use-Case-Diagramm",
|
||||
"hint": "Der Rahmen trennt innen von außen.",
|
||||
"text": "Wofür steht der Rahmen (das umschließende Rechteck) im Use-Case-Diagramm?",
|
||||
"answers": [
|
||||
"Für die Systemgrenze – er trennt das System mit seinen Use Cases (innen) von den Akteuren (außen).",
|
||||
"Für eine parallele Verarbeitung mehrerer Use Cases.",
|
||||
"Für eine Vererbungsbeziehung zwischen zwei Use Cases."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Der Rahmen ist die Systemgrenze: Innerhalb liegen die Use Cases des Systems, außerhalb stehen die Akteure. Parallele Verarbeitung modelliert man im Aktivitätsdiagramm, und Vererbung wird durch einen eigenen Generalisierungspfeil ausgedrückt, nicht durch den Rahmen."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"title": "IT-Sicherheit",
|
||||
"questions": [
|
||||
{
|
||||
"category": "Firewall & DMZ",
|
||||
"hint": "Verbindungszustand merken oder nicht.",
|
||||
"text": "Worin unterscheidet sich eine Stateful-Inspection-Firewall von einem einfachen Paketfilter?",
|
||||
"answers": [
|
||||
"Sie verfolgt den Verbindungszustand und lässt Antwortpakete zu bereits aufgebauten Verbindungen automatisch zu.",
|
||||
"Sie prüft ausschließlich einzelne Pakete ohne Bezug zueinander anhand starrer Regeln.",
|
||||
"Sie arbeitet nur auf der Anwendungsschicht und ersetzt jede Verbindung durch eine eigene Sitzung."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Stateful Inspection führt eine Zustandstabelle und erkennt, welche Pakete zu einer bestehenden Verbindung gehören – Rückantworten müssen nicht einzeln freigegeben werden. Die reine Einzelpaketprüfung ohne Kontext beschreibt gerade den zustandslosen Paketfilter, und das vollständige Ersetzen jeder Verbindung ist Aufgabe eines Proxys bzw. Application-Gateways."
|
||||
},
|
||||
{
|
||||
"category": "Firewall & DMZ",
|
||||
"hint": "Wo steht ein öffentlich erreichbarer Server sicher?",
|
||||
"text": "Ein aus dem Internet erreichbarer Webserver soll ins Firmennetz integriert werden. Wie platzierst du ihn sicher?",
|
||||
"answers": [
|
||||
"Direkt im internen LAN, damit er bequem auf die interne Datenbank zugreifen kann.",
|
||||
"In einer DMZ, die per Firewall sowohl vom Internet als auch vom internen LAN getrennt ist.",
|
||||
"Ohne Firewall direkt am Internet-Router, damit die Erreichbarkeit garantiert bleibt."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Eine DMZ isoliert öffentlich erreichbare Dienste, sodass ein kompromittierter Server nicht unmittelbar das interne Netz öffnet. Im internen LAN säße ein gekaperter Server sofort im vertrauenswürdigen Bereich, und ohne Firewall wäre er völlig ungeschützt exponiert."
|
||||
},
|
||||
{
|
||||
"category": "Firewall & DMZ",
|
||||
"hint": "Zwei Firewalls bilden einen Zwischenbereich.",
|
||||
"text": "Was beschreibt das Konzept eines Screened Subnet (DMZ mit zwei Firewalls)?",
|
||||
"answers": [
|
||||
"Eine einzelne Firewall mit drei Schnittstellen für LAN, WAN und DMZ.",
|
||||
"Zwei baugleiche Firewalls hintereinander, die nur der Lastverteilung dienen.",
|
||||
"Eine äußere und eine innere Firewall, zwischen denen die DMZ liegt, sodass ein Angreifer beide überwinden muss."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Beim Screened Subnet spannen eine äußere und eine innere Firewall (idealerweise verschiedener Hersteller) die DMZ dazwischen auf – der Weg ins LAN führt nur durch beide. Die einzelne Firewall mit drei Schnittstellen ist die kollabierte Ein-Firewall-Variante, und Lastverteilung ist nicht der Zweck dieses Sicherheitskonzepts."
|
||||
},
|
||||
{
|
||||
"category": "Firewall & DMZ",
|
||||
"hint": "Was ist nicht erlaubt, ist verboten.",
|
||||
"text": "Nach welchem Grundprinzip sollte ein Firewall-Regelwerk aufgebaut sein?",
|
||||
"answers": [
|
||||
"Default-Allow: grundsätzlich alles erlauben und nur bekannte Angriffe gezielt blockieren.",
|
||||
"Default-Deny: grundsätzlich alles verbieten und nur den explizit benötigten Verkehr freigeben.",
|
||||
"Die Reihenfolge der Regeln ist egal, weil die Firewall stets die am wenigsten restriktive Regel anwendet."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Default-Deny (Whitelist) lässt nur ausdrücklich freigegebenen Verkehr durch und hält die Angriffsfläche minimal. Default-Allow lässt unbekannten Verkehr offen, und die Reihenfolge ist keineswegs egal – die meisten Firewalls wenden die erste passende Regel an."
|
||||
},
|
||||
{
|
||||
"category": "VPN",
|
||||
"hint": "Welcher IPsec-Teil verschlüsselt die Nutzdaten?",
|
||||
"text": "Welcher Bestandteil von IPsec sorgt durch Verschlüsselung der Nutzdaten für Vertraulichkeit?",
|
||||
"answers": [
|
||||
"ESP (Encapsulating Security Payload).",
|
||||
"AH (Authentication Header).",
|
||||
"IKE (Internet Key Exchange)."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "ESP verschlüsselt die Nutzdaten und liefert damit Vertraulichkeit, optional zusätzlich Integrität und Authentizität. AH schützt nur Integrität und Herkunft, verschlüsselt aber nichts, und IKE handelt lediglich die Schlüssel aus, ohne den eigentlichen Datenverkehr zu verschlüsseln."
|
||||
},
|
||||
{
|
||||
"category": "VPN",
|
||||
"hint": "Ganzes Paket kapseln oder nur Nutzlast schützen?",
|
||||
"text": "Ein Site-to-Site-VPN zwischen zwei Standorten soll die kompletten Original-IP-Pakete schützen und verbergen. Welcher IPsec-Modus ist dafür vorgesehen?",
|
||||
"answers": [
|
||||
"Der Transportmodus, weil er den ursprünglichen IP-Header unverändert lässt.",
|
||||
"Der Transportmodus in Verbindung mit zusätzlichem NAT.",
|
||||
"Der Tunnelmodus, der das gesamte Original-Paket verschlüsselt und in ein neues IP-Paket kapselt."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Der Tunnelmodus kapselt das vollständige Originalpaket samt Header in ein neues IP-Paket – Standard für die Kopplung ganzer Standorte über Gateways. Der Transportmodus lässt den ursprünglichen IP-Header sichtbar und schützt nur die Nutzlast, was eher für direkte Host-zu-Host-Verbindungen gedacht ist; NAT ändert daran nichts."
|
||||
},
|
||||
{
|
||||
"category": "VPN",
|
||||
"hint": "Zugriff nur über den Browser.",
|
||||
"text": "Ein Außendienstmitarbeiter soll ohne installierten VPN-Client nur per Browser auf einzelne Webanwendungen zugreifen. Welche VPN-Variante passt am besten?",
|
||||
"answers": [
|
||||
"IPsec im Tunnelmodus mit vollständigem Zugriff auf das gesamte Firmennetz.",
|
||||
"Ein clientloses SSL/TLS-VPN, das den Zugriff über HTTPS direkt im Browser ermöglicht.",
|
||||
"Ein PPTP-VPN, weil es besonders starke Verschlüsselung bietet."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Clientlose SSL/TLS-VPNs laufen über HTTPS im Browser und eignen sich ideal für den gezielten Zugriff auf einzelne Webanwendungen ohne Softwareinstallation. IPsec benötigt einen Client und öffnet gleich das ganze Netz, und PPTP gilt als veraltet und kryptografisch gebrochen – keineswegs besonders stark."
|
||||
},
|
||||
{
|
||||
"category": "VPN",
|
||||
"hint": "Alte Aufzeichnungen bleiben geschützt.",
|
||||
"text": "Was bewirkt Perfect Forward Secrecy (PFS) bei einem VPN?",
|
||||
"answers": [
|
||||
"Ein später kompromittierter Langzeitschlüssel erlaubt es nicht, zuvor aufgezeichnete Sitzungen nachträglich zu entschlüsseln.",
|
||||
"Der Sitzungsschlüssel wird dauerhaft gespeichert, damit sich Verbindungen schneller wieder aufbauen lassen.",
|
||||
"Alle Teilnehmer verwenden denselben statischen Schlüssel, um maximale Kompatibilität zu erreichen."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "PFS erzeugt pro Sitzung flüchtige Schlüssel über einen frischen Diffie-Hellman-Austausch, sodass ein geknackter Langzeitschlüssel den mitgeschnittenen Altverkehr nicht offenlegt. Das dauerhafte Speichern von Sitzungsschlüsseln oder ein einziger statischer Schlüssel würden genau diesen Schutz aushebeln."
|
||||
},
|
||||
{
|
||||
"category": "Verschlüsselung",
|
||||
"hint": "Ein Schlüssel für beide Seiten.",
|
||||
"text": "Welche Aussage über AES trifft zu?",
|
||||
"answers": [
|
||||
"AES ist ein asymmetrisches Verfahren mit öffentlichem und privatem Schlüssel.",
|
||||
"AES ist ein Hashverfahren zur reinen Integritätsprüfung.",
|
||||
"AES ist eine symmetrische Blockchiffre, bei der beide Seiten denselben geheimen Schlüssel nutzen."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "AES ist eine symmetrische Blockchiffre: derselbe geheime Schlüssel ver- und entschlüsselt die Daten. Öffentlicher und privater Schlüssel gehören zu asymmetrischen Verfahren wie RSA, und eine Integritätsprüfung ohne Rückgewinnung der Daten leistet eine Hashfunktion wie SHA – beides andere Verfahrensklassen."
|
||||
},
|
||||
{
|
||||
"category": "Verschlüsselung",
|
||||
"hint": "Schnell und sicher zugleich kombinieren.",
|
||||
"text": "Warum kombiniert man in der Praxis (etwa bei TLS) asymmetrische und symmetrische Verschlüsselung zu einem Hybridverfahren?",
|
||||
"answers": [
|
||||
"Weil asymmetrische Verfahren als unsicher gelten und nur zur Tarnung mitlaufen.",
|
||||
"Weil der schnelle symmetrische Sitzungsschlüssel sicher über das langsame asymmetrische Verfahren ausgetauscht wird.",
|
||||
"Weil symmetrische Verfahren überhaupt keinen gemeinsamen Schlüssel benötigen."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Beim Hybridverfahren transportiert das asymmetrische Verfahren sicher den symmetrischen Sitzungsschlüssel, mit dem anschließend die Massendaten schnell verschlüsselt werden. Asymmetrische Verfahren sind nicht unsicher, sondern nur rechenintensiv, und symmetrische Verschlüsselung braucht sehr wohl einen gemeinsamen Schlüssel – dessen Austausch ist ja gerade das Problem."
|
||||
},
|
||||
{
|
||||
"category": "Verschlüsselung",
|
||||
"hint": "Nur der Empfänger soll lesen können.",
|
||||
"text": "Alice möchte Bob eine vertrauliche Nachricht asymmetrisch verschlüsselt senden. Welchen Schlüssel verwendet sie dafür?",
|
||||
"answers": [
|
||||
"Bobs öffentlichen Schlüssel.",
|
||||
"Bobs privaten Schlüssel.",
|
||||
"Ihren eigenen privaten Schlüssel."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Für Vertraulichkeit verschlüsselt Alice mit Bobs öffentlichem Schlüssel, denn nur Bobs privater Schlüssel kann das wieder entschlüsseln. Bobs privaten Schlüssel besitzt Alice gar nicht, und mit ihrem eigenen privaten Schlüssel würde sie signieren – lesbar für jeden mit ihrem öffentlichen Schlüssel."
|
||||
},
|
||||
{
|
||||
"category": "Verschlüsselung",
|
||||
"hint": "Fester Prüfwert, nicht umkehrbar.",
|
||||
"text": "Wozu dient eine kryptografische Hashfunktion wie SHA-256?",
|
||||
"answers": [
|
||||
"Zum umkehrbaren Verschlüsseln, das sich mit dem passenden Schlüssel wieder entschlüsseln lässt.",
|
||||
"Zum verlustfreien Komprimieren großer Dateien.",
|
||||
"Zum Erzeugen eines festen, praktisch kollisionsfreien Prüfwerts zur Integritätssicherung."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Eine kryptografische Hashfunktion bildet Daten auf einen festen, praktisch kollisionsfreien Prüfwert ab und sichert so die Integrität. Sie ist eine Einwegfunktion – es gibt keinen Schlüssel zum Zurückrechnen – und dient auch nicht der verlustfreien Kompression, da sich die Originaldaten nicht rekonstruieren lassen."
|
||||
},
|
||||
{
|
||||
"category": "Zertifikate & PKI",
|
||||
"hint": "Wer bürgt für den öffentlichen Schlüssel?",
|
||||
"text": "Welche Aufgabe hat eine Zertifizierungsstelle (CA) in einer PKI?",
|
||||
"answers": [
|
||||
"Sie verschlüsselt den gesamten Datenverkehr zwischen den Teilnehmern.",
|
||||
"Sie bestätigt mit ihrer digitalen Signatur die Zuordnung eines öffentlichen Schlüssels zu einer Identität.",
|
||||
"Sie speichert die privaten Schlüssel aller Teilnehmer zentral ab."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Die CA signiert Zertifikate und beglaubigt damit, dass ein öffentlicher Schlüssel wirklich zu einer bestimmten Identität gehört. Den eigentlichen Datenverkehr verschlüsselt nicht die CA, und private Schlüssel müssen beim jeweiligen Besitzer bleiben – eine zentrale Ablage würde das gesamte Vertrauensmodell zerstören."
|
||||
},
|
||||
{
|
||||
"category": "Zertifikate & PKI",
|
||||
"hint": "Signieren geschieht mit dem eigenen geheimen Schlüssel.",
|
||||
"text": "Mit welchem Schlüssel erzeugt der Absender eine digitale Signatur für ein Dokument?",
|
||||
"answers": [
|
||||
"Mit seinem eigenen privaten Schlüssel.",
|
||||
"Mit dem öffentlichen Schlüssel des Empfängers.",
|
||||
"Mit einem zuvor ausgehandelten gemeinsamen symmetrischen Schlüssel."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Der Absender signiert mit seinem privaten Schlüssel; jeder kann die Signatur mit dessen öffentlichem Schlüssel prüfen und so Echtheit und Urheberschaft bestätigen. Mit dem öffentlichen Schlüssel des Empfängers würde man zur Vertraulichkeit verschlüsseln, und ein gemeinsamer symmetrischer Schlüssel kann keinen eindeutigen Urheber belegen (keine Nichtabstreitbarkeit)."
|
||||
},
|
||||
{
|
||||
"category": "Zertifikate & PKI",
|
||||
"hint": "Zurückgezogen trotz gültigem Datum.",
|
||||
"text": "Wie prüft ein Client, ob ein vorgelegtes Zertifikat zwischenzeitlich vorzeitig zurückgezogen wurde?",
|
||||
"answers": [
|
||||
"Anhand des Ablaufdatums, das im Zertifikat eingetragen ist.",
|
||||
"Durch erneutes Berechnen des Fingerprints des Zertifikats.",
|
||||
"Über eine Sperrliste (CRL) oder eine OCSP-Abfrage bei der ausstellenden CA."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Den Sperrstatus liefern eine Zertifikatssperrliste (CRL) oder eine OCSP-Anfrage bei der CA. Das Ablaufdatum zeigt nur den regulären Gültigkeitszeitraum, nicht einen vorzeitigen Rückruf, und das Neuberechnen des Fingerprints prüft lediglich die Unverfälschtheit, nicht den Widerruf."
|
||||
},
|
||||
{
|
||||
"category": "Zertifikate & PKI",
|
||||
"hint": "Kette bis zu einer vertrauten Wurzel.",
|
||||
"text": "Worauf beruht das Vertrauen in ein Serverzertifikat beim Aufruf einer HTTPS-Seite?",
|
||||
"answers": [
|
||||
"Darauf, dass das Zertifikat selbstsigniert und damit automatisch fälschungssicher ist.",
|
||||
"Auf einer lückenlosen Vertrauenskette bis hinauf zu einer im System hinterlegten Root-CA.",
|
||||
"Darauf, dass die Domain im Zertifikat mit irgendeiner beliebigen anderen CA übereinstimmt."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Das Vertrauen folgt der Kette vom Serverzertifikat über Zwischenzertifikate bis zu einer Root-CA, der Betriebssystem oder Browser bereits vertrauen. Ein selbstsigniertes Zertifikat hat keinen externen Vertrauensanker, und die ausstellende Stelle muss eine vertrauenswürdige CA sein, nicht eine beliebige."
|
||||
},
|
||||
{
|
||||
"category": "Schutzziele & Authentisierung",
|
||||
"hint": "Daten wurden unbemerkt verändert.",
|
||||
"text": "Ein Angreifer verändert unbemerkt die Beträge in einer übertragenen Überweisungsdatei. Welches Schutzziel der CIA-Triade ist dadurch primär verletzt?",
|
||||
"answers": [
|
||||
"Integrität.",
|
||||
"Vertraulichkeit.",
|
||||
"Verfügbarkeit."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Die unbefugte, unbemerkte Veränderung von Daten verletzt die Integrität. Vertraulichkeit wäre erst bei unbefugtem Mitlesen betroffen und Verfügbarkeit erst, wenn die Daten oder das System nicht mehr erreichbar wären – beides ist hier nicht der Fall."
|
||||
},
|
||||
{
|
||||
"category": "Schutzziele & Authentisierung",
|
||||
"hint": "Der Dienst ist nicht mehr erreichbar.",
|
||||
"text": "Ein DDoS-Angriff überlastet einen Webshop so stark, dass Kunden ihn nicht mehr erreichen. Welches Schutzziel ist primär betroffen?",
|
||||
"answers": [
|
||||
"Integrität.",
|
||||
"Vertraulichkeit.",
|
||||
"Verfügbarkeit."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Ein DDoS-Angriff verhindert den legitimen Zugriff und trifft damit die Verfügbarkeit. Die Daten werden dabei weder verändert (Integrität) noch ausgelesen (Vertraulichkeit) – der Dienst ist schlicht nicht erreichbar."
|
||||
},
|
||||
{
|
||||
"category": "Schutzziele & Authentisierung",
|
||||
"hint": "Zwei verschiedene Faktorarten.",
|
||||
"text": "Eine Anmeldung verlangt ein Passwort und zusätzlich einen Einmalcode aus einer Authenticator-App. Warum gilt das als echte Zwei-Faktor-Authentisierung?",
|
||||
"answers": [
|
||||
"Weil zwei verschiedene Passwörter nacheinander abgefragt werden.",
|
||||
"Weil zwei unterschiedliche Faktorarten kombiniert werden: Wissen (Passwort) und Besitz (Gerät mit App).",
|
||||
"Weil dasselbe Passwort zur Sicherheit zweimal eingegeben werden muss."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Echte 2FA verbindet Faktoren aus verschiedenen Kategorien – hier Wissen (Passwort) und Besitz (das Gerät, das den Code erzeugt). Zwei Passwörter oder eine doppelte Eingabe bleiben im selben Faktor Wissen und fügen keinen zweiten, unabhängigen Faktor hinzu."
|
||||
},
|
||||
{
|
||||
"category": "Schutzziele & Authentisierung",
|
||||
"hint": "Erst wer bist du, dann was darfst du.",
|
||||
"text": "Worin unterscheiden sich Authentisierung und Autorisierung?",
|
||||
"answers": [
|
||||
"Die Authentisierung weist die Identität nach, die Autorisierung legt danach fest, worauf der Nutzer zugreifen darf.",
|
||||
"Die Authentisierung vergibt die Zugriffsrechte, die Autorisierung prüft das Passwort.",
|
||||
"Beide Begriffe bezeichnen denselben Vorgang der reinen Passwortprüfung."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Die Authentisierung beantwortet, wer jemand ist, und erst die Autorisierung entscheidet, welche Ressourcen dieser Nutzer verwenden darf. Die vertauschte Definition dreht beide Schritte um, und sie als denselben Vorgang zu sehen ignoriert, dass Identitätsnachweis und Rechtevergabe getrennte Stufen sind."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"title": "Monitoring & Backup",
|
||||
"questions": [
|
||||
{
|
||||
"category": "Monitoring & SNMP",
|
||||
"hint": "Software auf dem überwachten Gerät.",
|
||||
"text": "Welche Aufgabe hat ein SNMP-Agent in einer Monitoring-Umgebung?",
|
||||
"answers": [
|
||||
"Er läuft auf dem überwachten Gerät, sammelt dort Statuswerte in der MIB und liefert sie an das Monitoring-System.",
|
||||
"Er ist die zentrale Software, die alle Geräte abfragt und die Messwerte grafisch darstellt.",
|
||||
"Er verschlüsselt den gesamten Netzwerkverkehr zwischen den überwachten Geräten."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Der Agent sitzt auf dem Zielsystem (Switch, Server, Drucker), hält seine Werte in der MIB bereit und beantwortet Abfragen des Managers oder sendet Traps. Die zentrale Abfrage und Darstellung übernimmt der Manager beziehungsweise das NMS; mit Verschlüsselung des Nutzdatenverkehrs hat SNMP nichts zu tun (erst SNMPv3 sichert die Verwaltungsdaten selbst)."
|
||||
},
|
||||
{
|
||||
"category": "Monitoring & SNMP",
|
||||
"hint": "Zwei Stufen vor dem Ausfall.",
|
||||
"text": "Warum definiert man beim Monitoring für eine Kennzahl wie die Festplattenbelegung typischerweise einen Warning- und einen Critical-Schwellwert?",
|
||||
"answers": [
|
||||
"Weil ein einzelner Schwellwert technisch nicht überwacht werden kann.",
|
||||
"Damit man bei Annäherung an einen kritischen Zustand frühzeitig gewarnt wird und erst bei echter Gefahr Alarm ausgelöst wird.",
|
||||
"Weil der Warning-Wert den Dienst automatisch neu startet und der Critical-Wert ihn abschaltet."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Abgestufte Schwellwerte trennen 'beobachten' von 'sofort handeln': Warning gibt Vorlaufzeit zum Reagieren, Critical meldet den akuten Notfall. Ein einzelner Schwellwert wäre technisch problemlos möglich, nur weniger aussagekräftig; Schwellwerte lösen Meldungen aus und starten oder stoppen nicht von sich aus Dienste."
|
||||
},
|
||||
{
|
||||
"category": "Monitoring & SNMP",
|
||||
"hint": "Das Gerät meldet sich von selbst.",
|
||||
"text": "Worin unterscheidet sich ein SNMP-Trap vom klassischen Polling?",
|
||||
"answers": [
|
||||
"Ein Trap fragt die Geräte in kürzeren Intervallen ab als Polling.",
|
||||
"Ein Trap überträgt die Daten grundsätzlich verschlüsselt, Polling immer im Klartext.",
|
||||
"Beim Trap meldet der Agent ein Ereignis von sich aus an den Manager, während der Manager beim Polling die Geräte aktiv abfragt."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Polling ist ein aktives, zyklisches Abfragen durch den Manager; ein Trap ist eine ereignisgesteuerte Meldung, die der Agent ohne Aufforderung sendet und so die Reaktionszeit bei Störungen verkürzt. Ein Trap ist kein schnelleres Polling, und die Verschlüsselung hängt von der SNMP-Version ab, nicht von Trap gegenüber Polling."
|
||||
},
|
||||
{
|
||||
"category": "Monitoring & SNMP",
|
||||
"hint": "Zu viele Fehlalarme sind auch ein Problem.",
|
||||
"text": "Du bekommst nachts ständig Alarme, weil die CPU-Last kurzzeitig den Schwellwert überschreitet. Wie gehst du sinnvoll vor?",
|
||||
"answers": [
|
||||
"Ich lege eine Mindestdauer beziehungsweise mehrere Prüfintervalle fest, sodass erst bei anhaltender Überschreitung alarmiert wird.",
|
||||
"Ich schalte das Monitoring für die CPU-Last komplett ab, damit keine Meldungen mehr kommen.",
|
||||
"Ich setze den Schwellwert auf 100 %, damit praktisch nie ein Alarm ausgelöst wird."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Kurze Lastspitzen sind normal; mit einer Mindestdauer (etwa 'über 5 Minuten') oder mehreren aufeinanderfolgenden Prüfungen vermeidest du Fehlalarme, ohne echte Probleme zu übersehen. Das Monitoring ganz abzuschalten oder den Schwellwert unerreichbar hoch zu setzen macht dich für tatsächliche Engpässe blind."
|
||||
},
|
||||
{
|
||||
"category": "Backup-Strategie",
|
||||
"hint": "Drei Kopien, zwei Medien, eine extern.",
|
||||
"text": "Was besagt die 3-2-1-Backup-Regel?",
|
||||
"answers": [
|
||||
"Drei Vollbackups, zwei inkrementelle und ein differenzielles Backup pro Woche.",
|
||||
"Drei Kopien der Daten auf zwei verschiedenen Medientypen, davon eine Kopie an einem externen Standort.",
|
||||
"Drei Backup-Server, zwei Administratoren und eine Sicherung pro Tag."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Die 3-2-1-Regel meint: mindestens drei Datenkopien (Original plus zwei Backups) auf zwei unterschiedlichen Medientypen, und eine Kopie räumlich getrennt (offsite) - so übersteht man Hardware-, Standort- und Katastrophenschäden. Die anderen Deutungen mischen die Zahlen mit Backup-Arten oder Personal und treffen die Regel nicht."
|
||||
},
|
||||
{
|
||||
"category": "Backup-Strategie",
|
||||
"hint": "Immer der komplette Datenbestand.",
|
||||
"text": "Wodurch ist ein Vollbackup gekennzeichnet?",
|
||||
"answers": [
|
||||
"Es sichert nur die seit gestern geänderten Dateien.",
|
||||
"Es sichert alle Änderungen seit dem letzten Vollbackup.",
|
||||
"Es sichert bei jedem Durchlauf den gesamten ausgewählten Datenbestand vollständig."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Ein Vollbackup kopiert jedes Mal alle ausgewählten Daten komplett - dadurch ist der Restore am einfachsten (nur ein Satz nötig), es braucht aber am meisten Speicher und Zeit. Nur geänderte Dateien seit dem letzten Backup sichert das inkrementelle Backup; Änderungen seit dem letzten Vollbackup sichert das differenzielle Backup."
|
||||
},
|
||||
{
|
||||
"category": "Backup-Strategie",
|
||||
"hint": "Bezugspunkt ist das jeweils letzte Backup.",
|
||||
"text": "Was sichert ein inkrementelles Backup?",
|
||||
"answers": [
|
||||
"Alle Daten, die sich seit dem letzten Backup geändert haben - unabhängig davon, ob dieses ein Voll- oder ein inkrementelles Backup war.",
|
||||
"Alle Daten, die sich seit dem letzten Vollbackup geändert haben.",
|
||||
"Grundsätzlich den kompletten Datenbestand, nur stärker komprimiert."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Das inkrementelle Backup nimmt als Bezugspunkt das letzte Backup beliebigen Typs und sichert nur die seither geänderten Daten - das hält die einzelnen Sicherungen klein und schnell, verlängert aber die Restore-Kette. Der Bezug auf das letzte Vollbackup beschreibt dagegen das differenzielle Backup; ein Komplettbestand wäre ein Vollbackup."
|
||||
},
|
||||
{
|
||||
"category": "Backup-Strategie",
|
||||
"hint": "Bezugspunkt ist immer das Vollbackup.",
|
||||
"text": "Ein differenzielles Backup läuft täglich, das Vollbackup lief am Sonntag. Was enthält die Sicherung von Mittwoch?",
|
||||
"answers": [
|
||||
"Nur die Änderungen gegenüber der Sicherung von Dienstag.",
|
||||
"Alle Änderungen seit dem Vollbackup vom Sonntag.",
|
||||
"Den vollständigen Datenbestand vom Mittwoch."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Differenzielle Backups beziehen sich stets auf das letzte Vollbackup, also enthält die Mittwoch-Sicherung alle Änderungen seit Sonntag - dadurch wachsen die täglichen Sicherungen bis zum nächsten Vollbackup an. Nur die Differenz zum Vortag zu sichern wäre inkrementell; ein Komplettbestand wäre ein erneutes Vollbackup. Der Restore-Vorteil: Vollbackup plus ein einziges differenzielles Backup genügen."
|
||||
},
|
||||
{
|
||||
"category": "Generationenprinzip",
|
||||
"hint": "Drei Sicherungsebenen mit verschiedenen Zyklen.",
|
||||
"text": "Was beschreibt das Großvater-Vater-Sohn-Prinzip bei der Datensicherung?",
|
||||
"answers": [
|
||||
"Eine RAID-Konfiguration mit drei gespiegelten Festplatten.",
|
||||
"Die Reihenfolge, in der Backups verschlüsselt werden.",
|
||||
"Ein gestaffeltes Rotationsschema mit täglichen, wöchentlichen und monatlichen Sicherungen unterschiedlicher Aufbewahrungsdauer."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Großvater-Vater-Sohn ist ein Rotationsschema: 'Sohn'-Medien werden täglich, 'Vater' wöchentlich und 'Großvater' monatlich geschrieben und unterschiedlich lange aufbewahrt - so hat man kurz- und langfristige Wiederherstellungspunkte bei überschaubarer Medienzahl. Mit RAID (Redundanz im laufenden Betrieb) oder Verschlüsselung hat das Prinzip nichts zu tun."
|
||||
},
|
||||
{
|
||||
"category": "Generationenprinzip",
|
||||
"hint": "Sohn ist der kurzlebigste Stand.",
|
||||
"text": "Welche Zuordnung von Generation und Zyklus ist beim Großvater-Vater-Sohn-Prinzip üblich?",
|
||||
"answers": [
|
||||
"Sohn = tägliche Sicherung, Vater = wöchentliche Sicherung, Großvater = monatliche Sicherung.",
|
||||
"Sohn = monatliche Sicherung, Vater = wöchentliche Sicherung, Großvater = tägliche Sicherung.",
|
||||
"Sohn = jährliche Sicherung, Vater = monatliche Sicherung, Großvater = tägliche Sicherung."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Der 'Sohn' ist der jüngste, am häufigsten überschriebene Stand (täglich), der 'Vater' fasst die Woche zusammen, der 'Großvater' den Monat und wird am längsten aufbewahrt. Die vertauschten Varianten drehen die Zyklen um - der jüngste Stand darf gerade nicht am seltensten geschrieben werden."
|
||||
},
|
||||
{
|
||||
"category": "Generationenprinzip",
|
||||
"hint": "Ältere Generationen leben länger.",
|
||||
"text": "Wie werden die Medien im Generationenprinzip typischerweise aufbewahrt und wiederverwendet?",
|
||||
"answers": [
|
||||
"Alle Medien werden nach genau einem Tag gelöscht und sofort überschrieben.",
|
||||
"Tägliche Medien werden schnell wieder überschrieben, wöchentliche und monatliche dagegen deutlich länger aufbewahrt.",
|
||||
"Monatliche Sicherungen werden am schnellsten überschrieben, um Platz zu sparen."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Je höher die Generation, desto länger die Aufbewahrung: Tagesmedien rotieren schnell, Wochen- und Monatsmedien bleiben länger erhalten und liefern ältere Wiederherstellungspunkte. Alles nach einem Tag zu löschen zerstört die Langzeitstände, und ausgerechnet die Monatssicherung zuerst zu überschreiben widerspricht dem Zweck des Prinzips."
|
||||
},
|
||||
{
|
||||
"category": "Generationenprinzip",
|
||||
"hint": "Nicht nur der letzte Stand zählt.",
|
||||
"text": "Welchen Vorteil bietet das Generationenprinzip gegenüber dem ständigen Überschreiben nur einer einzigen Sicherung?",
|
||||
"answers": [
|
||||
"Es benötigt grundsätzlich weniger Speicher als eine einzelne Sicherung.",
|
||||
"Es macht ein externes Offsite-Backup vollständig überflüssig.",
|
||||
"Es hält mehrere zeitlich gestaffelte Wiederherstellungspunkte vor, sodass man auch auf ältere Datenstände zurückgreifen kann."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Mit gestaffelten Generationen kann man einen Fehler auch dann noch beheben, wenn er erst nach Tagen oder Wochen auffällt - etwa bei spät entdeckter Datenkorruption oder Verschlüsselung durch Ransomware. Es braucht dafür mehr statt weniger Medien und ersetzt kein Offsite-Backup; beide Maßnahmen ergänzen sich."
|
||||
},
|
||||
{
|
||||
"category": "Wiederherstellung",
|
||||
"hint": "Wie lange darf der Ausfall dauern?",
|
||||
"text": "Was gibt die Recovery Time Objective (RTO) an?",
|
||||
"answers": [
|
||||
"Die maximal tolerierbare Zeitspanne, bis ein System nach einem Ausfall wieder verfügbar sein muss.",
|
||||
"Die maximal tolerierbare Menge an Daten, die bei einem Ausfall verloren gehen darf.",
|
||||
"Den Zeitpunkt, zu dem das nächste Vollbackup geplant ist."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Die RTO ist die Zielvorgabe für die Wiederanlaufzeit - wie lange darf es maximal dauern, bis der Betrieb wieder läuft? Die maximal tolerierte Datenverlustmenge beschreibt dagegen die RPO; mit dem Zeitplan des nächsten Backups hat die RTO nichts zu tun."
|
||||
},
|
||||
{
|
||||
"category": "Wiederherstellung",
|
||||
"hint": "Wie viele Daten darf ich verlieren?",
|
||||
"text": "Ein Kunde verlangt eine RPO von einer Stunde. Was bedeutet das für deine Backup-Planung?",
|
||||
"answers": [
|
||||
"Das System muss innerhalb einer Stunde nach dem Ausfall wieder laufen.",
|
||||
"Es darf höchstens der Datenbestand einer Stunde verloren gehen, also musst du mindestens stündlich sichern.",
|
||||
"Das Backup darf maximal eine Stunde lang laufen."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Die RPO (Recovery Point Objective) legt den maximal zulässigen Datenverlust fest; eine RPO von einer Stunde erzwingt Sicherungsintervalle von höchstens einer Stunde. Die Wiederanlaufzeit beschreibt die RTO, und die reine Laufzeit eines Backups ist damit nicht gemeint."
|
||||
},
|
||||
{
|
||||
"category": "Wiederherstellung",
|
||||
"hint": "Ein Backup, das man nie zurückspielt, ist wertlos.",
|
||||
"text": "Warum sind regelmäßige Restore-Tests wichtig, obwohl die Backups laut Protokoll fehlerfrei durchlaufen?",
|
||||
"answers": [
|
||||
"Weil Restore-Tests die Backups automatisch verschlüsseln.",
|
||||
"Weil ohne Restore-Test kein Vollbackup erstellt werden kann.",
|
||||
"Weil ein erfolgreiches Backup-Protokoll nicht garantiert, dass sich die Daten auch tatsächlich vollständig und brauchbar wiederherstellen lassen."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Erst der Restore beweist, dass Medien lesbar, Datensätze konsistent und Abläufe dokumentiert sind - defekte Medien, fehlende Datenbank-Konsistenz oder unklare Prozesse fallen sonst erst im Ernstfall auf. Restore-Tests verschlüsseln nichts und sind keine Voraussetzung für ein Vollbackup; sie prüfen ausschließlich die Wiederherstellbarkeit."
|
||||
},
|
||||
{
|
||||
"category": "Wiederherstellung",
|
||||
"hint": "Vollbackup plus alle Zwischenschritte.",
|
||||
"text": "Am Sonntag lief ein Vollbackup, Montag bis Freitag jeweils inkrementell. Freitagabend fällt die Platte aus. Welche Sicherungen brauchst du zur vollständigen Wiederherstellung?",
|
||||
"answers": [
|
||||
"Nur das inkrementelle Backup von Freitag.",
|
||||
"Das Vollbackup vom Sonntag und alle inkrementellen Backups von Montag bis Freitag in Reihenfolge.",
|
||||
"Nur das Vollbackup vom Sonntag."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Da jedes inkrementelle Backup nur die Änderungen seit dem vorherigen Backup enthält, brauchst du das Vollbackup als Basis und lückenlos alle inkrementellen Sicherungen in der richtigen Reihenfolge - fehlt eine, ist die Kette unterbrochen. Das ist der Preis der schnellen inkrementellen Sicherung; bei differenziellen Backups genügte das Vollbackup plus die letzte Sicherung."
|
||||
},
|
||||
{
|
||||
"category": "Serverdimensionierung",
|
||||
"hint": "Nicht auf Kante nähen.",
|
||||
"text": "Warum plant man bei der Serverdimensionierung bewusst Kapazitätsreserven bei Speicher und Rechenleistung ein?",
|
||||
"answers": [
|
||||
"Um Lastspitzen und künftiges Wachstum abzufangen, ohne dass das System sofort an seine Grenzen stößt.",
|
||||
"Weil ein Server ohne freie Reserve gar nicht startet.",
|
||||
"Weil Reserven die Lizenzkosten der Software senken."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Reserven fangen Spitzenlasten, Datenwachstum und zusätzliche Dienste ab und verhindern, dass schon der normale Betrieb ein teures Upgrade erzwingt - üblich ist, nicht dauerhaft nahe 100 % Auslastung zu fahren. Ein Server startet auch ohne freie Reserve, und die Lizenzkosten sinken dadurch nicht, sie steigen mit mehr Ressourcen eher."
|
||||
},
|
||||
{
|
||||
"category": "Serverdimensionierung",
|
||||
"hint": "Verfügbarkeit trotz Plattenausfall.",
|
||||
"text": "Welche Maßnahme erhöht die Verfügbarkeit der Datenspeicherung in einem Server durch Redundanz?",
|
||||
"answers": [
|
||||
"Der Einsatz einer einzelnen, besonders großen Festplatte.",
|
||||
"Das Deaktivieren aller Hintergrunddienste.",
|
||||
"Der Einsatz eines RAID-Verbundes, der den Ausfall einzelner Festplatten ohne Datenverlust überbrückt."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "RAID-Level wie 1, 5, 6 oder 10 verteilen Daten redundant, sodass beim Ausfall einer (bei RAID 6 zweier) Platten weitergearbeitet werden kann - das erhöht die Ausfallsicherheit. Eine einzelne große Platte bündelt das Risiko, und das Abschalten von Diensten spart Last, schafft aber keine Redundanz. Wichtig: RAID ersetzt kein Backup."
|
||||
},
|
||||
{
|
||||
"category": "Serverdimensionierung",
|
||||
"hint": "Kennzahl für die Speicher-Leistung.",
|
||||
"text": "Welche Kennzahl ist bei der Dimensionierung eines Storage-Systems für eine datenbanklastige Anwendung besonders relevant?",
|
||||
"answers": [
|
||||
"Die Gehäusefarbe des Servers.",
|
||||
"Die IOPS (Ein-/Ausgabeoperationen pro Sekunde), da Datenbanken viele kleine, wahlfreie Zugriffe erzeugen.",
|
||||
"Die Anzahl der USB-Anschlüsse am Server."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Datenbanken erzeugen viele kleine, wahlfreie Lese- und Schreibzugriffe; hier entscheiden die IOPS über die Leistung, weshalb oft SSDs statt klassischer HDDs gewählt werden. Gehäusefarbe und USB-Anzahl sind für die Leistungsdimensionierung völlig irrelevant."
|
||||
},
|
||||
{
|
||||
"category": "Serverdimensionierung",
|
||||
"hint": "Eine Komponente mehr als nötig.",
|
||||
"text": "Was bedeutet eine N+1-Redundanz, etwa bei den Netzteilen eines Servers?",
|
||||
"answers": [
|
||||
"Es ist eine Komponente mehr vorhanden als für den Betrieb nötig, sodass der Ausfall einer einzelnen Komponente kompensiert wird.",
|
||||
"Es sind genau so viele Komponenten vorhanden, wie für den Betrieb rechnerisch nötig sind.",
|
||||
"Es ist die doppelte Anzahl aller Komponenten vorhanden, unabhängig vom tatsächlichen Bedarf."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "N+1 heißt: zu den N benötigten Einheiten kommt eine zusätzliche Reserve, die bei Ausfall einer Einheit einspringt - etwa ein zweites Netzteil, das die Last allein trägt. Genau N Komponenten hätten keine Reserve, und die glatte Verdopplung entspräche eher 2N beziehungsweise voller Redundanz."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"title": "Netzwerkplanung",
|
||||
"questions": [
|
||||
{
|
||||
"category": "Subnetting & VLSM",
|
||||
"hint": "Host-Bits zählen.",
|
||||
"text": "Du sollst 192.168.10.0/24 in Subnetze mit jeweils mindestens 50 nutzbaren Hosts aufteilen. Welche Maske ist passend und zugleich am sparsamsten?",
|
||||
"answers": [
|
||||
"/26 (255.255.255.192)",
|
||||
"/25 (255.255.255.128)",
|
||||
"/27 (255.255.255.224)"
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "6 Host-Bits ergeben 2^6−2 = 62 nutzbare Hosts – das reicht für 50 und ist am sparsamsten. /25 verschenkt Adressen (126 Hosts), /27 bietet nur 30 Hosts und ist zu klein."
|
||||
},
|
||||
{
|
||||
"category": "Subnetting & VLSM",
|
||||
"hint": "2 hoch Host-Bits, minus zwei.",
|
||||
"text": "Wie viele nutzbare Host-Adressen bietet ein /29-Subnetz?",
|
||||
"answers": [
|
||||
"8",
|
||||
"6",
|
||||
"14"
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "/29 lässt 3 Host-Bits: 2^3 = 8 Adressen, abzüglich Netz- und Broadcast-Adresse bleiben 6 nutzbare. 8 ist die Gesamtzahl inklusive Netz und Broadcast, 14 gehört zu /28."
|
||||
},
|
||||
{
|
||||
"category": "Subnetting & VLSM",
|
||||
"hint": "Blockgröße der Maske bestimmen.",
|
||||
"text": "Zu welchem Subnetz gehört die Host-Adresse 172.16.20.130/26?",
|
||||
"answers": [
|
||||
"172.16.20.0/26",
|
||||
"172.16.20.64/26",
|
||||
"172.16.20.128/26"
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Bei /26 ist die Blockgröße 64, die Netzgrenzen liegen bei .0, .64, .128, .192. Die 130 fällt in den Block ab .128; die Blöcke .0 und .64 enden schon vor 130."
|
||||
},
|
||||
{
|
||||
"category": "Subnetting & VLSM",
|
||||
"hint": "Vom größten Bedarf her planen.",
|
||||
"text": "Warum vergibt man bei VLSM die Subnetze in absteigender Reihenfolge der benötigten Host-Anzahl?",
|
||||
"answers": [
|
||||
"Weil so der Adressraum lückenlos genutzt wird und keine passenden Blöcke zerschnitten werden.",
|
||||
"Weil größere Subnetze zwingend niedrigere Netzadressen brauchen.",
|
||||
"Weil kleine Subnetze sonst keinen Broadcast bilden können."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Größte Netze zuerst zu vergeben verhindert Fragmentierung – kleine Netze passen danach genau in die Restblöcke. Die Adresslage folgt nicht der Größe, und der Broadcast hängt nicht von der Vergabereihenfolge ab."
|
||||
},
|
||||
{
|
||||
"category": "Subnetting & VLSM",
|
||||
"hint": "Broadcast ist die letzte Adresse im Block.",
|
||||
"text": "Wie lautet die Broadcast-Adresse des Subnetzes 10.0.5.0/27?",
|
||||
"answers": [
|
||||
"10.0.5.32",
|
||||
"10.0.5.31",
|
||||
"10.0.5.63"
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "/27 hat die Blockgröße 32, das erste Subnetz reicht von .0 bis .31; der Broadcast ist die letzte Adresse .31. Die .32 ist bereits die Netzadresse des nächsten Subnetzes, die .63 der Broadcast des zweiten Blocks."
|
||||
},
|
||||
{
|
||||
"category": "IPv6",
|
||||
"hint": "Führende Nullen und eine Nullfolge kürzen.",
|
||||
"text": "Wie lautet die korrekt gekürzte Schreibweise von 2001:0db8:0000:0000:0000:ff00:0042:8329?",
|
||||
"answers": [
|
||||
"2001:db8:0:0:0:ff00:42:8329::",
|
||||
"2001::db8::ff00:42:8329",
|
||||
"2001:db8::ff00:42:8329"
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Führende Nullen jeder Gruppe entfallen, eine zusammenhängende Nullfolge wird einmal durch :: ersetzt. Ein zweites :: ist unzulässig, und ein zusätzliches :: am Ende ist hier überflüssig und falsch platziert."
|
||||
},
|
||||
{
|
||||
"category": "IPv6",
|
||||
"hint": "Gültigkeit nur auf dem lokalen Link.",
|
||||
"text": "Wofür wird eine IPv6-Link-Local-Adresse (fe80::/10) verwendet?",
|
||||
"answers": [
|
||||
"Für die Kommunikation ausschließlich innerhalb desselben Netzsegments, z. B. für Neighbor Discovery.",
|
||||
"Für die weltweit eindeutige Erreichbarkeit im Internet.",
|
||||
"Als Ersatz für private IPv4-Adressen mit NAT ins Internet."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Link-Local-Adressen gelten nur auf dem lokalen Link und werden nicht geroutet – u. a. für Neighbor Discovery und Autokonfiguration. Global erreichbar sind Global-Unicast-Adressen (2000::/3); NAT ist typisch für IPv4."
|
||||
},
|
||||
{
|
||||
"category": "IPv6",
|
||||
"hint": "Standard-Präfix pro Segment.",
|
||||
"text": "Welche Präfixlänge ist für ein einzelnes LAN-Segment in IPv6 üblich und für SLAAC vorgesehen?",
|
||||
"answers": [
|
||||
"/48",
|
||||
"/64",
|
||||
"/128"
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "/64 ist das Standardpräfix pro Segment; die unteren 64 Bit bilden den Interface-Identifier (SLAAC). /48 ist eine typische Standort-Zuteilung, /128 bezeichnet eine einzelne Host-Adresse."
|
||||
},
|
||||
{
|
||||
"category": "IPv6",
|
||||
"hint": "IPv6 kennt keinen Broadcast.",
|
||||
"text": "Was übernimmt in IPv6 die Aufgaben der aus IPv4 bekannten Broadcast-Adresse?",
|
||||
"answers": [
|
||||
"Eine reservierte Broadcast-Adresse am Ende jedes /64-Netzes.",
|
||||
"Anycast-Adressen an den jeweils nächstgelegenen Empfänger.",
|
||||
"Multicast-Adressen (ff00::/8) an definierte Empfängergruppen."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "IPv6 hat keinen Broadcast; seine Aufgaben übernehmen Multicast-Gruppen (ff00::/8), etwa all-nodes ff02::1. Anycast liefert an genau einen (den nächsten) von mehreren gleichen Zielen; eine Broadcast-Adresse gibt es nicht mehr."
|
||||
},
|
||||
{
|
||||
"category": "WLAN-Planung",
|
||||
"hint": "Überlappungsfreie Kanäle im 2,4-GHz-Band.",
|
||||
"text": "Welche Kanäle im 2,4-GHz-Band überlappen sich in Europa nicht und eignen sich für benachbarte Access Points?",
|
||||
"answers": [
|
||||
"1, 6 und 11",
|
||||
"1, 5 und 9",
|
||||
"2, 7 und 12"
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Wegen 20 MHz Kanalbreite bei nur 5 MHz Kanalabstand sind im 2,4-GHz-Band nur 1, 6 und 11 überlappungsfrei. Die anderen Kombinationen liegen zu dicht beieinander und stören sich."
|
||||
},
|
||||
{
|
||||
"category": "WLAN-Planung",
|
||||
"hint": "Gleiche SSID, mehrere Sender.",
|
||||
"text": "Mehrere Access Points sollen dieselbe SSID ausstrahlen, damit Clients nahtlos wechseln. Worauf ist bei der Planung besonders zu achten?",
|
||||
"answers": [
|
||||
"Alle APs müssen denselben Funkkanal verwenden, damit Roaming funktioniert.",
|
||||
"Benachbarte APs sollten überlappungsfreie Kanäle nutzen und sich in der Ausleuchtung leicht überlappen.",
|
||||
"Jeder AP braucht ein eigenes VLAN, sonst ist Roaming nicht möglich."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Für sauberes Roaming überlappen sich die Funkzellen leicht, benachbarte APs funken aber auf verschiedenen, überlappungsfreien Kanälen (Co-Channel-Interference vermeiden). Gleicher Kanal überall stört; ein eigenes VLAN je AP ist für Roaming nicht nötig."
|
||||
},
|
||||
{
|
||||
"category": "WLAN-Planung",
|
||||
"hint": "Aktueller Standard mit Benutzer-Authentisierung.",
|
||||
"text": "Welches Verfahren solltest du für ein neu geplantes Firmen-WLAN vorsehen?",
|
||||
"answers": [
|
||||
"WEP mit 128-Bit-Schlüssel für maximale Kompatibilität.",
|
||||
"WPA2-Personal mit einem gemeinsamen Passwort für alle.",
|
||||
"WPA3-Enterprise mit Authentisierung pro Benutzer über einen RADIUS-Server."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "WPA3-Enterprise mit RADIUS (802.1X) authentisiert jeden Benutzer einzeln und ist der aktuelle Stand für Firmennetze. WPA2-Personal teilt ein Passwort für alle (schlecht sperrbar), WEP gilt seit Langem als gebrochen."
|
||||
},
|
||||
{
|
||||
"category": "WLAN-Planung",
|
||||
"hint": "Mehr Kanäle, aber kürzere Reichweite.",
|
||||
"text": "Ein Kunde klagt über volle 2,4-GHz-Kanäle. Welcher Vorteil spricht für den Einsatz des 5-GHz-Bandes?",
|
||||
"answers": [
|
||||
"Es bietet mehr überlappungsfreie Kanäle und weniger Störungen, hat aber geringere Reichweite und Wanddurchdringung.",
|
||||
"Es hat eine deutlich größere Reichweite als 2,4 GHz bei gleicher Sendeleistung.",
|
||||
"Es benötigt keine Kanalplanung, weil sich 5-GHz-Kanäle nie überlappen."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "5 GHz stellt viele überlappungsfreie Kanäle bereit und ist weniger überlastet, wird von Wänden aber stärker gedämpft. Die Reichweite ist geringer statt größer; wegen DFS bleibt eine Kanalplanung nötig."
|
||||
},
|
||||
{
|
||||
"category": "Routing",
|
||||
"hint": "Längste Übereinstimmung gewinnt.",
|
||||
"text": "Ein Router kennt Routen zu 10.0.0.0/8, 10.1.0.0/16 und 10.1.5.0/24. An welche stellt er ein Paket an 10.1.5.20 zu?",
|
||||
"answers": [
|
||||
"An 10.0.0.0/8, weil es die erste passende Route ist.",
|
||||
"An 10.1.5.0/24 nach dem Longest-Prefix-Match.",
|
||||
"An 10.1.0.0/16, weil sie in der Mitte liegt."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Router wählen immer die spezifischste passende Route (längstes Präfix). /24 passt genauer als /16 oder /8. Die Reihenfolge in der Tabelle spielt beim Longest-Prefix-Match keine Rolle."
|
||||
},
|
||||
{
|
||||
"category": "Routing",
|
||||
"hint": "Von Hand eingetragen vs. selbstlernend.",
|
||||
"text": "Worin unterscheidet sich statisches von dynamischem Routing?",
|
||||
"answers": [
|
||||
"Statisches Routing funktioniert nur mit IPv6, dynamisches nur mit IPv4.",
|
||||
"Dynamisches Routing kommt ohne Router aus, statisches braucht immer Router.",
|
||||
"Statische Routen trägt der Administrator manuell ein; dynamische lernen die Router über Protokolle wie OSPF selbst und passen sich an."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Statische Routen sind fest konfiguriert, dynamische werden per Routing-Protokoll (OSPF, RIP, BGP) ausgetauscht und reagieren auf Ausfälle. Die Protokollwahl ist unabhängig von IPv4/IPv6, und beide Varianten laufen auf Routern."
|
||||
},
|
||||
{
|
||||
"category": "Routing",
|
||||
"hint": "Auffangbecken für unbekannte Ziele.",
|
||||
"text": "Wofür dient die Default-Route 0.0.0.0/0 in einer Routing-Tabelle?",
|
||||
"answers": [
|
||||
"Sie legt fest, wohin Pakete gehen, für die keine spezifischere Route existiert – meist zum Internet-Gateway.",
|
||||
"Sie blockiert alle Pakete an unbekannte Ziele.",
|
||||
"Sie beschreibt ausschließlich das direkt angeschlossene lokale Netz."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Passt keine spezifischere Route, geht das Paket über die Default-Route (meist zum Gateway/Provider). Sie blockiert nichts und meint nicht das lokale Netz – das steht als direkt verbundene Route in der Tabelle."
|
||||
},
|
||||
{
|
||||
"category": "Routing",
|
||||
"hint": "Link-State-Protokoll im eigenen Netz.",
|
||||
"text": "Welches Routing-Protokoll ist ein Link-State-Protokoll und als Interior Gateway innerhalb eines Unternehmensnetzes typisch?",
|
||||
"answers": [
|
||||
"BGP",
|
||||
"OSPF",
|
||||
"RIPv1"
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "OSPF ist ein Link-State-IGP, das per Dijkstra kürzeste Pfade berechnet und schnell konvergiert. BGP verbindet autonome Systeme (Internet), RIPv1 ist ein veraltetes Distanzvektor-Protokoll mit 15-Hop-Grenze."
|
||||
},
|
||||
{
|
||||
"category": "Adressierung & Planung",
|
||||
"hint": "RFC 1918 – private Bereiche.",
|
||||
"text": "Welcher Adressbereich ist laut RFC 1918 für private Netze reserviert?",
|
||||
"answers": [
|
||||
"169.254.0.0 – 169.254.255.255",
|
||||
"100.64.0.0 – 100.127.255.255",
|
||||
"172.16.0.0 – 172.31.255.255"
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Zu den privaten Bereichen zählt 172.16.0.0/12 (172.16.–172.31.). 169.254.0.0/16 ist APIPA/Link-Local, 100.64.0.0/10 der Carrier-Grade-NAT-Bereich – beide sind keine RFC-1918-Netze."
|
||||
},
|
||||
{
|
||||
"category": "Adressierung & Planung",
|
||||
"hint": "Wachstum von Anfang an einplanen.",
|
||||
"text": "Warum plant man bei der Adressvergabe für Subnetze bewusst Reserve-Hosts ein?",
|
||||
"answers": [
|
||||
"Damit spätere Erweiterungen möglich sind, ohne das Netz neu segmentieren zu müssen.",
|
||||
"Weil sonst der Broadcast nicht funktioniert.",
|
||||
"Weil DHCP mindestens die Hälfte aller Adressen frei benötigt."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Reserven fangen künftiges Wachstum ab und ersparen ein Re-Design bei neuen Geräten. Der Broadcast funktioniert unabhängig von Reserven, und DHCP braucht keine feste 50-%-Freiquote."
|
||||
},
|
||||
{
|
||||
"category": "Adressierung & Planung",
|
||||
"hint": "Gleiches Netz oder über das Gateway?",
|
||||
"text": "Ein Host hat 192.168.1.10/24, sein Gateway ist 192.168.1.1. Warum erreicht er 10.0.0.5 nicht direkt, sondern nur über das Gateway?",
|
||||
"answers": [
|
||||
"Weil 10.0.0.5 eine öffentliche Adresse ist und darum immer per NAT übersetzt werden muss.",
|
||||
"Weil 10.0.0.5 in einem anderen Netz liegt und Pakete in fremde Netze über das Gateway geroutet werden.",
|
||||
"Weil /24-Netze grundsätzlich keine direkte Kommunikation erlauben."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Der Host vergleicht Ziel-IP und eigene Maske: 10.0.0.5 liegt außerhalb von 192.168.1.0/24, also schickt er das Paket an das Gateway. 10.0.0.0/8 ist zudem privat (kein Zwang zu NAT), und innerhalb eines /24 ist direkte Kommunikation gerade möglich."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"title": "Skripting",
|
||||
"questions": [
|
||||
{
|
||||
"category": "Shell-Grundlagen",
|
||||
"hint": "Leerzeichen und Dollarzeichen beachten.",
|
||||
"text": "Wie weist du in Bash der Variablen NAME korrekt den Wert server01 zu?",
|
||||
"answers": [
|
||||
"NAME=\"server01\" (ohne Leerzeichen um das Gleichheitszeichen)",
|
||||
"NAME = \"server01\"",
|
||||
"$NAME = \"server01\""
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "In Bash darf um das Gleichheitszeichen kein Leerzeichen stehen, sonst deutet die Shell NAME als Kommando. Beim Zuweisen schreibst du den Variablennamen ohne Dollarzeichen; das Dollarzeichen dient nur dem späteren Auslesen mit $NAME."
|
||||
},
|
||||
{
|
||||
"category": "Shell-Grundlagen",
|
||||
"hint": "Ausgabe wird zur Eingabe.",
|
||||
"text": "Was bewirkt der Pipe-Operator | in einer Bash-Zeile wie ls -l | grep txt?",
|
||||
"answers": [
|
||||
"Er führt beide Kommandos vollkommen unabhängig nacheinander aus.",
|
||||
"Er leitet die Ausgabe in eine Datei namens grep um.",
|
||||
"Er verbindet die Standardausgabe des ersten Kommandos mit der Standardeingabe des zweiten."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Eine Pipe reicht die Standardausgabe (stdout) des linken Kommandos direkt als Standardeingabe (stdin) an das rechte weiter, ganz ohne Zwischendatei. Ein Semikolon würde Kommandos nur nacheinander ausführen, und für eine Umleitung in eine Datei wäre > mit einem Dateinamen nötig."
|
||||
},
|
||||
{
|
||||
"category": "Shell-Grundlagen",
|
||||
"hint": "Anhängen statt überschreiben.",
|
||||
"text": "Womit hängst du die Ausgabe eines Kommandos an die bestehende Datei log.txt an, ohne deren Inhalt zu löschen?",
|
||||
"answers": [
|
||||
"kommando > log.txt",
|
||||
"kommando >> log.txt",
|
||||
"kommando < log.txt"
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Der doppelte Pfeil >> hängt die Ausgabe an das Dateiende an und erhält den bisherigen Inhalt. Ein einfaches > überschreibt die Datei vollständig, und < ist eine Eingabeumleitung, die den Dateiinhalt als stdin in das Kommando einliest."
|
||||
},
|
||||
{
|
||||
"category": "Shell-Grundlagen",
|
||||
"hint": "Kommandosubstitution.",
|
||||
"text": "Wie speicherst du in Bash die Ausgabe des Kommandos date in der Variablen JETZT?",
|
||||
"answers": [
|
||||
"JETZT=$(date)",
|
||||
"JETZT=date",
|
||||
"JETZT=|date|"
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Mit der Kommandosubstitution $(date) wird das Kommando ausgeführt und sein Ergebnis in die Variable geschrieben; die ältere Schreibweise mit Backticks leistet dasselbe. JETZT=date würde nur die Zeichenkette date speichern, und |date| ist keine gültige Syntax."
|
||||
},
|
||||
{
|
||||
"category": "PowerShell",
|
||||
"hint": "Tätigkeit und Objekt.",
|
||||
"text": "Nach welchem Namensschema sind PowerShell-Cmdlets aufgebaut?",
|
||||
"answers": [
|
||||
"Nomen-Verb, zum Beispiel Service-Get",
|
||||
"Verb-Nomen, zum Beispiel Get-Service",
|
||||
"nur ein Verb, zum Beispiel Get"
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "PowerShell-Cmdlets folgen dem Muster Verb-Nomen (Verb-Noun) wie Get-Service oder Stop-Process und nutzen anerkannte Verben wie Get, Set oder New. Die Reihenfolge Nomen-Verb ist falsch, und ein Cmdlet besteht nie nur aus dem Verb allein."
|
||||
},
|
||||
{
|
||||
"category": "PowerShell",
|
||||
"hint": "Objekte statt Text.",
|
||||
"text": "Worin unterscheidet sich die PowerShell-Pipeline grundlegend von einer klassischen Bash-Pipe?",
|
||||
"answers": [
|
||||
"PowerShell übergibt ausschließlich reinen Text Zeile für Zeile.",
|
||||
"PowerShell kann gar keine Kommandos verketten.",
|
||||
"PowerShell reicht strukturierte Objekte mit Eigenschaften weiter statt reinen Text."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "In der PowerShell-Pipeline fließen vollständige .NET-Objekte mit Eigenschaften und Methoden, sodass nachfolgende Cmdlets gezielt auf einzelne Felder zugreifen können. Die reine Textübergabe Zeile für Zeile ist typisch für Bash, und Verkettung ist in PowerShell selbstverständlich möglich."
|
||||
},
|
||||
{
|
||||
"category": "PowerShell",
|
||||
"hint": "Nach Bedingung filtern.",
|
||||
"text": "Mit welchem Cmdlet filterst du in einer PowerShell-Pipeline Objekte nach einer Bedingung, etwa nur laufende Dienste?",
|
||||
"answers": [
|
||||
"Where-Object",
|
||||
"ForEach-Object",
|
||||
"Select-Object"
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Where-Object filtert die Objekte anhand einer Bedingung und lässt nur die passenden passieren, etwa Where-Object Status -eq 'Running'. ForEach-Object führt für jedes Objekt eine Aktion aus, und Select-Object wählt Eigenschaften aus oder begrenzt die Anzahl, prüft aber keine Bedingung."
|
||||
},
|
||||
{
|
||||
"category": "PowerShell",
|
||||
"hint": "Eigenschaften auswählen.",
|
||||
"text": "Was bewirkt Get-Process | Select-Object Name, CPU in PowerShell?",
|
||||
"answers": [
|
||||
"Es beendet die Prozesse mit den Namen Name und CPU.",
|
||||
"Es gibt von jedem Prozess nur die Eigenschaften Name und CPU aus.",
|
||||
"Es sortiert die Prozesse nach Name und CPU."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Select-Object mit einer Eigenschaftsliste reduziert die Ausgabe auf genau diese Felder jedes Objekts, hier Name und CPU. Das Sortieren übernimmt Sort-Object, und zum Beenden von Prozessen dient Stop-Process."
|
||||
},
|
||||
{
|
||||
"category": "Kontrollstrukturen & Pseudocode",
|
||||
"hint": "Grenzen der Zählschleife durchgehen.",
|
||||
"text": "Wie oft wird die Schleife for (i = 1; i <= 5; i = i + 1) durchlaufen?",
|
||||
"answers": [
|
||||
"unendlich oft",
|
||||
"4-mal",
|
||||
"5-mal"
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Die Zählschleife startet bei 1 und läuft, solange i kleiner oder gleich 5 ist, also für die Werte 1, 2, 3, 4 und 5 – das sind fünf Durchläufe. Weil i bei jedem Schritt erhöht wird, endet die Schleife und läuft nicht unendlich; vier Durchläufe ergäben sich erst bei der Bedingung i < 5."
|
||||
},
|
||||
{
|
||||
"category": "Kontrollstrukturen & Pseudocode",
|
||||
"hint": "Wann wird die Bedingung geprüft?",
|
||||
"text": "Was ist charakteristisch für eine kopfgesteuerte while-Schleife?",
|
||||
"answers": [
|
||||
"Die Bedingung wird vor jedem Durchlauf geprüft, daher kann der Rumpf auch gar nicht ausgeführt werden.",
|
||||
"Der Rumpf wird immer mindestens einmal ausgeführt, weil die Bedingung erst am Ende geprüft wird.",
|
||||
"Sie zählt automatisch eine Schleifenvariable hoch."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Bei der kopfgesteuerten while-Schleife steht die Bedingungsprüfung am Anfang; ist sie schon zu Beginn falsch, wird der Rumpf keinmal durchlaufen. Die garantierte mindestens einmalige Ausführung ist Merkmal der fußgesteuerten do-while-Schleife, und ein automatisches Hochzählen leistet nur eine Zählschleife wie for."
|
||||
},
|
||||
{
|
||||
"category": "Kontrollstrukturen & Pseudocode",
|
||||
"hint": "Fallunterscheidung, keine Wiederholung.",
|
||||
"text": "Welche Kontrollstruktur wählst du im Pseudocode, um abhängig vom Wert einer Variablen zwischen mehreren Fällen zu verzweigen?",
|
||||
"answers": [
|
||||
"eine while-Schleife",
|
||||
"eine if-elseif-else-Verzweigung",
|
||||
"eine for-Schleife"
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Fallunterscheidungen bildest du mit if-elseif-else (oder alternativ einer switch-Anweisung) ab, die je nach Bedingung genau einen Zweig ausführt. Schleifen wie while oder for dienen dem Wiederholen von Anweisungen, nicht der einmaligen Verzweigung."
|
||||
},
|
||||
{
|
||||
"category": "Kontrollstrukturen & Pseudocode",
|
||||
"hint": "Ist die Anzahl bekannt?",
|
||||
"text": "Wann setzt du sinnvollerweise eine Zählschleife (for) statt einer while-Schleife ein?",
|
||||
"answers": [
|
||||
"Wenn die Anzahl der Durchläufe von vornherein bekannt ist.",
|
||||
"Wenn die Schleife auf jeden Fall unendlich laufen soll.",
|
||||
"Wenn die Bedingung niemals geprüft werden darf."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Die for-Schleife eignet sich, wenn die Zahl der Wiederholungen vorab feststeht, etwa für jedes Element von 1 bis n. Eine while-Schleife passt besser, wenn nur eine Abbruchbedingung, aber keine feste Anzahl bekannt ist; eine bewusst endlose Schleife oder gar keine Prüfung ist kein Auswahlkriterium für for."
|
||||
},
|
||||
{
|
||||
"category": "Automatisierung",
|
||||
"hint": "Fünf Felder vor dem Befehl.",
|
||||
"text": "In welcher Reihenfolge stehen die fünf Zeitfelder eines klassischen Cron-Eintrags?",
|
||||
"answers": [
|
||||
"Stunde, Minute, Monat, Tag, Wochentag",
|
||||
"Tag, Monat, Wochentag, Stunde, Minute",
|
||||
"Minute, Stunde, Tag des Monats, Monat, Wochentag"
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Ein Cron-Eintrag ordnet die Felder als Minute, Stunde, Tag des Monats, Monat und Wochentag, gefolgt vom auszuführenden Befehl. Die anderen Reihenfolgen vertauschen die Felder und würden zu völlig falschen Ausführungszeiten führen."
|
||||
},
|
||||
{
|
||||
"category": "Automatisierung",
|
||||
"hint": "Minute und Stunde lesen.",
|
||||
"text": "Zu welcher Zeit führt der Cron-Ausdruck 30 2 * * * ein Backup-Skript aus?",
|
||||
"answers": [
|
||||
"alle 30 Sekunden rund um die Uhr",
|
||||
"täglich um 02:30 Uhr",
|
||||
"jeden 30. Tag des Monats um 2 Uhr"
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Die Felder bedeuten Minute 30 und Stunde 2, während die drei Sterne für jeden Tag, jeden Monat und jeden Wochentag stehen – das Skript läuft also täglich um 02:30 Uhr. Cron kennt keine Sekunden, und der Tag des Monats steht hier auf *, nicht auf 30."
|
||||
},
|
||||
{
|
||||
"category": "Automatisierung",
|
||||
"hint": "Windows-Bordmittel.",
|
||||
"text": "Womit richtest du unter Windows eine zeitgesteuerte Aufgabe ein, die auch ohne angemeldeten Benutzer läuft?",
|
||||
"answers": [
|
||||
"mit der Aufgabenplanung (Taskplaner) bzw. dem Kommando schtasks",
|
||||
"mit dem crontab-Editor",
|
||||
"mit dem Kommando systemctl für einen systemd-Timer"
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Unter Windows übernimmt die Aufgabenplanung (Task Scheduler), bedienbar per GUI oder dem Kommando schtasks, das zeitgesteuerte Ausführen von Aufgaben auch ohne angemeldeten Benutzer. crontab gehört zu Unix/Linux, und systemd-Timer über systemctl sind ebenfalls eine Linux-Lösung."
|
||||
},
|
||||
{
|
||||
"category": "Automatisierung",
|
||||
"hint": "Schrittwert im Minutenfeld.",
|
||||
"text": "Wie gibst du in Cron an, dass ein Skript alle 15 Minuten laufen soll?",
|
||||
"answers": [
|
||||
"15 * * * *",
|
||||
"* 15 * * *",
|
||||
"*/15 * * * *"
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Der Schrittwert */15 im Minutenfeld startet die Ausführung zu den Minuten 0, 15, 30 und 45 jeder Stunde. Der Ausdruck 15 * * * * würde nur einmal pro Stunde zur Minute 15 laufen, und * 15 * * * bezöge sich auf die Stunde 15, also 15 Uhr."
|
||||
},
|
||||
{
|
||||
"category": "Exit-Codes & Fehlerbehandlung",
|
||||
"hint": "Erfolg hat einen festen Wert.",
|
||||
"text": "Welchen Exit-Code (Rückgabewert) liefert ein Programm üblicherweise, wenn es fehlerfrei durchgelaufen ist?",
|
||||
"answers": [
|
||||
"0",
|
||||
"1",
|
||||
"-1"
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Nach Konvention steht der Exit-Code 0 für einen erfolgreichen Ablauf, während jeder Wert ungleich 0 einen Fehler oder eine besondere Bedingung signalisiert. Der Wert 1 ist ein typischer allgemeiner Fehlercode, und negative Werte sind als reguläre Exit-Codes (Bereich 0 bis 255) nicht vorgesehen."
|
||||
},
|
||||
{
|
||||
"category": "Exit-Codes & Fehlerbehandlung",
|
||||
"hint": "Rückgabewert des letzten Kommandos.",
|
||||
"text": "Was liest du in Bash mit der Variablen $? aus?",
|
||||
"answers": [
|
||||
"den Namen des zuletzt ausgeführten Kommandos",
|
||||
"den Exit-Code des zuletzt beendeten Kommandos",
|
||||
"die Prozess-ID der aktuellen Shell"
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "$? enthält den Exit-Code des zuletzt beendeten Kommandos und erlaubt es, dessen Erfolg unmittelbar danach zu prüfen. Die Prozess-ID der aktuellen Shell steht in $$, und den Kommandonamen liefert $? nicht."
|
||||
},
|
||||
{
|
||||
"category": "Exit-Codes & Fehlerbehandlung",
|
||||
"hint": "Deskriptor 2.",
|
||||
"text": "Wie leitest du in Bash nur die Fehlermeldungen (Standardfehlerausgabe) eines Kommandos in die Datei fehler.log um?",
|
||||
"answers": [
|
||||
"kommando > fehler.log",
|
||||
"kommando 1> fehler.log",
|
||||
"kommando 2> fehler.log"
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Der Standardfehlerkanal (stderr) hat den Dateideskriptor 2, daher schreibt 2> ausschließlich die Fehlermeldungen in die Datei. Ein einfaches > oder 1> bezieht sich auf die Standardausgabe (stdout, Deskriptor 1) und lässt Fehlermeldungen weiterhin auf dem Bildschirm erscheinen."
|
||||
},
|
||||
{
|
||||
"category": "Exit-Codes & Fehlerbehandlung",
|
||||
"hint": "Fehler abfangen.",
|
||||
"text": "Mit welchem Mittel behandelst du in einem PowerShell-Skript Laufzeitfehler gezielt, um bei einem Problem eigene Schritte auszuführen?",
|
||||
"answers": [
|
||||
"mit dem Kommentarzeichen #",
|
||||
"mit einem try-catch-Block",
|
||||
"mit dem Cmdlet Write-Host"
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Ein try-catch-Block fängt auftretende (terminierende) Fehler ab und führt im catch-Zweig eine eigene Fehlerbehandlung aus. Das Zeichen # leitet nur einen Kommentar ein, und Write-Host gibt lediglich Text aus, ohne Fehler abzufangen."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"title": "Virtualisierung",
|
||||
"questions": [
|
||||
{
|
||||
"category": "Hypervisoren",
|
||||
"hint": "Läuft direkt auf der Hardware?",
|
||||
"text": "Was kennzeichnet einen Typ-1-Hypervisor?",
|
||||
"answers": [
|
||||
"Er läuft direkt auf der Hardware (Bare-Metal), ohne ein darunterliegendes Host-Betriebssystem.",
|
||||
"Er wird als gewöhnliche Anwendung innerhalb eines vorhandenen Host-Betriebssystems installiert.",
|
||||
"Er kann ausschließlich Linux-Gastsysteme betreiben."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Ein Typ-1-Hypervisor setzt direkt auf der Hardware auf und verwaltet die VMs selbst. Auf einem Host-Betriebssystem aufzusetzen ist das Merkmal eines Typ-2-Hypervisors, und die möglichen Gastsysteme sind unabhängig vom Hypervisor-Typ."
|
||||
},
|
||||
{
|
||||
"category": "Hypervisoren",
|
||||
"hint": "Welches Produkt braucht ein Host-Betriebssystem?",
|
||||
"text": "Welches Produkt ist ein typischer Typ-2-Hypervisor?",
|
||||
"answers": [
|
||||
"VMware ESXi",
|
||||
"Oracle VirtualBox",
|
||||
"Microsoft Hyper-V als Server-Rolle"
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "VirtualBox wird als Anwendung auf einem bestehenden Betriebssystem installiert und ist damit ein Typ-2-Hypervisor. ESXi und Hyper-V arbeiten dagegen als Bare-Metal-Lösungen direkt auf der Hardware und zählen zu Typ 1."
|
||||
},
|
||||
{
|
||||
"category": "Hypervisoren",
|
||||
"hint": "Denk an Overhead im Rechenzentrum.",
|
||||
"text": "Warum werden im Rechenzentrum meist Typ-1-Hypervisoren eingesetzt?",
|
||||
"answers": [
|
||||
"Weil sie eine vollständige grafische Desktop-Oberfläche für die Nutzer mitbringen.",
|
||||
"Weil sie im Gegensatz zu Typ-2 keine Hardwaretreiber benötigen.",
|
||||
"Weil sie ohne Umweg über ein Host-Betriebssystem geringeren Overhead und dadurch bessere Performance und Stabilität bieten."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Ohne dazwischenliegendes Host-OS entfällt eine ganze Software-Schicht, was Overhead senkt und die Leistung erhöht. Treiber für die Hardware braucht auch ein Typ-1-Hypervisor, und eine Desktop-Oberfläche ist im Serverbetrieb kein Vorteil."
|
||||
},
|
||||
{
|
||||
"category": "Hypervisoren",
|
||||
"hint": "Aufgabe des Virtual Machine Monitors.",
|
||||
"text": "Welche Kernaufgabe erfüllt ein Hypervisor (Virtual Machine Monitor)?",
|
||||
"answers": [
|
||||
"Er teilt die physischen Ressourcen wie CPU, RAM und I/O auf mehrere virtuelle Maschinen auf und isoliert diese voneinander.",
|
||||
"Er ersetzt die physische CPU vollständig durch eine reine Software-Emulation für jede VM.",
|
||||
"Er verschmilzt mehrere physische Server zu einer einzigen großen CPU."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Der Hypervisor verwaltet die Hardware und weist den VMs isolierte Anteile der Ressourcen zu. Moderne Virtualisierung nutzt Hardware-Unterstützung statt reiner Emulation, und das Zusammenfassen mehrerer Server zu einem System beschreibt eher Clustering, nicht die Aufgabe eines Hypervisors."
|
||||
},
|
||||
{
|
||||
"category": "Container vs. VM",
|
||||
"hint": "Wer bringt einen eigenen Kernel mit?",
|
||||
"text": "Worin liegt der grundlegende Unterschied zwischen einem Container und einer virtuellen Maschine?",
|
||||
"answers": [
|
||||
"Container bringen jeweils ein vollständiges eigenes Gast-Betriebssystem samt Kernel mit.",
|
||||
"Container teilen sich den Kernel des Host-Betriebssystems, während jede VM ein eigenes Betriebssystem samt Kernel betreibt.",
|
||||
"VMs sind grundsätzlich unsicherer als Container, weil sie ohne Hypervisor laufen."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Container nutzen gemeinsam den Kernel des Hosts und kapseln nur die Anwendung samt Abhängigkeiten, während eine VM ein komplettes eigenes Betriebssystem fährt. Ein eigenes Gast-OS haben Container gerade nicht, und VMs laufen sehr wohl auf einem Hypervisor."
|
||||
},
|
||||
{
|
||||
"category": "Container vs. VM",
|
||||
"hint": "Vorlage oder laufende Instanz?",
|
||||
"text": "Wie unterscheiden sich ein Docker-Image und ein Docker-Container?",
|
||||
"answers": [
|
||||
"Das Image ist die laufende Instanz, der Container ist die unveränderliche Vorlage.",
|
||||
"Image und Container bezeichnen denselben Zustand mit zwei Namen.",
|
||||
"Das Image ist die unveränderliche, schichtweise aufgebaute Vorlage; der Container ist eine daraus gestartete, laufende Instanz mit eigener beschreibbarer Schicht."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Ein Image ist ein schreibgeschütztes, aus Layern bestehendes Abbild, aus dem zur Laufzeit ein Container mit eigener beschreibbarer Schicht entsteht. Die Rollen von Vorlage und Instanz werden dabei gern vertauscht, und beide sind keineswegs derselbe Zustand."
|
||||
},
|
||||
{
|
||||
"category": "Container vs. VM",
|
||||
"hint": "Was muss beim Start nicht booten?",
|
||||
"text": "Warum starten Container in der Regel schneller und verbrauchen weniger Ressourcen als VMs?",
|
||||
"answers": [
|
||||
"Weil sie keinen eigenen Kernel und kein komplettes Betriebssystem booten müssen, sondern den Host-Kernel mitnutzen.",
|
||||
"Weil sie ihre Daten grundsätzlich nur flüchtig im Arbeitsspeicher halten.",
|
||||
"Weil ein Container immer nur einen einzigen CPU-Kern belegen darf."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Da kein vollständiges Betriebssystem hochgefahren wird, entfällt der Boot-Vorgang und der RAM-Bedarf sinkt. Container können sehr wohl persistente Daten über Volumes ablegen, und sie sind nicht auf einen einzelnen CPU-Kern beschränkt."
|
||||
},
|
||||
{
|
||||
"category": "Container vs. VM",
|
||||
"hint": "Gemeinsamer Kernel als Angriffsfläche.",
|
||||
"text": "Wie unterscheidet sich die Isolation von Containern und VMs sicherheitstechnisch?",
|
||||
"answers": [
|
||||
"Container isolieren stärker, weil jeder Container einen eigenen Hardware-Ring der CPU belegt.",
|
||||
"VMs bieten durch die Hardware-Virtualisierung eine stärkere Isolation, während Container sich einen gemeinsamen Kernel teilen und dort eine größere Angriffsfläche haben.",
|
||||
"Zwischen Containern und VMs besteht sicherheitstechnisch kein Unterschied."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Jede VM ist durch den Hypervisor auf Hardwareebene abgeschottet, während ein Fehler im gemeinsam genutzten Kernel bei Containern zu einem Ausbruch führen kann. Container belegen keine eigenen CPU-Ringe, und der Isolationsgrad unterscheidet sich deutlich."
|
||||
},
|
||||
{
|
||||
"category": "Snapshots & Klone",
|
||||
"hint": "Zustand zu einem Zeitpunkt.",
|
||||
"text": "Was ist ein Snapshot einer virtuellen Maschine?",
|
||||
"answers": [
|
||||
"Eine dauerhafte Vollsicherung, die ein reguläres Backup ersetzt.",
|
||||
"Eine zweite, eigenständige VM mit eigener Identität und eigener MAC-Adresse.",
|
||||
"Ein festgehaltener Zustand der VM zu einem bestimmten Zeitpunkt, zu dem man später zurückkehren kann."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Ein Snapshot friert den Zustand einer VM ein, sodass man Änderungen rückgängig machen kann. Er ersetzt kein Backup, weil er meist als Delta auf demselben Speicher liegt, und er erzeugt auch keine neue, eigenständige VM."
|
||||
},
|
||||
{
|
||||
"category": "Snapshots & Klone",
|
||||
"hint": "Wo liegen die Delta-Dateien?",
|
||||
"text": "Warum darf ein Snapshot nicht als vollwertiges Backup betrachtet werden?",
|
||||
"answers": [
|
||||
"Weil ein Snapshot meist als Delta auf demselben Storage wie die VM liegt und bei einem Ausfall dieses Speichers mit verloren geht.",
|
||||
"Weil ein Snapshot die VM dauerhaft anhält und erst durch ein Backup wieder startet.",
|
||||
"Weil ein Snapshot ausschließlich den Arbeitsspeicher, aber niemals den Festplattenzustand erfasst."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Ein Snapshot schützt nicht vor Storage-Ausfall oder -Zerstörung, weil er auf demselben Datenträger abgelegt wird. Die VM läuft während eines Snapshots normal weiter, und der Festplattenzustand wird gerade festgehalten, der RAM-Zustand nur optional."
|
||||
},
|
||||
{
|
||||
"category": "Snapshots & Klone",
|
||||
"hint": "Geteilte Basisplatten sparen Platz.",
|
||||
"text": "Worin unterscheidet sich ein Linked Clone von einem Full Clone?",
|
||||
"answers": [
|
||||
"Ein Linked Clone ist vollständig unabhängig vom Ausgangs-Image und belegt den vollen Speicher.",
|
||||
"Ein Linked Clone teilt sich die Basisplatten mit einem Snapshot des Originals und spart dadurch Speicher, bleibt aber von diesem abhängig.",
|
||||
"Ein Full Clone benötigt weniger Speicher als ein Linked Clone."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Ein Linked Clone verweist auf die gemeinsame Basisplatte des Originals und schreibt nur Änderungen in eigene Delta-Dateien, was Platz spart, aber eine Abhängigkeit schafft. Vollständig unabhängig und dafür speicherintensiver ist der Full Clone."
|
||||
},
|
||||
{
|
||||
"category": "Snapshots & Klone",
|
||||
"hint": "Was passiert mit den Delta-Dateien über die Zeit?",
|
||||
"text": "Welche Gefahr besteht, wenn Snapshots über lange Zeit bestehen bleiben?",
|
||||
"answers": [
|
||||
"Die VM wird mit jedem Tag automatisch schneller.",
|
||||
"Der Snapshot wandelt sich nach 24 Stunden selbstständig in ein Backup um.",
|
||||
"Die Delta-Dateien wachsen fortlaufend, belegen immer mehr Speicher und können die Leistung der VM spürbar mindern."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Solange ein Snapshot besteht, sammeln sich alle Änderungen in wachsenden Delta-Dateien, was Speicher frisst und I/O-Leistung kostet. Deshalb sollte man Snapshots nur kurz halten. Ein Snapshot wird weder von selbst zum Backup noch beschleunigt er die VM."
|
||||
},
|
||||
{
|
||||
"category": "Cloud-Servicemodelle",
|
||||
"hint": "Nackte Infrastruktur zum Selbstverwalten.",
|
||||
"text": "Was stellt der Anbieter beim Servicemodell IaaS (Infrastructure as a Service) bereit?",
|
||||
"answers": [
|
||||
"Virtuelle Infrastruktur wie Rechenleistung, Speicher und Netzwerk; Betriebssystem und Anwendungen verwaltest du selbst.",
|
||||
"Eine fertige Endanwendung, die du nur noch im Browser nutzt.",
|
||||
"Eine Entwicklungsplattform, bei der du dich weder um Laufzeitumgebung noch um Middleware kümmerst."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "IaaS liefert die virtualisierte Basis-Infrastruktur, während Betriebssystem, Middleware und Anwendungen in deiner Verantwortung bleiben. Eine fertige Anwendung beschreibt SaaS, eine bereitgestellte Plattform mit Laufzeitumgebung beschreibt PaaS."
|
||||
},
|
||||
{
|
||||
"category": "Cloud-Servicemodelle",
|
||||
"hint": "Fertige Anwendung aus dem Browser.",
|
||||
"text": "Wie verteilt sich die Verantwortung bei SaaS (Software as a Service)?",
|
||||
"answers": [
|
||||
"Du bist für das Einspielen der Betriebssystem-Patches auf den Servern des Anbieters verantwortlich.",
|
||||
"Du nutzt eine fertige Anwendung, während der Anbieter die gesamte darunterliegende Infrastruktur samt Betriebssystem und Anwendung betreibt und wartet.",
|
||||
"Du setzt eigene virtuelle Maschinen auf und administrierst sie vollständig selbst."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Bei SaaS betreibt der Anbieter die komplette Kette von Hardware über Betriebssystem bis zur Anwendung, du nutzt nur den Dienst. Patches des OS und der Betrieb eigener VMs fallen in andere Modelle wie IaaS und sind hier nicht deine Aufgabe."
|
||||
},
|
||||
{
|
||||
"category": "Cloud-Servicemodelle",
|
||||
"hint": "Denk an Shared Responsibility.",
|
||||
"text": "Wer trägt bei den Cloud-Servicemodellen die Verantwortung für die eigenen Daten und die Zugriffsberechtigungen?",
|
||||
"answers": [
|
||||
"In jedem Modell trägt allein der Anbieter die Verantwortung für die Daten und deren Zugriffsrechte.",
|
||||
"Die Verantwortung für Daten wechselt mit jedem Software-Update automatisch zum Anbieter.",
|
||||
"Für die eigenen Daten und die Verwaltung der Zugriffsberechtigungen bleibt in allen Modellen der Kunde verantwortlich (Shared Responsibility)."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Nach dem Modell der geteilten Verantwortung sichert der Anbieter die Infrastruktur, doch die Hoheit über Daten und Identitäten bleibt beim Kunden. Diese Verantwortung geht weder pauschal auf den Anbieter über noch wechselt sie durch Updates."
|
||||
},
|
||||
{
|
||||
"category": "Cloud-Servicemodelle",
|
||||
"hint": "Plattform, auf der nur dein Code fehlt.",
|
||||
"text": "Was ist charakteristisch für das Servicemodell PaaS (Platform as a Service)?",
|
||||
"answers": [
|
||||
"Der Anbieter stellt eine Plattform mit Laufzeitumgebung, Middleware und Datenbank bereit; du bringst nur deinen Anwendungscode ein.",
|
||||
"Du erhältst nackte virtuelle Maschinen und installierst Betriebssystem und Middleware selbst.",
|
||||
"Du erhältst nur eine fertige Endanwendung ohne jede Möglichkeit, eigenen Code auszuführen."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "PaaS nimmt dir Betrieb und Wartung der Plattform ab, sodass du dich auf die Entwicklung deiner Anwendung konzentrierst. Nackte VMs sind IaaS, eine reine Endanwendung ohne eigenen Code ist SaaS."
|
||||
},
|
||||
{
|
||||
"category": "Speicher & Ressourcen",
|
||||
"hint": "Wann wird der Speicher physisch belegt?",
|
||||
"text": "Wie unterscheiden sich Thin Provisioning und Thick Provisioning bei virtuellen Festplatten?",
|
||||
"answers": [
|
||||
"Thin Provisioning reserviert sofort den vollen konfigurierten Speicher auf dem Datenträger.",
|
||||
"Thin Provisioning belegt physischen Speicher erst bei tatsächlichem Bedarf, während Thick Provisioning die volle Kapazität von Anfang an reserviert.",
|
||||
"Thick Provisioning wächst dynamisch mit den geschriebenen Daten mit."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Thin belegt nur den wirklich genutzten Speicher und wächst mit den Daten, Thick reserviert die volle Größe sofort. Die Beschreibungen dürfen nicht vertauscht werden: das dynamische Wachstum gehört zu Thin, nicht zu Thick."
|
||||
},
|
||||
{
|
||||
"category": "Speicher & Ressourcen",
|
||||
"hint": "Summe der Zuweisungen über das Physische hinaus.",
|
||||
"text": "Was bedeutet Ressourcen-Overcommitment in einer Virtualisierungsumgebung und welches Risiko birgt es?",
|
||||
"answers": [
|
||||
"Overcommitment ist unmöglich, weil ein Hypervisor nie mehr Ressourcen zuweisen kann, als physisch vorhanden sind.",
|
||||
"Overcommitment verdoppelt die physisch vorhandene Hardware automatisch.",
|
||||
"Du weist den VMs in Summe mehr Ressourcen zu, als physisch vorhanden sind; bei gleichzeitiger Spitzenlast drohen Engpässe und Leistungseinbrüche, etwa durch Swapping."
|
||||
],
|
||||
"correct": 2,
|
||||
"explanation": "Weil selten alle VMs gleichzeitig ihre volle Zuweisung nutzen, kann man mehr zuteilen als vorhanden ist, riskiert aber Engpässe bei gleichzeitiger Spitzenlast. Der Hypervisor kann sehr wohl überbuchen, und physische Hardware entsteht dadurch nicht."
|
||||
},
|
||||
{
|
||||
"category": "Speicher & Ressourcen",
|
||||
"hint": "Laufende VM ohne Downtime verschieben.",
|
||||
"text": "Was versteht man unter einer Live-Migration einer virtuellen Maschine?",
|
||||
"answers": [
|
||||
"Das Verschieben einer laufenden VM von einem Host auf einen anderen, ohne sie herunterzufahren (nahezu unterbrechungsfrei).",
|
||||
"Das dauerhafte Löschen einer VM, um deren Speicher freizugeben.",
|
||||
"Das Umwandeln einer laufenden VM in einen Container zur Laufzeit."
|
||||
],
|
||||
"correct": 0,
|
||||
"explanation": "Bei der Live-Migration wechselt eine aktive VM den Host, während sie weiterläuft, etwa für Wartungsarbeiten am Original-Host. Ein Löschen der VM oder eine Umwandlung in einen Container hat damit nichts zu tun."
|
||||
},
|
||||
{
|
||||
"category": "Speicher & Ressourcen",
|
||||
"hint": "Wo liegen die Plattendateien der VM?",
|
||||
"text": "Welche Voraussetzung muss für eine klassische Live-Migration zwischen zwei Hosts in der Regel erfüllt sein?",
|
||||
"answers": [
|
||||
"Die beiden Hosts müssen zwingend unterschiedliche CPU-Architekturen besitzen.",
|
||||
"Beide Hosts brauchen üblicherweise Zugriff auf denselben gemeinsamen Speicher (Shared Storage) und ein gemeinsames Netzsegment.",
|
||||
"Die VM muss vor der Migration gelöscht und auf dem Zielhost neu erstellt werden."
|
||||
],
|
||||
"correct": 1,
|
||||
"explanation": "Über Shared Storage bleibt die virtuelle Platte am selben Ort erreichbar, sodass nur der Arbeitsspeicher-Zustand übertragen werden muss; ein gemeinsames Netz sichert die Erreichbarkeit. Die CPUs müssen kompatibel sein statt verschieden, und die VM wird gerade nicht gelöscht."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -7,69 +7,69 @@
|
||||
"text": "Wofür steht die Abkürzung USV?",
|
||||
"answers": ["Unterbrechungsfreie Stromversorgung", "Ungeregelte Spannungsverteilung", "Unabhängige Stromverteilung"],
|
||||
"correct": 0,
|
||||
"explanation": "USV steht fuer Unterbrechungsfreie Stromversorgung; im Englischen heisst sie UPS (Uninterruptible Power Supply)."
|
||||
"explanation": "USV steht für Unterbrechungsfreie Stromversorgung; im Englischen heißt sie UPS (Uninterruptible Power Supply)."
|
||||
},
|
||||
{
|
||||
"category": "USV-Grundlagen",
|
||||
"hint": "Verstehe Zweck, Grenzen und typische Einsatzbereiche einer USV.",
|
||||
"text": "Was ist ein Hauptzweck einer USV?",
|
||||
"answers": ["Die Netzspannung dauerhaft erhoehen", "Zeit fuer ein geordnetes Herunterfahren oder Umschalten schaffen", "Den Stromverbrauch der Server senken"],
|
||||
"answers": ["Die Netzspannung dauerhaft erhöhen", "Zeit für ein geordnetes Herunterfahren oder Umschalten schaffen", "Den Stromverbrauch der Server senken"],
|
||||
"correct": 1,
|
||||
"explanation": "Die USV ueberbrueckt kurze Ausfaelle und gibt Systemen Zeit, kontrolliert herunterzufahren oder auf eine andere Quelle umzuschalten."
|
||||
"explanation": "Die USV überbrückt kurze Ausfälle und gibt Systemen Zeit, kontrolliert herunterzufahren oder auf eine andere Quelle umzuschalten."
|
||||
},
|
||||
{
|
||||
"category": "USV-Grundlagen",
|
||||
"hint": "Verstehe Zweck, Grenzen und typische Einsatzbereiche einer USV.",
|
||||
"text": "Welche Stoerung kann eine USV typischerweise abfangen?",
|
||||
"answers": ["Eine fehlerhafte Netzwerkkonfiguration", "Eine ueberhitzte CPU im Server", "Einen kurzzeitigen Stromausfall und Spannungseinbrueche"],
|
||||
"text": "Welche Störung kann eine USV typischerweise abfangen?",
|
||||
"answers": ["Eine fehlerhafte Netzwerkkonfiguration", "Eine überhitzte CPU im Server", "Einen kurzzeitigen Stromausfall und Spannungseinbrüche"],
|
||||
"correct": 2,
|
||||
"explanation": "USV-Systeme schuetzen vor Stromausfaellen sowie - je nach Typ - vor Spannungsschwankungen, nicht aber vor IT- oder Hardwarefehlern."
|
||||
"explanation": "USV-Systeme schützen vor Stromausfällen sowie - je nach Typ - vor Spannungsschwankungen, nicht aber vor IT- oder Hardwarefehlern."
|
||||
},
|
||||
{
|
||||
"category": "USV-Grundlagen",
|
||||
"hint": "Verstehe Zweck, Grenzen und typische Einsatzbereiche einer USV.",
|
||||
"text": "Was kann eine normale USV nicht dauerhaft ersetzen?",
|
||||
"answers": ["Eine langfristige Stromversorgung durch Netz oder Notstromgenerator", "Eine Steckdosenleiste mit Ueberspannungsschutz", "Den Hauptverteiler im Serverraum"],
|
||||
"answers": ["Eine langfristige Stromversorgung durch Netz oder Notstromgenerator", "Eine Steckdosenleiste mit Überspannungsschutz", "Den Hauptverteiler im Serverraum"],
|
||||
"correct": 0,
|
||||
"explanation": "Eine Standard-USV ueberbrueckt meist nur Minuten; fuer laengere Ausfaelle ist ein Generator oder das Versorgungsnetz noetig."
|
||||
"explanation": "Eine Standard-USV überbrückt meist nur Minuten; für längere Ausfälle ist ein Generator oder das Versorgungsnetz nötig."
|
||||
},
|
||||
{
|
||||
"category": "USV-Grundlagen",
|
||||
"hint": "Verstehe Zweck, Grenzen und typische Einsatzbereiche einer USV.",
|
||||
"text": "Welche Systeme werden typischerweise an eine USV angeschlossen?",
|
||||
"answers": ["Drucker und Klimageraete im gesamten Gebaeude", "Server, Storage und aktive Netzwerkkomponenten", "Beleuchtung und Bueroarbeitsplaetze"],
|
||||
"answers": ["Drucker und Klimageräte im gesamten Gebäude", "Server, Storage und aktive Netzwerkkomponenten", "Beleuchtung und Büroarbeitsplätze"],
|
||||
"correct": 1,
|
||||
"explanation": "Kritische IT-Infrastruktur soll kontrolliert weiterlaufen oder herunterfahren; verbrauchsstarke, unkritische Lasten gehoeren nicht an die USV."
|
||||
"explanation": "Kritische IT-Infrastruktur soll kontrolliert weiterlaufen oder herunterfahren; verbrauchsstarke, unkritische Lasten gehören nicht an die USV."
|
||||
},
|
||||
{
|
||||
"category": "USV-Grundlagen",
|
||||
"hint": "Verstehe Zweck, Grenzen und typische Einsatzbereiche einer USV.",
|
||||
"text": "Was beschreibt die Autonomiezeit (Ueberbrueckungszeit) einer USV?",
|
||||
"answers": ["Die Zeit bis zum naechsten Batteriewechsel", "Die Ladedauer der Batterien nach einem Ausfall", "Die Zeit, die eine USV die angeschlossene Last aus der Batterie versorgen kann"],
|
||||
"text": "Was beschreibt die Autonomiezeit (Überbrückungszeit) einer USV?",
|
||||
"answers": ["Die Zeit bis zum nächsten Batteriewechsel", "Die Ladedauer der Batterien nach einem Ausfall", "Die Zeit, die eine USV die angeschlossene Last aus der Batterie versorgen kann"],
|
||||
"correct": 2,
|
||||
"explanation": "Die Autonomiezeit haengt von Batteriekapazitaet, angeschlossener Last und dem Alterungszustand der Batterien ab."
|
||||
"explanation": "Die Autonomiezeit hängt von Batteriekapazität, angeschlossener Last und dem Alterungszustand der Batterien ab."
|
||||
},
|
||||
{
|
||||
"category": "USV-Grundlagen",
|
||||
"hint": "Verstehe Zweck, Grenzen und typische Einsatzbereiche einer USV.",
|
||||
"text": "Warum ist eine USV fuer Datenbankserver besonders sinnvoll?",
|
||||
"answers": ["Sie reduziert das Risiko von Datenverlust durch abrupten Stromverlust", "Sie beschleunigt die Ausfuehrung von SQL-Abfragen", "Sie erstellt automatisch Transaktionsprotokolle"],
|
||||
"text": "Warum ist eine USV für Datenbankserver besonders sinnvoll?",
|
||||
"answers": ["Sie reduziert das Risiko von Datenverlust durch abrupten Stromverlust", "Sie beschleunigt die Ausführung von SQL-Abfragen", "Sie erstellt automatisch Transaktionsprotokolle"],
|
||||
"correct": 0,
|
||||
"explanation": "Abrupte Stromausfaelle koennen offene Transaktionen abbrechen und zu Inkonsistenzen oder Datenverlust fuehren."
|
||||
"explanation": "Abrupte Stromausfälle können offene Transaktionen abbrechen und zu Inkonsistenzen oder Datenverlust führen."
|
||||
},
|
||||
{
|
||||
"category": "USV-Grundlagen",
|
||||
"hint": "Verstehe Zweck, Grenzen und typische Einsatzbereiche einer USV.",
|
||||
"text": "Welche Aussage zur Schutzwirkung einer USV ist korrekt?",
|
||||
"answers": ["Eine USV ersetzt eine Backup-Strategie vollstaendig", "Eine USV schuetzt nicht vor jedem IT-Risiko und ist nur ein Baustein im Schutzkonzept", "Eine USV verhindert Angriffe ueber das Netzwerk"],
|
||||
"answers": ["Eine USV ersetzt eine Backup-Strategie vollständig", "Eine USV schützt nicht vor jedem IT-Risiko und ist nur ein Baustein im Schutzkonzept", "Eine USV verhindert Angriffe über das Netzwerk"],
|
||||
"correct": 1,
|
||||
"explanation": "Eine USV schuetzt gegen Stromprobleme, nicht gegen Datenverlust, Schadsoftware oder Bedienfehler - Backups und Security bleiben noetig."
|
||||
"explanation": "Eine USV schützt gegen Stromprobleme, nicht gegen Datenverlust, Schadsoftware oder Bedienfehler - Backups und Security bleiben nötig."
|
||||
},
|
||||
{
|
||||
"category": "USV-Grundlagen",
|
||||
"hint": "Verstehe Zweck, Grenzen und typische Einsatzbereiche einer USV.",
|
||||
"text": "Was sollte bei einem laengeren Stromausfall automatisch ausgeloest werden?",
|
||||
"answers": ["Ein hartes Trennen der Stromzufuhr zu allen Servern", "Ein Zuruecksetzen der Netzwerkkonfiguration", "Ein geordnetes Herunterfahren der Server vor Erschoepfung der Batterie"],
|
||||
"text": "Was sollte bei einem längeren Stromausfall automatisch ausgelöst werden?",
|
||||
"answers": ["Ein hartes Trennen der Stromzufuhr zu allen Servern", "Ein Zurücksetzen der Netzwerkkonfiguration", "Ein geordnetes Herunterfahren der Server vor Erschöpfung der Batterie"],
|
||||
"correct": 2,
|
||||
"explanation": "Eine Shutdown-Software fahrt Systeme rechtzeitig kontrolliert herunter, solange die Batterie noch Energie liefert."
|
||||
},
|
||||
@@ -77,7 +77,7 @@
|
||||
"category": "USV-Grundlagen",
|
||||
"hint": "Verstehe Zweck, Grenzen und typische Einsatzbereiche einer USV.",
|
||||
"text": "Welche drei USV-Klassen werden nach IEC 62040-3 unterschieden?",
|
||||
"answers": ["Offline/Standby (VFD), Line-Interactive (VI) und Online/Doppelwandler (VFI)", "Aktiv, Passiv und Hybrid", "Primaer-, Sekundaer- und Reserve-USV"],
|
||||
"answers": ["Offline/Standby (VFD), Line-Interactive (VI) und Online/Doppelwandler (VFI)", "Aktiv, Passiv und Hybrid", "Primär-, Sekundär- und Reserve-USV"],
|
||||
"correct": 0,
|
||||
"explanation": "VFD = Offline, VI = Line-Interactive (Voltage Independent), VFI = Online/Doppelwandler (Voltage and Frequency Independent)."
|
||||
},
|
||||
@@ -92,88 +92,88 @@
|
||||
{
|
||||
"category": "Leistung & Dimensionierung",
|
||||
"hint": "Dimensioniere die USV nach Typ, Last, Laufzeit und Reserve.",
|
||||
"text": "Welche USV-Klasse versorgt die Last per Doppelwandlung ohne Umschaltzeit und bietet den hoechsten Schutz?",
|
||||
"text": "Welche USV-Klasse versorgt die Last per Doppelwandlung ohne Umschaltzeit und bietet den höchsten Schutz?",
|
||||
"answers": ["Online-USV (VFI, Doppelwandler)", "Offline-/Standby-USV (VFD)", "Line-Interactive-USV (VI)"],
|
||||
"correct": 0,
|
||||
"explanation": "Bei der Online-USV erzeugt der Wechselrichter die Ausgangsspannung staendig neu; die Last ist gegen nahezu alle Netzstoerungen geschuetzt."
|
||||
"explanation": "Bei der Online-USV erzeugt der Wechselrichter die Ausgangsspannung ständig neu; die Last ist gegen nahezu alle Netzstörungen geschützt."
|
||||
},
|
||||
{
|
||||
"category": "Leistung & Dimensionierung",
|
||||
"hint": "Dimensioniere die USV nach Typ, Last, Laufzeit und Reserve.",
|
||||
"text": "Wodurch unterscheidet sich eine Line-Interactive-USV (VI) von einer einfachen Offline-USV?",
|
||||
"answers": ["Sie versorgt die Last grundsaetzlich ohne jede Umschaltzeit", "Sie gleicht Spannungsschwankungen ueber einen regelbaren Transformator (AVR) aus", "Sie kommt vollstaendig ohne Batterie aus"],
|
||||
"answers": ["Sie versorgt die Last grundsätzlich ohne jede Umschaltzeit", "Sie gleicht Spannungsschwankungen über einen regelbaren Transformator (AVR) aus", "Sie kommt vollständig ohne Batterie aus"],
|
||||
"correct": 1,
|
||||
"explanation": "Die Line-Interactive-USV regelt mit einem AVR kleinere Ueber- und Unterspannungen aus, ohne dafuer auf Batterie zu wechseln."
|
||||
"explanation": "Die Line-Interactive-USV regelt mit einem AVR kleinere Über- und Unterspannungen aus, ohne dafür auf Batterie zu wechseln."
|
||||
},
|
||||
{
|
||||
"category": "Leistung & Dimensionierung",
|
||||
"hint": "Dimensioniere die USV nach Typ, Last, Laufzeit und Reserve.",
|
||||
"text": "In welchen Einheiten wird die Leistung einer USV ueblicherweise angegeben?",
|
||||
"text": "In welchen Einheiten wird die Leistung einer USV üblicherweise angegeben?",
|
||||
"answers": ["In Voltampere (VA) und Watt (W)", "In Amperestunden (Ah) und Volt (V)", "In Kilowattstunden (kWh) pro Tag"],
|
||||
"correct": 0,
|
||||
"explanation": "VA gibt die Scheinleistung, Watt die Wirkleistung an; das Verhaeltnis beider ist der Leistungsfaktor der USV."
|
||||
"explanation": "VA gibt die Scheinleistung, Watt die Wirkleistung an; das Verhältnis beider ist der Leistungsfaktor der USV."
|
||||
},
|
||||
{
|
||||
"category": "Leistung & Dimensionierung",
|
||||
"hint": "Dimensioniere die USV nach Typ, Last, Laufzeit und Reserve.",
|
||||
"text": "Warum darf man eine USV nicht allein nach der VA-Angabe auswaehlen?",
|
||||
"text": "Warum darf man eine USV nicht allein nach der VA-Angabe auswählen?",
|
||||
"answers": ["VA und Watt sind immer identisch, der Unterschied ist irrelevant", "Die nutzbare Wirkleistung in Watt liegt wegen des Leistungsfaktors unter dem VA-Wert", "Die VA-Angabe bezieht sich nur auf die Batterieladung"],
|
||||
"correct": 1,
|
||||
"explanation": "Wegen des Leistungsfaktors (z. B. 0,8 oder 0,9) ist die in Watt nutzbare Leistung kleiner als der VA-Wert - beide Grenzen muessen passen."
|
||||
"explanation": "Wegen des Leistungsfaktors (z. B. 0,8 oder 0,9) ist die in Watt nutzbare Leistung kleiner als der VA-Wert - beide Grenzen müssen passen."
|
||||
},
|
||||
{
|
||||
"category": "Leistung & Dimensionierung",
|
||||
"hint": "Dimensioniere die USV nach Typ, Last, Laufzeit und Reserve.",
|
||||
"text": "Warum sollte bei der Dimensionierung eine Leistungsreserve eingeplant werden?",
|
||||
"answers": ["Damit die USV moeglichst nah an der Ueberlastgrenze betrieben wird", "Damit Batterietests komplett entfallen koennen", "Fuer Lastspitzen, Batteriealterung und spaetere Erweiterungen"],
|
||||
"answers": ["Damit die USV möglichst nah an der Überlastgrenze betrieben wird", "Damit Batterietests komplett entfallen können", "Für Lastspitzen, Batteriealterung und spätere Erweiterungen"],
|
||||
"correct": 2,
|
||||
"explanation": "Eine Reserve (oft rund 20-30 Prozent) faengt Einschaltspitzen, nachlassende Batteriekapazitaet und zusaetzliche Geraete ab."
|
||||
"explanation": "Eine Reserve (oft rund 20-30 Prozent) fängt Einschaltspitzen, nachlassende Batteriekapazität und zusätzliche Geräte ab."
|
||||
},
|
||||
{
|
||||
"category": "Leistung & Dimensionierung",
|
||||
"hint": "Dimensioniere die USV nach Typ, Last, Laufzeit und Reserve.",
|
||||
"text": "Was passiert typischerweise bei einer Ueberlastung der USV?",
|
||||
"answers": ["Sie schaltet ab oder geht in den Bypass und kann die Last nicht zuverlaessig schuetzen", "Sie erhoeht selbststaendig ihre VA-Leistung", "Sie verlaengert automatisch die Autonomiezeit"],
|
||||
"text": "Was passiert typischerweise bei einer Überlastung der USV?",
|
||||
"answers": ["Sie schaltet ab oder geht in den Bypass und kann die Last nicht zuverlässig schützen", "Sie erhöht selbstständig ihre VA-Leistung", "Sie verlängert automatisch die Autonomiezeit"],
|
||||
"correct": 0,
|
||||
"explanation": "Bei Ueberlast greift der Schutz: Die USV warnt, geht in den Bypass oder schaltet ab - die Schutzwirkung geht verloren."
|
||||
"explanation": "Bei Überlast greift der Schutz: Die USV warnt, geht in den Bypass oder schaltet ab - die Schutzwirkung geht verloren."
|
||||
},
|
||||
{
|
||||
"category": "Leistung & Dimensionierung",
|
||||
"hint": "Dimensioniere die USV nach Typ, Last, Laufzeit und Reserve.",
|
||||
"text": "Welcher Faktor beeinflusst die Autonomiezeit am staerksten?",
|
||||
"answers": ["Die Hoehe der angeschlossenen Last", "Die Laenge der verwendeten Stromkabel", "Die Anzahl der Steckdosen am Geraet"],
|
||||
"text": "Welcher Faktor beeinflusst die Autonomiezeit am stärksten?",
|
||||
"answers": ["Die Höhe der angeschlossenen Last", "Die Länge der verwendeten Stromkabel", "Die Anzahl der Steckdosen am Gerät"],
|
||||
"correct": 0,
|
||||
"explanation": "Je hoeher die Last, desto schneller wird die Batterie entladen und desto kuerzer ist die Ueberbrueckungszeit."
|
||||
"explanation": "Je höher die Last, desto schneller wird die Batterie entladen und desto kürzer ist die Überbrückungszeit."
|
||||
},
|
||||
{
|
||||
"category": "Leistung & Dimensionierung",
|
||||
"hint": "Dimensioniere die USV nach Typ, Last, Laufzeit und Reserve.",
|
||||
"text": "Was sollte vor dem Kauf einer USV neben der Leistung geprueft werden?",
|
||||
"answers": ["Die Gehaeusefarbe und das Gewicht", "Anzahl und Art der Anschluesse sowie die Kommunikationsschnittstelle", "Der Stromverbrauch der Bueroklimaanlage"],
|
||||
"text": "Was sollte vor dem Kauf einer USV neben der Leistung geprüft werden?",
|
||||
"answers": ["Die Gehäusefarbe und das Gewicht", "Anzahl und Art der Anschlüsse sowie die Kommunikationsschnittstelle", "Der Stromverbrauch der Büroklimaanlage"],
|
||||
"correct": 1,
|
||||
"explanation": "Anschlussart, genug Steckplaetze und eine Management-Schnittstelle (USB oder Netzwerk fuer die Shutdown-Software) muessen zur Infrastruktur passen."
|
||||
"explanation": "Anschlussart, genug Steckplätze und eine Management-Schnittstelle (USB oder Netzwerk für die Shutdown-Software) müssen zur Infrastruktur passen."
|
||||
},
|
||||
{
|
||||
"category": "Leistung & Dimensionierung",
|
||||
"hint": "Dimensioniere die USV nach Typ, Last, Laufzeit und Reserve.",
|
||||
"text": "Wie sollte die benoetigte Autonomiezeit geplant werden?",
|
||||
"answers": ["So kurz wie moeglich, um Batterien zu sparen", "Mindestens so lang wie die Shutdown-Zeit aller Systeme plus Reserve", "Genau gleich der Umschaltzeit der USV"],
|
||||
"text": "Wie sollte die benötigte Autonomiezeit geplant werden?",
|
||||
"answers": ["So kurz wie möglich, um Batterien zu sparen", "Mindestens so lang wie die Shutdown-Zeit aller Systeme plus Reserve", "Genau gleich der Umschaltzeit der USV"],
|
||||
"correct": 1,
|
||||
"explanation": "Die Batterie muss lange genug durchhalten, damit alle Systeme geordnet herunterfahren koennen, mit Puffer fuer Unwaegbarkeiten."
|
||||
"explanation": "Die Batterie muss lange genug durchhalten, damit alle Systeme geordnet herunterfahren können, mit Puffer für Unwägbarkeiten."
|
||||
},
|
||||
{
|
||||
"category": "Betrieb, Wartung & Praxis",
|
||||
"hint": "Plane Tests, Monitoring, Batterieaustausch und Reaktion auf Stromereignisse.",
|
||||
"text": "Warum sollte eine USV ueberwacht (Monitoring) werden?",
|
||||
"answers": ["Damit Batteriestatus, Last und Stoerungen rechtzeitig erkannt werden", "Damit die USV moeglichst unauffaellig bleibt", "Damit der Stromzaehler langsamer laeuft"],
|
||||
"text": "Warum sollte eine USV überwacht (Monitoring) werden?",
|
||||
"answers": ["Damit Batteriestatus, Last und Störungen rechtzeitig erkannt werden", "Damit die USV möglichst unauffällig bleibt", "Damit der Stromzähler langsamer läuft"],
|
||||
"correct": 0,
|
||||
"explanation": "Monitoring meldet schwache Batterien, Ueberlast oder Defekte, bevor sie im Ernstfall zum Ausfall fuehren."
|
||||
"explanation": "Monitoring meldet schwache Batterien, Überlast oder Defekte, bevor sie im Ernstfall zum Ausfall führen."
|
||||
},
|
||||
{
|
||||
"category": "Betrieb, Wartung & Praxis",
|
||||
"hint": "Plane Tests, Monitoring, Batterieaustausch und Reaktion auf Stromereignisse.",
|
||||
"text": "Warum sind regelmaessige USV-Tests wichtig?",
|
||||
"answers": ["Sie machen die Batterien dauerhaft haltbar", "Sie pruefen, ob Ueberbrueckung und automatischer Shutdown tatsaechlich funktionieren", "Sie ersetzen den planmaessigen Batterietausch"],
|
||||
"text": "Warum sind regelmäßige USV-Tests wichtig?",
|
||||
"answers": ["Sie machen die Batterien dauerhaft haltbar", "Sie prüfen, ob Überbrückung und automatischer Shutdown tatsächlich funktionieren", "Sie ersetzen den planmäßigen Batterietausch"],
|
||||
"correct": 1,
|
||||
"explanation": "Ein Test unter realen Bedingungen zeigt, ob das Konzept aus Autonomiezeit und Shutdown-Software in der Praxis greift."
|
||||
},
|
||||
@@ -181,25 +181,25 @@
|
||||
"category": "Betrieb, Wartung & Praxis",
|
||||
"hint": "Plane Tests, Monitoring, Batterieaustausch und Reaktion auf Stromereignisse.",
|
||||
"text": "Was ist beim Umgang mit USV-Batterien zu beachten?",
|
||||
"answers": ["Sie sind Verschleissteile und muessen nach Herstellerangaben geprueft und ersetzt werden", "Sie halten ueber die gesamte Lebensdauer der USV ohne Pruefung", "Sie laden sich bei Nichtnutzung selbst nach"],
|
||||
"answers": ["Sie sind Verschleissteile und müssen nach Herstellerangaben geprüft und ersetzt werden", "Sie halten über die gesamte Lebensdauer der USV ohne Prüfung", "Sie laden sich bei Nichtnutzung selbst nach"],
|
||||
"correct": 0,
|
||||
"explanation": "Batterien altern; nur regelmaessige Pruefung und rechtzeitiger Austausch sichern die geplante Ueberbrueckungszeit."
|
||||
"explanation": "Batterien altern; nur regelmäßige Prüfung und rechtzeitiger Austausch sichern die geplante Überbrückungszeit."
|
||||
},
|
||||
{
|
||||
"category": "Betrieb, Wartung & Praxis",
|
||||
"hint": "Plane Tests, Monitoring, Batterieaustausch und Reaktion auf Stromereignisse.",
|
||||
"text": "Wie sollte auf einen USV-Alarm reagiert werden?",
|
||||
"answers": ["Den Alarm quittieren und ignorieren, solange der Strom noch da ist", "Sofort alle Logdateien loeschen", "Die Ursache pruefen und bei Bedarf einen geordneten Shutdown einleiten"],
|
||||
"answers": ["Den Alarm quittieren und ignorieren, solange der Strom noch da ist", "Sofort alle Logdateien löschen", "Die Ursache prüfen und bei Bedarf einen geordneten Shutdown einleiten"],
|
||||
"correct": 2,
|
||||
"explanation": "Ein Alarm signalisiert Handlungsbedarf, etwa Netzausfall, Ueberlast oder schwache Batterie - er darf nicht ignoriert werden."
|
||||
"explanation": "Ein Alarm signalisiert Handlungsbedarf, etwa Netzausfall, Überlast oder schwache Batterie - er darf nicht ignoriert werden."
|
||||
},
|
||||
{
|
||||
"category": "Betrieb, Wartung & Praxis",
|
||||
"hint": "Plane Tests, Monitoring, Batterieaustausch und Reaktion auf Stromereignisse.",
|
||||
"text": "Warum ist es sinnvoll, auch Switches und Router an die USV anzuschliessen?",
|
||||
"answers": ["Damit die Internetgeschwindigkeit steigt", "Damit Server waehrend des Ausfalls erreichbar und steuerbar bleiben", "Damit die VLAN-Konfiguration erhalten bleibt"],
|
||||
"text": "Warum ist es sinnvoll, auch Switches und Router an die USV anzuschließen?",
|
||||
"answers": ["Damit die Internetgeschwindigkeit steigt", "Damit Server während des Ausfalls erreichbar und steuerbar bleiben", "Damit die VLAN-Konfiguration erhalten bleibt"],
|
||||
"correct": 1,
|
||||
"explanation": "Ohne aktives Netzwerk koennen Shutdown-Befehle und Monitoring waehrend eines Stromausfalls die Server nicht mehr erreichen."
|
||||
"explanation": "Ohne aktives Netzwerk können Shutdown-Befehle und Monitoring während eines Stromausfalls die Server nicht mehr erreichen."
|
||||
},
|
||||
{
|
||||
"category": "Betrieb, Wartung & Praxis",
|
||||
@@ -207,39 +207,39 @@
|
||||
"text": "Was kennzeichnet einen geordneten Shutdown bei Stromausfall?",
|
||||
"answers": ["Dienste und Betriebssysteme werden kontrolliert beendet, bevor die Batterie leer ist", "Die Stromzufuhr wird sofort hart getrennt", "Nur der Bildschirm wird abgeschaltet"],
|
||||
"correct": 0,
|
||||
"explanation": "Kontrolliertes Herunterfahren schreibt offene Daten zurueck und vermeidet Dateisystem- und Datenbankschaeden."
|
||||
"explanation": "Kontrolliertes Herunterfahren schreibt offene Daten zurück und vermeidet Dateisystem- und Datenbankschäden."
|
||||
},
|
||||
{
|
||||
"category": "Betrieb, Wartung & Praxis",
|
||||
"hint": "Plane Tests, Monitoring, Batterieaustausch und Reaktion auf Stromereignisse.",
|
||||
"text": "Was sollte nach einem Stromereignis geprueft werden?",
|
||||
"answers": ["Ausschliesslich, ob die Lampen wieder leuchten", "Nur, ob die USV piept", "Logs, Batteriestatus und der Zustand der angeschlossenen Systeme"],
|
||||
"text": "Was sollte nach einem Stromereignis geprüft werden?",
|
||||
"answers": ["Ausschließlich, ob die Lampen wieder leuchten", "Nur, ob die USV piept", "Logs, Batteriestatus und der Zustand der angeschlossenen Systeme"],
|
||||
"correct": 2,
|
||||
"explanation": "Eine Nachkontrolle deckt Folgeschaeden, eine teilentladene Batterie oder noetigen Wartungsbedarf auf."
|
||||
"explanation": "Eine Nachkontrolle deckt Folgeschäden, eine teilentladene Batterie oder nötigen Wartungsbedarf auf."
|
||||
},
|
||||
{
|
||||
"category": "Betrieb, Wartung & Praxis",
|
||||
"hint": "Plane Tests, Monitoring, Batterieaustausch und Reaktion auf Stromereignisse.",
|
||||
"text": "Welche Aussage zum Verhaeltnis von USV und Backup ist richtig?",
|
||||
"answers": ["Eine USV macht Backups ueberfluessig", "Eine USV ersetzt kein Backup, da beide gegen unterschiedliche Risiken schuetzen", "Backups verhindern Stromausfaelle"],
|
||||
"text": "Welche Aussage zum Verhältnis von USV und Backup ist richtig?",
|
||||
"answers": ["Eine USV macht Backups überflüssig", "Eine USV ersetzt kein Backup, da beide gegen unterschiedliche Risiken schützen", "Backups verhindern Stromausfälle"],
|
||||
"correct": 1,
|
||||
"explanation": "Die USV schuetzt vor Stromproblemen, das Backup vor Datenverlust - beide ergaenzen sich, ersetzen sich aber nicht."
|
||||
"explanation": "Die USV schützt vor Stromproblemen, das Backup vor Datenverlust - beide ergänzen sich, ersetzen sich aber nicht."
|
||||
},
|
||||
{
|
||||
"category": "Betrieb, Wartung & Praxis",
|
||||
"hint": "Plane Tests, Monitoring, Batterieaustausch und Reaktion auf Stromereignisse.",
|
||||
"text": "Welche Information ist fuer den Betrieb einer USV besonders wichtig?",
|
||||
"answers": ["Welche Systeme angeschlossen sind und wie kritisch sie sind", "Welcher Mitarbeiter sie zuletzt gesehen hat", "In welchem Jahr das Gehaeuse lackiert wurde"],
|
||||
"text": "Welche Information ist für den Betrieb einer USV besonders wichtig?",
|
||||
"answers": ["Welche Systeme angeschlossen sind und wie kritisch sie sind", "Welcher Mitarbeiter sie zuletzt gesehen hat", "In welchem Jahr das Gehäuse lackiert wurde"],
|
||||
"correct": 0,
|
||||
"explanation": "Der Betrieb muss wissen, welche Lasten geschuetzt sind, um Prioritaeten und Shutdown-Reihenfolge festzulegen."
|
||||
"explanation": "Der Betrieb muss wissen, welche Lasten geschützt sind, um Prioritäten und Shutdown-Reihenfolge festzulegen."
|
||||
},
|
||||
{
|
||||
"category": "Betrieb, Wartung & Praxis",
|
||||
"hint": "Plane Tests, Monitoring, Batterieaustausch und Reaktion auf Stromereignisse.",
|
||||
"text": "Was ist ein typischer Fehler beim Einsatz einer USV?",
|
||||
"answers": ["Die Autonomiezeit zu dokumentieren", "Regelmaessige Batterietests einzuplanen", "Die USV zu installieren, aber die Shutdown-Software nie zu konfigurieren"],
|
||||
"answers": ["Die Autonomiezeit zu dokumentieren", "Regelmäßige Batterietests einzuplanen", "Die USV zu installieren, aber die Shutdown-Software nie zu konfigurieren"],
|
||||
"correct": 2,
|
||||
"explanation": "Ohne konfigurierte Shutdown-Software fahren die Server bei laengerem Ausfall nicht geordnet herunter und die Schutzwirkung bleibt unvollstaendig."
|
||||
"explanation": "Ohne konfigurierte Shutdown-Software fahren die Server bei längerem Ausfall nicht geordnet herunter und die Schutzwirkung bleibt unvollständig."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Backuparten - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Datenbanksysteme - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>DSL und Internetanschluss - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>OSI-Modell - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Storage Systeme - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Thin Clients - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Netzwerktopologien - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>USV - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>VDI & DaaS - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>VLAN - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Amortisation - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Angebotsvergleich - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Bezugskalkulation - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Harvard-Konzept - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Kosten-Nutzen-Vergleich - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Make or Buy - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Marktformen - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Nutzwertanalyse - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Ratendarlehen - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Rentabilitätsrechnung - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Return on Investment - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Vertragsarten - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Win-Win-Strategie - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<title>Wirtschaftssektoren - FinalProtocol AP1</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=3">
|
||||
<link rel="stylesheet" href="/css/theme.css?v=4">
|
||||
<link rel="stylesheet" href="/css/finalflux.css">
|
||||
<script src="/js/theme.js?v=3"></script>
|
||||
<script src="/js/theme.js?v=5"></script>
|
||||
</head>
|
||||
<body class="finalflux-page">
|
||||
<div class="mf-theme-float"><button class="mf-theme"></button></div>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
It works!
|
||||
@@ -5,8 +5,17 @@ apply();
|
||||
function applySkin(){var root=document.querySelector('[data-a11y-skin]'),skin=root&&root.getAttribute('data-a11y-skin');if(!skin&&document.querySelector('.mm-body'))skin='mindmatch';if(!skin&&document.querySelector('.tt-body'))skin='techtacular';if(!skin&&document.querySelector('.dl-body,.dlg-body'))skin='devlab';if(!skin&&document.querySelector('.ss-body'))skin='syntaxsaga';if(!skin&&document.querySelector('.finalflux-shell,.finalflux-page'))skin='finalflux';if(!skin&&document.querySelector('.esc-app'))skin='codeescape';if(!skin&&document.querySelector('.bh-app'))skin='bughunter';if(!skin&&document.querySelector('.sc-app'))skin='serviceclash';if(!skin&&document.querySelector('.nw-app'))skin='networker';if(skin)document.body.classList.add('mf-a11y-skin-'+skin);}
|
||||
function button(id,icon,label,detail){return '<button type="button" class="mf-a11y-option" data-a11y="'+id+'"><strong aria-hidden="true">'+icon+'</strong><span>'+label+'</span>'+(detail?'<small>'+detail+'</small>':'')+'</button>';}
|
||||
function init(){if(document.querySelector('.mf-a11y-trigger'))return;var trigger=document.createElement('button');trigger.type='button';trigger.className='mf-a11y-trigger';trigger.setAttribute('aria-label','Barrierefreiheit öffnen');trigger.setAttribute('aria-expanded','false');trigger.setAttribute('aria-controls','mf-a11y-panel');trigger.innerHTML='<svg viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><circle cx="18" cy="5" r="3" fill="currentColor"/><path d="M13 11l5-2 5 4m-7-2-2 7-6 1m8-4 6 2 3 7M13 18a7 7 0 1 0 6 7"/></svg>';
|
||||
var panel=document.createElement('section');panel.id='mf-a11y-panel';panel.className='mf-a11y-panel';panel.hidden=true;panel.setAttribute('role','dialog');panel.setAttribute('aria-modal','false');panel.setAttribute('aria-labelledby','mf-a11y-title');panel.innerHTML='<div class="mf-a11y-head"><button type="button" class="mf-a11y-close" aria-label="Barrierefreiheit schließen">×</button><h2 id="mf-a11y-title">Barrierefreiheit</h2></div><div class="mf-a11y-grid">'+button('font','T⁺','Text vergrößern','100 %')+button('contrast','◐','Hochkontrast')+button('color','◉','Farbenblind-Modus')+button('mono','◒','Monochrom')+button('readable','Aa','Lesemodus (Dyslexie)','Verdana + Abstände')+button('links','🔗','Links unterstreichen')+button('motion','Ⅱ','Animationen stoppen')+button('cursor','➤','Großer Cursor')+button('reset','↺','Alles zurücksetzen')+'</div>';
|
||||
document.body.appendChild(trigger);document.body.appendChild(panel);var close=panel.querySelector('.mf-a11y-close');function paint(){panel.querySelectorAll('[data-a11y]').forEach(function(b){var id=b.dataset.a11y,on=id==='font'?state.font>100:id==='motion'?!state.motion:id==='reset'?false:!!state[id];b.setAttribute('aria-pressed',on);if(id==='font')b.querySelector('small').textContent=state.font+' %';});}
|
||||
var panel=document.createElement('section');panel.id='mf-a11y-panel';panel.className='mf-a11y-panel';panel.hidden=true;panel.setAttribute('role','dialog');panel.setAttribute('aria-modal','false');panel.setAttribute('aria-labelledby','mf-a11y-title');panel.innerHTML='<div class="mf-a11y-head"><button type="button" class="mf-a11y-close" aria-label="Barrierefreiheit schließen">×</button><h2 id="mf-a11y-title">Barrierefreiheit</h2></div><div class="mf-a11y-grid">'+button('font','T⁺','Text vergrößern','100 %')+button('contrast','◐','Hochkontrast','immer dunkel')+button('color','◉','Farbenblind-Modus')+button('mono','◒','Monochrom')+button('readable','Aa','Lesemodus (Dyslexie)','Verdana + Abstände')+button('links','🔗','Links unterstreichen')+button('motion','Ⅱ','Animationen stoppen')+button('cursor','➤','Großer Cursor')+button('reset','↺','Alles zurücksetzen')+'</div>';
|
||||
document.body.appendChild(trigger);document.body.appendChild(panel);var close=panel.querySelector('.mf-a11y-close');
|
||||
function paint(){panel.querySelectorAll('[data-a11y]').forEach(function(b){var id=b.dataset.a11y;
|
||||
if(id==='font'){var on=state.font>100;b.classList.toggle('is-active',on);b.removeAttribute('aria-pressed');b.setAttribute('aria-label','Textgröße ändern – aktuell '+state.font+' %');var s=b.querySelector('small');if(s)s.textContent=state.font+' %';}
|
||||
else if(id==='reset'){b.removeAttribute('aria-pressed');}
|
||||
else{b.setAttribute('aria-pressed',id==='motion'?!state.motion:!!state[id]);}});}
|
||||
var host=document.querySelector('.mf-a11y-host,.mf-tools,.tt-header-tools,.mm-header-tools,.ss-header-tools,.dlg-header-tools,.mf-theme-float');if(host){host.insertBefore(trigger,host.firstChild);}else{var utility=document.createElement('div');utility.className='mf-a11y-utility';utility.setAttribute('aria-label','Globale Werkzeuge');utility.appendChild(trigger);document.body.insertBefore(utility,document.body.firstChild);}
|
||||
function hide(){panel.hidden=true;trigger.setAttribute('aria-expanded','false');trigger.focus();}function show(){panel.hidden=false;trigger.setAttribute('aria-expanded','true');paint();close.focus();}trigger.addEventListener('click',function(){panel.hidden?show():hide();});close.addEventListener('click',hide);window.addEventListener('keydown',function(e){if(e.key==='Escape'&&!panel.hidden)hide();});panel.addEventListener('click',function(e){var b=e.target.closest('[data-a11y]');if(!b)return;var id=b.dataset.a11y;if(id==='reset')state=Object.assign({},defaults);else if(id==='font')state.font=levels[(levels.indexOf(state.font)+1)%levels.length];else if(id==='motion')state.motion=!state.motion;else state[id]=!state[id];apply();paint();});paint();}
|
||||
function focusables(){return Array.prototype.slice.call(panel.querySelectorAll('button:not([disabled])'));}
|
||||
function trap(e){if(e.key!=='Tab')return;var f=focusables();if(!f.length)return;var first=f[0],last=f[f.length-1];if(e.shiftKey&&document.activeElement===first){e.preventDefault();last.focus();}else if(!e.shiftKey&&document.activeElement===last){e.preventDefault();first.focus();}}
|
||||
function onDocPointer(e){if(panel.hidden)return;if(panel.contains(e.target)||trigger.contains(e.target))return;hide();}
|
||||
function hide(){panel.hidden=true;panel.setAttribute('aria-modal','false');trigger.setAttribute('aria-expanded','false');document.removeEventListener('keydown',trap,true);document.removeEventListener('mousedown',onDocPointer,true);trigger.focus();}
|
||||
function show(){panel.hidden=false;panel.setAttribute('aria-modal','true');trigger.setAttribute('aria-expanded','true');paint();document.addEventListener('keydown',trap,true);document.addEventListener('mousedown',onDocPointer,true);close.focus();}
|
||||
trigger.addEventListener('click',function(){panel.hidden?show():hide();});close.addEventListener('click',hide);window.addEventListener('keydown',function(e){if(e.key==='Escape'&&!panel.hidden)hide();});panel.addEventListener('click',function(e){var b=e.target.closest('[data-a11y]');if(!b)return;var id=b.dataset.a11y;if(id==='reset')state=Object.assign({},defaults);else if(id==='font')state.font=levels[(levels.indexOf(state.font)+1)%levels.length];else if(id==='motion')state.motion=!state.motion;else state[id]=!state[id];apply();paint();});paint();}
|
||||
function start(){applySkin();init();}if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',start);else start();})();
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
if (last < tpl.length) code.appendChild(document.createTextNode(tpl.slice(last)));
|
||||
pre.appendChild(code);
|
||||
r.playfield.appendChild(pre);
|
||||
const first = Object.values(inputs)[0]; if (first) first.focus();
|
||||
const first = Object.values(inputs)[0]; if (first) first.focus({ preventScroll: true });
|
||||
return {
|
||||
collect: () => ({ gaps: Object.entries(inputs).map(([pos, i]) => ({ position: Number(pos), text: i.value })) }),
|
||||
lock: () => Object.values(inputs).forEach((i) => { i.disabled = true; }),
|
||||
@@ -230,7 +230,7 @@
|
||||
input.addEventListener('input', preview);
|
||||
input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); doCheck(false); } });
|
||||
const wrap = document.createElement('div'); wrap.className = 'dlg-regex';
|
||||
wrap.append(input, list); r.playfield.appendChild(wrap); preview(); input.focus();
|
||||
wrap.append(input, list); r.playfield.appendChild(wrap); preview(); input.focus({ preventScroll: true });
|
||||
return {
|
||||
collect: () => ({ pattern: input.value }),
|
||||
lock: () => { input.disabled = true; },
|
||||
@@ -241,6 +241,7 @@
|
||||
|
||||
// ---------- Ablauf ----------
|
||||
function renderTask() {
|
||||
window.scrollTo(0, 0);
|
||||
const task = session.tasks[index];
|
||||
cur = { task, attempts: 0, solved: false, revealed: false };
|
||||
r.kind.textContent = `Aufgabe ${index + 1}`;
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('[data-fluxcheck]').forEach((check) => {
|
||||
const button = check.querySelector('button');
|
||||
const result = check.querySelector('.fluxcheck-result');
|
||||
|
||||
if (!button || !result) {
|
||||
return;
|
||||
}
|
||||
|
||||
button.addEventListener('click', () => {
|
||||
const selected = check.querySelector('input[type="radio"]:checked');
|
||||
if (!selected) {
|
||||
result.textContent = 'Wähle zuerst eine Antwort aus.';
|
||||
result.className = 'fluxcheck-result wrong';
|
||||
return;
|
||||
}
|
||||
|
||||
const expected = check.dataset.answer;
|
||||
const explanation = check.dataset.explanation || '';
|
||||
const isCorrect = selected.value === expected;
|
||||
|
||||
result.textContent = isCorrect
|
||||
? 'Richtig. ' + explanation
|
||||
: 'Noch nicht. ' + explanation;
|
||||
result.className = 'fluxcheck-result ' + (isCorrect ? 'correct' : 'wrong');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user