fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
439
h5p-service/players/drag-drop-player.html
Normal file
439
h5p-service/players/drag-drop-player.html
Normal file
@@ -0,0 +1,439 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Drag and Drop - BreakPilot H5P</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background: #f5f5f5;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
padding: 30px;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 12px;
|
||||
font-size: 28px;
|
||||
}
|
||||
.question {
|
||||
color: #6b7280;
|
||||
margin-bottom: 32px;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.game-area {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.zones-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
.drop-zone {
|
||||
background: #f9fafb;
|
||||
border: 3px dashed #d1d5db;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
min-height: 120px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.drop-zone.drag-over {
|
||||
background: #e0e7ff;
|
||||
border-color: #667eea;
|
||||
}
|
||||
.drop-zone.correct {
|
||||
background: #d1fae5;
|
||||
border-color: #10b981;
|
||||
border-style: solid;
|
||||
}
|
||||
.zone-title {
|
||||
font-weight: 700;
|
||||
color: #374151;
|
||||
margin-bottom: 12px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.zone-items {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
min-height: 40px;
|
||||
}
|
||||
.draggables-container {
|
||||
background: #fef3c7;
|
||||
border: 2px solid #f59e0b;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
}
|
||||
.draggables-title {
|
||||
font-weight: 700;
|
||||
color: #92400e;
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.draggables-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.draggable {
|
||||
background: white;
|
||||
border: 2px solid #f59e0b;
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
transition: all 0.2s;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
.draggable:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
.draggable.dragging {
|
||||
opacity: 0.5;
|
||||
cursor: grabbing;
|
||||
}
|
||||
.draggable.correct {
|
||||
border-color: #10b981;
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
cursor: default;
|
||||
}
|
||||
.draggable.incorrect {
|
||||
border-color: #ef4444;
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
}
|
||||
.btn {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.btn-primary {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background: #5568d3;
|
||||
}
|
||||
.btn-primary:disabled {
|
||||
background: #9ca3af;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.btn-secondary {
|
||||
background: #e5e7eb;
|
||||
color: #374151;
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background: #d1d5db;
|
||||
}
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
.results {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border-radius: 12px;
|
||||
padding: 32px;
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
display: none;
|
||||
}
|
||||
.results h2 {
|
||||
font-size: 32px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.results .score {
|
||||
font-size: 64px;
|
||||
font-weight: 700;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.feedback {
|
||||
background: #f0f9ff;
|
||||
border-left: 4px solid #3b82f6;
|
||||
padding: 16px;
|
||||
margin-bottom: 24px;
|
||||
border-radius: 4px;
|
||||
display: none;
|
||||
}
|
||||
.feedback.show {
|
||||
display: block;
|
||||
}
|
||||
.feedback.correct {
|
||||
background: #d1fae5;
|
||||
border-color: #10b981;
|
||||
color: #065f46;
|
||||
}
|
||||
.feedback.incorrect {
|
||||
background: #fee2e2;
|
||||
border-color: #ef4444;
|
||||
color: #991b1b;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.game-area {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1 id="title"></h1>
|
||||
<p class="question" id="question"></p>
|
||||
|
||||
<div class="results" id="results">
|
||||
<h2>🎉 Geschafft!</h2>
|
||||
<div class="score" id="scoreDisplay"></div>
|
||||
<p id="resultMessage"></p>
|
||||
</div>
|
||||
|
||||
<div class="feedback" id="feedback"></div>
|
||||
|
||||
<div class="game-area">
|
||||
<div class="zones-container" id="zonesContainer"></div>
|
||||
|
||||
<div class="draggables-container">
|
||||
<div class="draggables-title">🔲 Ziehe die Elemente</div>
|
||||
<div class="draggables-items" id="draggablesContainer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" id="checkBtn" onclick="checkAnswers()">
|
||||
Antworten überprüfen
|
||||
</button>
|
||||
<button class="btn btn-secondary" id="retryBtn" onclick="retry()" style="display: none;">
|
||||
Nochmal versuchen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let data = null;
|
||||
let isChecked = false;
|
||||
|
||||
function loadContent() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const dataParam = urlParams.get('data');
|
||||
|
||||
if (dataParam) {
|
||||
try {
|
||||
data = JSON.parse(decodeURIComponent(dataParam));
|
||||
} catch (e) {
|
||||
alert('Fehler beim Laden!');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
alert('Keine Daten gefunden!');
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('title').textContent = data.title;
|
||||
document.getElementById('question').textContent = data.question || 'Ziehe die Elemente in die richtigen Zonen.';
|
||||
|
||||
renderZones();
|
||||
renderDraggables();
|
||||
setupDragAndDrop();
|
||||
}
|
||||
|
||||
function renderZones() {
|
||||
const container = document.getElementById('zonesContainer');
|
||||
container.innerHTML = '';
|
||||
|
||||
data.zones.forEach((zone) => {
|
||||
const zoneEl = document.createElement('div');
|
||||
zoneEl.className = 'drop-zone';
|
||||
zoneEl.dataset.zoneId = zone.id;
|
||||
zoneEl.innerHTML = `
|
||||
<div class="zone-title">${zone.name}</div>
|
||||
<div class="zone-items" data-zone-id="${zone.id}"></div>
|
||||
`;
|
||||
container.appendChild(zoneEl);
|
||||
});
|
||||
}
|
||||
|
||||
function renderDraggables() {
|
||||
const container = document.getElementById('draggablesContainer');
|
||||
container.innerHTML = '';
|
||||
|
||||
// Shuffle draggables
|
||||
const shuffled = [...data.draggables].sort(() => Math.random() - 0.5);
|
||||
|
||||
shuffled.forEach((draggable) => {
|
||||
const draggableEl = document.createElement('div');
|
||||
draggableEl.className = 'draggable';
|
||||
draggableEl.draggable = true;
|
||||
draggableEl.dataset.draggableId = draggable.id;
|
||||
draggableEl.dataset.correctZoneId = draggable.correctZoneId;
|
||||
draggableEl.textContent = draggable.text;
|
||||
container.appendChild(draggableEl);
|
||||
});
|
||||
}
|
||||
|
||||
function setupDragAndDrop() {
|
||||
// Draggable elements
|
||||
document.querySelectorAll('.draggable').forEach(draggable => {
|
||||
draggable.addEventListener('dragstart', (e) => {
|
||||
if (isChecked) return;
|
||||
draggable.classList.add('dragging');
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setData('text/html', draggable.outerHTML);
|
||||
});
|
||||
|
||||
draggable.addEventListener('dragend', (e) => {
|
||||
draggable.classList.remove('dragging');
|
||||
});
|
||||
});
|
||||
|
||||
// Drop zones
|
||||
document.querySelectorAll('.zone-items').forEach(zone => {
|
||||
zone.addEventListener('dragover', (e) => {
|
||||
if (isChecked) return;
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
zone.closest('.drop-zone').classList.add('drag-over');
|
||||
});
|
||||
|
||||
zone.addEventListener('dragleave', (e) => {
|
||||
zone.closest('.drop-zone').classList.remove('drag-over');
|
||||
});
|
||||
|
||||
zone.addEventListener('drop', (e) => {
|
||||
if (isChecked) return;
|
||||
e.preventDefault();
|
||||
zone.closest('.drop-zone').classList.remove('drag-over');
|
||||
|
||||
const dragging = document.querySelector('.dragging');
|
||||
if (dragging) {
|
||||
zone.appendChild(dragging);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Original container
|
||||
const draggablesContainer = document.getElementById('draggablesContainer');
|
||||
draggablesContainer.addEventListener('dragover', (e) => {
|
||||
if (isChecked) return;
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
draggablesContainer.addEventListener('drop', (e) => {
|
||||
if (isChecked) return;
|
||||
e.preventDefault();
|
||||
const dragging = document.querySelector('.dragging');
|
||||
if (dragging) {
|
||||
draggablesContainer.appendChild(dragging);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function checkAnswers() {
|
||||
if (isChecked) return;
|
||||
isChecked = true;
|
||||
|
||||
let correct = 0;
|
||||
const total = data.draggables.length;
|
||||
|
||||
data.zones.forEach(zone => {
|
||||
const zoneItems = document.querySelector(`.zone-items[data-zone-id="${zone.id}"]`);
|
||||
const draggablesInZone = zoneItems.querySelectorAll('.draggable');
|
||||
|
||||
draggablesInZone.forEach(draggable => {
|
||||
const correctZoneId = parseInt(draggable.dataset.correctZoneId);
|
||||
draggable.draggable = false;
|
||||
|
||||
if (correctZoneId === zone.id) {
|
||||
draggable.classList.add('correct');
|
||||
zoneItems.closest('.drop-zone').classList.add('correct');
|
||||
correct++;
|
||||
} else {
|
||||
draggable.classList.add('incorrect');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Check items still in draggables container
|
||||
document.getElementById('draggablesContainer').querySelectorAll('.draggable').forEach(draggable => {
|
||||
draggable.classList.add('incorrect');
|
||||
draggable.draggable = false;
|
||||
});
|
||||
|
||||
const percentage = Math.round((correct / total) * 100);
|
||||
|
||||
// Show feedback
|
||||
const feedback = document.getElementById('feedback');
|
||||
if (correct === total) {
|
||||
feedback.className = 'feedback correct show';
|
||||
feedback.textContent = `🎉 Perfekt! Alle ${total} Elemente richtig zugeordnet!`;
|
||||
|
||||
setTimeout(() => {
|
||||
showResults(correct, total);
|
||||
}, 1500);
|
||||
} else {
|
||||
feedback.className = 'feedback incorrect show';
|
||||
feedback.textContent = `Du hast ${correct} von ${total} Elementen richtig zugeordnet (${percentage}%).`;
|
||||
}
|
||||
|
||||
document.getElementById('checkBtn').style.display = 'none';
|
||||
document.getElementById('retryBtn').style.display = 'inline-block';
|
||||
}
|
||||
|
||||
function showResults(correct, total) {
|
||||
const percentage = Math.round((correct / total) * 100);
|
||||
|
||||
document.getElementById('scoreDisplay').textContent = `${correct} / ${total}`;
|
||||
document.getElementById('resultMessage').textContent = `${percentage}% richtig - Sehr gut!`;
|
||||
document.getElementById('results').style.display = 'block';
|
||||
}
|
||||
|
||||
function retry() {
|
||||
isChecked = false;
|
||||
|
||||
// Move all draggables back
|
||||
const draggablesContainer = document.getElementById('draggablesContainer');
|
||||
document.querySelectorAll('.draggable').forEach(draggable => {
|
||||
draggable.classList.remove('correct', 'incorrect');
|
||||
draggable.draggable = true;
|
||||
draggablesContainer.appendChild(draggable);
|
||||
});
|
||||
|
||||
// Clear zone correct state
|
||||
document.querySelectorAll('.drop-zone').forEach(zone => {
|
||||
zone.classList.remove('correct');
|
||||
});
|
||||
|
||||
document.getElementById('feedback').classList.remove('show');
|
||||
document.getElementById('results').style.display = 'none';
|
||||
|
||||
document.getElementById('checkBtn').style.display = 'inline-block';
|
||||
document.getElementById('retryBtn').style.display = 'none';
|
||||
}
|
||||
|
||||
loadContent();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user