Volle Mindflux-App ins Deploy-Repo uebernehmen #1

Merged
AliceDarkest merged 1 commits from deploy/full-app into main 2026-06-26 12:59:01 +00:00
185 changed files with 22772 additions and 142 deletions
+11
View File
@@ -0,0 +1,11 @@
.git
target
uploads
*.log
.env
.env.*
.claude
.idea
.settings
.classpath
.project
+2
View File
@@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf
+3
View File
@@ -21,6 +21,9 @@ jobs:
docker push localhost:5000/mindflux:latest docker push localhost:5000/mindflux:latest
- name: Deploy - name: Deploy
env:
MINDFLUX_ADMIN_PASSWORD: ${{ secrets.MINDFLUX_ADMIN_PASSWORD }}
MINDFLUX_ADMIN2_PASSWORD: ${{ secrets.MINDFLUX_ADMIN2_PASSWORD }}
run: | run: |
docker compose -f docker-compose.yml pull docker compose -f docker-compose.yml pull
docker compose -f docker-compose.yml up -d docker compose -f docker-compose.yml up -d
+36 -41
View File
@@ -1,45 +1,40 @@
# ---> Java HELP.md
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
# ---> Maven
target/ target/
pom.xml.tag uploads/
pom.xml.releaseBackup .env
pom.xml.versionsBackup .env.*
pom.xml.next !.env.example
release.properties *.log
dependency-reduced-pom.xml .claude/
buildNumber.properties hs_err_pid*.log
.mvn/timing.properties !.mvn/wrapper/maven-wrapper.jar
# https://github.com/takari/maven-wrapper#usage-without-binary-jar !**/src/main/**/target/
.mvn/wrapper/maven-wrapper.jar !**/src/test/**/target/
# Eclipse m2e generated files ### STS ###
# Eclipse Core .apt_generated
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath .classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
+63
View File
@@ -0,0 +1,63 @@
# Changelog
Alle nennenswerten Änderungen an **Mindflux** 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-06-25
Großer Ausbau: zwei vollständige Spiele, durchgängig DB-gestützte und
admin-pflegbare Inhalte sowie ein kompletter Optik-Relaunch (Theme-System
mit Dark/Light-Umschaltung).
### Hinzugefügt
#### Spiel: MindMatch (Lern-Memory)
- Memory-Spiel, das **Begriff ↔ Bedeutung** paart (klassische Aufdeck-Mechanik mit Lernwert).
- Deck-Auswahl, Zug-/Zeitzähler, Sternebewertung und „Spiel abbrechen".
- **No-Scroll-Layout**: alle Karten einer Runde passen ohne Scrollen auf den Schirm (Brett skaliert die Karten).
- Kartenrückseiten tragen das leuchtende Mindflux-Emblem (grün/magenta je Theme).
- Inhalte **DB-gestützt und im Admin pflegbar** (`/admin/mindmatch`): Decks und Paare jederzeit erweiterbar.
- Mitgelieferte Decks: Netzwerk, Hardware, IT-Sicherheit, SQL-Befehle sowie ein großer Fachinformatiker-Satz (15 Decks insgesamt).
#### Spiel: TechTacular (Jeopardy-Quiz)
- **Echtes Jeopardy-Prinzip (umgekehrt):** angezeigt wird eine Aussage, gesucht ist der Begriff als „Was ist …?".
- 5×5-Board (Kategorie × Punktwert 100–500), Antwortprüfung mit Erklärung.
- **Server-autoritativ & Anti-Cheat:** das Board enthält nie die Lösung; Prüfung und Wertung passieren ausschließlich serverseitig.
- **≥ 60 % neue Fragen pro Runde:** das Board schließt die Fragen der Vorrunde aus.
- **Kuratierter Inhalt:** 12 Kategorien über das ganze Informatik-Prüfungsspektrum (Netzwerk, Betriebssysteme, Programmierung, Datenstrukturen & Algorithmen, Datenbanken/SQL, IT-Sicherheit & Krypto, Web, Rechnerarchitektur, Software-Engineering, Theoretische Informatik, Cloud/DevOps, IT-Recht), **384 Hinweise**.
- Admin-Verwaltung als Drill-down (`/admin/techtacular`).
#### Generisches Spiel-Backend
- Tabelle `game_scores` + `GameScoreService`/`GameScoreController` mit `POST /api/games/{game}/scores` und `GET /api/games/{game}/stats` für alle Spiele.
- `/progress` zeigt echte Spiel-Statistiken (Highscore, Spiele, zuletzt gespielt).
#### Optik-Relaunch / Theme-System
- **Zentrale Tokens** in `theme.css` + Umschalter `theme.js`, gesteuert über `<html data-theme="dark|light">` (Persistenz via `localStorage`, kein Aufblitzen).
- **Dark = Grün-Neon, Light = heller Grund + Magenta** (bewusst invertiert).
- Wiederverwendbare `mf-`-Komponenten (Shell/Sidebar/Topbar, HUD-Karten, Panels, Buttons, Formulare, Tabellen, Footer …).
- **Dashboard** im „Cyber-Grid"-Look neu: linke Sidebar (Nav, Profil, Level/XP, Streak), Mitte mit HUD-Spielkarten, rechte Widget-Spalte (System-Status, Fortschritt, Top-Kategorien, Challenge), Top-Leiste mit Suche/Glocke/Theme-Schalter.
- Original-Mindflux-Logo per CSS-Maske theme-abhängig eingefärbt (`/img/mindflux-emblem.png`).
- Theme + Umschalter **app-weit** ausgerollt: Login/Registrierung, beide Spiele, Profil/Fortschritt/Achievements/Einstellungen/Soziales/Knowledge, alle Admin-Seiten, FinalFlux-Hub und 28 statische Lernseiten.
- **Fußzeile** (Support · Feedback · Nutzungsbedingungen · Richtlinien · Datenschutz · Impressum) als `.mf-footer`-Komponente, einheitlich auf allen Content-Seiten.
### Geändert
- `ProgressService` liefert echte DB-Daten statt der bisherigen Dummy-Spiele.
- Admin-Bootstrap-User auf `AliceDarkest` umbenannt (vorher „Alice Darkest" mit Leerzeichen); zusätzlicher Admin `KevCipher` angelegt.
### Sicherheit
- Statische JS-Ressourcen (`/js/**`) wie CSS/Bilder öffentlich (`permitAll`) — kein schützenswerter Inhalt.
- Score-Endpunkte gegen eine Allowlist bekannter Spiele abgesichert; Punkte werden serverseitig berechnet (kein vom Client gemeldeter Score).
### Technisch
- **Flyway-Migrationen V2–V6:** `game_scores` (V2), MindMatch-Decks/-Paare (V3) + Fachinformatiker-Inhalte (V4), TechTacular-Schema (V5) + kuratierte Reverse-Inhalte (V6).
- Inhalts-Import für TechTacular über einen `CommandLineRunner`-Seeder aus `techtacular/content.json`.
- Hinweis (Thymeleaf): Inline-`<script>` mit `[[`/`]]` benötigt `th:inline="none"`, sonst bricht die Antwort ab.
### Hinweise
- Footer-Links sind aktuell Platzhalter (`href="#"`); echte Impressum-/AGB-/Datenschutz-Seiten existieren noch nicht.
- Die fünf übrigen Dashboard-Spiele (Syntax Saga, Code Escape Room, Service Clash, Bug Hunter, Netzwerker) sind noch nicht umgesetzt und verweisen auf „coming soon".
---
_Frühere Stände (Grundgerüst der Lernplattform: Benutzer/Login, Profil, Achievements, FinalFlux, Admin) wurden vor Einführung dieses Changelogs entwickelt._
+20 -3
View File
@@ -5,16 +5,33 @@ services:
restart: unless-stopped restart: unless-stopped
environment: environment:
# --- Datenbank: unverändert wie im bisherigen Deploy ---
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/mindfluxdb SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/mindfluxdb
SPRING_DATASOURCE_USERNAME: mindflux SPRING_DATASOURCE_USERNAME: mindflux
SPRING_DATASOURCE_PASSWORD: mindflux_pass SPRING_DATASOURCE_PASSWORD: mindflux_pass
# --- App: lauscht intern auf 8080 (nginx -> mindflux:8080) ---
SERVER_PORT: "8080"
MINDFLUX_PUBLIC_URL: "https://mindflux.sysnaptik.de"
MINDFLUX_COOKIE_SECURE: "true"
# --- Bootstrap-Admins: Passwörter kommen aus Gitea-Secrets (siehe Workflow) ---
MINDFLUX_ADMIN_USERNAME: "AliceDarkest"
MINDFLUX_ADMIN_EMAIL: "AliceDarkest@gmail.com"
MINDFLUX_ADMIN_PASSWORD: "${MINDFLUX_ADMIN_PASSWORD}"
MINDFLUX_ADMIN2_USERNAME: "KevCipher"
MINDFLUX_ADMIN2_EMAIL: "kevcipher@proton.me"
MINDFLUX_ADMIN2_PASSWORD: "${MINDFLUX_ADMIN2_PASSWORD}"
volumes:
- mindflux_uploads:/app/uploads
networks: networks:
- backend_network - backend_network
ports:
- "8081:8080"
networks: networks:
backend_network: backend_network:
external: true external: true
volumes:
mindflux_uploads:
-17
View File
@@ -1,17 +0,0 @@
hello ci pipeline
versuch 2
Versuch 3
Versuch 4
Versuch 5
Versuch 6
Versuch 7
Versuch 8
Versuch 9
Versuch 10
Versuch 11
Versuch 12
Versuch 13
Versuch 14 Claude Fix
Versuch 15 Maven Fix
Versuch 16 Maven Fix pushed
rollback
+66 -13
View File
@@ -1,40 +1,72 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"> <?xml version="1.0" encoding="UTF-8"?>
<modelVersion>4.0.0</modelVersion> <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<groupId>com.mindflux</groupId> <modelVersion>4.0.0</modelVersion>
<artifactId>mindflux</artifactId>
<version>1.0.0</version>
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.2</version> <version>3.4.4</version>
<relativePath/>
</parent> </parent>
<groupId>com.mindflux</groupId>
<artifactId>mindflux</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mindflux</name>
<description>Mindflux – Lernplattform zur Prüfungsvorbereitung</description>
<properties> <properties>
<java.version>21</java.version> <java.version>17</java.version>
</properties> </properties>
<dependencies> <dependencies>
<!-- Spring Boot Web -->
<!-- Web -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependency>
<!-- PostgreSQL --> <!-- Thymeleaf für Templates -->
<dependency> <dependency>
<groupId>org.postgresql</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>postgresql</artifactId> <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency> </dependency>
<!-- JPA optional aber sinnvoll --> <!-- Thymeleaf + Spring Security Integration -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>
<!-- JPA + Hibernate -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId> <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency> </dependency>
<!-- Spring Security komplett -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Passwort-Verschlüsselung (BCrypt) -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<!-- PostgreSQL Datenbanktreiber -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Datenbank-Migrationen -->
<dependency> <dependency>
<groupId>org.flywaydb</groupId> <groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId> <artifactId>flyway-core</artifactId>
@@ -45,9 +77,30 @@
<artifactId>flyway-database-postgresql</artifactId> <artifactId>flyway-database-postgresql</artifactId>
</dependency> </dependency>
<!-- Hot Reload für Entwicklung -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- Unit Tests -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
</resource>
</resources>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@@ -0,0 +1,12 @@
package com.example.mindflux;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class AppStartCheck implements CommandLineRunner {
@Override
public void run(String... args) {
System.out.println("✅ App läuft. Sicherheitsmodul sollte aktiv sein.");
}
}
@@ -1,10 +1,11 @@
package com.mindflux; package com.example.mindflux;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication
public class MindfluxApplication { public class MindfluxApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(MindfluxApplication.class, args); SpringApplication.run(MindfluxApplication.class, args);
} }
@@ -0,0 +1,60 @@
package com.example.mindflux.config;
import com.example.mindflux.model.User;
import com.example.mindflux.repository.UserRepository;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
public class AdminSetup {
@Value("${MINDFLUX_ADMIN_USERNAME:admin}")
private String adminUsername;
@Value("${MINDFLUX_ADMIN_EMAIL:admin@mindflux.de}")
private String adminEmail;
@Value("${MINDFLUX_ADMIN_PASSWORD:}")
private String adminPassword;
// Optionaler zweiter Bootstrap-Admin. Wird nur angelegt, wenn Username + Passwort gesetzt sind.
@Value("${MINDFLUX_ADMIN2_USERNAME:}")
private String admin2Username;
@Value("${MINDFLUX_ADMIN2_EMAIL:}")
private String admin2Email;
@Value("${MINDFLUX_ADMIN2_PASSWORD:}")
private String admin2Password;
@Bean
public CommandLineRunner createAdmin(UserRepository userRepo, BCryptPasswordEncoder passwordEncoder) {
return args -> {
createAdminIfAbsent(userRepo, passwordEncoder, adminUsername, adminEmail, adminPassword);
createAdminIfAbsent(userRepo, passwordEncoder, admin2Username, admin2Email, admin2Password);
};
}
private void createAdminIfAbsent(UserRepository userRepo, BCryptPasswordEncoder passwordEncoder,
String username, String email, String password) {
if (username == null || username.isBlank()) {
return; // Admin-Slot nicht konfiguriert -> still überspringen.
}
if (password == null || password.isBlank()) {
System.out.println("Kein Bootstrap-Admin erstellt: Passwort für '" + username + "' ist nicht gesetzt.");
return;
}
if (userRepo.findByUsername(username).isEmpty()) {
User admin = new User();
admin.setUsername(username);
admin.setEmail(email);
admin.setPassword(passwordEncoder.encode(password));
admin.setRole("ADMIN");
userRepo.save(admin);
System.out.println("Bootstrap-Admin erstellt: " + username);
}
}
}
@@ -0,0 +1,65 @@
package com.example.mindflux.config;
import com.example.mindflux.model.User;
import com.example.mindflux.repository.UserRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login", "/register", "/css/**", "/js/**", "/img/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN") // NEU: Adminbereich absichern
.requestMatchers("/api/users/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/") // login.html liegt auf "/"
.loginProcessingUrl("/login")
.defaultSuccessUrl("/dashboard", true)
.failureUrl("/?error") // zeigt Loginfehler an
.permitAll()
)
.logout(logout -> logout
.logoutSuccessUrl("/?logout")
.permitAll()
)
.csrf(csrf -> csrf.disable()); // optional deaktivieren bei Custom-Logik/API-Nutzung
return http.build();
}
@Bean
public UserDetailsService userDetailsService(UserRepository userRepo) {
return username -> {
User user = userRepo.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("Benutzer nicht gefunden"));
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPassword())
.roles(user.getRole()) // Achtung: Nur ein String, z.B. "USER" oder "ADMIN"
.build();
};
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}
@@ -0,0 +1,127 @@
package com.example.mindflux.config;
import com.example.mindflux.model.TechtacularAnswer;
import com.example.mindflux.model.TechtacularCategory;
import com.example.mindflux.model.TechtacularQuestion;
import com.example.mindflux.repository.TechtacularCategoryRepository;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashSet;
import java.util.Set;
/**
* Befüllt TechTacular einmalig (wenn leer) aus der kuratierten Inhaltsdatei
* techtacular/content.json. Format: Jeopardy umgekehrt – pro Hinweis eine Aussage
* ("clue") und der gesuchte Begriff ("solution", = richtige Antwort) plus Distraktoren.
*/
@Component
public class TechtacularSeeder implements CommandLineRunner {
private final TechtacularCategoryRepository categoryRepository;
private final ObjectMapper objectMapper;
public TechtacularSeeder(TechtacularCategoryRepository categoryRepository, ObjectMapper objectMapper) {
this.categoryRepository = categoryRepository;
this.objectMapper = objectMapper;
}
@Override
@Transactional
public void run(String... args) {
if (categoryRepository.count() > 0) {
return; // schon befüllt
}
try {
JsonNode root = objectMapper.readTree(new ClassPathResource("techtacular/content.json").getInputStream());
JsonNode categories = root.get("categories");
if (categories == null || !categories.isArray()) {
System.out.println("⚠️ TechTacular-Seed: content.json ohne 'categories'.");
return;
}
Set<String> usedSlugs = new HashSet<>();
int order = 1;
int catCount = 0;
int clueCount = 0;
for (JsonNode catNode : categories) {
String title = catNode.path("title").asText("");
JsonNode clues = catNode.get("clues");
if (title.isBlank() || clues == null || !clues.isArray()) {
continue;
}
TechtacularCategory category =
new TechtacularCategory(uniqueSlug(title, usedSlugs), trim(title, 200), order, true);
for (JsonNode clue : clues) {
String clueText = clue.path("clue").asText("");
String solution = clue.path("solution").asText("");
JsonNode distractors = clue.get("distractors");
if (clueText.isBlank() || solution.isBlank() || distractors == null || !distractors.isArray()) {
continue;
}
int points = clue.path("points").asInt(100);
String explanation = clue.hasNonNull("explanation") ? clue.get("explanation").asText() : null;
TechtacularQuestion question = new TechtacularQuestion(
trim(clueText, 1000),
(explanation == null || explanation.isBlank()) ? null : trim(explanation, 1000),
points);
int sort = 0;
question.addAnswer(new TechtacularAnswer(trim(solution, 500), true, sort++)); // richtige Antwort
for (JsonNode d : distractors) {
String dt = d.asText("");
if (!dt.isBlank()) {
question.addAnswer(new TechtacularAnswer(trim(dt, 500), false, sort++));
}
}
if (question.getAnswers().size() >= 2) {
category.addQuestion(question);
clueCount++;
}
}
if (!category.getQuestions().isEmpty()) {
categoryRepository.save(category);
order++;
catCount++;
}
}
System.out.println("✅ TechTacular: " + catCount + " Kategorien / " + clueCount + " Hinweise geladen.");
} catch (Exception e) {
System.out.println("⚠️ TechTacular-Seed übersprungen: " + e.getMessage());
}
}
private String uniqueSlug(String title, Set<String> used) {
String base = title.toLowerCase()
.replace("ä", "ae").replace("ö", "oe").replace("ü", "ue").replace("ß", "ss")
.replaceAll("[^a-z0-9]+", "-")
.replaceAll("(^-|-$)", "");
if (base.isBlank()) {
base = "kategorie";
}
String slug = base;
int counter = 2;
while (used.contains(slug) || categoryRepository.existsBySlug(slug)) {
slug = base + "-" + counter++;
}
used.add(slug);
return slug;
}
private String trim(String value, int max) {
if (value == null) {
return null;
}
return value.length() <= max ? value : value.substring(0, max);
}
}
@@ -0,0 +1,16 @@
package com.example.mindflux.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/uploads/**")
.addResourceLocations("file:" + System.getProperty("user.dir") + "/uploads/");
}
}
@@ -0,0 +1,38 @@
package com.example.mindflux.controller;
import com.example.mindflux.model.Achievement;
import com.example.mindflux.service.AchievementService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.security.Principal;
import java.util.List;
@Controller
public class AchievementController {
@Autowired
private AchievementService achievementService;
@GetMapping("/profile/achievements")
public String showAchievements(Model model, Principal principal) {
String username = principal.getName();
List<Achievement> achievements = achievementService.getAchievementsForUser(username);
// 🔥 Dummy-Daten nur für Tests!
if (achievements == null || achievements.isEmpty()) {
achievements = List.of(
new Achievement("Hangman Hero", "Gewinne 10 Runden Hangman", "hangman.png", true),
new Achievement("Memory Meister", "Erreiche 1000 Punkte im Memory", "memory.png", false),
new Achievement("Quizkönig", "Beantworte 50 Fragen richtig", "quiz.png", true),
new Achievement("Early Bird", "Lerne vor 7 Uhr morgens", "earlybird.png", false)
);
}
model.addAttribute("achievements", achievements);
return "achievements"; // lädt achievements.html
}
}
@@ -0,0 +1,59 @@
package com.example.mindflux.controller;
import com.example.mindflux.service.MindMatchService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
/**
* Admin-Verwaltung der MindMatch-Decks und -Paare. Liegt unter /admin/** und ist
* damit per SecurityConfig auf ROLE_ADMIN beschränkt.
*/
@Controller
@RequestMapping("/admin/mindmatch")
public class AdminMindMatchController {
private final MindMatchService mindMatchService;
public AdminMindMatchController(MindMatchService mindMatchService) {
this.mindMatchService = mindMatchService;
}
@GetMapping("")
public String manage(Model model) {
model.addAttribute("decks", mindMatchService.allDecksWithPairs());
return "admin/mindmatch";
}
@PostMapping("/decks")
public String createDeck(@RequestParam String title) {
mindMatchService.createDeck(title);
return "redirect:/admin/mindmatch";
}
@PostMapping("/decks/{deckId}/pairs")
public String addPair(@PathVariable Long deckId,
@RequestParam String term,
@RequestParam String match) {
mindMatchService.addPair(deckId, term, match);
return "redirect:/admin/mindmatch";
}
@PostMapping("/pairs/{pairId}/delete")
public String deletePair(@PathVariable Long pairId) {
mindMatchService.deletePair(pairId);
return "redirect:/admin/mindmatch";
}
@PostMapping("/decks/{deckId}/delete")
public String deleteDeck(@PathVariable Long deckId) {
mindMatchService.deleteDeck(deckId);
return "redirect:/admin/mindmatch";
}
@PostMapping("/decks/{deckId}/toggle")
public String toggleDeck(@PathVariable Long deckId) {
mindMatchService.toggleDeck(deckId);
return "redirect:/admin/mindmatch";
}
}
@@ -0,0 +1,79 @@
package com.example.mindflux.controller;
import com.example.mindflux.model.TechtacularCategory;
import com.example.mindflux.service.TechtacularService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
/** Admin-Verwaltung der TechTacular-Kategorien, -Fragen und -Antworten (ROLE_ADMIN via /admin/**). */
@Controller
@RequestMapping("/admin/techtacular")
public class AdminTechtacularController {
private final TechtacularService techtacularService;
public AdminTechtacularController(TechtacularService techtacularService) {
this.techtacularService = techtacularService;
}
/** Übersicht aller Kategorien. */
@GetMapping("")
public String manage(Model model) {
model.addAttribute("categories", techtacularService.categoryRows());
return "admin/techtacular";
}
/** Detailseite einer Kategorie mit ihren Fragen. */
@GetMapping("/categories/{categoryId}")
public String category(@PathVariable Long categoryId, Model model) {
TechtacularCategory category = techtacularService.categoryWithQuestions(categoryId);
if (category == null) {
return "redirect:/admin/techtacular";
}
model.addAttribute("category", category);
return "admin/techtacular-category";
}
@PostMapping("/categories")
public String createCategory(@RequestParam String title) {
techtacularService.createCategory(title);
return "redirect:/admin/techtacular";
}
@PostMapping("/categories/{categoryId}/questions")
public String addQuestion(@PathVariable Long categoryId,
@RequestParam String text,
@RequestParam(required = false) String explanation,
@RequestParam(defaultValue = "100") int points,
@RequestParam(required = false, defaultValue = "") String answer1,
@RequestParam(required = false, defaultValue = "") String answer2,
@RequestParam(required = false, defaultValue = "") String answer3,
@RequestParam(required = false, defaultValue = "") String answer4,
@RequestParam(defaultValue = "0") int correct) {
List<String> answers = Arrays.asList(answer1, answer2, answer3, answer4);
techtacularService.addQuestion(categoryId, text, explanation, points, answers, correct);
return "redirect:/admin/techtacular/categories/" + categoryId;
}
@PostMapping("/questions/{questionId}/delete")
public String deleteQuestion(@PathVariable Long questionId, @RequestParam Long categoryId) {
techtacularService.deleteQuestion(questionId);
return "redirect:/admin/techtacular/categories/" + categoryId;
}
@PostMapping("/categories/{categoryId}/delete")
public String deleteCategory(@PathVariable Long categoryId) {
techtacularService.deleteCategory(categoryId);
return "redirect:/admin/techtacular";
}
@PostMapping("/categories/{categoryId}/toggle")
public String toggleCategory(@PathVariable Long categoryId) {
techtacularService.toggleCategory(categoryId);
return "redirect:/admin/techtacular";
}
}
@@ -0,0 +1,66 @@
package com.example.mindflux.controller;
import com.example.mindflux.model.User;
import com.example.mindflux.service.AdminUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/admin")
public class AdminUserController {
@Autowired
private AdminUserService userService;
@Autowired
private PasswordEncoder passwordEncoder;
// ⬅️ Neu: Dashboard-Startseite
@GetMapping("")
public String adminDashboard() {
return "admin/dashboard"; // du brauchst: templates/admin/dashboard.html
}
// ⬅️ Benutzerverwaltung
@GetMapping("/users")
public String listUsers(Model model) {
model.addAttribute("users", userService.findAll());
return "admin/users/list";
}
@GetMapping("/users/new")
public String createUserForm(Model model) {
model.addAttribute("user", new User());
return "admin/users/form";
}
@PostMapping("/users/save")
public String saveUser(@ModelAttribute("user") User user) {
if (user.getId() != null && (user.getPassword() == null || user.getPassword().isBlank())) {
User existingUser = userService.findById(user.getId())
.orElseThrow(() -> new IllegalArgumentException("Ungültige User-ID: " + user.getId()));
user.setPassword(existingUser.getPassword());
} else {
user.setPassword(passwordEncoder.encode(user.getPassword()));
}
userService.save(user);
return "redirect:/admin/users";
}
@GetMapping("/users/edit/{id}")
public String editUserForm(@PathVariable Long id, Model model) {
User user = userService.findById(id).orElseThrow(() -> new IllegalArgumentException("Ungültige User-ID: " + id));
model.addAttribute("user", user);
return "admin/users/form";
}
@PostMapping("/users/delete/{id}")
public String deleteUser(@PathVariable Long id) {
userService.deleteById(id);
return "redirect:/admin/users";
}
}
@@ -0,0 +1,13 @@
package com.example.mindflux.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class DashboardController {
@GetMapping("/dashboard")
public String showDashboard() {
return "dashboard"; // lädt dashboard.html aus templates
}
}
@@ -0,0 +1,13 @@
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,13 @@
package com.example.mindflux.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class FinalfluxController {
@GetMapping("/finalflux")
public String showFinalfluxOverview() {
return "finalflux"; // Lädt templates/finalflux.html
}
}
@@ -0,0 +1,50 @@
package com.example.mindflux.controller;
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 org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
import java.util.Set;
/**
* Generisches Score-API für alle Mindflux-Spiele. Der Spielname kommt aus der URL
* und wird gegen eine Allowlist geprüft, damit keine beliebigen Spiele-Kennungen
* in die Datenbank wandern.
*/
@RestController
@RequestMapping("/api/games")
public class GameScoreController {
/** Bekannte Spiele (entsprechen den Dashboard-Routen). */
private static final Set<String> KNOWN_GAMES = Set.of(
"mindmatch", "techtacular", "syntax-saga", "escape-room",
"service-clash", "bug-hunter", "netzwerker");
private final GameScoreService gameScoreService;
public GameScoreController(GameScoreService gameScoreService) {
this.gameScoreService = gameScoreService;
}
@PostMapping("/{game}/scores")
public ResponseEntity<ScoreResult> submitScore(@PathVariable String game,
@RequestBody ScoreSubmission submission,
Principal principal) {
if (!KNOWN_GAMES.contains(game)) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(gameScoreService.record(principal.getName(), game, submission));
}
@GetMapping("/{game}/stats")
public ResponseEntity<GameStats> getStats(@PathVariable String game, Principal principal) {
if (!KNOWN_GAMES.contains(game)) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(gameScoreService.getStats(principal.getName(), game));
}
}
@@ -0,0 +1,26 @@
package com.example.mindflux.controller;
import com.example.mindflux.dto.MindMatchDeckView;
import com.example.mindflux.service.MindMatchService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/** Liefert die aktiven MindMatch-Decks (inkl. Paare) an die Spielseite. */
@RestController
@RequestMapping("/api/mindmatch")
public class MindMatchApiController {
private final MindMatchService mindMatchService;
public MindMatchApiController(MindMatchService mindMatchService) {
this.mindMatchService = mindMatchService;
}
@GetMapping("/decks")
public List<MindMatchDeckView> decks() {
return mindMatchService.activeDecks();
}
}
@@ -0,0 +1,13 @@
package com.example.mindflux.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class MindMatchController {
@GetMapping("/mindmatch")
public String showMindMatch() {
return "mindmatch"; // lädt templates/mindmatch.html
}
}
@@ -0,0 +1,172 @@
package com.example.mindflux.controller;
import com.example.mindflux.model.User;
import com.example.mindflux.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
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;
@Controller
public class ProfileController {
@Autowired
private UserService userService;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
private static final String UPLOAD_DIR = System.getProperty("user.dir") + "/uploads/profiles/";
@GetMapping("/profile")
public String profile(Model model, Principal principal, @RequestParam(value = "success", required = false) Boolean success) {
String username = principal.getName();
User user = userService.findByUsername(username);
model.addAttribute("user", user);
if (Boolean.TRUE.equals(success)) {
model.addAttribute("message", "Profil erfolgreich aktualisiert!");
}
return "profile";
}
@PostMapping("/profile/delete-picture")
public String deleteProfilePicture(Principal principal) {
String username = principal.getName();
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);
System.out.println("Versuche zu löschen: " + picturePath.toAbsolutePath());
if (Files.exists(picturePath)) {
Files.delete(picturePath);
System.out.println("Datei erfolgreich gelöscht: " + picturePath.getFileName());
} else {
System.out.println("Datei existiert nicht: " + picturePath.toAbsolutePath());
}
} catch (IOException e) {
System.err.println("Fehler beim Löschen der Datei: " + e.getMessage());
}
} else {
System.out.println("Kein Profilbild gesetzt, nichts zu löschen.");
}
existingUser.setProfilePicture(null); // Wichtig: auch wenn Datei nicht existiert
userService.save(existingUser);
}
return "redirect:/profile?success=true";
}
@GetMapping("/profile/knowledge")
public String knowledgeCenter(Model model, Principal principal) {
String username = principal.getName();
User user = userService.findByUsername(username);
model.addAttribute("user", user);
return "knowledge";
}
@PostMapping("/profile/update")
public String updateProfile(@RequestParam("email") String email,
@RequestParam(value = "profilePicture", required = false) MultipartFile file,
Principal principal) {
String username = principal.getName();
User existingUser = userService.findByUsername(username);
if (existingUser != null) {
existingUser.setEmail(email); // Email aktualisieren
if (file != null && !file.isEmpty()) {
try {
// 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());
if (Files.exists(oldFilePath)) {
Files.delete(oldFilePath);
}
}
// Neues Profilbild speichern
String fileExtension = getFileExtension(file.getOriginalFilename());
String fileName = UUID.randomUUID() + fileExtension;
Path uploadPath = Paths.get(UPLOAD_DIR);
Files.createDirectories(uploadPath);
Path filePath = uploadPath.resolve(fileName);
file.transferTo(filePath.toFile());
existingUser.setProfilePicture(fileName);
} catch (IOException e) {
e.printStackTrace();
}
}
userService.save(existingUser);
}
return "redirect:/profile?success=true";
}
@PostMapping("/profile/change-password")
public String changePassword(@RequestParam("oldPassword") String oldPassword,
@RequestParam("newPassword") String newPassword,
Principal principal,
Model model) {
String username = principal.getName();
User user = userService.findByUsername(username);
if (user == null) {
model.addAttribute("error", "Benutzer nicht gefunden.");
return "profile";
}
if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
model.addAttribute("error", "Das aktuelle Passwort ist falsch.");
return "profile";
}
user.setPassword(passwordEncoder.encode(newPassword));
userService.save(user);
model.addAttribute("message", "Passwort erfolgreich geändert.");
return "profile";
}
@PostMapping("/profile/delete")
public String deleteProfile(Principal principal, Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
String username = principal.getName();
userService.deleteByUsername(username);
new SecurityContextLogoutHandler().logout(request, response, authentication);
return "redirect:/?logout";
}
// Hilfsmethode: Dateiendung extrahieren
private String getFileExtension(String filename) {
if (filename == null || filename.lastIndexOf(".") == -1) {
return "";
}
return filename.substring(filename.lastIndexOf("."));
}
}
@@ -0,0 +1,47 @@
package com.example.mindflux.controller;
import com.example.mindflux.dto.GameProgress;
import com.example.mindflux.dto.ProgressData;
import com.example.mindflux.service.ProgressService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.security.Principal;
import java.util.List;
@Controller
public class ProgressController {
@Autowired
private ProgressService progressService;
@GetMapping("/progress") // <-- hier geändert!
public String showProgress(Model model, Principal principal) {
String username = principal.getName(); // aktueller eingeloggter Benutzer
ProgressData progress = progressService.getOverallProgress(username);
List<GameProgress> games = progressService.getGameProgress(username);
if (progress == null) {
// Falls keine Fortschrittsdaten vorhanden sind, Dummy-Daten setzen
model.addAttribute("correctAnswers", 0);
model.addAttribute("currentXp", 0);
model.addAttribute("xpForNextLevel", 1000);
model.addAttribute("userLevel", 1);
model.addAttribute("xpPercentage", 0);
} else {
model.addAttribute("correctAnswers", progress.getCorrectAnswers());
model.addAttribute("currentXp", progress.getCurrentXp());
model.addAttribute("xpForNextLevel", progress.getXpForNextLevel());
model.addAttribute("userLevel", progress.getUserLevel());
model.addAttribute("xpPercentage", progress.getProgressPercent());
}
model.addAttribute("gameProgress", games != null ? games : List.of()); // leere Liste, falls null
return "progress"; // ruft progress.html auf
}
}
@@ -0,0 +1,44 @@
package com.example.mindflux.controller;
import com.example.mindflux.model.User;
import com.example.mindflux.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Controller
public class RegisterController {
@Autowired
private UserService userService;
@Autowired
private PasswordEncoder passwordEncoder;
@GetMapping("/register")
public String showRegistrationForm(Model model) {
model.addAttribute("user", new User());
return "register";
}
@PostMapping("/register")
public String processRegistration(@ModelAttribute("user") User user, Model model) {
// Benutzername klein schreiben, um Doppelungen zu vermeiden
user.setUsername(user.getUsername().toLowerCase());
// Prüfen, ob Benutzername schon existiert
if (userService.findByUsername(user.getUsername()) != null) {
model.addAttribute("error", "Benutzername bereits vergeben.");
return "register";
}
// Passwort verschlüsseln
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setRole("USER"); // Standardrolle
userService.save(user);
return "redirect:/?registerSuccess"; // zurück zum Login mit Erfolgsmeldung
}
}
@@ -0,0 +1,13 @@
package com.example.mindflux.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class SettingsController {
@GetMapping("/profile/settings")
public String showSettingsPage() {
return "settings"; // lädt templates/settings.html
}
}
@@ -0,0 +1,13 @@
package com.example.mindflux.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class SozialesController {
@GetMapping("/soziales")
public String soziales() {
return "soziales"; // -> templates/soziales.html
}
}
@@ -0,0 +1,51 @@
package com.example.mindflux.controller;
import com.example.mindflux.dto.*;
import com.example.mindflux.service.TechtacularService;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
import java.util.HashSet;
import java.util.Set;
/** Spieler-API für TechTacular: Board laden, Antwort prüfen, Runde abschließen. */
@RestController
@RequestMapping("/api/techtacular")
public class TechtacularApiController {
private final TechtacularService techtacularService;
public TechtacularApiController(TechtacularService techtacularService) {
this.techtacularService = techtacularService;
}
/** exclude = komma-separierte Frage-IDs der Vorrunde, die möglichst nicht erneut kommen sollen. */
@GetMapping("/board")
public TechtacularBoardView board(@RequestParam(required = false) String exclude) {
return techtacularService.buildBoard(parseIds(exclude));
}
private Set<Long> parseIds(String csv) {
Set<Long> ids = new HashSet<>();
if (csv != null && !csv.isBlank()) {
for (String part : csv.split(",")) {
try {
ids.add(Long.parseLong(part.trim()));
} catch (NumberFormatException ignored) {
// ungültige IDs einfach überspringen
}
}
}
return ids;
}
@PostMapping("/answer")
public AnswerResult answer(@RequestBody AnswerRequest request) {
return techtacularService.checkAnswer(request.getQuestionId(), request.getAnswerId());
}
@PostMapping("/finish")
public TechtacularResult finish(@RequestBody FinishRequest request, Principal principal) {
return techtacularService.finish(principal.getName(), request);
}
}
@@ -0,0 +1,13 @@
package com.example.mindflux.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class TechtacularController {
@GetMapping("/techtacular")
public String showTechtacular() {
return "techtacular"; // lädt templates/techtacular.html
}
}
@@ -0,0 +1,67 @@
package com.example.mindflux.controller;
import com.example.mindflux.model.User;
import com.example.mindflux.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
// 📥 User erstellen
@PostMapping
public User createUser(@RequestBody User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
return userRepository.save(user);
}
// 📋 Alle User anzeigen
@GetMapping
public List<User> getAllUsers() {
return userRepository.findAll();
}
// 📍 Einzelnen User anzeigen
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
Optional<User> user = userRepository.findById(id);
return user.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
// ✏️ User aktualisieren
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User updatedUser) {
return userRepository.findById(id).map(user -> {
user.setUsername(updatedUser.getUsername());
if (updatedUser.getPassword() != null && !updatedUser.getPassword().isBlank()) {
user.setPassword(passwordEncoder.encode(updatedUser.getPassword()));
}
user.setEmail(updatedUser.getEmail());
user.setRole(updatedUser.getRole());
return ResponseEntity.ok(userRepository.save(user));
}).orElseGet(() -> ResponseEntity.notFound().build());
}
// 🗑️ User löschen
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
if (userRepository.existsById(id)) {
userRepository.deleteById(id);
return ResponseEntity.noContent().build();
}
return ResponseEntity.notFound().build();
}
}
@@ -0,0 +1,31 @@
package com.example.mindflux.controller;
import com.example.mindflux.model.User;
import com.example.mindflux.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/users")
public class UserViewController {
@Autowired
private UserRepository userRepository;
// Formular anzeigen
@GetMapping("/form")
public String showForm(Model model) {
model.addAttribute("user", new User());
return "user-form";
}
// Benutzer speichern
@PostMapping("/save")
public String saveUser(@ModelAttribute User user) {
userRepository.save(user);
return "user-success"; // statt redirect
}
}
@@ -0,0 +1,24 @@
package com.example.mindflux.dto;
/** Eine Zeile in der TechTacular-Kategorienübersicht (ohne alle Fragen zu laden). */
public class AdminCategoryRow {
private final Long id;
private final String slug;
private final String title;
private final boolean active;
private final long questionCount;
public AdminCategoryRow(Long id, String slug, String title, boolean active, long questionCount) {
this.id = id;
this.slug = slug;
this.title = title;
this.active = active;
this.questionCount = questionCount;
}
public Long getId() { return id; }
public String getSlug() { return slug; }
public String getTitle() { return title; }
public boolean isActive() { return active; }
public long getQuestionCount() { return questionCount; }
}
@@ -0,0 +1,13 @@
package com.example.mindflux.dto;
/** Eine vom Spieler gewählte Antwort. */
public class AnswerRequest {
private Long questionId;
private Long answerId;
public Long getQuestionId() { return questionId; }
public void setQuestionId(Long questionId) { this.questionId = questionId; }
public Long getAnswerId() { return answerId; }
public void setAnswerId(Long answerId) { this.answerId = answerId; }
}
@@ -0,0 +1,21 @@
package com.example.mindflux.dto;
/** Serverseitige Bewertung einer einzelnen Antwort (instant feedback). */
public class AnswerResult {
private final boolean correct;
private final Long correctAnswerId;
private final String explanation;
private final int points;
public AnswerResult(boolean correct, Long correctAnswerId, String explanation, int points) {
this.correct = correct;
this.correctAnswerId = correctAnswerId;
this.explanation = explanation;
this.points = points;
}
public boolean isCorrect() { return correct; }
public Long getCorrectAnswerId() { return correctAnswerId; }
public String getExplanation() { return explanation; }
public int getPoints() { return points; }
}
@@ -0,0 +1,15 @@
package com.example.mindflux.dto;
import java.util.List;
/** Abschluss einer TechTacular-Runde: alle gewählten Antworten + Dauer. Score wird serverseitig recomputed. */
public class FinishRequest {
private int durationSeconds;
private List<AnswerRequest> answers;
public int getDurationSeconds() { return durationSeconds; }
public void setDurationSeconds(int durationSeconds) { this.durationSeconds = durationSeconds; }
public List<AnswerRequest> getAnswers() { return answers; }
public void setAnswers(List<AnswerRequest> answers) { this.answers = answers; }
}
@@ -0,0 +1,25 @@
package com.example.mindflux.dto;
import java.time.LocalDate;
public class GameProgress {
private String name;
private int progressPercent;
private int highscore;
private int plays;
private LocalDate lastPlayed;
// Getter
public String getName() { return name; }
public int getProgressPercent() { return progressPercent; }
public int getHighscore() { return highscore; }
public int getPlays() { return plays; }
public LocalDate getLastPlayed() { return lastPlayed; }
// Setter
public void setName(String name) { this.name = name; }
public void setProgressPercent(int progressPercent) { this.progressPercent = progressPercent; }
public void setHighscore(int highscore) { this.highscore = highscore; }
public void setPlays(int plays) { this.plays = plays; }
public void setLastPlayed(LocalDate lastPlayed) { this.lastPlayed = lastPlayed; }
}
@@ -0,0 +1,20 @@
package com.example.mindflux.dto;
import java.time.LocalDate;
/** Zusammengefasste Statistik eines Spielers für ein Spiel. */
public class GameStats {
private final int highscore;
private final long plays;
private final LocalDate lastPlayed;
public GameStats(int highscore, long plays, LocalDate lastPlayed) {
this.highscore = highscore;
this.plays = plays;
this.lastPlayed = lastPlayed;
}
public int getHighscore() { return highscore; }
public long getPlays() { return plays; }
public LocalDate getLastPlayed() { return lastPlayed; }
}
@@ -0,0 +1,20 @@
package com.example.mindflux.dto;
import java.util.List;
/** Ein aktives Deck mit seinen Paaren fürs Spiel-API. */
public class MindMatchDeckView {
private final String slug;
private final String title;
private final List<MindMatchPairView> pairs;
public MindMatchDeckView(String slug, String title, List<MindMatchPairView> pairs) {
this.slug = slug;
this.title = title;
this.pairs = pairs;
}
public String getSlug() { return slug; }
public String getTitle() { return title; }
public List<MindMatchPairView> getPairs() { return pairs; }
}
@@ -0,0 +1,15 @@
package com.example.mindflux.dto;
/** Ein Paar fürs Spiel-API. Feld heißt "match" (passend zur bisherigen JS-Logik). */
public class MindMatchPairView {
private final String term;
private final String match;
public MindMatchPairView(String term, String match) {
this.term = term;
this.match = match;
}
public String getTerm() { return term; }
public String getMatch() { return match; }
}
@@ -0,0 +1,49 @@
package com.example.mindflux.dto;
public class ProgressData {
private int correctAnswers;
private int currentXp;
private int xpForNextLevel;
private int userLevel;
private int progressPercent;
public int getCorrectAnswers() {
return correctAnswers;
}
public void setCorrectAnswers(int correctAnswers) {
this.correctAnswers = correctAnswers;
}
public int getCurrentXp() {
return currentXp;
}
public void setCurrentXp(int currentXp) {
this.currentXp = currentXp;
}
public int getXpForNextLevel() {
return xpForNextLevel;
}
public void setXpForNextLevel(int xpForNextLevel) {
this.xpForNextLevel = xpForNextLevel;
}
public int getUserLevel() {
return userLevel;
}
public void setUserLevel(int userLevel) {
this.userLevel = userLevel;
}
public int getProgressPercent() {
return progressPercent;
}
public void setProgressPercent(int progressPercent) {
this.progressPercent = progressPercent;
}
}
@@ -0,0 +1,21 @@
package com.example.mindflux.dto;
/** Antwort nach dem Speichern eines Ergebnisses. */
public class ScoreResult {
private final int score;
private final int highscore;
private final boolean newHighscore;
private final long plays;
public ScoreResult(int score, int highscore, boolean newHighscore, long plays) {
this.score = score;
this.highscore = highscore;
this.newHighscore = newHighscore;
this.plays = plays;
}
public int getScore() { return score; }
public int getHighscore() { return highscore; }
public boolean isNewHighscore() { return newHighscore; }
public long getPlays() { return plays; }
}
@@ -0,0 +1,25 @@
package com.example.mindflux.dto;
/**
* 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.
*/
public class ScoreSubmission {
private String deck;
private int pairs;
private int moves;
private int durationSeconds;
public String getDeck() { return deck; }
public void setDeck(String deck) { this.deck = deck; }
public int getPairs() { return pairs; }
public void setPairs(int pairs) { this.pairs = pairs; }
public int getMoves() { return moves; }
public void setMoves(int moves) { this.moves = moves; }
public int getDurationSeconds() { return durationSeconds; }
public void setDurationSeconds(int durationSeconds) { this.durationSeconds = durationSeconds; }
}
@@ -0,0 +1,66 @@
package com.example.mindflux.dto;
import java.util.List;
/**
* Das Jeopardy-Board fürs Frontend: Spalten (Kategorien) mit Kacheln (Fragen).
* Bewusst OHNE Information darüber, welche Antwort korrekt ist – geprüft wird serverseitig.
*/
public class TechtacularBoardView {
private final List<Category> categories;
private final int maxScore;
public TechtacularBoardView(List<Category> categories, int maxScore) {
this.categories = categories;
this.maxScore = maxScore;
}
public List<Category> getCategories() { return categories; }
public int getMaxScore() { return maxScore; }
public static class Category {
private final String title;
private final List<Tile> tiles;
public Category(String title, List<Tile> tiles) {
this.title = title;
this.tiles = tiles;
}
public String getTitle() { return title; }
public List<Tile> getTiles() { return tiles; }
}
public static class Tile {
private final Long questionId;
private final int points;
private final String text;
private final List<Option> answers;
public Tile(Long questionId, int points, String text, List<Option> answers) {
this.questionId = questionId;
this.points = points;
this.text = text;
this.answers = answers;
}
public Long getQuestionId() { return questionId; }
public int getPoints() { return points; }
public String getText() { return text; }
public List<Option> getAnswers() { return answers; }
}
public static class Option {
private final Long id;
private final String text;
public Option(Long id, String text) {
this.id = id;
this.text = text;
}
public Long getId() { return id; }
public String getText() { return text; }
}
}
@@ -0,0 +1,31 @@
package com.example.mindflux.dto;
/** Ergebnis einer abgeschlossenen TechTacular-Runde. */
public class TechtacularResult {
private final int score;
private final int maxScore;
private final int correctCount;
private final int totalCount;
private final int highscore;
private final boolean newHighscore;
private final long plays;
public TechtacularResult(int score, int maxScore, int correctCount, int totalCount,
int highscore, boolean newHighscore, long plays) {
this.score = score;
this.maxScore = maxScore;
this.correctCount = correctCount;
this.totalCount = totalCount;
this.highscore = highscore;
this.newHighscore = newHighscore;
this.plays = plays;
}
public int getScore() { return score; }
public int getMaxScore() { return maxScore; }
public int getCorrectCount() { return correctCount; }
public int getTotalCount() { return totalCount; }
public int getHighscore() { return highscore; }
public boolean isNewHighscore() { return newHighscore; }
public long getPlays() { return plays; }
}
@@ -0,0 +1,51 @@
package com.example.mindflux.model;
import jakarta.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "achievements")
public class Achievement {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String title;
private String description;
private String icon; // Dateiname des Icons (z.B. "star.png")
private LocalDateTime dateAchieved;
// 🔥 Neuer Konstruktor für schnelle Dummy-Erstellung (ohne username/dateAchieved)
public Achievement(String title, String description, String icon, boolean unlocked) {
this.title = title;
this.description = description;
this.icon = icon;
this.dateAchieved = unlocked ? LocalDateTime.now() : null;
}
// ⚡ Standard-Konstruktor (wichtig für JPA!)
public Achievement() {
}
// Getter und Setter
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getIcon() { return icon; }
public void setIcon(String icon) { this.icon = icon; }
public LocalDateTime getDateAchieved() { return dateAchieved; }
public void setDateAchieved(LocalDateTime dateAchieved) { this.dateAchieved = dateAchieved; }
}
@@ -0,0 +1,75 @@
package com.example.mindflux.model;
import jakarta.persistence.*;
import java.time.LocalDateTime;
/**
* Ein einzelnes Spielergebnis. Bewusst generisch gehalten, damit alle
* Mindflux-Spiele (MindMatch, TechTacular, ...) dieselbe Tabelle nutzen können.
* Welches Spiel das Ergebnis erzeugt hat, steht in {@link #game}.
*/
@Entity
@Table(name = "game_scores")
public class GameScore {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
/** Spiel-Kennung, z.B. "mindmatch" (entspricht der Route im Dashboard). */
private String game;
/** Optionales Deck / Level innerhalb des Spiels, z.B. "netzwerk". */
private String deck;
private int score;
/** Anzahl der Zug-Versuche (kann je nach Spiel null sein). */
private Integer moves;
@Column(name = "duration_seconds")
private Integer durationSeconds;
@Column(name = "played_at")
private LocalDateTime playedAt;
public GameScore() {
}
public GameScore(String username, String game, String deck, int score,
Integer moves, Integer durationSeconds, LocalDateTime playedAt) {
this.username = username;
this.game = game;
this.deck = deck;
this.score = score;
this.moves = moves;
this.durationSeconds = durationSeconds;
this.playedAt = playedAt;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getGame() { return game; }
public void setGame(String game) { this.game = game; }
public String getDeck() { return deck; }
public void setDeck(String deck) { this.deck = deck; }
public int getScore() { return score; }
public void setScore(int score) { this.score = score; }
public Integer getMoves() { return moves; }
public void setMoves(Integer moves) { this.moves = moves; }
public Integer getDurationSeconds() { return durationSeconds; }
public void setDurationSeconds(Integer durationSeconds) { this.durationSeconds = durationSeconds; }
public LocalDateTime getPlayedAt() { return playedAt; }
public void setPlayedAt(LocalDateTime playedAt) { this.playedAt = playedAt; }
}
@@ -0,0 +1,63 @@
package com.example.mindflux.model;
import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;
/** Ein MindMatch-Deck (Kategorie) mit seinen Begriffspaaren. Von Admins pflegbar. */
@Entity
@Table(name = "mindmatch_decks")
public class MindMatchDeck {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String slug;
private String title;
@Column(name = "sort_order")
private int sortOrder;
private boolean active = true;
@OneToMany(mappedBy = "deck", cascade = CascadeType.ALL, orphanRemoval = true)
@OrderBy("id ASC")
private List<MindMatchPair> pairs = new ArrayList<>();
public MindMatchDeck() {
}
public MindMatchDeck(String slug, String title, int sortOrder, boolean active) {
this.slug = slug;
this.title = title;
this.sortOrder = sortOrder;
this.active = active;
}
/** Fügt ein Paar hinzu und hält die Rückreferenz konsistent. */
public void addPair(MindMatchPair pair) {
pair.setDeck(this);
this.pairs.add(pair);
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getSlug() { return slug; }
public void setSlug(String slug) { this.slug = slug; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public int getSortOrder() { return sortOrder; }
public void setSortOrder(int sortOrder) { this.sortOrder = sortOrder; }
public boolean isActive() { return active; }
public void setActive(boolean active) { this.active = active; }
public List<MindMatchPair> getPairs() { return pairs; }
public void setPairs(List<MindMatchPair> pairs) { this.pairs = pairs; }
}
@@ -0,0 +1,43 @@
package com.example.mindflux.model;
import jakarta.persistence.*;
/** Ein Begriffspaar innerhalb eines {@link MindMatchDeck}: Begriff + zugehörige Bedeutung. */
@Entity
@Table(name = "mindmatch_pairs")
public class MindMatchPair {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "deck_id")
private MindMatchDeck deck;
private String term;
/** Spalte heißt "match_text", da MATCH in SQL ein reserviertes Wort ist. */
@Column(name = "match_text")
private String matchText;
public MindMatchPair() {
}
public MindMatchPair(String term, String matchText) {
this.term = term;
this.matchText = matchText;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public MindMatchDeck getDeck() { return deck; }
public void setDeck(MindMatchDeck deck) { this.deck = deck; }
public String getTerm() { return term; }
public void setTerm(String term) { this.term = term; }
public String getMatchText() { return matchText; }
public void setMatchText(String matchText) { this.matchText = matchText; }
}
@@ -0,0 +1,49 @@
package com.example.mindflux.model;
import jakarta.persistence.*;
/** Eine Antwortmöglichkeit zu einer {@link TechtacularQuestion}. */
@Entity
@Table(name = "techtacular_answers")
public class TechtacularAnswer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "question_id")
private TechtacularQuestion question;
@Column(name = "answer_text")
private String text;
private boolean correct;
@Column(name = "sort_order")
private int sortOrder;
public TechtacularAnswer() {
}
public TechtacularAnswer(String text, boolean correct, int sortOrder) {
this.text = text;
this.correct = correct;
this.sortOrder = sortOrder;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public TechtacularQuestion getQuestion() { return question; }
public void setQuestion(TechtacularQuestion question) { this.question = question; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
public boolean isCorrect() { return correct; }
public void setCorrect(boolean correct) { this.correct = correct; }
public int getSortOrder() { return sortOrder; }
public void setSortOrder(int sortOrder) { this.sortOrder = sortOrder; }
}
@@ -0,0 +1,62 @@
package com.example.mindflux.model;
import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;
/** Eine TechTacular-Kategorie (Spalte im Jeopardy-Board) mit ihren Fragen. */
@Entity
@Table(name = "techtacular_categories")
public class TechtacularCategory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String slug;
private String title;
@Column(name = "sort_order")
private int sortOrder;
private boolean active = true;
@OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
@OrderBy("points ASC, id ASC")
private List<TechtacularQuestion> questions = new ArrayList<>();
public TechtacularCategory() {
}
public TechtacularCategory(String slug, String title, int sortOrder, boolean active) {
this.slug = slug;
this.title = title;
this.sortOrder = sortOrder;
this.active = active;
}
public void addQuestion(TechtacularQuestion question) {
question.setCategory(this);
this.questions.add(question);
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getSlug() { return slug; }
public void setSlug(String slug) { this.slug = slug; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public int getSortOrder() { return sortOrder; }
public void setSortOrder(int sortOrder) { this.sortOrder = sortOrder; }
public boolean isActive() { return active; }
public void setActive(boolean active) { this.active = active; }
public List<TechtacularQuestion> getQuestions() { return questions; }
public void setQuestions(List<TechtacularQuestion> questions) { this.questions = questions; }
}
@@ -0,0 +1,67 @@
package com.example.mindflux.model;
import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;
/** Eine Frage mit ihren Antwortmöglichkeiten (genau eine ist korrekt) und Punktwert. */
@Entity
@Table(name = "techtacular_questions")
public class TechtacularQuestion {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
private TechtacularCategory category;
@Column(name = "question_text")
private String text;
private String explanation;
private int points;
@OneToMany(mappedBy = "question", cascade = CascadeType.ALL, orphanRemoval = true)
@OrderBy("sortOrder ASC, id ASC")
private List<TechtacularAnswer> answers = new ArrayList<>();
public TechtacularQuestion() {
}
public TechtacularQuestion(String text, String explanation, int points) {
this.text = text;
this.explanation = explanation;
this.points = points;
}
public void addAnswer(TechtacularAnswer answer) {
answer.setQuestion(this);
this.answers.add(answer);
}
/** Liefert die als korrekt markierte Antwort (oder null). */
public TechtacularAnswer correctAnswer() {
return answers.stream().filter(TechtacularAnswer::isCorrect).findFirst().orElse(null);
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public TechtacularCategory getCategory() { return category; }
public void setCategory(TechtacularCategory category) { this.category = category; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
public String getExplanation() { return explanation; }
public void setExplanation(String explanation) { this.explanation = explanation; }
public int getPoints() { return points; }
public void setPoints(int points) { this.points = points; }
public List<TechtacularAnswer> getAnswers() { return answers; }
public void setAnswers(List<TechtacularAnswer> answers) { this.answers = answers; }
}
@@ -0,0 +1,54 @@
package com.example.mindflux.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.persistence.*;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
private String email;
private String role;
@Column(name = "profile_picture")
private String profilePicture;
public User() {}
public User(String username, String password, String email, String role) {
this.username = username;
this.password = password;
this.email = email;
this.role = role;
}
// —————————————
// Getter / Setter
// —————————————
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String u) { this.username = u; }
public String getPassword() { return password; }
public void setPassword(String p) { this.password = p; }
public String getEmail() { return email; }
public void setEmail(String e) { this.email = e; }
public String getRole() { return role; }
public void setRole(String r) { this.role = r; }
public String getProfilePicture() { return profilePicture; }
public void setProfilePicture(String pictureUrl) { this.profilePicture = pictureUrl; }
}
@@ -0,0 +1,10 @@
package com.example.mindflux.repository;
import com.example.mindflux.model.Achievement;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface AchievementRepository extends JpaRepository<Achievement, Long> {
List<Achievement> findByUsername(String username);
}
@@ -0,0 +1,7 @@
package com.example.mindflux.repository;
import com.example.mindflux.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface AdminUserRepository extends JpaRepository<User, Long> {
}
@@ -0,0 +1,18 @@
package com.example.mindflux.repository;
import com.example.mindflux.model.GameScore;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface GameScoreRepository extends JpaRepository<GameScore, Long> {
/** Bestes Ergebnis eines Spielers in einem Spiel (Highscore). */
Optional<GameScore> findTopByUsernameAndGameOrderByScoreDesc(String username, String game);
/** Zuletzt gespieltes Ergebnis eines Spielers in einem Spiel. */
Optional<GameScore> findTopByUsernameAndGameOrderByPlayedAtDesc(String username, String game);
/** Wie oft ein Spieler ein Spiel abgeschlossen hat. */
long countByUsernameAndGame(String username, String game);
}
@@ -0,0 +1,18 @@
package com.example.mindflux.repository;
import com.example.mindflux.model.MindMatchDeck;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
public interface MindMatchDeckRepository extends JpaRepository<MindMatchDeck, Long> {
List<MindMatchDeck> findAllByActiveTrueOrderBySortOrderAscIdAsc();
List<MindMatchDeck> findAllByOrderBySortOrderAscIdAsc();
Optional<MindMatchDeck> findBySlug(String slug);
boolean existsBySlug(String slug);
}
@@ -0,0 +1,7 @@
package com.example.mindflux.repository;
import com.example.mindflux.model.MindMatchPair;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MindMatchPairRepository extends JpaRepository<MindMatchPair, Long> {
}
@@ -0,0 +1,7 @@
package com.example.mindflux.repository;
import com.example.mindflux.model.TechtacularAnswer;
import org.springframework.data.jpa.repository.JpaRepository;
public interface TechtacularAnswerRepository extends JpaRepository<TechtacularAnswer, Long> {
}
@@ -0,0 +1,15 @@
package com.example.mindflux.repository;
import com.example.mindflux.model.TechtacularCategory;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface TechtacularCategoryRepository extends JpaRepository<TechtacularCategory, Long> {
List<TechtacularCategory> findAllByActiveTrueOrderBySortOrderAscIdAsc();
List<TechtacularCategory> findAllByOrderBySortOrderAscIdAsc();
boolean existsBySlug(String slug);
}
@@ -0,0 +1,9 @@
package com.example.mindflux.repository;
import com.example.mindflux.model.TechtacularQuestion;
import org.springframework.data.jpa.repository.JpaRepository;
public interface TechtacularQuestionRepository extends JpaRepository<TechtacularQuestion, Long> {
long countByCategoryId(Long categoryId);
}
@@ -0,0 +1,14 @@
package com.example.mindflux.repository;
import com.example.mindflux.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
// Benutzer anhand des Benutzernamens suchen
Optional<User> findByUsername(String username);
// Benutzer anhand des Benutzernamens löschen
void deleteByUsername(String username);
}
@@ -0,0 +1,9 @@
package com.example.mindflux.service;
import com.example.mindflux.model.Achievement;
import java.util.List;
public interface AchievementService {
List<Achievement> getAchievementsForUser(String username);
}
@@ -0,0 +1,20 @@
package com.example.mindflux.service;
import com.example.mindflux.model.Achievement;
import com.example.mindflux.repository.AchievementRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class AchievementServiceImpl implements AchievementService {
@Autowired
private AchievementRepository achievementRepository;
@Override
public List<Achievement> getAchievementsForUser(String username) {
return achievementRepository.findByUsername(username);
}
}
@@ -0,0 +1,32 @@
package com.example.mindflux.service;
import com.example.mindflux.model.User;
import com.example.mindflux.repository.AdminUserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class AdminUserService {
@Autowired
private AdminUserRepository userRepository;
public List<User> findAll() {
return userRepository.findAll();
}
public Optional<User> findById(Long id) {
return userRepository.findById(id);
}
public User save(User user) {
return userRepository.save(user);
}
public void deleteById(Long id) {
userRepository.deleteById(id);
}
}
@@ -0,0 +1,83 @@
package com.example.mindflux.service;
import com.example.mindflux.dto.GameStats;
import com.example.mindflux.dto.ScoreResult;
import com.example.mindflux.dto.ScoreSubmission;
import com.example.mindflux.model.GameScore;
import com.example.mindflux.repository.GameScoreRepository;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
/**
* Speichert Spielergebnisse und liefert Statistiken. Der Score wird hier
* autoritativ berechnet – der Client schickt nur die Rohwerte.
*/
@Service
public class GameScoreService {
private final GameScoreRepository repository;
public GameScoreService(GameScoreRepository repository) {
this.repository = repository;
}
/**
* Punktezahl aus Genauigkeit und Tempo.
* <ul>
* <li>Genauigkeit: {@code 1000 * Paare / Züge} – 1000 Punkte bei perfektem Spiel
* (jeder Aufdeck-Versuch ein Treffer), weniger bei vielen Fehlversuchen.</li>
* <li>Zeitbonus: bis zu 500 Punkte, der nach 100 Sekunden auf 0 fällt.</li>
* </ul>
* Identisch in {@code mindmatch.js} gespiegelt, damit der Client sofort denselben
* Wert anzeigt.
*/
public static int calculateScore(int pairs, int moves, int durationSeconds) {
if (pairs <= 0) {
return 0;
}
int safeMoves = Math.max(moves, pairs); // niemals weniger Züge als Paare
int safeSeconds = Math.max(durationSeconds, 0);
int accuracyScore = Math.round(1000f * pairs / safeMoves);
int timeBonus = Math.max(0, 500 - safeSeconds * 5);
return accuracyScore + timeBonus;
}
/** Speichert ein abgeschlossenes MindMatch-Spiel (Score aus Rohwerten berechnet). */
public ScoreResult record(String username, String game, ScoreSubmission submission) {
int moves = Math.max(submission.getMoves(), submission.getPairs());
int seconds = Math.max(submission.getDurationSeconds(), 0);
int score = calculateScore(submission.getPairs(), moves, seconds);
return recordScore(username, game, submission.getDeck(), score, moves, seconds);
}
/** Speichert ein bereits berechnetes Ergebnis (z.B. TechTacular) und liefert den Highscore-Stand. */
public ScoreResult recordScore(String username, String game, String deck,
int score, Integer moves, Integer durationSeconds) {
GameScore previousBest = repository
.findTopByUsernameAndGameOrderByScoreDesc(username, game)
.orElse(null);
repository.save(new GameScore(
username, game, deck, score, moves, durationSeconds, LocalDateTime.now()));
boolean newHighscore = previousBest == null || score > previousBest.getScore();
int highscore = previousBest == null ? score : Math.max(previousBest.getScore(), score);
long plays = repository.countByUsernameAndGame(username, game);
return new ScoreResult(score, highscore, newHighscore, plays);
}
/** Aktuelle Statistik (Highscore, Anzahl Spiele, zuletzt gespielt) für ein Spiel. */
public GameStats getStats(String username, String game) {
int highscore = repository.findTopByUsernameAndGameOrderByScoreDesc(username, game)
.map(GameScore::getScore)
.orElse(0);
long plays = repository.countByUsernameAndGame(username, game);
var lastPlayed = repository.findTopByUsernameAndGameOrderByPlayedAtDesc(username, game)
.map(score -> score.getPlayedAt().toLocalDate())
.orElse(null);
return new GameStats(highscore, plays, lastPlayed);
}
}
@@ -0,0 +1,102 @@
package com.example.mindflux.service;
import com.example.mindflux.dto.MindMatchDeckView;
import com.example.mindflux.dto.MindMatchPairView;
import com.example.mindflux.model.MindMatchDeck;
import com.example.mindflux.model.MindMatchPair;
import com.example.mindflux.repository.MindMatchDeckRepository;
import com.example.mindflux.repository.MindMatchPairRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class MindMatchService {
private final MindMatchDeckRepository deckRepository;
private final MindMatchPairRepository pairRepository;
public MindMatchService(MindMatchDeckRepository deckRepository, MindMatchPairRepository pairRepository) {
this.deckRepository = deckRepository;
this.pairRepository = pairRepository;
}
// ---------- Spieler ----------
/** Aktive Decks mit Paaren fürs Spiel (open-in-view=false -> hier initialisiert). */
@Transactional(readOnly = true)
public List<MindMatchDeckView> activeDecks() {
return deckRepository.findAllByActiveTrueOrderBySortOrderAscIdAsc().stream()
.map(deck -> new MindMatchDeckView(
deck.getSlug(),
deck.getTitle(),
deck.getPairs().stream()
.map(pair -> new MindMatchPairView(pair.getTerm(), pair.getMatchText()))
.toList()))
.toList();
}
// ---------- Admin ----------
/** Alle Decks (auch inaktive) inkl. Paaren für die Verwaltung. */
@Transactional(readOnly = true)
public List<MindMatchDeck> allDecksWithPairs() {
List<MindMatchDeck> decks = deckRepository.findAllByOrderBySortOrderAscIdAsc();
decks.forEach(deck -> deck.getPairs().size()); // Lazy-Collection laden
return decks;
}
@Transactional
public void createDeck(String title) {
if (title == null || title.isBlank()) {
return;
}
String clean = title.trim();
int order = (int) deckRepository.count() + 1;
deckRepository.save(new MindMatchDeck(uniqueSlug(clean), clean, order, true));
}
@Transactional
public void addPair(Long deckId, String term, String match) {
if (term == null || term.isBlank() || match == null || match.isBlank()) {
return;
}
deckRepository.findById(deckId).ifPresent(deck -> {
deck.addPair(new MindMatchPair(term.trim(), match.trim()));
deckRepository.save(deck);
});
}
@Transactional
public void deletePair(Long pairId) {
pairRepository.deleteById(pairId);
}
@Transactional
public void deleteDeck(Long deckId) {
deckRepository.deleteById(deckId);
}
@Transactional
public void toggleDeck(Long deckId) {
deckRepository.findById(deckId).ifPresent(deck -> deck.setActive(!deck.isActive()));
}
/** Erzeugt aus dem Titel einen URL-tauglichen, eindeutigen Slug. */
private String uniqueSlug(String title) {
String base = title.toLowerCase()
.replace("ä", "ae").replace("ö", "oe").replace("ü", "ue").replace("ß", "ss")
.replaceAll("[^a-z0-9]+", "-")
.replaceAll("(^-|-$)", "");
if (base.isBlank()) {
base = "deck";
}
String slug = base;
int counter = 2;
while (deckRepository.existsBySlug(slug)) {
slug = base + "-" + counter++;
}
return slug;
}
}
@@ -0,0 +1,66 @@
package com.example.mindflux.service;
import com.example.mindflux.dto.GameProgress;
import com.example.mindflux.dto.GameStats;
import com.example.mindflux.dto.ProgressData;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Service
public class ProgressService {
/** Spiel-Kennung -> Anzeigename, in Dashboard-Reihenfolge. */
private static final Map<String, String> GAMES = new LinkedHashMap<>();
static {
GAMES.put("mindmatch", "MindMatch");
GAMES.put("techtacular", "TechTacular");
GAMES.put("syntax-saga", "Syntax Saga");
GAMES.put("escape-room", "Code Escape Room");
GAMES.put("service-clash", "Service Clash");
GAMES.put("bug-hunter", "Bug Hunter");
GAMES.put("netzwerker", "Netzwerker");
}
private final GameScoreService gameScoreService;
public ProgressService(GameScoreService gameScoreService) {
this.gameScoreService = gameScoreService;
}
public ProgressData getOverallProgress(String username) {
// Dummy-Daten – später aus DB berechnen
ProgressData data = new ProgressData();
data.setCorrectAnswers(432);
data.setCurrentXp(2400);
data.setXpForNextLevel(3000);
data.setUserLevel(5);
data.setProgressPercent(78);
return data;
}
/** Echter Spiel-Fortschritt pro Spiel aus der Datenbank (game_scores). */
public List<GameProgress> getGameProgress(String username) {
List<GameProgress> result = new ArrayList<>();
for (Map.Entry<String, String> entry : GAMES.entrySet()) {
GameStats stats = gameScoreService.getStats(username, entry.getKey());
result.add(toGameProgress(entry.getValue(), stats));
}
return result;
}
private GameProgress toGameProgress(String displayName, GameStats stats) {
GameProgress game = new GameProgress();
game.setName(displayName);
game.setHighscore(stats.getHighscore());
game.setPlays((int) stats.getPlays());
game.setLastPlayed(stats.getLastPlayed());
// Fortschritt auf der Score-Skala (max ~1500 Punkte -> 100 %).
game.setProgressPercent(Math.min(100, Math.round(stats.getHighscore() / 15.0f)));
return game;
}
}
@@ -0,0 +1,227 @@
package com.example.mindflux.service;
import com.example.mindflux.dto.*;
import com.example.mindflux.model.TechtacularAnswer;
import com.example.mindflux.model.TechtacularCategory;
import com.example.mindflux.model.TechtacularQuestion;
import com.example.mindflux.repository.TechtacularCategoryRepository;
import com.example.mindflux.repository.TechtacularQuestionRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class TechtacularService {
private static final String GAME = "techtacular";
private static final int BOARD_CATEGORIES = 5;
private static final int[] POINT_TIERS = {100, 200, 300, 400, 500};
private final TechtacularCategoryRepository categoryRepository;
private final TechtacularQuestionRepository questionRepository;
private final GameScoreService gameScoreService;
private final Random random = new Random();
public TechtacularService(TechtacularCategoryRepository categoryRepository,
TechtacularQuestionRepository questionRepository,
GameScoreService gameScoreService) {
this.categoryRepository = categoryRepository;
this.questionRepository = questionRepository;
this.gameScoreService = gameScoreService;
}
// ---------- Spieler ----------
/**
* Baut ein zufälliges Board: bis zu 5 Kategorien, je eine Frage pro Punktstufe (100–500).
* Fragen aus {@code exclude} (z.B. die Vorrunde) werden möglichst vermieden, damit jede
* Runde mit überwiegend neuen Fragen gefüllt wird.
*/
@Transactional(readOnly = true)
public TechtacularBoardView buildBoard(Set<Long> exclude) {
Set<Long> excluded = (exclude == null) ? Set.of() : exclude;
List<TechtacularCategory> usable = categoryRepository.findAllByActiveTrueOrderBySortOrderAscIdAsc()
.stream().filter(c -> !c.getQuestions().isEmpty()).collect(Collectors.toList());
Collections.shuffle(usable, random);
List<TechtacularCategory> chosen = usable.stream().limit(BOARD_CATEGORIES).toList();
List<TechtacularBoardView.Category> columns = new ArrayList<>();
int maxScore = 0;
for (TechtacularCategory category : chosen) {
Map<Integer, List<TechtacularQuestion>> byPoints = category.getQuestions().stream()
.collect(Collectors.groupingBy(TechtacularQuestion::getPoints));
List<TechtacularBoardView.Tile> tiles = new ArrayList<>();
for (int value : POINT_TIERS) {
List<TechtacularQuestion> pool = byPoints.get(value);
if (pool == null || pool.isEmpty()) {
continue;
}
// Bevorzugt Fragen, die nicht in der Vorrunde dran waren.
List<TechtacularQuestion> fresh = pool.stream()
.filter(q -> !excluded.contains(q.getId())).collect(Collectors.toList());
List<TechtacularQuestion> pick = fresh.isEmpty() ? pool : fresh;
TechtacularQuestion question = pick.get(random.nextInt(pick.size()));
List<TechtacularBoardView.Option> options = question.getAnswers().stream()
.map(a -> new TechtacularBoardView.Option(a.getId(), a.getText()))
.collect(Collectors.toList());
Collections.shuffle(options, random);
tiles.add(new TechtacularBoardView.Tile(question.getId(), value, question.getText(), options));
maxScore += value;
}
if (!tiles.isEmpty()) {
columns.add(new TechtacularBoardView.Category(category.getTitle(), tiles));
}
}
return new TechtacularBoardView(columns, maxScore);
}
/** Bewertet eine einzelne Antwort serverseitig (Frontend kennt die richtige Antwort vorher nicht). */
@Transactional(readOnly = true)
public AnswerResult checkAnswer(Long questionId, Long answerId) {
TechtacularQuestion question = questionRepository.findById(questionId).orElse(null);
if (question == null) {
return new AnswerResult(false, null, "", 0);
}
TechtacularAnswer correct = question.correctAnswer();
Long correctId = correct != null ? correct.getId() : null;
boolean isCorrect = correctId != null && correctId.equals(answerId);
return new AnswerResult(isCorrect, correctId, question.getExplanation(), isCorrect ? question.getPoints() : 0);
}
/** Schließt eine Runde ab: Score wird aus den tatsächlichen Antworten recomputed und gespeichert. */
@Transactional
public TechtacularResult finish(String username, FinishRequest request) {
int score = 0;
int maxScore = 0;
int correctCount = 0;
int total = 0;
if (request.getAnswers() != null) {
for (AnswerRequest answer : request.getAnswers()) {
if (answer.getQuestionId() == null) {
continue;
}
TechtacularQuestion question = questionRepository.findById(answer.getQuestionId()).orElse(null);
if (question == null) {
continue;
}
total++;
maxScore += question.getPoints();
TechtacularAnswer correct = question.correctAnswer();
if (correct != null && correct.getId().equals(answer.getAnswerId())) {
score += question.getPoints();
correctCount++;
}
}
}
int seconds = Math.max(request.getDurationSeconds(), 0);
ScoreResult saved = gameScoreService.recordScore(username, GAME, null, score, correctCount, seconds);
return new TechtacularResult(score, maxScore, correctCount, total,
saved.getHighscore(), saved.isNewHighscore(), saved.getPlays());
}
// ---------- Admin ----------
/** Leichte Übersicht: Kategorien mit Fragenanzahl (lädt nicht alle Fragen). */
@Transactional(readOnly = true)
public List<AdminCategoryRow> categoryRows() {
return categoryRepository.findAllByOrderBySortOrderAscIdAsc().stream()
.map(c -> new AdminCategoryRow(c.getId(), c.getSlug(), c.getTitle(), c.isActive(),
questionRepository.countByCategoryId(c.getId())))
.toList();
}
/** Eine Kategorie inkl. Fragen und Antworten (für die Detailseite). */
@Transactional(readOnly = true)
public TechtacularCategory categoryWithQuestions(Long id) {
TechtacularCategory category = categoryRepository.findById(id).orElse(null);
if (category != null) {
category.getQuestions().forEach(q -> q.getAnswers().size()); // lazy init
}
return category;
}
@Transactional
public void createCategory(String title) {
if (title == null || title.isBlank()) {
return;
}
String clean = title.trim();
int order = (int) categoryRepository.count() + 1;
categoryRepository.save(new TechtacularCategory(uniqueSlug(clean), clean, order, true));
}
@Transactional
public void addQuestion(Long categoryId, String text, String explanation, int points,
List<String> answers, int correctIndex) {
if (text == null || text.isBlank() || answers == null) {
return;
}
categoryRepository.findById(categoryId).ifPresent(category -> {
TechtacularQuestion question = new TechtacularQuestion(
text.trim(),
(explanation == null || explanation.isBlank()) ? null : explanation.trim(),
normalizePoints(points));
int sort = 0;
boolean hasCorrect = false;
for (int i = 0; i < answers.size(); i++) {
String answerText = answers.get(i);
if (answerText == null || answerText.isBlank()) {
continue;
}
boolean isCorrect = (i == correctIndex);
hasCorrect = hasCorrect || isCorrect;
question.addAnswer(new TechtacularAnswer(answerText.trim(), isCorrect, sort++));
}
// Nur sinnvolle Fragen speichern: mind. 2 Antworten und genau die markierte ist korrekt.
if (question.getAnswers().size() >= 2 && hasCorrect) {
category.addQuestion(question);
categoryRepository.save(category);
}
});
}
@Transactional
public void deleteQuestion(Long questionId) {
questionRepository.deleteById(questionId);
}
@Transactional
public void deleteCategory(Long categoryId) {
categoryRepository.deleteById(categoryId);
}
@Transactional
public void toggleCategory(Long categoryId) {
categoryRepository.findById(categoryId).ifPresent(c -> c.setActive(!c.isActive()));
}
private int normalizePoints(int points) {
int rounded = Math.round(points / 100f) * 100;
return Math.max(100, Math.min(500, rounded));
}
private String uniqueSlug(String title) {
String base = title.toLowerCase()
.replace("ä", "ae").replace("ö", "oe").replace("ü", "ue").replace("ß", "ss")
.replaceAll("[^a-z0-9]+", "-")
.replaceAll("(^-|-$)", "");
if (base.isBlank()) {
base = "kategorie";
}
String slug = base;
int counter = 2;
while (categoryRepository.existsBySlug(slug)) {
slug = base + "-" + counter++;
}
return slug;
}
}
@@ -0,0 +1,9 @@
package com.example.mindflux.service;
import com.example.mindflux.model.User;
public interface UserService {
User findByUsername(String username);
void save(User user);
void deleteByUsername(String username); // <-- Nur die Methodensignatur, ohne {}
}
@@ -0,0 +1,28 @@
package com.example.mindflux.service;
import com.example.mindflux.model.User;
import com.example.mindflux.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
public User findByUsername(String username) {
return userRepository.findByUsername(username).orElse(null); // <-- Optional aufgelöst
}
@Override
public void save(User user) {
userRepository.save(user);
}
@Override
public void deleteByUsername(String username) {
userRepository.deleteByUsername(username);
}
}
@@ -1,19 +0,0 @@
package com.mindflux;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class HelloController {
@GetMapping("/")
public Map<String, String> hello() {
return Map.of(
"app", "mindflux",
"status", "running",
"message", "Hello from Spring Boot 🚀, automatic deploy is working. :-)"
);
}
}
+40 -5
View File
@@ -1,8 +1,43 @@
server.port=8080 # =======================
# Datenbankverbindung (PostgreSQL)
# =======================
spring.datasource.url=jdbc:postgresql://postgres:5432/mindfluxdb spring.config.import=optional:file:.env[.properties]
spring.datasource.username=mindflux spring.datasource.url=${MINDFLUX_DB_URL:jdbc:postgresql://localhost:5433/mindflux_db}
spring.datasource.password=mindflux_pass spring.datasource.username=${MINDFLUX_DB_USER:mindflux_user}
spring.datasource.password=${MINDFLUX_DB_PASSWORD}
spring.datasource.driver-class-name=org.postgresql.Driver
# =======================
# Hibernate / JPA
# =======================
spring.jpa.hibernate.ddl-auto=validate spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=true spring.jpa.show-sql=false
spring.jpa.open-in-view=false
# =======================
# Datenbank-Migrationen
# =======================
spring.flyway.enabled=true
# =======================
# SQL-Initialisierung
# =======================
spring.sql.init.mode=never
# =======================
# Server
# =======================
server.port=8282
server.forward-headers-strategy=framework
server.servlet.session.cookie.secure=${MINDFLUX_COOKIE_SECURE:false}
server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.same-site=lax
spring.output.ansi.enabled=ALWAYS
# Öffentliche Basis-URL, z. B. für künftig erzeugte absolute Links.
mindflux.public-url=${MINDFLUX_PUBLIC_URL:http://localhost:8282}
@@ -16,5 +16,4 @@ CREATE TABLE achievements (
date_achieved TIMESTAMP date_achieved TIMESTAMP
); );
CREATE INDEX idx_achievements_username CREATE INDEX idx_achievements_username ON achievements (username);
ON achievements (username);
@@ -0,0 +1,14 @@
-- Generische Spiel-Ergebnistabelle für alle Mindflux-Spiele
-- (MindMatch ist das erste Spiel, das hier schreibt – die übrigen docken später an).
CREATE TABLE game_scores (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
username VARCHAR(100) NOT NULL,
game VARCHAR(50) NOT NULL,
deck VARCHAR(100),
score INT NOT NULL,
moves INT,
duration_seconds INT,
played_at TIMESTAMP NOT NULL
);
CREATE INDEX idx_game_scores_user_game ON game_scores (username, game);
@@ -0,0 +1,68 @@
-- MindMatch-Inhalte in die DB verlagern, damit Admins Decks (Kategorien) und
-- Paare jederzeit zur Laufzeit erweitern können (vorher statische JSON-Dateien).
CREATE TABLE mindmatch_decks (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
slug VARCHAR(100) NOT NULL UNIQUE,
title VARCHAR(150) NOT NULL,
sort_order INT NOT NULL DEFAULT 0,
active BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE mindmatch_pairs (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
deck_id BIGINT NOT NULL REFERENCES mindmatch_decks (id) ON DELETE CASCADE,
term VARCHAR(255) NOT NULL,
match_text VARCHAR(255) NOT NULL
);
CREATE INDEX idx_mindmatch_pairs_deck ON mindmatch_pairs (deck_id);
-- Seed: die bisherigen 3 Decks plus das neue SQL-Befehle-Deck.
INSERT INTO mindmatch_decks (slug, title, sort_order, active) VALUES
('netzwerk', 'Netzwerk-Grundlagen', 1, TRUE),
('hardware', 'Hardware & Komponenten', 2, TRUE),
('it-sicherheit', 'IT-Sicherheit', 3, TRUE),
('sql-befehle', 'SQL-Befehle', 4, TRUE);
INSERT INTO mindmatch_pairs (deck_id, term, match_text)
SELECT d.id, v.term, v.match_text
FROM mindmatch_decks d
JOIN (
VALUES
('netzwerk', 'DNS', 'Löst Namen in IP-Adressen auf'),
('netzwerk', 'DHCP', 'Vergibt IP-Adressen automatisch'),
('netzwerk', 'Router', 'Verbindet verschiedene Netze'),
('netzwerk', 'Switch', 'Verbindet Geräte im LAN'),
('netzwerk', 'Gateway', 'Übergang in ein anderes Netz'),
('netzwerk', 'Firewall', 'Filtert den Netzwerkverkehr'),
('netzwerk', 'MAC-Adresse', 'Physische Hardware-Adresse'),
('netzwerk', 'Subnetzmaske', 'Trennt Netz- und Hostanteil'),
('hardware', 'CPU', 'Zentrale Recheneinheit'),
('hardware', 'RAM', 'Flüchtiger Arbeitsspeicher'),
('hardware', 'SSD', 'Schneller Flash-Speicher'),
('hardware', 'GPU', 'Grafikprozessor'),
('hardware', 'Mainboard', 'Hauptplatine des Rechners'),
('hardware', 'BIOS', 'Startet die Hardware'),
('hardware', 'Netzteil', 'Wandelt die Netzspannung um'),
('hardware', 'Cache', 'Schneller Zwischenspeicher der CPU'),
('it-sicherheit', 'Phishing', 'Täuschung zum Datenklau'),
('it-sicherheit', 'Verschlüsselung', 'Macht Daten unlesbar'),
('it-sicherheit', 'Backup', 'Sicherungskopie von Daten'),
('it-sicherheit', 'VPN', 'Verschlüsselter Tunnel'),
('it-sicherheit', '2FA', 'Zwei-Faktor-Anmeldung'),
('it-sicherheit', 'Malware', 'Schädliche Software'),
('it-sicherheit', 'Patch', 'Schließt Sicherheitslücken'),
('it-sicherheit', 'Brute Force', 'Systematisches Passwortraten'),
('sql-befehle', 'SELECT', 'Daten abfragen'),
('sql-befehle', 'INSERT', 'Datensatz einfügen'),
('sql-befehle', 'UPDATE', 'Datensatz ändern'),
('sql-befehle', 'DELETE', 'Datensatz löschen'),
('sql-befehle', 'WHERE', 'Zeilen filtern'),
('sql-befehle', 'JOIN', 'Tabellen verknüpfen'),
('sql-befehle', 'GROUP BY', 'Zeilen gruppieren'),
('sql-befehle', 'ORDER BY', 'Ergebnis sortieren')
) AS v(slug, term, match_text) ON v.slug = d.slug;
@@ -0,0 +1,152 @@
-- Weitere MindMatch-Decks aus dem Dokument "Memory-Kategorien – Fachinformatiker".
-- 5 Themen-Decks + die Oberkategorie "Abkürzungen" als 6 eigene Decks (Deck-Modell ist flach).
INSERT INTO mindmatch_decks (slug, title, sort_order, active) VALUES
('betriebssysteme', 'Betriebssysteme', 5, TRUE),
('programmierung', 'Programmierung', 6, TRUE),
('webentwicklung', 'Webentwicklung', 7, TRUE),
('softwareentwicklung', 'Softwareentwicklung', 8, TRUE),
('cloud-devops', 'Cloud & DevOps', 9, TRUE),
('abk-netzwerk', 'Abkürzungen: Netzwerk', 10, TRUE),
('abk-hardware', 'Abkürzungen: Hardware', 11, TRUE),
('abk-programmierung-datenformate','Abkürzungen: Programmierung & Datenformate', 12, TRUE),
('abk-web', 'Abkürzungen: Web', 13, TRUE),
('abk-it-sicherheit', 'Abkürzungen: IT-Sicherheit', 14, TRUE),
('abk-betriebssysteme-devops', 'Abkürzungen: Betriebssysteme & DevOps', 15, TRUE);
INSERT INTO mindmatch_pairs (deck_id, term, match_text)
SELECT d.id, v.term, v.match_text
FROM mindmatch_decks d
JOIN (
VALUES
-- Betriebssysteme
('betriebssysteme', 'Windows', 'Microsoft'),
('betriebssysteme', 'Linux', 'Open Source'),
('betriebssysteme', 'Kernel', 'Betriebssystemkern'),
('betriebssysteme', 'Prozess', 'Laufendes Programm'),
('betriebssysteme', 'Thread', 'Ausführungsstrang'),
('betriebssysteme', 'Dateisystem', 'Datenorganisation'),
('betriebssysteme', 'NTFS', 'Windows-Dateisystem'),
('betriebssysteme', 'ext4', 'Linux-Dateisystem'),
('betriebssysteme', 'Shell', 'Kommandozeile'),
('betriebssysteme', 'Terminal', 'Konsolenzugriff'),
('betriebssysteme', 'Dienst', 'Hintergrundprozess'),
('betriebssysteme', 'Rechte', 'Zugriffskontrolle'),
-- Programmierung
('programmierung', 'Variable', 'Speicherplatz'),
('programmierung', 'Schleife', 'Wiederholung'),
('programmierung', 'Array', 'Feld'),
('programmierung', 'Objekt', 'Instanz'),
('programmierung', 'Klasse', 'Bauplan'),
('programmierung', 'Methode', 'Funktion'),
('programmierung', 'Interface', 'Vertrag'),
('programmierung', 'Vererbung', 'OOP-Konzept'),
('programmierung', 'Exception', 'Fehlerbehandlung'),
('programmierung', 'Compiler', 'Übersetzer'),
('programmierung', 'Debugger', 'Fehlersuche'),
('programmierung', 'Git', 'Versionsverwaltung'),
-- Webentwicklung
('webentwicklung', 'HTML', 'Struktur'),
('webentwicklung', 'CSS', 'Gestaltung'),
('webentwicklung', 'JavaScript', 'Interaktivität'),
('webentwicklung', 'DOM', 'Dokumentenmodell'),
('webentwicklung', 'API', 'Programmierschnittstelle'),
('webentwicklung', 'JSON', 'Datenaustausch'),
('webentwicklung', 'REST', 'Architekturstil'),
('webentwicklung', 'HTTP', 'Übertragungsprotokoll'),
('webentwicklung', 'Cookie', 'Browserdaten'),
('webentwicklung', 'Session', 'Benutzersitzung'),
('webentwicklung', 'Backend', 'Serverlogik'),
('webentwicklung', 'Frontend', 'Benutzeroberfläche'),
-- Softwareentwicklung
('softwareentwicklung', 'Scrum', 'Agiles Vorgehen'),
('softwareentwicklung', 'Sprint', 'Entwicklungszyklus'),
('softwareentwicklung', 'Product Owner', 'Produktverantwortung'),
('softwareentwicklung', 'Kanban', 'Aufgabenfluss'),
('softwareentwicklung', 'UML', 'Modellierung'),
('softwareentwicklung', 'Use Case', 'Anwendungsfall'),
('softwareentwicklung', 'ER-Modell', 'Datenmodell'),
('softwareentwicklung', 'Testfall', 'Prüfung'),
('softwareentwicklung', 'CI/CD', 'Automatisierte Auslieferung'),
('softwareentwicklung', 'Refactoring', 'Codeverbesserung'),
('softwareentwicklung', 'Repository', 'Quellcodeablage'),
('softwareentwicklung', 'Merge', 'Zusammenführen'),
-- Cloud & DevOps
('cloud-devops', 'Docker', 'Container'),
('cloud-devops', 'Kubernetes', 'Container-Orchestrierung'),
('cloud-devops', 'VM', 'Virtuelle Maschine'),
('cloud-devops', 'Cloud', 'Ressourcen aus dem Internet'),
('cloud-devops', 'Azure', 'Microsoft Cloud'),
('cloud-devops', 'AWS', 'Amazon Web Services'),
('cloud-devops', 'GitHub', 'Codehosting'),
('cloud-devops', 'Jenkins', 'CI-Server'),
('cloud-devops', 'Pipeline', 'Automatisierung'),
('cloud-devops', 'YAML', 'Konfigurationsdatei'),
('cloud-devops', 'Deployment', 'Bereitstellung'),
('cloud-devops', 'Monitoring', 'Überwachung'),
-- Abkürzungen: Netzwerk
('abk-netzwerk', 'DNS', 'Domain Name System'),
('abk-netzwerk', 'DHCP', 'Dynamic Host Configuration Protocol'),
('abk-netzwerk', 'IP', 'Internet Protocol'),
('abk-netzwerk', 'TCP', 'Transmission Control Protocol'),
('abk-netzwerk', 'UDP', 'User Datagram Protocol'),
('abk-netzwerk', 'FTP', 'File Transfer Protocol'),
('abk-netzwerk', 'SSH', 'Secure Shell'),
('abk-netzwerk', 'VPN', 'Virtual Private Network'),
-- Abkürzungen: Hardware
('abk-hardware', 'CPU', 'Central Processing Unit'),
('abk-hardware', 'GPU', 'Graphics Processing Unit'),
('abk-hardware', 'RAM', 'Random Access Memory'),
('abk-hardware', 'ROM', 'Read Only Memory'),
('abk-hardware', 'SSD', 'Solid State Drive'),
('abk-hardware', 'HDD', 'Hard Disk Drive'),
('abk-hardware', 'PSU', 'Power Supply Unit'),
('abk-hardware', 'BIOS', 'Basic Input/Output System'),
('abk-hardware', 'UEFI', 'Unified Extensible Firmware Interface'),
('abk-hardware', 'PCIe', 'Peripheral Component Interconnect Express'),
-- Abkürzungen: Programmierung & Datenformate
('abk-programmierung-datenformate', 'SQL', 'Structured Query Language'),
('abk-programmierung-datenformate', 'IDE', 'Integrated Development Environment'),
('abk-programmierung-datenformate', 'SDK', 'Software Development Kit'),
('abk-programmierung-datenformate', 'OOP', 'Object Oriented Programming'),
('abk-programmierung-datenformate', 'CRUD', 'Create Read Update Delete'),
('abk-programmierung-datenformate', 'MVC', 'Model View Controller'),
('abk-programmierung-datenformate', 'ORM', 'Object Relational Mapping'),
('abk-programmierung-datenformate', 'XML', 'Extensible Markup Language'),
-- Abkürzungen: Web
('abk-web', 'URL', 'Uniform Resource Locator'),
('abk-web', 'URI', 'Uniform Resource Identifier'),
('abk-web', 'CDN', 'Content Delivery Network'),
('abk-web', 'SOAP', 'Simple Object Access Protocol'),
('abk-web', 'JWT', 'JSON Web Token'),
('abk-web', 'CORS', 'Cross Origin Resource Sharing'),
-- Abkürzungen: IT-Sicherheit
('abk-it-sicherheit', 'MFA', 'Multi Factor Authentication'),
('abk-it-sicherheit', '2FA', 'Two Factor Authentication'),
('abk-it-sicherheit', 'TLS', 'Transport Layer Security'),
('abk-it-sicherheit', 'AES', 'Advanced Encryption Standard'),
('abk-it-sicherheit', 'RSA', 'Rivest Shamir Adleman'),
('abk-it-sicherheit', 'PKI', 'Public Key Infrastructure'),
('abk-it-sicherheit', 'IDS', 'Intrusion Detection System'),
('abk-it-sicherheit', 'IPS', 'Intrusion Prevention System'),
('abk-it-sicherheit', 'SIEM', 'Security Information and Event Management'),
('abk-it-sicherheit', 'CVE', 'Common Vulnerabilities and Exposures'),
-- Abkürzungen: Betriebssysteme & DevOps
('abk-betriebssysteme-devops', 'CLI', 'Command Line Interface'),
('abk-betriebssysteme-devops', 'GUI', 'Graphical User Interface'),
('abk-betriebssysteme-devops', 'VM', 'Virtual Machine'),
('abk-betriebssysteme-devops', 'CI', 'Continuous Integration'),
('abk-betriebssysteme-devops', 'CD', 'Continuous Delivery'),
('abk-betriebssysteme-devops', 'LTS', 'Long Term Support'),
('abk-betriebssysteme-devops', 'FQDN', 'Fully Qualified Domain Name')
) AS v(slug, term, match_text) ON v.slug = d.slug;
@@ -0,0 +1,30 @@
-- TechTacular: IT-Quizduell im Jeopardy-Stil. Inhalte DB-basiert und Admin-pflegbar.
-- Kategorie -> Fragen -> Antworten (genau eine korrekt). Gefüllt wird per Seeder
-- aus den vorhandenen FinalFlux-Fragenkatalogen (siehe TechtacularSeeder).
CREATE TABLE techtacular_categories (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
slug VARCHAR(150) NOT NULL UNIQUE,
title VARCHAR(200) NOT NULL,
sort_order INT NOT NULL DEFAULT 0,
active BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE techtacular_questions (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
category_id BIGINT NOT NULL REFERENCES techtacular_categories (id) ON DELETE CASCADE,
question_text VARCHAR(1000) NOT NULL,
explanation VARCHAR(1000),
points INT NOT NULL DEFAULT 100
);
CREATE TABLE techtacular_answers (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
question_id BIGINT NOT NULL REFERENCES techtacular_questions (id) ON DELETE CASCADE,
answer_text VARCHAR(500) NOT NULL,
correct BOOLEAN NOT NULL DEFAULT FALSE,
sort_order INT NOT NULL DEFAULT 0
);
CREATE INDEX idx_techtacular_questions_cat ON techtacular_questions (category_id);
CREATE INDEX idx_techtacular_answers_q ON techtacular_answers (question_id);
@@ -0,0 +1,4 @@
-- Umstellung auf das echte Jeopardy-Format (umgekehrt) mit kuratierten Inhalten.
-- Der bisherige (templatehafte) FinalFlux-Import wird geleert; der TechtacularSeeder
-- befüllt anschließend neu aus techtacular/content.json.
TRUNCATE TABLE techtacular_categories RESTART IDENTITY CASCADE;
@@ -0,0 +1,165 @@
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;
+273
View File
@@ -0,0 +1,273 @@
body.finalflux-shell,
body.finalflux-page {
background: var(--bg-glow), var(--bg);
color: var(--text);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.finalflux-shell {
min-height: 100vh;
display: grid;
grid-template-columns: 320px minmax(0, 1fr);
}
.finalflux-sidebar {
background: var(--surface);
border-right: 1px solid var(--border-accent);
padding: 1.5rem;
overflow-y: auto;
}
.finalflux-sidebar h1,
.finalflux-main h2,
.finalflux-main h3,
.finalflux-page h1,
.finalflux-page h2,
.finalflux-page h3 {
color: var(--accent);
}
.finalflux-sidebar p,
.finalflux-main p,
.finalflux-page p,
.finalflux-page li {
line-height: 1.65;
}
.finalflux-back,
.finalflux-page .back-link {
display: inline-block;
margin-bottom: 1rem;
padding: 0.65rem 1rem;
background: var(--accent);
color: var(--accent-ink);
border-radius: 8px;
font-weight: 700;
text-decoration: none;
}
.finalflux-nav section {
margin-top: 1.5rem;
}
.finalflux-nav h2 {
color: var(--accent-2);
font-size: 1rem;
margin-bottom: 0.7rem;
}
.finalflux-nav a {
display: block;
color: var(--muted);
text-decoration: none;
padding: 0.42rem 0.6rem;
border-radius: 6px;
}
.finalflux-nav a:hover {
background: var(--surface-2);
color: var(--accent);
}
.finalflux-main {
padding: 2rem;
max-width: 1100px;
}
.finalflux-hero,
.finalflux-panel,
.finalflux-page section {
background: var(--surface);
border-left: 4px solid var(--accent);
border-radius: 8px;
padding: 1.25rem;
margin-bottom: 1.5rem;
}
.finalflux-hero h2 {
font-size: 2rem;
margin: 0.4rem 0 1rem;
}
.eyebrow {
color: var(--accent-2);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.finalflux-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1rem;
margin-bottom: 1.5rem;
}
.finalflux-grid article,
.fluxcheck {
background: var(--surface);
border: 1px solid var(--border-accent);
border-radius: 8px;
padding: 1rem;
}
.finalflux-actions {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-top: 1rem;
}
.finalflux-actions a,
.fluxcheck button {
background: var(--accent);
color: var(--accent-ink);
border: 0;
border-radius: 8px;
cursor: pointer;
font-weight: 700;
padding: 0.65rem 1rem;
text-decoration: none;
}
.finalflux-page {
padding: 2rem;
}
.finalflux-page main {
display: block;
max-width: 980px;
margin: 0 auto;
padding: 0;
}
.finalflux-page code {
background: var(--surface-2);
border-radius: 4px;
color: var(--accent-2);
padding: 0.15rem 0.35rem;
}
.key-list {
padding-left: 1.2rem;
}
.fluxcheck label {
display: block;
margin: 0.5rem 0;
}
.fluxcheck-result {
font-weight: 700;
margin-top: 0.8rem;
}
.fluxcheck-result.correct {
color: #00ff88;
}
.fluxcheck-result.wrong {
color: #ff6b6b;
}
.finalflux-test {
background: var(--surface);
border-left: 4px solid var(--accent);
border-radius: 8px;
padding: 1.25rem;
margin-bottom: 1.5rem;
}
.test-meta {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
justify-content: space-between;
color: var(--accent-2);
font-weight: 700;
margin-bottom: 1rem;
}
.category-block {
background: var(--surface-2);
border: 1px solid var(--border-accent);
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
}
.category-block h2 {
margin: 0.2rem 0 0.5rem;
}
.question-card {
background: var(--bg);
border-radius: 8px;
padding: 1rem;
}
.answer-options {
display: grid;
gap: 0.65rem;
margin: 1rem 0;
}
.answer-options label {
align-items: flex-start;
background: var(--surface);
border: 1px solid var(--border-accent);
border-radius: 8px;
cursor: pointer;
display: flex;
gap: 0.65rem;
padding: 0.75rem;
}
.answer-options label:hover {
border-color: var(--accent);
}
.test-actions {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-top: 1rem;
}
.test-actions button,
[data-restart-test] {
background: var(--accent);
border: 0;
border-radius: 8px;
color: var(--accent-ink);
cursor: pointer;
font-weight: 700;
padding: 0.65rem 1rem;
}
.test-actions button.secondary {
background: var(--surface-2);
border: 1px solid var(--border-accent);
color: var(--text);
}
.test-actions button:disabled {
cursor: not-allowed;
opacity: 0.45;
}
@media (max-width: 820px) {
.finalflux-shell {
grid-template-columns: 1fr;
}
.finalflux-sidebar {
border-right: 0;
border-bottom: 1px solid var(--border-accent);
}
.finalflux-main,
.finalflux-page {
padding: 1rem;
}
}
+351
View File
@@ -0,0 +1,351 @@
/* === MindMatch === Farben über zentrale Theme-Tokens (theme.css):
Dark = Grün-Neon, Light = heller Grund + Magenta. Layout: feste Viewport-Höhe,
kein Scrollen – das Spielfeld füllt den verbleibenden Platz und skaliert die Karten. */
/* Overlay-Einblendung (kam früher aus style.css). */
@keyframes fadeInUp {
0% { opacity: 0; transform: translateY(20px); }
100% { opacity: 1; transform: translateY(0); }
}
/* body bekommt Layout (Shell füllt 100vh über flex:1). Farben/Hintergrund liefert theme.css. */
body.mm-body {
height: 100vh;
min-height: 100vh;
overflow: hidden; /* Seite scrollt nie */
display: flex;
flex-direction: column;
color: var(--text);
}
.mm-shell {
flex: 1; /* body ist flex-column -> Shell füllt 100vh */
min-height: 0;
display: flex;
flex-direction: column;
padding: 0.6rem 0.85rem;
gap: 0.55rem;
}
/* --- Kopfzeile (kompakt) --- */
.mm-header {
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.5rem 1.1rem;
background-color: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
box-shadow: var(--g-soft), 0 0 8px var(--glow-w);
}
.mm-header-tools {
display: flex;
align-items: center;
gap: 0.7rem;
}
.mm-title {
font-size: 1.3rem;
color: var(--accent);
margin: 0;
text-shadow: 0 0 8px var(--glow-s);
}
.mm-back, .mm-logout { text-decoration: none; font-weight: bold; }
.mm-back { color: var(--accent); }
.mm-logout { color: var(--danger); }
/* --- Leiste: Steuerung + Werte --- */
.mm-bar {
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 0.55rem 0.85rem;
}
.mm-bar-group {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.5rem;
}
.mm-field {
display: flex;
align-items: center;
gap: 0.45rem;
font-weight: 700;
color: var(--accent);
}
.mm-field select {
padding: 0.5rem 0.7rem;
border-radius: 8px;
border: 1px solid var(--border-accent);
background: var(--surface-2);
color: var(--text);
font-size: 0.95rem;
min-width: 200px;
}
.mm-btn {
padding: 0.5rem 1.1rem;
border: 0;
border-radius: 8px;
background: var(--accent);
color: var(--accent-ink);
font-weight: 700;
font-size: 0.95rem;
cursor: pointer;
text-decoration: none;
display: inline-block;
box-shadow: 0 0 14px var(--glow-w);
transition: filter 0.25s ease, transform 0.15s ease, background-color 0.25s ease;
}
.mm-btn:hover { filter: brightness(1.08); transform: translateY(-2px); }
.mm-btn-secondary {
background: var(--surface-2);
border: 1px solid var(--border-accent);
color: var(--text);
box-shadow: none;
}
/* Abbrechen-Button (rötlich, klar als Abbruch erkennbar) */
.mm-btn-cancel {
background: rgba(255, 107, 107, 0.14);
border: 1px solid rgba(255, 107, 107, 0.55);
color: var(--danger);
box-shadow: none;
}
.mm-btn-cancel:hover {
background: rgba(255, 107, 107, 0.28);
color: #ffffff;
}
/* Damit das hidden-Attribut den display:inline-block der .mm-btn überstimmt */
.mm-btn[hidden] { display: none; }
/* --- HUD als kompakte Chips --- */
.mm-hud { gap: 0.4rem; }
.mm-stat {
display: inline-flex;
align-items: baseline;
gap: 0.4rem;
padding: 0.35rem 0.7rem;
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: 10px;
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted);
white-space: nowrap;
}
.mm-stat b { font-size: 1.05rem; color: var(--accent); }
/* --- Spielfeld: füllt den Rest, kein Scrollen --- */
.mm-stage {
flex: 1;
min-height: 0;
display: flex;
justify-content: center;
overflow: hidden;
}
.mm-board {
display: grid;
gap: clamp(0.3rem, 1.2vmin, 0.75rem);
width: 100%;
height: 100%;
min-height: 0;
margin: 0 auto;
/* grid-template-columns + max-width werden per JS gesetzt;
gleich hohe Reihen über grid-auto-rows, damit alles in die Höhe passt. */
grid-auto-rows: 1fr;
}
.mm-idle {
margin: auto;
color: var(--muted);
text-align: center;
padding: 2rem 1rem;
font-size: 1rem;
}
/* --- Karte (füllt ihre Zelle) --- */
.mm-card {
position: relative;
min-width: 0;
min-height: 0;
perspective: 900px;
cursor: pointer;
border: 0;
background: transparent;
padding: 0;
}
.mm-card-inner {
position: absolute;
inset: 0;
transform-style: preserve-3d;
transition: transform 0.45s ease;
}
.mm-card.flipped .mm-card-inner,
.mm-card.matched .mm-card-inner {
transform: rotateY(180deg);
}
.mm-card-face {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 0.4rem;
border-radius: 10px;
overflow: hidden;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
}
/* Rückseite (verdeckt): leuchtendes Mindflux-Emblem statt ⚡ */
.mm-card-front {
background: linear-gradient(145deg, var(--surface), var(--surface-2));
border: 1px solid var(--border-accent);
box-shadow: inset 0 0 18px var(--glow-w);
font-size: 0; /* versteckt das alte ⚡-Zeichen */
}
.mm-card-front::after {
content: "";
display: block;
width: 56%;
height: 56%;
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 9px var(--glow-s));
}
/* Vorderseite (Inhalt) */
.mm-card-back {
background: var(--surface);
border: 1px solid var(--border-accent);
transform: rotateY(180deg);
flex-direction: column;
gap: 0.2rem;
color: var(--text);
font-weight: 600;
line-height: 1.2;
hyphens: auto;
}
.mm-card-back.mm-term {
color: var(--accent);
font-size: clamp(0.72rem, 1.7vw, 1.15rem);
font-weight: 800;
}
.mm-card-back.mm-def {
font-size: clamp(0.62rem, 1.25vw, 0.9rem);
}
.mm-kind {
font-size: clamp(0.5rem, 1vw, 0.65rem);
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted);
opacity: 0.9;
}
.mm-text {
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
}
.mm-card:not(.matched):hover .mm-card-front {
border-color: var(--accent);
box-shadow: 0 0 14px var(--glow-s), inset 0 0 18px var(--glow-w);
}
.mm-card.matched { cursor: default; }
.mm-card.matched .mm-card-back {
border-color: #00ff88;
box-shadow: 0 0 14px rgba(0, 255, 136, 0.3);
animation: mm-pop 0.4s ease;
}
@keyframes mm-pop {
0% { transform: rotateY(180deg) scale(1); }
50% { transform: rotateY(180deg) scale(1.06); }
100% { transform: rotateY(180deg) scale(1); }
}
.mm-card.wrong .mm-card-back {
border-color: #ff6b6b;
box-shadow: 0 0 14px rgba(255, 107, 107, 0.3);
}
/* --- Ergebnis-Overlay --- */
.mm-result {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-glow), rgba(0, 0, 0, 0.55);
backdrop-filter: blur(4px);
z-index: 1000;
}
.mm-result[hidden] { display: none; }
.mm-result-card {
background: var(--surface);
border: 1px solid var(--border-accent);
border-radius: 18px;
padding: 2rem 2.5rem;
text-align: center;
max-width: 420px;
width: 90%;
box-shadow: 0 0 30px var(--glow-w);
animation: fadeInUp 0.5s ease-out;
}
.mm-result-card h2 { color: var(--accent); margin-bottom: 0.5rem; }
.mm-stars { font-size: 1.8rem; letter-spacing: 0.2rem; margin-bottom: 0.5rem; color: var(--accent); }
.mm-result-score { font-size: 1.3rem; margin-bottom: 0.3rem; color: var(--text); }
.mm-result-score strong { color: var(--accent); }
.mm-result-detail { color: var(--muted); font-size: 0.95rem; margin-bottom: 0.6rem; }
.mm-result-highscore { color: #00ff88; font-weight: 800; margin-bottom: 1rem; }
.mm-result-highscore[hidden] { display: none; }
.mm-result-actions {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
justify-content: center;
margin-top: 1rem;
}
/* --- Mobil --- */
@media (max-width: 600px) {
.mm-header { padding: 0.45rem 0.8rem; }
.mm-title { font-size: 1.15rem; }
.mm-field select { min-width: 150px; }
.mm-stat { font-size: 0.65rem; padding: 0.3rem 0.55rem; }
.mm-stat b { font-size: 0.95rem; }
}
+184
View File
@@ -0,0 +1,184 @@
/* === 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;
+207
View File
@@ -0,0 +1,207 @@
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;
}
@@ -0,0 +1,78 @@
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;
}
}
+551
View File
@@ -0,0 +1,551 @@
/* === 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, #1f1f2e, #2c2c3d);
color: #ffffff;
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: #000916;
border-radius: 20px;
box-shadow:
0 8px 20px rgba(0, 229, 255, 0.15),
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 rgba(0, 229, 255, 0.25),
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: #00e5ff;
font-size: 1.1rem;
letter-spacing: 0.5px;
text-shadow: 0 0 5px rgba(0, 229, 255, 0.2);
}
/* === 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 rgba(0, 229, 255, 0.05),
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 rgba(0, 229, 255, 0.07),
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: #66d9ef;
}
.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: #ffffff;
color: #000;
}
/* === Button mit Glanz- und Licht-Effekt === */
.login-box button {
background-color: #00e5ff;
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 rgba(0, 229, 255, 0.3);
}
.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: #4cc3dd;
transform: scale(1.02);
box-shadow: 0 0 15px rgba(0, 229, 255, 0.4), 0 0 30px rgba(0, 229, 255, 0.1) inset;
}
/* === 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;
}
.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: #888;
transition: 0.4s;
border-radius: 30px;
box-shadow: 0 0 6px rgba(0, 229, 255, 0.3);
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: #fff;
transition: 0.4s;
border-radius: 50%;
}
/* Bei aktiviertem Switch */
input:checked + .slider {
background-color: #00e5ff;
color: #fff;
}
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: #00e5ff;
font-weight: bold;
font-size: 1.2rem;
box-shadow:
0 0 10px rgba(0, 229, 255, 0.1),
0 4px 12px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease-in-out;
}
.dashboard-tile:hover {
background-color: rgba(0, 229, 255, 0.1);
transform: translateY(-3px);
box-shadow:
0 6px 16px rgba(0, 229, 255, 0.2),
0 0 20px rgba(0, 229, 255, 0.1) inset;
}
.dashboard-tile span {
color: #00e5ff;
text-shadow: 0 0 5px rgba(0, 229, 255, 0.2);
}
/* === 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 rgba(0, 229, 255, 0.1);
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: #00e5ff;
}
.profile-form input {
padding: 0.75rem;
background-color: #1a1a2e;
color: #fff;
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: #00e5ff;
color: #00172e;
}
.btn.save:hover {
background-color: #4cc3dd;
}
.btn.danger {
background-color: #ff4d4d;
color: white;
}
.btn.danger:hover {
background-color: #cc0000;
}
/* 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: rgba(0, 229, 255, 0.1);
color: #00e5ff;
border-radius: 12px;
text-decoration: none;
font-weight: bold;
transition: all 0.3s ease-in-out;
box-shadow: 0 2px 6px rgba(0, 229, 255, 0.1);
}
.tab-nav .tab:hover {
background-color: rgba(0, 229, 255, 0.25);
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(0, 229, 255, 0.2);
}
.tab-nav .tab.active {
background-color: #00e5ff;
color: #000;
}
.success-message {
background-color: #00d9ff;
color: #0d1117;
padding: 1rem;
margin: 1rem auto;
max-width: 600px;
border-radius: 10px;
text-align: center;
font-weight: bold;
box-shadow: 0 0 10px rgba(0, 217, 255, 0.5);
}
.success-message {
background-color: #00d9ff;
color: #0d1117;
padding: 1rem;
margin: 1rem auto;
max-width: 600px;
border-radius: 10px;
text-align: center;
font-weight: bold;
box-shadow: 0 0 10px rgba(0, 217, 255, 0.5);
opacity: 1;
transition: opacity 1.5s ease-out;
}
.back-button {
position: absolute;
top: 20px;
left: 20px;
background-color: #00e5ff;
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 {
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);
}
@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: #00e5ff;
color: #0d1117;
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 rgba(0, 229, 255, 0.3);
}
.admin-btn:hover {
background-color: #4cc3dd;
box-shadow: 0 0 15px rgba(0, 229, 255, 0.6), 0 0 25px rgba(0, 229, 255, 0.4);
transform: scale(1.05);
color: #00172e;
}
</style>
.admin-actions {
display: flex;
flex-direction: column;
gap: 1rem;
margin-top: 1rem;
}
.admin-card {
display: block;
background-color: rgba(0, 229, 255, 0.1);
padding: 1rem 1.5rem;
border-radius: 12px;
color: #00e5ff;
text-decoration: none;
font-weight: bold;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(0, 229, 255, 0.05);
}
.admin-card:hover {
background-color: rgba(0, 229, 255, 0.25);
transform: translateY(-2px);
box-shadow: 0 6px 18px rgba(0, 229, 255, 0.15);
}
.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: #00e5ff;
color: #0d1117;
border: none;
border-radius: 10px;
font-weight: bold;
text-decoration: none;
box-shadow: 0 4px 10px rgba(0, 229, 255, 0.2);
transition: background-color 0.3s, transform 0.2s;
}
.btn-create:hover {
background-color: #00bcd4;
transform: translateY(-2px);
}
@@ -0,0 +1,264 @@
/* === TechTacular === Jeopardy-Board, Farben über zentrale Theme-Tokens (theme.css):
Dark = Grün-Neon, Light = heller Grund + Magenta. Feste Viewport-Höhe (kein Scrollen). */
/* Overlay-Einblendung (kam früher aus style.css). */
@keyframes fadeInUp {
0% { opacity: 0; transform: translateY(20px); }
100% { opacity: 1; transform: translateY(0); }
}
/* body bekommt Layout (Shell füllt 100vh über flex:1). Farben/Hintergrund liefert theme.css. */
body.tt-body {
height: 100vh;
min-height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
color: var(--text);
}
.tt-shell {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
padding: 0.6rem 0.85rem;
gap: 0.55rem;
}
/* --- Kopf + Leiste --- */
.tt-header {
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.5rem 1.1rem;
background-color: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
box-shadow: var(--g-soft), 0 0 8px var(--glow-w);
}
.tt-header-tools {
display: flex;
align-items: center;
gap: 0.7rem;
}
.tt-title { font-size: 1.3rem; color: var(--accent); margin: 0; text-shadow: 0 0 8px var(--glow-s); }
.tt-back, .tt-logout { text-decoration: none; font-weight: bold; }
.tt-back { color: var(--accent); }
.tt-logout { color: var(--danger); }
.tt-bar {
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 0.55rem 0.85rem;
}
.tt-bar-group { display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem; }
.tt-btn {
padding: 0.5rem 1.1rem;
border: 0;
border-radius: 8px;
background: var(--accent);
color: var(--accent-ink);
font-weight: 700;
font-size: 0.95rem;
cursor: pointer;
text-decoration: none;
display: inline-block;
box-shadow: 0 0 14px var(--glow-w);
transition: filter 0.25s ease, transform 0.15s ease, background-color 0.25s ease;
}
.tt-btn:hover { filter: brightness(1.08); transform: translateY(-2px); }
.tt-btn[hidden] { display: none; }
.tt-btn-secondary {
background: var(--surface-2);
border: 1px solid var(--border-accent);
color: var(--text);
box-shadow: none;
}
.tt-btn-cancel {
background: rgba(255, 107, 107, 0.14);
border: 1px solid rgba(255, 107, 107, 0.55);
color: var(--danger);
box-shadow: none;
}
.tt-btn-cancel:hover { background: rgba(255, 107, 107, 0.28); color: #fff; }
.tt-hud { gap: 0.4rem; }
.tt-stat {
display: inline-flex;
align-items: baseline;
gap: 0.4rem;
padding: 0.35rem 0.7rem;
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: 10px;
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted);
white-space: nowrap;
}
.tt-stat b { font-size: 1.05rem; color: var(--accent); }
/* --- Board --- */
.tt-stage {
flex: 1;
min-height: 0;
display: flex;
justify-content: center;
overflow: hidden;
}
.tt-board {
display: grid;
gap: clamp(0.3rem, 1vmin, 0.7rem);
width: 100%;
height: 100%;
min-height: 0;
/* grid-template-columns wird per JS gesetzt (eine Spalte je Kategorie) */
}
.tt-col {
display: flex;
flex-direction: column;
gap: clamp(0.3rem, 1vmin, 0.7rem);
min-height: 0;
}
.tt-cat-title {
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 0.5rem 0.4rem;
background: var(--surface-2);
border: 1px solid var(--border-accent);
border-radius: 8px;
color: var(--accent);
font-weight: 800;
font-size: clamp(0.7rem, 1.1vw, 0.95rem);
line-height: 1.15;
hyphens: auto;
}
.tt-tile {
flex: 1;
min-height: 0;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid var(--border-accent);
border-radius: 8px;
background: linear-gradient(145deg, var(--surface), var(--surface-2));
color: var(--accent);
font-weight: 800;
font-size: clamp(1rem, 2.2vw, 1.8rem);
cursor: pointer;
transition: transform 0.12s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.tt-tile:hover:not(.answered) {
border-color: var(--accent);
box-shadow: 0 0 14px var(--glow-s), inset 0 0 18px var(--glow-w);
transform: translateY(-2px);
}
.tt-tile.answered {
cursor: default;
font-size: clamp(1.2rem, 3vw, 2rem);
}
.tt-tile.answered.correct { border-color: #00ff88; color: #00ff88; background: rgba(0, 255, 136, 0.08); }
.tt-tile.answered.wrong { border-color: #ff6b6b; color: #ff6b6b; background: rgba(255, 107, 107, 0.08); }
/* --- Overlays --- */
.tt-overlay {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-glow), rgba(0, 0, 0, 0.55);
backdrop-filter: blur(4px);
z-index: 1000;
padding: 1rem;
}
.tt-overlay[hidden] { display: none; }
.tt-q-card, .tt-result-card {
background: var(--surface);
border: 1px solid var(--border-accent);
border-radius: 16px;
padding: 1.5rem 1.8rem;
width: min(640px, 94vw);
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 0 30px var(--glow-w);
animation: fadeInUp 0.4s ease-out;
}
.tt-q-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.6rem;
}
.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-instruction { color: var(--muted); font-size: 0.85rem; font-weight: 700; margin-bottom: 0.35rem; }
.tt-q-text {
color: var(--heading); font-size: 1.3rem; margin-bottom: 1.1rem; line-height: 1.4;
border-left: 3px solid var(--accent); padding-left: 0.75rem; font-style: italic;
}
.tt-q-answers { display: grid; gap: 0.6rem; }
.tt-answer {
text-align: left;
padding: 0.75rem 1rem;
border: 1px solid var(--border-accent);
border-radius: 10px;
background: var(--surface-2);
color: var(--text);
font-size: 1rem;
cursor: pointer;
transition: border-color 0.2s ease, background 0.2s ease;
}
.tt-answer:hover:not(:disabled) { border-color: var(--accent); background: var(--surface); }
.tt-answer:disabled { cursor: default; }
.tt-answer.correct { border-color: #00ff88; background: rgba(0, 255, 136, 0.12); color: #b8ffdc; }
.tt-answer.wrong { border-color: #ff6b6b; background: rgba(255, 107, 107, 0.12); color: #ffd0d0; }
.tt-q-feedback { margin-top: 0.9rem; font-weight: 700; min-height: 1.2rem; }
.tt-q-feedback.correct { color: #00ff88; }
.tt-q-feedback.wrong { color: #ff6b6b; }
.tt-q-actions { display: flex; justify-content: flex-end; margin-top: 1rem; }
.tt-result-card { text-align: center; width: min(440px, 94vw); }
.tt-result-card h2 { color: var(--accent); margin-bottom: 0.6rem; }
.tt-result-score { font-size: 1.4rem; margin-bottom: 0.4rem; color: var(--text); }
.tt-result-score strong { color: var(--accent); }
.tt-result-detail { color: var(--muted); margin-bottom: 0.6rem; }
.tt-result-high { color: #00ff88; font-weight: 800; margin-bottom: 1rem; }
.tt-result-high[hidden] { display: none; }
.tt-result-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: center; margin-top: 1rem; }
.tt-idle { margin: auto; color: var(--muted); text-align: center; padding: 2rem 1rem; }
@media (max-width: 600px) {
.tt-title { font-size: 1.15rem; }
.tt-stat { font-size: 0.62rem; padding: 0.3rem 0.5rem; }
.tt-q-text { font-size: 1.05rem; }
}
+170
View File
@@ -0,0 +1,170 @@
/* ============================================================
MINDFLUX THEME – zentrale Design-Tokens + Komponenten
Dark = Grün-Neon | Light = heller Grund + Magenta-Glow
Umschaltung über <html data-theme="dark|light"> (siehe theme.js)
============================================================ */
:root, [data-theme="dark"] {
--bg:#06090a; --bg-glow: radial-gradient(900px 420px at 60% -10%, rgba(43,217,106,.10), transparent 60%);
--panel:#0c1110; --panel-2:#0e1513; --surface:#0b100e; --surface-2:#101713;
--border:#1a2520; --line:#16201b;
--border-accent:rgba(43,217,106,.45); --accent:#2bd96a; --accent-2:#6ef58f; --accent-ink:#04140a;
--text:#d7e0d9; --muted:#717c74; --heading:#ffffff; --danger:#ff5f6b;
--glow-s:rgba(43,217,106,.55); --glow-w:rgba(43,217,106,.16);
--g-soft:0 0 18px var(--glow-w);
}
[data-theme="light"] {
--bg:#f4eef3; --bg-glow: radial-gradient(900px 420px at 60% -10%, rgba(209,29,146,.10), transparent 60%);
--panel:#ffffff; --panel-2:#fbf6fa; --surface:#ffffff; --surface-2:#faf4f9;
--border:#e9dee8; --line:#efe6ee;
--border-accent:rgba(209,29,146,.40); --accent:#d11d92; --accent-2:#ff5bc0; --accent-ink:#ffffff;
--text:#3c2c39; --muted:#8a7d88; --heading:#1b1019; --danger:#d12942;
--glow-s:rgba(209,29,146,.40); --glow-w:rgba(209,29,146,.12);
--g-soft:0 6px 18px var(--glow-w);
}
* { box-sizing:border-box; margin:0; padding:0; }
html,body { height:100%; }
body {
font-family:"Inter","Segoe UI",system-ui,-apple-system,Roboto,sans-serif;
background:var(--bg-glow), var(--bg); color:var(--text);
min-height:100vh; transition:background-color .35s ease,color .35s ease;
}
a { color:var(--accent); }
::-webkit-scrollbar{width:10px;height:10px} ::-webkit-scrollbar-thumb{background:var(--line);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; }
.mf-shell.no-right { grid-template-columns:264px 1fr; }
.mf-sidebar { grid-column:1; grid-row:1 / 3; background:var(--panel); border-right:1px solid var(--border); padding:1.2rem 1rem; display:flex; flex-direction:column; gap:1.1rem; }
.mf-topbar { grid-column:2 / 4; grid-row:1; display:flex; align-items:flex-start; gap:1rem; padding:1.4rem 1.6rem 0; }
.mf-shell.no-right .mf-topbar { grid-column:2; }
.mf-main { grid-column:2; grid-row:2; padding:1.3rem 1.2rem 1.6rem 1.6rem; min-width:0; }
.mf-rightbar { grid-column:3; grid-row:2; padding:1.3rem 1.6rem 1.6rem 0; display:flex; flex-direction:column; gap:1rem; }
/* ===== Logo ===== */
.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); }
.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 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 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; }
.mf-meta { font-size:.74rem; }
.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 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 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 p { color:var(--muted); margin-top:.35rem; font-size:.95rem; }
.mf-pulse { flex:1; min-width:0; display:flex; justify-content:center; align-items:center; }
.mf-pulse svg { width:200px; max-width:100%; filter:drop-shadow(0 0 7px var(--glow-s)); }
.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-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; }
/* ===== 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; }
.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 .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-play svg { width:14px; height:14px; }
/* ===== 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 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); }
/* ===== 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: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; }
.mf-btn.outline:hover { background:var(--surface-2); }
.mf-btn.ghost { background:var(--surface-2); color:var(--text); border:1px solid var(--border); box-shadow:none; }
.mf-btn.danger { background:var(--danger); color:#fff; box-shadow:none; }
/* ===== Formulare ===== */
.mf-field { display:flex; flex-direction:column; gap:.35rem; margin-bottom:.9rem; }
.mf-label { color:var(--accent); font-weight:700; font-size:.82rem; }
.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::placeholder { color:var(--muted); }
/* ===== Tabellen ===== */
.mf-table { width:100%; border-collapse:collapse; }
.mf-table th, .mf-table td { text-align:left; padding:.7rem .8rem; border-bottom:1px solid var(--border); font-size:.9rem; }
.mf-table th { color:var(--accent); text-transform:uppercase; font-size:.72rem; letter-spacing:.08em; }
.mf-table tr:hover td { background:var(--surface-2); }
/* ===== Badges ===== */
.mf-badge { font-size:.68rem; text-transform:uppercase; letter-spacing:.05em; padding:.18rem .5rem; border-radius:7px; background:var(--accent-dim,rgba(43,217,106,.12)); color:var(--accent); border:1px solid var(--border-accent); }
/* ===== 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-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; }
/* schwebender Theme-Umschalter für Seiten ohne Topbar */
.mf-theme-float { position:fixed; top:16px; right:16px; z-index:1000; }
/* ===== Fußzeile (Support / AGB / Impressum …) ===== */
.mf-footer {
display:flex; flex-wrap:wrap; justify-content:center; align-items:center;
gap:.35rem 1rem; padding:1.1rem 1.5rem;
border-top:1px solid var(--border); background:var(--panel);
font-size:.82rem; color:var(--muted);
}
.mf-footer a { color:var(--muted); text-decoration:none; position:relative; }
.mf-footer a:hover { color:var(--accent); }
.mf-footer a + a::before { content:"·"; position:absolute; left:-.6rem; color:var(--border); }
@media (max-width:1180px){ .mf-shell{grid-template-columns:240px 1fr;} .mf-rightbar{display:none;} .mf-topbar{grid-column:2;} .mf-main{grid-column:2;} }
@media (max-width:820px){ .mf-shell{grid-template-columns:1fr;} .mf-sidebar{display:none;} .mf-topbar,.mf-main{grid-column:1;} .mf-grid{grid-template-columns:repeat(2,1fr);} }
@media (max-width:560px){ .mf-grid{grid-template-columns:1fr;} }
@@ -0,0 +1,73 @@
<!DOCTYPE html>
<html lang="de" data-theme="dark">
<head>
<meta charset="UTF-8">
<title>Datenschutz - FinalFlux 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">
<link rel="stylesheet" href="/css/finalflux.css">
<script src="/js/theme.js"></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 FinalFlux</a>
<h1>Datenschutz & Datensicherheit</h1>
<section>
<h2>Prüfungsmodus</h2>
<p>
Dieser FluxCheck trainiert Datenschutz so, wie er in AP1-Handlungssituationen
auftaucht: personenbezogene Daten erkennen, DSGVO-Grundsätze anwenden,
Betroffenenrechte einordnen, Schutzmaßnahmen auswählen und Praxisfälle bewerten.
</p>
</section>
<section>
<h2>Merksatz</h2>
<p>
Datenschutz schützt Menschen hinter den Daten. Datensicherheit schützt die Daten
technisch und organisatorisch. In der Prüfung musst du meistens beides zusammen denken.
</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>Datenschutz</h2>
<p data-category-hint>Personenbezogene Daten, Rechte und Schutzmaßnahmen.</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/datenschutz.json" defer></script>
</body>
</html>
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="de" data-theme="dark">
<head>
<meta charset="UTF-8">
<title>IP-Adressberechnung - FinalFlux 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">
<link rel="stylesheet" href="/css/finalflux.css">
<script src="/js/theme.js"></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 FinalFlux</a>
<h1>IP-Adressberechnung</h1>
<section>
<h2>Lernfokus</h2>
<p>Trainiere IPv4, CIDR, Netzadresse, Broadcast und Hostanzahlen.</p>
<ul class="key-list">
<li>CIDR und Subnetzmaske zusammenbringen.</li>
<li>Netzadresse, Broadcast und Hostbereich bestimmen.</li>
<li>Typische Subnetting-Fehler vermeiden.</li>
</ul>
</section>
<section>
<h2>Prüfungsmodus</h2>
<p>Dieser FluxCheck führt dich durch die wichtigsten AP1-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>IP-Adressberechnung</h2>
<p data-category-hint>AP1-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/ap1_ip-berechnung.json" defer></script>
</body>
</html>
@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="de" data-theme="dark">
<head>
<meta charset="UTF-8">
<title>Projektmanagement - FinalFlux 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">
<link rel="stylesheet" href="/css/finalflux.css">
<script src="/js/theme.js"></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 FinalFlux</a>
<h1>Projektmanagement in der AP1</h1>
<section>
<h2>Prüfungsmodus</h2>
<p>
Dieser FluxCheck führt dich durch prüfungsrelevante Projektmanagement-Themen:
Auftrag, Ziele, Anforderungen, Planung, Risiken, Qualität, Kommunikation,
Dokumentation und Abnahme. Jede Frage gehört zu einer Kategorie, die oben im
Themenblock angezeigt wird.
</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>Projektstart</h2>
<p data-category-hint>Auftrag, Ausgangslage und Ziel verstehen.</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/projektmanagement.json" defer></script>
</body>
</html>
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="de" data-theme="dark">
<head>
<meta charset="UTF-8">
<title>Zahlensysteme & Speicher - FinalFlux 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">
<link rel="stylesheet" href="/css/finalflux.css">
<script src="/js/theme.js"></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 FinalFlux</a>
<h1>Zahlensysteme & Speicher</h1>
<section>
<h2>Lernfokus</h2>
<p>Trainiere Binär-, Dezimal- und Hexadezimalumrechnung sowie Speichereinheiten für typische AP1-Rechenaufgaben.</p>
<ul class="key-list">
<li>Binär, Dezimal und Hexadezimal sicher unterscheiden.</li>
<li>Bit, Byte, KiB, MiB und GiB sauber umrechnen.</li>
<li>Rechenwege nachvollziehbar dokumentieren.</li>
</ul>
</section>
<section>
<h2>Prüfungsmodus</h2>
<p>Dieser FluxCheck führt dich durch die wichtigsten AP1-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>Zahlensysteme & Speicher</h2>
<p data-category-hint>AP1-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/ap1_zahlensysteme.json" defer></script>
</body>
</html>
@@ -0,0 +1,365 @@
{
"title": "IP-Adressberechnung",
"questions": [
{
"category": "IPv4-Grundlagen",
"hint": "Adressaufbau, Oktette und Masken verstehen.",
"text": "Was beschreibt IPv4-Adresse am besten?",
"answers": [
"Sie besteht aus 4 Oktetten mit Werten von 0 bis 255.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Sie besteht aus 4 Oktetten mit Werten von 0 bis 255."
},
{
"category": "IPv4-Grundlagen",
"hint": "Adressaufbau, Oktette und Masken verstehen.",
"text": "Welche Aussage zu Oktett ist richtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Ein Oktett umfasst 8 Bit.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "Ein Oktett umfasst 8 Bit."
},
{
"category": "IPv4-Grundlagen",
"hint": "Adressaufbau, Oktette und Masken verstehen.",
"text": "Woran erkennst du Subnetzmaske in einer AP1-Aufgabe?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Sie trennt Netz- und Hostanteil."
],
"correct": 2,
"explanation": "Sie trennt Netz- und Hostanteil."
},
{
"category": "IPv4-Grundlagen",
"hint": "Adressaufbau, Oktette und Masken verstehen.",
"text": "Was ist bei CIDR besonders wichtig?",
"answers": [
"CIDR gibt die Anzahl der Netzbits an.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "CIDR gibt die Anzahl der Netzbits an."
},
{
"category": "IPv4-Grundlagen",
"hint": "Adressaufbau, Oktette und Masken verstehen.",
"text": "Was beschreibt /24 am besten?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"/24 entspricht häufig 255.255.255.0.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "/24 entspricht häufig 255.255.255.0."
},
{
"category": "IPv4-Grundlagen",
"hint": "Adressaufbau, Oktette und Masken verstehen.",
"text": "Welche Aussage zu Netzanteil ist richtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Der Netzanteil identifiziert das Subnetz."
],
"correct": 2,
"explanation": "Der Netzanteil identifiziert das Subnetz."
},
{
"category": "IPv4-Grundlagen",
"hint": "Adressaufbau, Oktette und Masken verstehen.",
"text": "Woran erkennst du Hostanteil in einer AP1-Aufgabe?",
"answers": [
"Der Hostanteil identifiziert Geräte im Subnetz.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Der Hostanteil identifiziert Geräte im Subnetz."
},
{
"category": "IPv4-Grundlagen",
"hint": "Adressaufbau, Oktette und Masken verstehen.",
"text": "Was ist bei private IPv4-Bereiche besonders wichtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Sie werden intern genutzt, etwa 192.168.0.0/16.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "Sie werden intern genutzt, etwa 192.168.0.0/16."
},
{
"category": "IPv4-Grundlagen",
"hint": "Adressaufbau, Oktette und Masken verstehen.",
"text": "Was beschreibt Standardgateway am besten?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Es verbindet das lokale Netz mit anderen Netzen."
],
"correct": 2,
"explanation": "Es verbindet das lokale Netz mit anderen Netzen."
},
{
"category": "IPv4-Grundlagen",
"hint": "Adressaufbau, Oktette und Masken verstehen.",
"text": "Welche Aussage zu APIPA ist richtig?",
"answers": [
"169.254.x.x deutet oft auf DHCP-Probleme hin.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "169.254.x.x deutet oft auf DHCP-Probleme hin."
},
{
"category": "Subnetting",
"hint": "Netz, Broadcast und Hostanzahl berechnen.",
"text": "Was beschreibt Netzadresse am besten?",
"answers": [
"Sie ist die erste Adresse eines Subnetzes.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Sie ist die erste Adresse eines Subnetzes."
},
{
"category": "Subnetting",
"hint": "Netz, Broadcast und Hostanzahl berechnen.",
"text": "Welche Aussage zu Broadcast-Adresse ist richtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Sie ist die letzte Adresse eines klassischen IPv4-Subnetzes.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "Sie ist die letzte Adresse eines klassischen IPv4-Subnetzes."
},
{
"category": "Subnetting",
"hint": "Netz, Broadcast und Hostanzahl berechnen.",
"text": "Woran erkennst du Hostanzahl in einer AP1-Aufgabe?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Bei normalen Subnetzen gilt 2 hoch Hostbits minus 2."
],
"correct": 2,
"explanation": "Bei normalen Subnetzen gilt 2 hoch Hostbits minus 2."
},
{
"category": "Subnetting",
"hint": "Netz, Broadcast und Hostanzahl berechnen.",
"text": "Was ist bei /26 besonders wichtig?",
"answers": [
"/26 hat 64 Adressen pro Subnetz.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "/26 hat 64 Adressen pro Subnetz."
},
{
"category": "Subnetting",
"hint": "Netz, Broadcast und Hostanzahl berechnen.",
"text": "Was beschreibt /30 am besten?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"/30 wird oft für Punkt-zu-Punkt-Netze genutzt.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "/30 wird oft für Punkt-zu-Punkt-Netze genutzt."
},
{
"category": "Subnetting",
"hint": "Netz, Broadcast und Hostanzahl berechnen.",
"text": "Welche Aussage zu Blockgröße ist richtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Sie ergibt sich aus der Schrittweite im relevanten Oktett."
],
"correct": 2,
"explanation": "Sie ergibt sich aus der Schrittweite im relevanten Oktett."
},
{
"category": "Subnetting",
"hint": "Netz, Broadcast und Hostanzahl berechnen.",
"text": "Woran erkennst du Hostbits in einer AP1-Aufgabe?",
"answers": [
"Hostbits bestimmen die Anzahl möglicher Adressen.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Hostbits bestimmen die Anzahl möglicher Adressen."
},
{
"category": "Subnetting",
"hint": "Netz, Broadcast und Hostanzahl berechnen.",
"text": "Was ist bei Netzbits besonders wichtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Netzbits bestimmen die Subnetzgröße.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "Netzbits bestimmen die Subnetzgröße."
},
{
"category": "Subnetting",
"hint": "Netz, Broadcast und Hostanzahl berechnen.",
"text": "Was beschreibt Subnetzgrenze am besten?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Adressen müssen zur richtigen Blockgrenze gehören."
],
"correct": 2,
"explanation": "Adressen müssen zur richtigen Blockgrenze gehören."
},
{
"category": "Subnetting",
"hint": "Netz, Broadcast und Hostanzahl berechnen.",
"text": "Welche Aussage zu Broadcast nicht vergeben ist richtig?",
"answers": [
"Broadcast ist keine normale Hostadresse.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Broadcast ist keine normale Hostadresse."
},
{
"category": "Fehleranalyse IP",
"hint": "IP-Probleme in Handlungssituationen erkennen.",
"text": "Was beschreibt falsche Maske am besten?",
"answers": [
"Sie kann Hosts scheinbar in falsche Netze legen.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Sie kann Hosts scheinbar in falsche Netze legen."
},
{
"category": "Fehleranalyse IP",
"hint": "IP-Probleme in Handlungssituationen erkennen.",
"text": "Welche Aussage zu falsches Gateway ist richtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Dann sind andere Netze oft nicht erreichbar.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "Dann sind andere Netze oft nicht erreichbar."
},
{
"category": "Fehleranalyse IP",
"hint": "IP-Probleme in Handlungssituationen erkennen.",
"text": "Woran erkennst du DHCP-Problem in einer AP1-Aufgabe?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Fehlende automatische Adresse kann auf DHCP hindeuten."
],
"correct": 2,
"explanation": "Fehlende automatische Adresse kann auf DHCP hindeuten."
},
{
"category": "Fehleranalyse IP",
"hint": "IP-Probleme in Handlungssituationen erkennen.",
"text": "Was ist bei Doppelvergabe besonders wichtig?",
"answers": [
"Gleiche IP auf zwei Hosts verursacht Konflikte.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Gleiche IP auf zwei Hosts verursacht Konflikte."
},
{
"category": "Fehleranalyse IP",
"hint": "IP-Probleme in Handlungssituationen erkennen.",
"text": "Was beschreibt DNS vs IP am besten?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Ping per IP, aber nicht per Name deutet auf DNS.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "Ping per IP, aber nicht per Name deutet auf DNS."
},
{
"category": "Fehleranalyse IP",
"hint": "IP-Probleme in Handlungssituationen erkennen.",
"text": "Welche Aussage zu lokale Erreichbarkeit ist richtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Erst IP, Maske und Link prüfen."
],
"correct": 2,
"explanation": "Erst IP, Maske und Link prüfen."
},
{
"category": "Fehleranalyse IP",
"hint": "IP-Probleme in Handlungssituationen erkennen.",
"text": "Woran erkennst du Routingproblem in einer AP1-Aufgabe?",
"answers": [
"Andere Netze brauchen passende Route oder Gateway.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Andere Netze brauchen passende Route oder Gateway."
},
{
"category": "Fehleranalyse IP",
"hint": "IP-Probleme in Handlungssituationen erkennen.",
"text": "Was ist bei Dokumentation besonders wichtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Adresspläne verhindern Überschneidungen.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "Adresspläne verhindern Überschneidungen."
},
{
"category": "Fehleranalyse IP",
"hint": "IP-Probleme in Handlungssituationen erkennen.",
"text": "Was beschreibt Reservierungen am besten?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Server und Drucker bekommen oft feste Adressen."
],
"correct": 2,
"explanation": "Server und Drucker bekommen oft feste Adressen."
},
{
"category": "Fehleranalyse IP",
"hint": "IP-Probleme in Handlungssituationen erkennen.",
"text": "Welche Aussage zu Prüfungsweg IP ist richtig?",
"answers": [
"Rechenschritte sauber notieren und Einheiten prüfen.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Rechenschritte sauber notieren und Einheiten prüfen."
}
]
}
@@ -0,0 +1,365 @@
{
"title": "Zahlensysteme & Speicher",
"questions": [
{
"category": "Binär & Dezimal",
"hint": "Stellenwerte, Potenzen und Umrechnung verstehen.",
"text": "Was beschreibt Binärsystem am besten?",
"answers": [
"Es nutzt nur 0 und 1 und basiert auf Potenzen von 2.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Es nutzt nur 0 und 1 und basiert auf Potenzen von 2."
},
{
"category": "Binär & Dezimal",
"hint": "Stellenwerte, Potenzen und Umrechnung verstehen.",
"text": "Welche Aussage zu Stellenwert ist richtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Jede Position hat einen Wert wie 1, 2, 4, 8 oder 16.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "Jede Position hat einen Wert wie 1, 2, 4, 8 oder 16."
},
{
"category": "Binär & Dezimal",
"hint": "Stellenwerte, Potenzen und Umrechnung verstehen.",
"text": "Woran erkennst du Dezimalumrechnung in einer AP1-Aufgabe?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Binärstellen werden mit ihren Zweierpotenzen addiert."
],
"correct": 2,
"explanation": "Binärstellen werden mit ihren Zweierpotenzen addiert."
},
{
"category": "Binär & Dezimal",
"hint": "Stellenwerte, Potenzen und Umrechnung verstehen.",
"text": "Was ist bei Bit besonders wichtig?",
"answers": [
"Ein Bit ist die kleinste Informationseinheit mit 0 oder 1.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Ein Bit ist die kleinste Informationseinheit mit 0 oder 1."
},
{
"category": "Binär & Dezimal",
"hint": "Stellenwerte, Potenzen und Umrechnung verstehen.",
"text": "Was beschreibt Nibble am besten?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Ein Nibble besteht aus 4 Bit.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "Ein Nibble besteht aus 4 Bit."
},
{
"category": "Binär & Dezimal",
"hint": "Stellenwerte, Potenzen und Umrechnung verstehen.",
"text": "Welche Aussage zu Byte ist richtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Ein Byte besteht aus 8 Bit."
],
"correct": 2,
"explanation": "Ein Byte besteht aus 8 Bit."
},
{
"category": "Binär & Dezimal",
"hint": "Stellenwerte, Potenzen und Umrechnung verstehen.",
"text": "Woran erkennst du Potenzen von 2 in einer AP1-Aufgabe?",
"answers": [
"Sie sind Grundlage vieler IT-Rechenaufgaben.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Sie sind Grundlage vieler IT-Rechenaufgaben."
},
{
"category": "Binär & Dezimal",
"hint": "Stellenwerte, Potenzen und Umrechnung verstehen.",
"text": "Was ist bei führende Nullen besonders wichtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Sie ändern den Zahlenwert nicht, können aber die Darstellung auffüllen.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "Sie ändern den Zahlenwert nicht, können aber die Darstellung auffüllen."
},
{
"category": "Binär & Dezimal",
"hint": "Stellenwerte, Potenzen und Umrechnung verstehen.",
"text": "Was beschreibt Dualzahl am besten?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Eine Dualzahl ist eine Zahl im Binärsystem."
],
"correct": 2,
"explanation": "Eine Dualzahl ist eine Zahl im Binärsystem."
},
{
"category": "Binär & Dezimal",
"hint": "Stellenwerte, Potenzen und Umrechnung verstehen.",
"text": "Welche Aussage zu Prüfungsweg ist richtig?",
"answers": [
"Der Rechenweg ist oft wichtiger als nur das Endergebnis.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Der Rechenweg ist oft wichtiger als nur das Endergebnis."
},
{
"category": "Hexadezimal",
"hint": "Hexwerte kompakt lesen und in Binär/Dezimal übertragen.",
"text": "Was beschreibt Hexadezimalsystem am besten?",
"answers": [
"Es nutzt Basis 16 mit Ziffern 0-9 und A-F.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Es nutzt Basis 16 mit Ziffern 0-9 und A-F."
},
{
"category": "Hexadezimal",
"hint": "Hexwerte kompakt lesen und in Binär/Dezimal übertragen.",
"text": "Welche Aussage zu A bis F ist richtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"A bis F stehen für die Werte 10 bis 15.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "A bis F stehen für die Werte 10 bis 15."
},
{
"category": "Hexadezimal",
"hint": "Hexwerte kompakt lesen und in Binär/Dezimal übertragen.",
"text": "Woran erkennst du Hex zu Binär in einer AP1-Aufgabe?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Eine Hexstelle entspricht genau 4 Bit."
],
"correct": 2,
"explanation": "Eine Hexstelle entspricht genau 4 Bit."
},
{
"category": "Hexadezimal",
"hint": "Hexwerte kompakt lesen und in Binär/Dezimal übertragen.",
"text": "Was ist bei Binär zu Hex besonders wichtig?",
"answers": [
"Binärzahlen werden in Vierergruppen geteilt.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Binärzahlen werden in Vierergruppen geteilt."
},
{
"category": "Hexadezimal",
"hint": "Hexwerte kompakt lesen und in Binär/Dezimal übertragen.",
"text": "Was beschreibt Hex FF am besten?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"FF entspricht dezimal 255.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "FF entspricht dezimal 255."
},
{
"category": "Hexadezimal",
"hint": "Hexwerte kompakt lesen und in Binär/Dezimal übertragen.",
"text": "Welche Aussage zu Hex in Adressen ist richtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Hex wird häufig bei Speicher- und Netzwerkdarstellungen genutzt."
],
"correct": 2,
"explanation": "Hex wird häufig bei Speicher- und Netzwerkdarstellungen genutzt."
},
{
"category": "Hexadezimal",
"hint": "Hexwerte kompakt lesen und in Binär/Dezimal übertragen.",
"text": "Woran erkennst du Basis 16 in einer AP1-Aufgabe?",
"answers": [
"Jede Stelle ist eine Potenz von 16.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Jede Stelle ist eine Potenz von 16."
},
{
"category": "Hexadezimal",
"hint": "Hexwerte kompakt lesen und in Binär/Dezimal übertragen.",
"text": "Was ist bei 2F besonders wichtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"2F bedeutet 2 mal 16 plus 15.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "2F bedeutet 2 mal 16 plus 15."
},
{
"category": "Hexadezimal",
"hint": "Hexwerte kompakt lesen und in Binär/Dezimal übertragen.",
"text": "Was beschreibt BC am besten?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"BC kann aus der Binärgruppe 1011 1100 entstehen."
],
"correct": 2,
"explanation": "BC kann aus der Binärgruppe 1011 1100 entstehen."
},
{
"category": "Hexadezimal",
"hint": "Hexwerte kompakt lesen und in Binär/Dezimal übertragen.",
"text": "Welche Aussage zu Prüfungstipp Hex ist richtig?",
"answers": [
"Vierergruppen verhindern viele Umrechnungsfehler.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Vierergruppen verhindern viele Umrechnungsfehler."
},
{
"category": "Speichereinheiten",
"hint": "Bit, Byte und Binärpräfixe in Aufgaben sicher anwenden.",
"text": "Was beschreibt kB und KiB am besten?",
"answers": [
"kB nutzt 1000, KiB nutzt 1024 Byte.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "kB nutzt 1000, KiB nutzt 1024 Byte."
},
{
"category": "Speichereinheiten",
"hint": "Bit, Byte und Binärpräfixe in Aufgaben sicher anwenden.",
"text": "Welche Aussage zu MB und MiB ist richtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"MB ist dezimal, MiB ist binär gerechnet.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "MB ist dezimal, MiB ist binär gerechnet."
},
{
"category": "Speichereinheiten",
"hint": "Bit, Byte und Binärpräfixe in Aufgaben sicher anwenden.",
"text": "Woran erkennst du GiB in einer AP1-Aufgabe?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Ein GiB entspricht 1024 hoch 3 Byte."
],
"correct": 2,
"explanation": "Ein GiB entspricht 1024 hoch 3 Byte."
},
{
"category": "Speichereinheiten",
"hint": "Bit, Byte und Binärpräfixe in Aufgaben sicher anwenden.",
"text": "Was ist bei Bit zu Byte besonders wichtig?",
"answers": [
"8 Bit ergeben 1 Byte.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "8 Bit ergeben 1 Byte."
},
{
"category": "Speichereinheiten",
"hint": "Bit, Byte und Binärpräfixe in Aufgaben sicher anwenden.",
"text": "Was beschreibt Übertragungsrate am besten?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Bit pro Sekunde ist nicht dasselbe wie Byte pro Sekunde.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "Bit pro Sekunde ist nicht dasselbe wie Byte pro Sekunde."
},
{
"category": "Speichereinheiten",
"hint": "Bit, Byte und Binärpräfixe in Aufgaben sicher anwenden.",
"text": "Welche Aussage zu Marketingangaben ist richtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Speicherhersteller nutzen oft dezimale Einheiten."
],
"correct": 2,
"explanation": "Speicherhersteller nutzen oft dezimale Einheiten."
},
{
"category": "Speichereinheiten",
"hint": "Bit, Byte und Binärpräfixe in Aufgaben sicher anwenden.",
"text": "Woran erkennst du Betriebssystemanzeige in einer AP1-Aufgabe?",
"answers": [
"Systeme zeigen Kapazitäten häufig binärnah an.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Systeme zeigen Kapazitäten häufig binärnah an."
},
{
"category": "Speichereinheiten",
"hint": "Bit, Byte und Binärpräfixe in Aufgaben sicher anwenden.",
"text": "Was ist bei Rundung besonders wichtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Rundungen müssen in Prüfungen nachvollziehbar sein.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "Rundungen müssen in Prüfungen nachvollziehbar sein."
},
{
"category": "Speichereinheiten",
"hint": "Bit, Byte und Binärpräfixe in Aufgaben sicher anwenden.",
"text": "Was beschreibt Einheitenfehler am besten?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Verwechslung von Bit und Byte ist ein klassischer Fehler."
],
"correct": 2,
"explanation": "Verwechslung von Bit und Byte ist ein klassischer Fehler."
},
{
"category": "Speichereinheiten",
"hint": "Bit, Byte und Binärpräfixe in Aufgaben sicher anwenden.",
"text": "Welche Aussage zu Rechenweg Speicher ist richtig?",
"answers": [
"Schreibe Einheitenschritte sauber auf.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Schreibe Einheitenschritte sauber auf."
}
]
}
@@ -0,0 +1,245 @@
{
"title": "Backuparten",
"questions": [
{
"category": "Backup-Grundlagen",
"hint": "Verstehe Zweck, Begriffe und Anforderungen an Datensicherung.",
"text": "Was ist das Hauptziel eines Backups?",
"answers": ["Daten nach Verlust wiederherstellen können", "Daten absichtlich verlangsamen", "Passwörter öffentlich speichern"],
"correct": 0,
"explanation": "Backups dienen der Wiederherstellung nach Verlust, Fehlern oder Ausfällen."
},
{
"category": "Backup-Grundlagen",
"hint": "Verstehe Zweck, Begriffe und Anforderungen an Datensicherung.",
"text": "Was bedeutet Restore?",
"answers": ["Wiederherstellung von Daten aus einer Sicherung", "Löschen aller Sicherungen", "Komprimieren eines Monitors"],
"correct": 0,
"explanation": "Restore ist das Zurückspielen gesicherter Daten."
},
{
"category": "Backup-Grundlagen",
"hint": "Verstehe Zweck, Begriffe und Anforderungen an Datensicherung.",
"text": "Warum sollte ein Restore regelmäßig getestet werden?",
"answers": ["Damit sicher ist, dass die Sicherung im Ernstfall funktioniert", "Damit Backups größer werden", "Damit keine Dokumentation nötig ist"],
"correct": 0,
"explanation": "Ein Backup ist nur wertvoll, wenn die Wiederherstellung klappt."
},
{
"category": "Backup-Grundlagen",
"hint": "Verstehe Zweck, Begriffe und Anforderungen an Datensicherung.",
"text": "Was ist RPO?",
"answers": ["Maximal tolerierbarer Datenverlust in Zeit", "Maximale Bildschirmauflösung", "Ein Druckerprotokoll"],
"correct": 0,
"explanation": "RPO beschreibt, wie alt die wiederhergestellten Daten höchstens sein dürfen."
},
{
"category": "Backup-Grundlagen",
"hint": "Verstehe Zweck, Begriffe und Anforderungen an Datensicherung.",
"text": "Was ist RTO?",
"answers": ["Maximal tolerierbare Wiederherstellungszeit", "Router-Temperatur-Option", "Eine Dateiendung"],
"correct": 0,
"explanation": "RTO beschreibt, wie schnell ein System nach Ausfall wieder laufen muss."
},
{
"category": "Backup-Grundlagen",
"hint": "Verstehe Zweck, Begriffe und Anforderungen an Datensicherung.",
"text": "Warum ist eine Backup-Dokumentation wichtig?",
"answers": ["Damit Zuständigkeiten, Zeitpunkte und Wiederherstellung nachvollziehbar sind", "Damit niemand Backups findet", "Damit Tests überflüssig sind"],
"correct": 0,
"explanation": "Dokumentation macht Sicherung und Restore planbar."
},
{
"category": "Backup-Grundlagen",
"hint": "Verstehe Zweck, Begriffe und Anforderungen an Datensicherung.",
"text": "Welche Daten sollten gesichert werden?",
"answers": ["Geschäftskritische Daten und notwendige Konfigurationen", "Nur Hintergrundbilder", "Ausschließlich leere Ordner"],
"correct": 0,
"explanation": "Sicherungsumfang richtet sich nach Wiederherstellungsbedarf und Kritikalität."
},
{
"category": "Backup-Grundlagen",
"hint": "Verstehe Zweck, Begriffe und Anforderungen an Datensicherung.",
"text": "Was ist ein Backup-Fenster?",
"answers": ["Zeitraum, in dem Sicherungen durchgeführt werden", "Ein Fenster im Serverraum", "Eine Monitorfunktion"],
"correct": 0,
"explanation": "Backups werden oft in Zeiten geringer Nutzung geplant."
},
{
"category": "Backup-Grundlagen",
"hint": "Verstehe Zweck, Begriffe und Anforderungen an Datensicherung.",
"text": "Was ist bei personenbezogenen Daten in Backups wichtig?",
"answers": ["Schutz, Zugriff und Löschkonzept beachten", "Backups immer öffentlich machen", "Datenschutz gilt dort nie"],
"correct": 0,
"explanation": "Auch Backups können personenbezogene Daten enthalten."
},
{
"category": "Backup-Grundlagen",
"hint": "Verstehe Zweck, Begriffe und Anforderungen an Datensicherung.",
"text": "Welche Aussage ist korrekt?",
"answers": ["Backups müssen zum Risiko und Wiederherstellungsbedarf passen", "Ein Backup ersetzt jede IT-Sicherheit", "Ein einziges ungetestetes Backup reicht immer"],
"correct": 0,
"explanation": "Backup-Konzepte müssen Anforderungen, Risiken und Tests berücksichtigen."
},
{
"category": "Voll, differentiell, inkrementell",
"hint": "Unterscheide Sicherungsarten nach Speicherbedarf, Dauer und Restore-Aufwand.",
"text": "Was sichert ein Vollbackup?",
"answers": ["Alle ausgewählten Daten", "Nur Änderungen seit gestern", "Nur gelöschte Dateien"],
"correct": 0,
"explanation": "Ein Vollbackup enthält den kompletten ausgewählten Datenbestand."
},
{
"category": "Voll, differentiell, inkrementell",
"hint": "Unterscheide Sicherungsarten nach Speicherbedarf, Dauer und Restore-Aufwand.",
"text": "Was sichert ein differentielles Backup?",
"answers": ["Alle Änderungen seit dem letzten Vollbackup", "Nur Änderungen seit dem letzten beliebigen Backup", "Immer alle Daten"],
"correct": 0,
"explanation": "Differentiell bezieht sich auf das letzte Vollbackup."
},
{
"category": "Voll, differentiell, inkrementell",
"hint": "Unterscheide Sicherungsarten nach Speicherbedarf, Dauer und Restore-Aufwand.",
"text": "Was sichert ein inkrementelles Backup?",
"answers": ["Änderungen seit dem letzten Backup", "Immer alle Daten", "Nur Betriebssystemdateien"],
"correct": 0,
"explanation": "Inkrementell bezieht sich auf das letzte Backup, egal ob voll oder inkrementell."
},
{
"category": "Voll, differentiell, inkrementell",
"hint": "Unterscheide Sicherungsarten nach Speicherbedarf, Dauer und Restore-Aufwand.",
"text": "Welche Sicherungsart braucht meist am meisten Speicherplatz?",
"answers": ["Vollbackup", "Inkrementelles Backup", "Keine Sicherung"],
"correct": 0,
"explanation": "Vollbackups enthalten den gesamten ausgewählten Datenbestand."
},
{
"category": "Voll, differentiell, inkrementell",
"hint": "Unterscheide Sicherungsarten nach Speicherbedarf, Dauer und Restore-Aufwand.",
"text": "Welche Sicherungsart spart im Alltag meist am meisten Speicherplatz?",
"answers": ["Inkrementelles Backup", "Tägliches Vollbackup", "Doppelte Vollsicherung"],
"correct": 0,
"explanation": "Inkrementelle Backups sichern nur die Änderungen seit der letzten Sicherung."
},
{
"category": "Voll, differentiell, inkrementell",
"hint": "Unterscheide Sicherungsarten nach Speicherbedarf, Dauer und Restore-Aufwand.",
"text": "Was brauchst du beim differentiellen Restore?",
"answers": ["Letztes Vollbackup und letztes differentielles Backup", "Alle differentiellen Backups", "Nur das erste inkrementelle Backup"],
"correct": 0,
"explanation": "Differentiell enthält alle Änderungen seit dem letzten Vollbackup."
},
{
"category": "Voll, differentiell, inkrementell",
"hint": "Unterscheide Sicherungsarten nach Speicherbedarf, Dauer und Restore-Aufwand.",
"text": "Was brauchst du beim inkrementellen Restore typischerweise?",
"answers": ["Vollbackup und alle benötigten inkrementellen Backups der Kette", "Nur das letzte inkrementelle Backup", "Kein Vollbackup"],
"correct": 0,
"explanation": "Die inkrementelle Kette muss vollständig sein."
},
{
"category": "Voll, differentiell, inkrementell",
"hint": "Unterscheide Sicherungsarten nach Speicherbedarf, Dauer und Restore-Aufwand.",
"text": "Was ist ein Nachteil langer inkrementeller Ketten?",
"answers": ["Restore kann länger dauern und anfälliger sein", "Sie brauchen immer mehr Kabel", "Sie verhindern Datenschutz"],
"correct": 0,
"explanation": "Wenn ein Teil der Kette fehlt oder defekt ist, kann der Restore scheitern."
},
{
"category": "Voll, differentiell, inkrementell",
"hint": "Unterscheide Sicherungsarten nach Speicherbedarf, Dauer und Restore-Aufwand.",
"text": "Welche Strategie ist häufig praktikabel?",
"answers": ["Regelmäßige Vollbackups plus tägliche inkrementelle oder differentielle Sicherungen", "Nie sichern", "Nur einmal im Jahr prüfen"],
"correct": 0,
"explanation": "Kombinationen balancieren Speicherbedarf, Sicherungsdauer und Restore-Aufwand."
},
{
"category": "Voll, differentiell, inkrementell",
"hint": "Unterscheide Sicherungsarten nach Speicherbedarf, Dauer und Restore-Aufwand.",
"text": "Welche Aussage zum Vollbackup stimmt?",
"answers": ["Restore ist oft einfacher als bei langen Ketten", "Es enthält nie vollständige Daten", "Es ist immer verboten"],
"correct": 0,
"explanation": "Ein vollständiger Sicherungsstand vereinfacht die Wiederherstellung."
},
{
"category": "Backup-Strategie & Praxis",
"hint": "Bewerte Sicherungsorte, Sicherheit, Tests und typische AP1-Szenarien.",
"text": "Was bedeutet die 3-2-1-Regel grob?",
"answers": ["3 Kopien, 2 Medientypen, 1 Kopie extern", "3 Passwörter, 2 Nutzer, 1 Drucker", "3 Monitore, 2 Kabel, 1 Maus"],
"correct": 0,
"explanation": "Die Regel reduziert Risiko durch mehrere Kopien und externe Lagerung."
},
{
"category": "Backup-Strategie & Praxis",
"hint": "Bewerte Sicherungsorte, Sicherheit, Tests und typische AP1-Szenarien.",
"text": "Warum ist eine externe oder offline Kopie sinnvoll?",
"answers": ["Sie schützt besser gegen Brand, Diebstahl oder Ransomware", "Sie macht Daten automatisch öffentlich", "Sie ersetzt alle Benutzerrechte"],
"correct": 0,
"explanation": "Getrennte Kopien erhöhen die Chance auf Wiederherstellung."
},
{
"category": "Backup-Strategie & Praxis",
"hint": "Bewerte Sicherungsorte, Sicherheit, Tests und typische AP1-Szenarien.",
"text": "Was ist bei Cloud-Backups wichtig?",
"answers": ["Datenschutz, Verschlüsselung, Vertrag und Wiederherstellbarkeit", "Nur die Farbe des Cloud-Logos", "Keine Zugriffskontrolle"],
"correct": 0,
"explanation": "Cloud-Backups brauchen technische, rechtliche und organisatorische Bewertung."
},
{
"category": "Backup-Strategie & Praxis",
"hint": "Bewerte Sicherungsorte, Sicherheit, Tests und typische AP1-Szenarien.",
"text": "Warum sollten Backups verschlüsselt werden?",
"answers": ["Zum Schutz vor unbefugtem Zugriff", "Damit sie langsamer gelöscht werden", "Damit sie nie wiederherstellbar sind"],
"correct": 0,
"explanation": "Verschlüsselung unterstützt die Vertraulichkeit der gesicherten Daten."
},
{
"category": "Backup-Strategie & Praxis",
"hint": "Bewerte Sicherungsorte, Sicherheit, Tests und typische AP1-Szenarien.",
"text": "Was sollte nach einer Backup-Einrichtung dokumentiert werden?",
"answers": ["Zeitplan, Umfang, Speicherort, Verantwortliche und Restore-Verfahren", "Nur der Name des Praktikanten", "Nichts"],
"correct": 0,
"explanation": "Dokumentation macht Betrieb und Notfallwiederherstellung möglich."
},
{
"category": "Backup-Strategie & Praxis",
"hint": "Bewerte Sicherungsorte, Sicherheit, Tests und typische AP1-Szenarien.",
"text": "Ein Backup läuft täglich, schlägt aber seit Wochen fehl. Was ist das Hauptproblem?",
"answers": ["Fehlende Überwachung oder Reaktion auf Fehler", "Zu viele erfolgreiche Tests", "Zu viel Dokumentation"],
"correct": 0,
"explanation": "Backups müssen überwacht und Fehler müssen bearbeitet werden."
},
{
"category": "Backup-Strategie & Praxis",
"hint": "Bewerte Sicherungsorte, Sicherheit, Tests und typische AP1-Szenarien.",
"text": "Was ist bei Ransomware besonders wichtig?",
"answers": ["Nicht dauerhaft beschreibbare oder getrennte Backup-Kopien", "Backups immer im selben Ordner offen lassen", "Backups nie prüfen"],
"correct": 0,
"explanation": "Ransomware kann erreichbare Backups verschlüsseln oder löschen."
},
{
"category": "Backup-Strategie & Praxis",
"hint": "Bewerte Sicherungsorte, Sicherheit, Tests und typische AP1-Szenarien.",
"text": "Was ist ein typischer Fehler in Backup-Konzepten?",
"answers": ["Sicherung nie wiederherstellen testen", "RPO und RTO definieren", "Zugriffe einschränken"],
"correct": 0,
"explanation": "Ungetestete Sicherungen können im Ernstfall wertlos sein."
},
{
"category": "Backup-Strategie & Praxis",
"hint": "Bewerte Sicherungsorte, Sicherheit, Tests und typische AP1-Szenarien.",
"text": "Welche Frage ist bei Backup-Planung zentral?",
"answers": ["Wie viel Datenverlust und Ausfallzeit sind akzeptabel?", "Welche Tapete hat der Serverraum?", "Welcher Monitor ist am hellsten?"],
"correct": 0,
"explanation": "RPO und RTO bestimmen die passende Backup-Strategie."
},
{
"category": "Backup-Strategie & Praxis",
"hint": "Bewerte Sicherungsorte, Sicherheit, Tests und typische AP1-Szenarien.",
"text": "Welche Aussage ist richtig?",
"answers": ["Backup, Restore-Test und Dokumentation gehören zusammen", "Backup ohne Restore ist immer ausreichend", "Dokumentation verhindert Wiederherstellung"],
"correct": 0,
"explanation": "Ein gutes Konzept umfasst Sicherung, Prüfung und nachvollziehbare Abläufe."
}
]
}
@@ -0,0 +1,405 @@
{
"title": "Datenschutz & Datensicherheit",
"questions": [
{
"category": "Personenbezogene Daten",
"hint": "Erkenne, wann Daten sich auf eine identifizierte oder identifizierbare Person beziehen.",
"text": "Welche Angabe ist eindeutig personenbezogen?",
"answers": ["Seriennummer eines Switches", "Name und E-Mail-Adresse eines Kunden", "Temperatur im Serverraum"],
"correct": 1,
"explanation": "Name und E-Mail-Adresse können direkt einer Person zugeordnet werden."
},
{
"category": "Personenbezogene Daten",
"hint": "Erkenne, wann Daten sich auf eine identifizierte oder identifizierbare Person beziehen.",
"text": "Welche Daten können indirekt personenbezogen sein?",
"answers": ["Eine IP-Adresse in Logdateien", "Eine zufällige Gehäusefarbe", "Eine leere Tabelle ohne Bezug"],
"correct": 0,
"explanation": "IP-Adressen können unter Umständen einer Person oder einem Anschluss zugeordnet werden."
},
{
"category": "Personenbezogene Daten",
"hint": "Erkenne, wann Daten sich auf eine identifizierte oder identifizierbare Person beziehen.",
"text": "Welche Aussage zu Personalnummern ist richtig?",
"answers": ["Sie sind nie personenbezogen", "Sie können Beschäftigte eindeutig identifizieren", "Sie sind nur bei Auszubildenden geschützt"],
"correct": 1,
"explanation": "Personalnummern beziehen sich auf konkrete Beschäftigte und sind personenbezogen."
},
{
"category": "Personenbezogene Daten",
"hint": "Erkenne, wann Daten sich auf eine identifizierte oder identifizierbare Person beziehen.",
"text": "Was bedeutet Anonymisierung?",
"answers": ["Personenbezug wird so entfernt, dass keine Zuordnung mehr möglich ist", "Daten werden nur umbenannt", "Daten werden unverschlüsselt gespeichert"],
"correct": 0,
"explanation": "Anonyme Daten lassen sich nicht mehr auf eine Person zurückführen."
},
{
"category": "Personenbezogene Daten",
"hint": "Erkenne, wann Daten sich auf eine identifizierte oder identifizierbare Person beziehen.",
"text": "Was bedeutet Pseudonymisierung?",
"answers": ["Identifizierende Merkmale werden ersetzt, eine Zuordnung bleibt mit Zusatzwissen möglich", "Alle Daten werden gelöscht", "Daten werden öffentlich gemacht"],
"correct": 0,
"explanation": "Pseudonyme reduzieren Risiken, beseitigen den Personenbezug aber nicht vollständig."
},
{
"category": "Personenbezogene Daten",
"hint": "Erkenne, wann Daten sich auf eine identifizierte oder identifizierbare Person beziehen.",
"text": "Welche Information ist besonders sensibel?",
"answers": ["Gesundheitsdaten eines Mitarbeiters", "Rack-Nummer eines Servers", "Hersteller eines Monitors"],
"correct": 0,
"explanation": "Gesundheitsdaten gehören zu besonders schützenswerten personenbezogenen Daten."
},
{
"category": "Personenbezogene Daten",
"hint": "Erkenne, wann Daten sich auf eine identifizierte oder identifizierbare Person beziehen.",
"text": "Was ist bei Logdateien mit Benutzerkennungen wichtig?",
"answers": ["Sie können personenbezogene Daten enthalten", "Sie sind automatisch anonym", "Sie dürfen nie ausgewertet werden"],
"correct": 0,
"explanation": "Benutzerkennungen, Zeitpunkte und Aktionen können Nutzerverhalten nachvollziehbar machen."
},
{
"category": "Personenbezogene Daten",
"hint": "Erkenne, wann Daten sich auf eine identifizierte oder identifizierbare Person beziehen.",
"text": "Welche Kombination ist personenbezogen?",
"answers": ["Abteilung, Name und Telefonnummer", "Nur die Anzahl der Switchports", "Ein leerer USB-Stick"],
"correct": 0,
"explanation": "Die Kombination ermöglicht eine klare Zuordnung zu einer Person."
},
{
"category": "Personenbezogene Daten",
"hint": "Erkenne, wann Daten sich auf eine identifizierte oder identifizierbare Person beziehen.",
"text": "Warum ist der Personenbezug in Prüfungsaufgaben wichtig?",
"answers": ["Er entscheidet, ob Datenschutzregeln zu beachten sind", "Er entscheidet nur über die Bildschirmauflösung", "Er ist nie relevant"],
"correct": 0,
"explanation": "Sobald personenbezogene Daten verarbeitet werden, greifen Datenschutzanforderungen."
},
{
"category": "Personenbezogene Daten",
"hint": "Erkenne, wann Daten sich auf eine identifizierte oder identifizierbare Person beziehen.",
"text": "Welche Aussage ist korrekt?",
"answers": ["Auch Kundendaten und Mitarbeiterdaten können personenbezogen sein", "Nur Passwörter sind personenbezogen", "Personenbezogene Daten gibt es nur in Papierakten"],
"correct": 0,
"explanation": "Personenbezogene Daten können digital oder analog und in vielen Kontexten vorkommen."
},
{
"category": "DSGVO-Grundsätze",
"hint": "Grundsätze helfen, Verarbeitung rechtmäßig und nachvollziehbar zu bewerten.",
"text": "Was bedeutet Datenminimierung?",
"answers": ["Nur Daten verarbeiten, die für den Zweck notwendig sind", "Alle verfügbaren Daten speichern", "Daten nie löschen"],
"correct": 0,
"explanation": "Es sollen nicht mehr personenbezogene Daten verarbeitet werden als nötig."
},
{
"category": "DSGVO-Grundsätze",
"hint": "Grundsätze helfen, Verarbeitung rechtmäßig und nachvollziehbar zu bewerten.",
"text": "Was bedeutet Zweckbindung?",
"answers": ["Daten dürfen nur für festgelegte Zwecke verarbeitet werden", "Daten dürfen für alles genutzt werden", "Der Zweck muss geheim bleiben"],
"correct": 0,
"explanation": "Der Zweck muss vorab feststehen und die Verarbeitung daran gebunden sein."
},
{
"category": "DSGVO-Grundsätze",
"hint": "Grundsätze helfen, Verarbeitung rechtmäßig und nachvollziehbar zu bewerten.",
"text": "Was beschreibt der Grundsatz der Richtigkeit?",
"answers": ["Personenbezogene Daten sollen sachlich richtig und aktuell sein", "Daten sollen möglichst bunt sein", "Daten sollen nie geprüft werden"],
"correct": 0,
"explanation": "Unrichtige Daten können zu falschen Entscheidungen führen."
},
{
"category": "DSGVO-Grundsätze",
"hint": "Grundsätze helfen, Verarbeitung rechtmäßig und nachvollziehbar zu bewerten.",
"text": "Was bedeutet Speicherbegrenzung?",
"answers": ["Daten nur so lange speichern, wie es für den Zweck nötig ist", "Daten unbegrenzt behalten", "Nur kleine Dateien speichern"],
"correct": 0,
"explanation": "Personenbezogene Daten brauchen Löschfristen oder Prüffristen."
},
{
"category": "DSGVO-Grundsätze",
"hint": "Grundsätze helfen, Verarbeitung rechtmäßig und nachvollziehbar zu bewerten.",
"text": "Was bedeutet Integrität und Vertraulichkeit im Datenschutzkontext?",
"answers": ["Daten vor unbefugtem Zugriff, Verlust und Manipulation schützen", "Daten öffentlich verteilen", "Daten ohne Backup speichern"],
"correct": 0,
"explanation": "Dieser Grundsatz verlangt angemessene Sicherheit der Verarbeitung."
},
{
"category": "DSGVO-Grundsätze",
"hint": "Grundsätze helfen, Verarbeitung rechtmäßig und nachvollziehbar zu bewerten.",
"text": "Was ist Rechenschaftspflicht?",
"answers": ["Nachweisen können, dass Datenschutzregeln eingehalten werden", "Nur mündlich behaupten, alles sei sicher", "Alle Nachweise löschen"],
"correct": 0,
"explanation": "Organisationen müssen Datenschutzmaßnahmen dokumentieren und belegen können."
},
{
"category": "DSGVO-Grundsätze",
"hint": "Grundsätze helfen, Verarbeitung rechtmäßig und nachvollziehbar zu bewerten.",
"text": "Welche Verarbeitung ist problematisch?",
"answers": ["Kundendaten ohne Zweck unbegrenzt speichern", "Zugriffe rollenbasiert einschränken", "Daten nach Frist löschen"],
"correct": 0,
"explanation": "Ohne Zweck und Frist werden Zweckbindung und Speicherbegrenzung verletzt."
},
{
"category": "DSGVO-Grundsätze",
"hint": "Grundsätze helfen, Verarbeitung rechtmäßig und nachvollziehbar zu bewerten.",
"text": "Warum braucht eine Verarbeitung eine Rechtsgrundlage?",
"answers": ["Damit personenbezogene Daten rechtmäßig verarbeitet werden", "Damit die Daten größer werden", "Damit Backups schneller laufen"],
"correct": 0,
"explanation": "Rechtmäßigkeit ist ein zentraler Grundsatz."
},
{
"category": "DSGVO-Grundsätze",
"hint": "Grundsätze helfen, Verarbeitung rechtmäßig und nachvollziehbar zu bewerten.",
"text": "Welche Aussage passt zur Transparenz?",
"answers": ["Betroffene sollen verständlich informiert werden", "Verarbeitung soll geheim bleiben", "Informationen dürfen absichtlich verwirrend sein"],
"correct": 0,
"explanation": "Transparenz verlangt verständliche Informationen über die Verarbeitung."
},
{
"category": "DSGVO-Grundsätze",
"hint": "Grundsätze helfen, Verarbeitung rechtmäßig und nachvollziehbar zu bewerten.",
"text": "Was ist ein typischer Prüfungsfehler?",
"answers": ["Mehr Daten erfassen als für den Auftrag nötig", "Zugriffsrechte begrenzen", "Löschfristen definieren"],
"correct": 0,
"explanation": "Übermäßige Datenerhebung widerspricht Datenminimierung."
},
{
"category": "Betroffenenrechte",
"hint": "Betroffene Personen haben Rechte gegenüber Verantwortlichen.",
"text": "Was bedeutet Auskunftsrecht?",
"answers": ["Betroffene können Informationen über gespeicherte Daten verlangen", "Betroffene müssen alle Daten selbst sichern", "Betroffene verlieren alle Rechte"],
"correct": 0,
"explanation": "Betroffene dürfen erfahren, welche Daten zu welchem Zweck verarbeitet werden."
},
{
"category": "Betroffenenrechte",
"hint": "Betroffene Personen haben Rechte gegenüber Verantwortlichen.",
"text": "Was bedeutet Recht auf Berichtigung?",
"answers": ["Falsche personenbezogene Daten müssen korrigiert werden", "Daten müssen immer veröffentlicht werden", "Nur Passwörter dürfen geändert werden"],
"correct": 0,
"explanation": "Unrichtige Daten können berichtigt werden."
},
{
"category": "Betroffenenrechte",
"hint": "Betroffene Personen haben Rechte gegenüber Verantwortlichen.",
"text": "Was bedeutet Recht auf Löschung?",
"answers": ["Daten können unter bestimmten Voraussetzungen gelöscht werden", "Alle Daten müssen immer sofort gelöscht werden", "Daten dürfen nie gelöscht werden"],
"correct": 0,
"explanation": "Löschung hängt von Zweck, Rechtsgrundlage und Aufbewahrungspflichten ab."
},
{
"category": "Betroffenenrechte",
"hint": "Betroffene Personen haben Rechte gegenüber Verantwortlichen.",
"text": "Was bedeutet Widerspruchsrecht?",
"answers": ["Betroffene können bestimmten Verarbeitungen widersprechen", "Betroffene müssen jede Verarbeitung akzeptieren", "Nur Administratoren dürfen widersprechen"],
"correct": 0,
"explanation": "Unter bestimmten Bedingungen kann Verarbeitung abgelehnt werden."
},
{
"category": "Betroffenenrechte",
"hint": "Betroffene Personen haben Rechte gegenüber Verantwortlichen.",
"text": "Was bedeutet Datenübertragbarkeit?",
"answers": ["Daten in einem strukturierten, gängigen Format erhalten können", "Daten nur auf Papier bekommen", "Daten nie exportieren dürfen"],
"correct": 0,
"explanation": "Das Recht erleichtert Wechsel zwischen Diensten."
},
{
"category": "Betroffenenrechte",
"hint": "Betroffene Personen haben Rechte gegenüber Verantwortlichen.",
"text": "Wer ist eine betroffene Person?",
"answers": ["Die Person, auf die sich personenbezogene Daten beziehen", "Nur der Datenschutzbeauftragte", "Nur der Serveradministrator"],
"correct": 0,
"explanation": "Betroffen ist die identifizierte oder identifizierbare Person."
},
{
"category": "Betroffenenrechte",
"hint": "Betroffene Personen haben Rechte gegenüber Verantwortlichen.",
"text": "Was sollte bei einer Auskunftsanfrage zuerst geprüft werden?",
"answers": ["Identität der anfragenden Person", "Monitorauflösung", "Farbe der Datenbank"],
"correct": 0,
"explanation": "Daten dürfen nicht an unberechtigte Personen herausgegeben werden."
},
{
"category": "Betroffenenrechte",
"hint": "Betroffene Personen haben Rechte gegenüber Verantwortlichen.",
"text": "Warum dürfen Betroffenenrechte nicht ignoriert werden?",
"answers": ["Sie sind rechtlich relevante Ansprüche", "Sie sind freiwillige Dekoration", "Sie gelten nur im Urlaub"],
"correct": 0,
"explanation": "Organisationen müssen Anfragen angemessen bearbeiten."
},
{
"category": "Betroffenenrechte",
"hint": "Betroffene Personen haben Rechte gegenüber Verantwortlichen.",
"text": "Was ist bei Löschanfragen zu beachten?",
"answers": ["Ob gesetzliche Aufbewahrungspflichten entgegenstehen", "Ob der Drucker eingeschaltet ist", "Ob die Datei hübsch benannt ist"],
"correct": 0,
"explanation": "Nicht jede Löschung ist sofort möglich, wenn Pflichten zur Aufbewahrung bestehen."
},
{
"category": "Betroffenenrechte",
"hint": "Betroffene Personen haben Rechte gegenüber Verantwortlichen.",
"text": "Welche Antwort auf eine Betroffenenanfrage ist professionell?",
"answers": ["Anfrage dokumentieren, prüfen und fristgerecht beantworten", "Anfrage löschen", "Anfrage öffentlich posten"],
"correct": 0,
"explanation": "Dokumentation und fristgerechte Bearbeitung sind wichtig."
},
{
"category": "TOMs & Schutzziele",
"hint": "Technische und organisatorische Maßnahmen schützen Daten im Betrieb.",
"text": "Wofür steht TOM?",
"answers": ["Technische und organisatorische Maßnahmen", "Tägliche Online-Meldung", "Temporäre Ordner-Migration"],
"correct": 0,
"explanation": "TOMs sind Schutzmaßnahmen für die Verarbeitung personenbezogener Daten."
},
{
"category": "TOMs & Schutzziele",
"hint": "Technische und organisatorische Maßnahmen schützen Daten im Betrieb.",
"text": "Welches Schutzziel wird durch Verschlüsselung besonders unterstützt?",
"answers": ["Vertraulichkeit", "Lautstärke", "Bildschirmgröße"],
"correct": 0,
"explanation": "Verschlüsselung schützt Inhalte vor unberechtigtem Lesen."
},
{
"category": "TOMs & Schutzziele",
"hint": "Technische und organisatorische Maßnahmen schützen Daten im Betrieb.",
"text": "Welches Schutzziel wird durch Backups besonders unterstützt?",
"answers": ["Verfügbarkeit", "Farbtreue", "Kabellänge"],
"correct": 0,
"explanation": "Backups helfen, Daten nach Verlust wiederherzustellen."
},
{
"category": "TOMs & Schutzziele",
"hint": "Technische und organisatorische Maßnahmen schützen Daten im Betrieb.",
"text": "Was bedeutet Integrität?",
"answers": ["Daten sind vollständig und unverändert", "Daten sind immer öffentlich", "Daten sind besonders groß"],
"correct": 0,
"explanation": "Integrität schützt vor unbemerkter Manipulation oder Fehlern."
},
{
"category": "TOMs & Schutzziele",
"hint": "Technische und organisatorische Maßnahmen schützen Daten im Betrieb.",
"text": "Welche Maßnahme unterstützt Zugriffskontrolle?",
"answers": ["Rollen- und Rechtekonzept", "Offene Admin-Passwörter", "Gemeinsamer Account für alle"],
"correct": 0,
"explanation": "Rollen und Rechte begrenzen Zugriff nach Aufgabe."
},
{
"category": "TOMs & Schutzziele",
"hint": "Technische und organisatorische Maßnahmen schützen Daten im Betrieb.",
"text": "Warum ist Protokollierung wichtig?",
"answers": ["Zugriffe und Änderungen können nachvollzogen werden", "Alle Daten werden automatisch anonym", "Sie ersetzt jedes Backup"],
"correct": 0,
"explanation": "Logs unterstützen Kontrolle, Nachvollziehbarkeit und Fehleranalyse."
},
{
"category": "TOMs & Schutzziele",
"hint": "Technische und organisatorische Maßnahmen schützen Daten im Betrieb.",
"text": "Was ist ein gutes Passwortkonzept?",
"answers": ["Starke Passwörter, individuelle Accounts und wenn möglich Mehrfaktor-Authentifizierung", "Ein Passwort für alle", "Passwörter auf Monitor kleben"],
"correct": 0,
"explanation": "Individuelle starke Authentifizierung reduziert Missbrauch."
},
{
"category": "TOMs & Schutzziele",
"hint": "Technische und organisatorische Maßnahmen schützen Daten im Betrieb.",
"text": "Welche organisatorische Maßnahme ist sinnvoll?",
"answers": ["Mitarbeiter zu Datenschutz und Sicherheit schulen", "Sicherheitsregeln geheim halten", "Keine Zuständigkeiten definieren"],
"correct": 0,
"explanation": "Schulung hilft, Fehlverhalten und Unsicherheit zu reduzieren."
},
{
"category": "TOMs & Schutzziele",
"hint": "Technische und organisatorische Maßnahmen schützen Daten im Betrieb.",
"text": "Was bedeutet Need-to-know?",
"answers": ["Zugriff nur, wenn er für die Aufgabe nötig ist", "Alle dürfen alles sehen", "Nur externe Personen bekommen Zugriff"],
"correct": 0,
"explanation": "Need-to-know begrenzt Zugriff auf notwendige Informationen."
},
{
"category": "TOMs & Schutzziele",
"hint": "Technische und organisatorische Maßnahmen schützen Daten im Betrieb.",
"text": "Welche Maßnahme passt zur Verfügbarkeit?",
"answers": ["USV, Backup und Redundanz", "Unverschlüsselte Passwortliste", "Keine Wartung"],
"correct": 0,
"explanation": "Verfügbarkeit wird durch Ausfallschutz und Wiederherstellbarkeit unterstützt."
},
{
"category": "Praxisfälle & Verantwortung",
"hint": "Wende Datenschutz in typischen AP1-Handlungssituationen an.",
"text": "Ein USB-Stick mit Kundendaten wurde verloren. Was ist sinnvoll?",
"answers": ["Vorfall melden, Risiko prüfen und zuständige Stellen informieren", "Den Vorfall verschweigen", "Neue Aufkleber drucken"],
"correct": 0,
"explanation": "Datenschutzvorfälle müssen bewertet und nach Prozess behandelt werden."
},
{
"category": "Praxisfälle & Verantwortung",
"hint": "Wende Datenschutz in typischen AP1-Handlungssituationen an.",
"text": "Wer ist Verantwortlicher im Datenschutz?",
"answers": ["Die Stelle, die über Zweck und Mittel der Verarbeitung entscheidet", "Immer der Auszubildende", "Immer der Druckerhersteller"],
"correct": 0,
"explanation": "Verantwortliche bestimmen Zweck und Mittel der Verarbeitung."
},
{
"category": "Praxisfälle & Verantwortung",
"hint": "Wende Datenschutz in typischen AP1-Handlungssituationen an.",
"text": "Was ist Auftragsverarbeitung?",
"answers": ["Ein Dienstleister verarbeitet personenbezogene Daten im Auftrag", "Ein Kunde kauft einen Monitor", "Ein Projekt wird ohne Auftrag begonnen"],
"correct": 0,
"explanation": "Bei Auftragsverarbeitung braucht es klare vertragliche Regelungen."
},
{
"category": "Praxisfälle & Verantwortung",
"hint": "Wende Datenschutz in typischen AP1-Handlungssituationen an.",
"text": "Ein externer IT-Dienstleister wartet ein System mit Kundendaten. Was ist zu prüfen?",
"answers": ["Zugriff, Vertrag, Vertraulichkeit und Zweckbindung", "Nur die Farbe seines Firmenwagens", "Ob er private Musik hört"],
"correct": 0,
"explanation": "Externe Zugriffe müssen rechtlich und technisch geregelt sein."
},
{
"category": "Praxisfälle & Verantwortung",
"hint": "Wende Datenschutz in typischen AP1-Handlungssituationen an.",
"text": "Was ist bei Bildschirmarbeitsplätzen mit Kundendaten sinnvoll?",
"answers": ["Bildschirm sperren und Sichtschutz bei Bedarf nutzen", "Daten dauerhaft offen anzeigen", "Passwort unter die Tastatur legen"],
"correct": 0,
"explanation": "Arbeitsplätze müssen gegen unbefugte Einsicht geschützt werden."
},
{
"category": "Praxisfälle & Verantwortung",
"hint": "Wende Datenschutz in typischen AP1-Handlungssituationen an.",
"text": "Warum sollten alte Datenträger sicher gelöscht oder vernichtet werden?",
"answers": ["Damit personenbezogene Daten nicht wiederhergestellt werden können", "Damit sie schöner aussehen", "Damit Backups schneller werden"],
"correct": 0,
"explanation": "Normales Löschen reicht oft nicht gegen Wiederherstellung."
},
{
"category": "Praxisfälle & Verantwortung",
"hint": "Wende Datenschutz in typischen AP1-Handlungssituationen an.",
"text": "Was ist bei Testdaten in Entwicklungssystemen sinnvoll?",
"answers": ["Keine echten personenbezogenen Daten nutzen oder sie anonymisieren", "Produktivdaten frei kopieren", "Kundendaten öffentlich bereitstellen"],
"correct": 0,
"explanation": "Testsysteme sollten keine unnötigen echten personenbezogenen Daten enthalten."
},
{
"category": "Praxisfälle & Verantwortung",
"hint": "Wende Datenschutz in typischen AP1-Handlungssituationen an.",
"text": "Was sollte bei neuen IT-Systemen früh bedacht werden?",
"answers": ["Datenschutz durch Technikgestaltung und datenschutzfreundliche Voreinstellungen", "Nur die Gehäusefarbe", "Möglichst viele unnötige Daten sammeln"],
"correct": 0,
"explanation": "Privacy by Design und Privacy by Default sind wichtige Prinzipien."
},
{
"category": "Praxisfälle & Verantwortung",
"hint": "Wende Datenschutz in typischen AP1-Handlungssituationen an.",
"text": "Ein Kollege bittet um fremde Personaldaten ohne dienstlichen Grund. Was ist richtig?",
"answers": ["Zugriff verweigern und auf Berechtigung verweisen", "Daten sofort senden", "Daten öffentlich im Chat posten"],
"correct": 0,
"explanation": "Ohne berechtigten Zweck darf kein Zugriff erfolgen."
},
{
"category": "Praxisfälle & Verantwortung",
"hint": "Wende Datenschutz in typischen AP1-Handlungssituationen an.",
"text": "Warum ist Datenschutz in AP1-Aufgaben oft mit IT-Sicherheit verbunden?",
"answers": ["Weil technische Schutzmaßnahmen personenbezogene Daten sichern", "Weil Datenschutz nur aus Firewalls besteht", "Weil Sicherheit Datenschutz ersetzt"],
"correct": 0,
"explanation": "Datenschutz und Datensicherheit überschneiden sich, sind aber nicht identisch."
}
]
}
@@ -0,0 +1,405 @@
{
"title": "OSI-Modell",
"questions": [
{
"category": "OSI-Grundlagen",
"hint": "Verstehe Zweck, Reihenfolge und Nutzen des Schichtenmodells.",
"text": "Wozu dient das OSI-Modell hauptsächlich?",
"answers": ["Zur strukturierten Beschreibung von Netzwerkkommunikation", "Zum Formatieren von Festplatten", "Zum Berechnen von Rabatten"],
"correct": 0,
"explanation": "Das OSI-Modell teilt Netzwerkkommunikation in sieben verständliche Schichten."
},
{
"category": "OSI-Grundlagen",
"hint": "Verstehe Zweck, Reihenfolge und Nutzen des Schichtenmodells.",
"text": "Wie viele Schichten hat das OSI-Modell?",
"answers": ["4", "7", "12"],
"correct": 1,
"explanation": "Das OSI-Modell besteht aus sieben Schichten."
},
{
"category": "OSI-Grundlagen",
"hint": "Verstehe Zweck, Reihenfolge und Nutzen des Schichtenmodells.",
"text": "Welche Schicht ist die unterste Schicht?",
"answers": ["Anwendung", "Bitübertragung", "Transport"],
"correct": 1,
"explanation": "Schicht 1 ist die Bitübertragungsschicht."
},
{
"category": "OSI-Grundlagen",
"hint": "Verstehe Zweck, Reihenfolge und Nutzen des Schichtenmodells.",
"text": "Welche Schicht ist die oberste Schicht?",
"answers": ["Anwendung", "Vermittlung", "Sicherung"],
"correct": 0,
"explanation": "Schicht 7 ist die Anwendungsschicht."
},
{
"category": "OSI-Grundlagen",
"hint": "Verstehe Zweck, Reihenfolge und Nutzen des Schichtenmodells.",
"text": "Warum ist das OSI-Modell in der Fehleranalyse nützlich?",
"answers": ["Fehler können schichtweise eingegrenzt werden", "Es löscht automatisch Störungen", "Es ersetzt jede Dokumentation"],
"correct": 0,
"explanation": "Man prüft vom physischen Anschluss bis zur Anwendung systematisch."
},
{
"category": "OSI-Grundlagen",
"hint": "Verstehe Zweck, Reihenfolge und Nutzen des Schichtenmodells.",
"text": "Welche Reihenfolge ist korrekt?",
"answers": ["Bitübertragung, Sicherung, Vermittlung, Transport", "Anwendung, Sicherung, Bitübertragung, Transport", "Transport, Darstellung, Bitübertragung, Sicherung"],
"correct": 0,
"explanation": "Die unteren vier Schichten lauten 1 Bitübertragung, 2 Sicherung, 3 Vermittlung, 4 Transport."
},
{
"category": "OSI-Grundlagen",
"hint": "Verstehe Zweck, Reihenfolge und Nutzen des Schichtenmodells.",
"text": "Was bedeutet Kapselung in Netzwerkmodellen?",
"answers": ["Jede Schicht fügt eigene Informationen hinzu", "Daten werden immer gelöscht", "Nur Kabel werden beschriftet"],
"correct": 0,
"explanation": "Beim Senden werden Daten von Schicht zu Schicht mit Headern versehen."
},
{
"category": "OSI-Grundlagen",
"hint": "Verstehe Zweck, Reihenfolge und Nutzen des Schichtenmodells.",
"text": "Welche Aussage ist korrekt?",
"answers": ["Protokolle lassen sich bestimmten Schichten zuordnen", "Alle Protokolle gehören immer zu Schicht 1", "Das OSI-Modell gilt nur für Drucker"],
"correct": 0,
"explanation": "Beispiele sind Ethernet auf Schicht 2, IP auf Schicht 3 und HTTP auf Schicht 7."
},
{
"category": "OSI-Grundlagen",
"hint": "Verstehe Zweck, Reihenfolge und Nutzen des Schichtenmodells.",
"text": "Welcher Begriff passt zum OSI-Modell?",
"answers": ["Schichtenmodell", "Bilanzkonto", "Zeiterfassung"],
"correct": 0,
"explanation": "OSI ist ein Schichtenmodell für Kommunikation."
},
{
"category": "OSI-Grundlagen",
"hint": "Verstehe Zweck, Reihenfolge und Nutzen des Schichtenmodells.",
"text": "Was ist in AP1 besonders wichtig?",
"answers": ["Typische Geräte, Protokolle und Fehler den Schichten zuordnen", "Alle RFCs auswendig lernen", "Nur IPv6-Adressen malen"],
"correct": 0,
"explanation": "Prüfungsaufgaben arbeiten oft mit Zuordnung und Fehlerdiagnose."
},
{
"category": "Schicht 1-2: Bitübertragung & Sicherung",
"hint": "Kabel, Signale, MAC-Adressen, Frames und Switches sicher zuordnen.",
"text": "Was gehört typischerweise zu Schicht 1?",
"answers": ["Kabel und elektrische Signale", "HTTP-Statuscodes", "Benutzerrechte"],
"correct": 0,
"explanation": "Schicht 1 beschreibt die physische Übertragung."
},
{
"category": "Schicht 1-2: Bitübertragung & Sicherung",
"hint": "Kabel, Signale, MAC-Adressen, Frames und Switches sicher zuordnen.",
"text": "Welches Gerät arbeitet klassisch auf Schicht 2?",
"answers": ["Switch", "Webserver", "Druckertreiber"],
"correct": 0,
"explanation": "Switches treffen Weiterleitungsentscheidungen anhand von MAC-Adressen."
},
{
"category": "Schicht 1-2: Bitübertragung & Sicherung",
"hint": "Kabel, Signale, MAC-Adressen, Frames und Switches sicher zuordnen.",
"text": "Welche Adresse gehört zu Schicht 2?",
"answers": ["MAC-Adresse", "IP-Adresse", "URL"],
"correct": 0,
"explanation": "MAC-Adressen werden auf der Sicherungsschicht verwendet."
},
{
"category": "Schicht 1-2: Bitübertragung & Sicherung",
"hint": "Kabel, Signale, MAC-Adressen, Frames und Switches sicher zuordnen.",
"text": "Welche Einheit passt zu Schicht 2?",
"answers": ["Frame", "Segment", "Datei"],
"correct": 0,
"explanation": "Auf Schicht 2 werden Daten typischerweise als Frames betrachtet."
},
{
"category": "Schicht 1-2: Bitübertragung & Sicherung",
"hint": "Kabel, Signale, MAC-Adressen, Frames und Switches sicher zuordnen.",
"text": "Ein Netzwerkkabel ist defekt. Welche Schicht ist zuerst betroffen?",
"answers": ["Schicht 1", "Schicht 4", "Schicht 7"],
"correct": 0,
"explanation": "Kabel gehören zur Bitübertragungsschicht."
},
{
"category": "Schicht 1-2: Bitübertragung & Sicherung",
"hint": "Kabel, Signale, MAC-Adressen, Frames und Switches sicher zuordnen.",
"text": "Was macht ein Switch mit seiner MAC-Tabelle?",
"answers": ["Er ordnet MAC-Adressen Ports zu", "Er vergibt HTTP-Cookies", "Er berechnet Kreditzinsen"],
"correct": 0,
"explanation": "Die MAC-Tabelle hilft beim gezielten Weiterleiten von Frames."
},
{
"category": "Schicht 1-2: Bitübertragung & Sicherung",
"hint": "Kabel, Signale, MAC-Adressen, Frames und Switches sicher zuordnen.",
"text": "Was ist ein typisches Schicht-1-Problem?",
"answers": ["Kein Link-Signal am Port", "Falsches Passwort im Webformular", "Falscher Mehrwertsteuersatz"],
"correct": 0,
"explanation": "Fehlendes Link-Signal deutet auf physische Verbindung oder Portproblem hin."
},
{
"category": "Schicht 1-2: Bitübertragung & Sicherung",
"hint": "Kabel, Signale, MAC-Adressen, Frames und Switches sicher zuordnen.",
"text": "Was ist ein typisches Schicht-2-Thema?",
"answers": ["VLAN", "DNS-Name", "HTML-Formular"],
"correct": 0,
"explanation": "VLANs werden häufig der Sicherungsschicht zugeordnet."
},
{
"category": "Schicht 1-2: Bitübertragung & Sicherung",
"hint": "Kabel, Signale, MAC-Adressen, Frames und Switches sicher zuordnen.",
"text": "Was prüfst du bei Verdacht auf Schicht-1-Fehler?",
"answers": ["Kabel, Link-LED, Port und physische Verbindung", "Nur den Browsercache", "Nur Benutzerrechte"],
"correct": 0,
"explanation": "Schicht 1 beginnt bei der physischen Verbindung."
},
{
"category": "Schicht 1-2: Bitübertragung & Sicherung",
"hint": "Kabel, Signale, MAC-Adressen, Frames und Switches sicher zuordnen.",
"text": "Welche Aussage zu Switches ist korrekt?",
"answers": ["Sie arbeiten überwiegend mit MAC-Adressen", "Sie ersetzen jede IP-Adresse", "Sie sind immer Webbrowser"],
"correct": 0,
"explanation": "Switches leiten Frames anhand von MAC-Adressen weiter."
},
{
"category": "Schicht 3-4: Vermittlung & Transport",
"hint": "IP, Routing, TCP, UDP und Ports sauber unterscheiden.",
"text": "Auf welcher Schicht arbeitet IP?",
"answers": ["Schicht 3", "Schicht 1", "Schicht 7"],
"correct": 0,
"explanation": "IP gehört zur Vermittlungsschicht."
},
{
"category": "Schicht 3-4: Vermittlung & Transport",
"hint": "IP, Routing, TCP, UDP und Ports sauber unterscheiden.",
"text": "Auf welcher Schicht arbeitet TCP?",
"answers": ["Schicht 4", "Schicht 2", "Schicht 6"],
"correct": 0,
"explanation": "TCP ist ein Transportprotokoll."
},
{
"category": "Schicht 3-4: Vermittlung & Transport",
"hint": "IP, Routing, TCP, UDP und Ports sauber unterscheiden.",
"text": "Welches Gerät arbeitet klassisch auf Schicht 3?",
"answers": ["Router", "Hub", "Monitor"],
"correct": 0,
"explanation": "Router verbinden Netze anhand von IP-Informationen."
},
{
"category": "Schicht 3-4: Vermittlung & Transport",
"hint": "IP, Routing, TCP, UDP und Ports sauber unterscheiden.",
"text": "Welche Adresse gehört zu Schicht 3?",
"answers": ["IP-Adresse", "MAC-Adresse", "Seriennummer"],
"correct": 0,
"explanation": "IP-Adressen dienen zur logischen Adressierung im Netzwerk."
},
{
"category": "Schicht 3-4: Vermittlung & Transport",
"hint": "IP, Routing, TCP, UDP und Ports sauber unterscheiden.",
"text": "Welche Protokolle gehören typischerweise zu Schicht 4?",
"answers": ["TCP und UDP", "HTTP und HTML", "VGA und HDMI"],
"correct": 0,
"explanation": "TCP und UDP sind Transportprotokolle."
},
{
"category": "Schicht 3-4: Vermittlung & Transport",
"hint": "IP, Routing, TCP, UDP und Ports sauber unterscheiden.",
"text": "Wozu dienen Ports?",
"answers": ["Zur Zuordnung von Daten zu Anwendungen oder Diensten", "Zur Stromversorgung", "Zur Berechnung von Subventionen"],
"correct": 0,
"explanation": "Ports helfen, Daten an den richtigen Dienst weiterzugeben."
},
{
"category": "Schicht 3-4: Vermittlung & Transport",
"hint": "IP, Routing, TCP, UDP und Ports sauber unterscheiden.",
"text": "Was ist ein typischer TCP-Vorteil?",
"answers": ["Zuverlässige, verbindungsorientierte Übertragung", "Keine Fehlerkontrolle", "Nur Broadcast"],
"correct": 0,
"explanation": "TCP bestätigt Daten und sorgt für Reihenfolge und Wiederholung bei Verlust."
},
{
"category": "Schicht 3-4: Vermittlung & Transport",
"hint": "IP, Routing, TCP, UDP und Ports sauber unterscheiden.",
"text": "Was ist ein typischer UDP-Vorteil?",
"answers": ["Geringer Overhead und schnelle Übertragung", "Garantierte Zustellung jedes Pakets", "Automatische Verschlüsselung"],
"correct": 0,
"explanation": "UDP ist schlanker, aber bietet weniger Zuverlässigkeitsmechanismen."
},
{
"category": "Schicht 3-4: Vermittlung & Transport",
"hint": "IP, Routing, TCP, UDP und Ports sauber unterscheiden.",
"text": "Ein Host hat eine falsche Standardgateway-Adresse. Welche Schicht ist besonders betroffen?",
"answers": ["Schicht 3", "Schicht 1", "Schicht 6"],
"correct": 0,
"explanation": "Gateway und Routing gehören zur Vermittlungsschicht."
},
{
"category": "Schicht 3-4: Vermittlung & Transport",
"hint": "IP, Routing, TCP, UDP und Ports sauber unterscheiden.",
"text": "Ein Dienst hört nicht auf Port 443. Welche Schicht ist bei der Portprüfung relevant?",
"answers": ["Schicht 4", "Schicht 1", "Schicht 2"],
"correct": 0,
"explanation": "Ports sind ein Thema der Transportschicht."
},
{
"category": "Schicht 5-7: Sitzung, Darstellung, Anwendung",
"hint": "Dienste, Formate, Verschlüsselung und Anwendungsprotokolle zuordnen.",
"text": "Welche Schicht ist die Anwendungsschicht?",
"answers": ["Schicht 7", "Schicht 3", "Schicht 1"],
"correct": 0,
"explanation": "Schicht 7 stellt Dienste für Anwendungen bereit."
},
{
"category": "Schicht 5-7: Sitzung, Darstellung, Anwendung",
"hint": "Dienste, Formate, Verschlüsselung und Anwendungsprotokolle zuordnen.",
"text": "Welches Protokoll gehört typischerweise zur Anwendungsschicht?",
"answers": ["HTTP", "IP", "Ethernet"],
"correct": 0,
"explanation": "HTTP ist ein Anwendungsprotokoll."
},
{
"category": "Schicht 5-7: Sitzung, Darstellung, Anwendung",
"hint": "Dienste, Formate, Verschlüsselung und Anwendungsprotokolle zuordnen.",
"text": "Welche Aufgabe passt zur Darstellungsschicht?",
"answers": ["Datenformat, Kodierung oder Verschlüsselung", "Kabel einstecken", "MAC-Tabelle lernen"],
"correct": 0,
"explanation": "Schicht 6 befasst sich unter anderem mit Darstellung und Kodierung."
},
{
"category": "Schicht 5-7: Sitzung, Darstellung, Anwendung",
"hint": "Dienste, Formate, Verschlüsselung und Anwendungsprotokolle zuordnen.",
"text": "Welche Aufgabe passt zur Sitzungsschicht?",
"answers": ["Sitzungen und Dialoge zwischen Systemen steuern", "IP-Routing durchführen", "Kabelschirmung prüfen"],
"correct": 0,
"explanation": "Schicht 5 koordiniert Sitzungen und Verbindungsdialoge."
},
{
"category": "Schicht 5-7: Sitzung, Darstellung, Anwendung",
"hint": "Dienste, Formate, Verschlüsselung und Anwendungsprotokolle zuordnen.",
"text": "DNS wird in Prüfungen meist welcher Schicht zugeordnet?",
"answers": ["Schicht 7", "Schicht 2", "Schicht 1"],
"correct": 0,
"explanation": "DNS ist ein Anwendungsdienst zur Namensauflösung."
},
{
"category": "Schicht 5-7: Sitzung, Darstellung, Anwendung",
"hint": "Dienste, Formate, Verschlüsselung und Anwendungsprotokolle zuordnen.",
"text": "SMTP wird typischerweise wofür verwendet?",
"answers": ["E-Mail-Versand", "MAC-Adressierung", "Stromversorgung"],
"correct": 0,
"explanation": "SMTP ist ein Protokoll für den Versand von E-Mails."
},
{
"category": "Schicht 5-7: Sitzung, Darstellung, Anwendung",
"hint": "Dienste, Formate, Verschlüsselung und Anwendungsprotokolle zuordnen.",
"text": "Welche Aussage zu HTTPS ist passend?",
"answers": ["Es schützt HTTP-Kommunikation durch TLS", "Es ersetzt jedes Routing", "Es gehört nur zu Schicht 1"],
"correct": 0,
"explanation": "HTTPS nutzt TLS für verschlüsselte Webkommunikation."
},
{
"category": "Schicht 5-7: Sitzung, Darstellung, Anwendung",
"hint": "Dienste, Formate, Verschlüsselung und Anwendungsprotokolle zuordnen.",
"text": "Ein Browser zeigt 'DNS_PROBE_FINISHED'. Welcher Bereich ist naheliegend?",
"answers": ["Namensauflösung auf Anwendungsebene", "Defektes Netzwerkkabel zwingend", "Falsche Mehrwertsteuer"],
"correct": 0,
"explanation": "DNS-Probleme liegen bei der Namensauflösung, typischerweise Anwendungsschicht."
},
{
"category": "Schicht 5-7: Sitzung, Darstellung, Anwendung",
"hint": "Dienste, Formate, Verschlüsselung und Anwendungsprotokolle zuordnen.",
"text": "Welches Beispiel passt zur Darstellungsschicht?",
"answers": ["Zeichencodierung wie UTF-8", "Switchport lernen", "Subnetzmaske berechnen"],
"correct": 0,
"explanation": "Zeichencodierung betrifft die Darstellung von Daten."
},
{
"category": "Schicht 5-7: Sitzung, Darstellung, Anwendung",
"hint": "Dienste, Formate, Verschlüsselung und Anwendungsprotokolle zuordnen.",
"text": "Welche Protokolle sind typische Anwendungsprotokolle?",
"answers": ["HTTP, DNS, SMTP", "IP, ICMP, ARP", "Kupfer, Glasfaser, Funk"],
"correct": 0,
"explanation": "HTTP, DNS und SMTP werden meist der Anwendungsschicht zugeordnet."
},
{
"category": "Fehleranalyse nach OSI",
"hint": "Gehe systematisch von physischer Verbindung bis Anwendung vor.",
"text": "Ein PC hat keine Link-LED am Netzwerkport. Was prüfst du zuerst?",
"answers": ["Kabel, Port und physische Verbindung", "DNS-Eintrag", "HTTP-Cookie"],
"correct": 0,
"explanation": "Ohne Link ist zuerst Schicht 1 zu prüfen."
},
{
"category": "Fehleranalyse nach OSI",
"hint": "Gehe systematisch von physischer Verbindung bis Anwendung vor.",
"text": "Ein PC hat Link, aber keine gültige IP-Adresse. Welche Ebene ist naheliegend?",
"answers": ["Schicht 3 beziehungsweise IP-Konfiguration", "Nur Schicht 7", "Nur das Netzteil"],
"correct": 0,
"explanation": "IP-Adresse, Gateway und Netzmaske gehören zur Vermittlungsschicht."
},
{
"category": "Fehleranalyse nach OSI",
"hint": "Gehe systematisch von physischer Verbindung bis Anwendung vor.",
"text": "Ping zur IP funktioniert, aber der Name nicht. Was ist wahrscheinlich?",
"answers": ["DNS-Problem", "Kabel zwingend defekt", "Monitor kaputt"],
"correct": 0,
"explanation": "Wenn IP erreichbar ist, aber Name nicht, liegt der Fokus auf Namensauflösung."
},
{
"category": "Fehleranalyse nach OSI",
"hint": "Gehe systematisch von physischer Verbindung bis Anwendung vor.",
"text": "Ein Webserver ist per Ping erreichbar, aber HTTPS geht nicht. Was prüfst du?",
"answers": ["Port 443, Dienst und Firewall", "Nur die Kabelfarbe", "Nur den Druckertreiber"],
"correct": 0,
"explanation": "Hier sind Transport- und Anwendungsebene relevant."
},
{
"category": "Fehleranalyse nach OSI",
"hint": "Gehe systematisch von physischer Verbindung bis Anwendung vor.",
"text": "Warum ist schichtweises Vorgehen sinnvoll?",
"answers": ["Es verhindert planloses Raten", "Es macht Dokumentation unnötig", "Es löscht automatisch Fehler"],
"correct": 0,
"explanation": "Schichtweises Prüfen grenzt Ursachen systematisch ein."
},
{
"category": "Fehleranalyse nach OSI",
"hint": "Gehe systematisch von physischer Verbindung bis Anwendung vor.",
"text": "Welche Reihenfolge ist bei Fehlersuche oft sinnvoll?",
"answers": ["Physisch, lokale Konfiguration, Routing, Ports, Anwendung", "Anwendung, Rechnung, Gehäusefarbe", "Nur Neustart ohne Prüfung"],
"correct": 0,
"explanation": "Man beginnt häufig unten und arbeitet sich nach oben."
},
{
"category": "Fehleranalyse nach OSI",
"hint": "Gehe systematisch von physischer Verbindung bis Anwendung vor.",
"text": "Ein falsches VLAN am Switchport betrifft vor allem welche Schicht?",
"answers": ["Schicht 2", "Schicht 6", "Schicht 7"],
"correct": 0,
"explanation": "VLAN-Zuordnung ist ein Thema der Sicherungsschicht."
},
{
"category": "Fehleranalyse nach OSI",
"hint": "Gehe systematisch von physischer Verbindung bis Anwendung vor.",
"text": "Ein falsches Standardgateway verhindert Zugriff auf andere Netze. Welche Schicht ist betroffen?",
"answers": ["Schicht 3", "Schicht 1", "Schicht 5"],
"correct": 0,
"explanation": "Gateway und Routing gehören zu Schicht 3."
},
{
"category": "Fehleranalyse nach OSI",
"hint": "Gehe systematisch von physischer Verbindung bis Anwendung vor.",
"text": "Ein Dienst läuft, aber die Firewall blockiert den Port. Welche Prüfung ist relevant?",
"answers": ["Transportebene und Portfreigabe", "Nur Bildschirmhelligkeit", "Nur Speicherplatz"],
"correct": 0,
"explanation": "Ports und Verbindungen gehören zur Transportschicht."
},
{
"category": "Fehleranalyse nach OSI",
"hint": "Gehe systematisch von physischer Verbindung bis Anwendung vor.",
"text": "Eine Webseite liefert HTTP 500. Wo liegt der Fokus eher?",
"answers": ["Anwendung oder Serverdienst", "Defektes Patchkabel zwingend", "MAC-Adresse des Monitors"],
"correct": 0,
"explanation": "HTTP-Fehlercodes gehören zur Anwendungsebene beziehungsweise zum Webdienst."
}
]
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,245 @@
{
"title": "Datenbanksysteme",
"questions": [
{
"category": "Datenbank-Grundlagen",
"hint": "Tabellen, Datensätze und Beziehungen verstehen.",
"text": "Was beschreibt Tabelle am besten?",
"answers": [
"Sie strukturiert Daten in Zeilen und Spalten.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Sie strukturiert Daten in Zeilen und Spalten."
},
{
"category": "Datenbank-Grundlagen",
"hint": "Tabellen, Datensätze und Beziehungen verstehen.",
"text": "Welche Aussage zu Datensatz ist richtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Eine Zeile beschreibt ein Objekt oder Ereignis.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "Eine Zeile beschreibt ein Objekt oder Ereignis."
},
{
"category": "Datenbank-Grundlagen",
"hint": "Tabellen, Datensätze und Beziehungen verstehen.",
"text": "Woran erkennst du Attribut in einer AP1-Aufgabe?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Eine Spalte beschreibt eine Eigenschaft."
],
"correct": 2,
"explanation": "Eine Spalte beschreibt eine Eigenschaft."
},
{
"category": "Datenbank-Grundlagen",
"hint": "Tabellen, Datensätze und Beziehungen verstehen.",
"text": "Was ist bei Primärschlüssel besonders wichtig?",
"answers": [
"Er identifiziert Datensätze eindeutig.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Er identifiziert Datensätze eindeutig."
},
{
"category": "Datenbank-Grundlagen",
"hint": "Tabellen, Datensätze und Beziehungen verstehen.",
"text": "Was beschreibt Fremdschlüssel am besten?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Er verweist auf Datensätze anderer Tabellen.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "Er verweist auf Datensätze anderer Tabellen."
},
{
"category": "Datenbank-Grundlagen",
"hint": "Tabellen, Datensätze und Beziehungen verstehen.",
"text": "Welche Aussage zu Relation ist richtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Beziehung zwischen Tabellen."
],
"correct": 2,
"explanation": "Beziehung zwischen Tabellen."
},
{
"category": "Datenbank-Grundlagen",
"hint": "Tabellen, Datensätze und Beziehungen verstehen.",
"text": "Woran erkennst du Redundanz in einer AP1-Aufgabe?",
"answers": [
"Doppelte Daten können Inkonsistenzen erzeugen.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Doppelte Daten können Inkonsistenzen erzeugen."
},
{
"category": "Datenbank-Grundlagen",
"hint": "Tabellen, Datensätze und Beziehungen verstehen.",
"text": "Was ist bei Normalisierung besonders wichtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Sie reduziert unnötige Wiederholungen.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "Sie reduziert unnötige Wiederholungen."
},
{
"category": "Datenbank-Grundlagen",
"hint": "Tabellen, Datensätze und Beziehungen verstehen.",
"text": "Was beschreibt DBMS am besten?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Es verwaltet Daten, Zugriffe und Transaktionen."
],
"correct": 2,
"explanation": "Es verwaltet Daten, Zugriffe und Transaktionen."
},
{
"category": "Datenbank-Grundlagen",
"hint": "Tabellen, Datensätze und Beziehungen verstehen.",
"text": "Welche Aussage zu Transaktion ist richtig?",
"answers": [
"Sie fasst Operationen zuverlässig zusammen.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Sie fasst Operationen zuverlässig zusammen."
},
{
"category": "SQL & Betrieb",
"hint": "Abfragen, Rechte und Betriebsaspekte einordnen.",
"text": "Was beschreibt SELECT am besten?",
"answers": [
"Liest Daten aus Tabellen.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Liest Daten aus Tabellen."
},
{
"category": "SQL & Betrieb",
"hint": "Abfragen, Rechte und Betriebsaspekte einordnen.",
"text": "Welche Aussage zu INSERT ist richtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Fügt neue Datensätze ein.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "Fügt neue Datensätze ein."
},
{
"category": "SQL & Betrieb",
"hint": "Abfragen, Rechte und Betriebsaspekte einordnen.",
"text": "Woran erkennst du UPDATE in einer AP1-Aufgabe?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Ändert bestehende Datensätze."
],
"correct": 2,
"explanation": "Ändert bestehende Datensätze."
},
{
"category": "SQL & Betrieb",
"hint": "Abfragen, Rechte und Betriebsaspekte einordnen.",
"text": "Was ist bei DELETE besonders wichtig?",
"answers": [
"Löscht Datensätze.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Löscht Datensätze."
},
{
"category": "SQL & Betrieb",
"hint": "Abfragen, Rechte und Betriebsaspekte einordnen.",
"text": "Was beschreibt WHERE am besten?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Filtert Datensätze nach Bedingungen.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "Filtert Datensätze nach Bedingungen."
},
{
"category": "SQL & Betrieb",
"hint": "Abfragen, Rechte und Betriebsaspekte einordnen.",
"text": "Welche Aussage zu JOIN ist richtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Verknüpft Tabellen über Beziehungen."
],
"correct": 2,
"explanation": "Verknüpft Tabellen über Beziehungen."
},
{
"category": "SQL & Betrieb",
"hint": "Abfragen, Rechte und Betriebsaspekte einordnen.",
"text": "Woran erkennst du Backup in einer AP1-Aufgabe?",
"answers": [
"Datenbanken brauchen Sicherung und Restore-Test.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Datenbanken brauchen Sicherung und Restore-Test."
},
{
"category": "SQL & Betrieb",
"hint": "Abfragen, Rechte und Betriebsaspekte einordnen.",
"text": "Was ist bei Rechtekonzept besonders wichtig?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Nutzer sollten nur notwendige Rechte haben.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 1,
"explanation": "Nutzer sollten nur notwendige Rechte haben."
},
{
"category": "SQL & Betrieb",
"hint": "Abfragen, Rechte und Betriebsaspekte einordnen.",
"text": "Was beschreibt Datenschutz am besten?",
"answers": [
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung.",
"Personenbezogene Daten müssen geschützt werden."
],
"correct": 2,
"explanation": "Personenbezogene Daten müssen geschützt werden."
},
{
"category": "SQL & Betrieb",
"hint": "Abfragen, Rechte und Betriebsaspekte einordnen.",
"text": "Welche Aussage zu Index ist richtig?",
"answers": [
"Kann Abfragen beschleunigen, braucht aber Speicher.",
"Es ist nur Dekoration und hat keinen Prüfungsbezug.",
"Es ist ein zufälliger Begriff ohne technische Bedeutung."
],
"correct": 0,
"explanation": "Kann Abfragen beschleunigen, braucht aber Speicher."
}
]
}

Some files were not shown because too many files have changed in this diff Show More