ed
This commit is contained in:
		
							parent
							
								
									028317e2ec
								
							
						
					
					
						commit
						7f51e16f6b
					
				
					 2 changed files with 527 additions and 67 deletions
				
			
		| 
						 | 
				
			
			@ -1,4 +1,6 @@
 | 
			
		|||
let currentLicense = null;
 | 
			
		||||
let isCardFlipped = false;
 | 
			
		||||
let cameraStream = null;
 | 
			
		||||
 | 
			
		||||
// Event Listener für Nachrichten von FiveM
 | 
			
		||||
window.addEventListener('message', function(event) {
 | 
			
		||||
| 
						 | 
				
			
			@ -8,6 +10,12 @@ window.addEventListener('message', function(event) {
 | 
			
		|||
        case 'showLicense':
 | 
			
		||||
            showLicense(data.data);
 | 
			
		||||
            break;
 | 
			
		||||
        case 'hideLicense':
 | 
			
		||||
            closeLicense();
 | 
			
		||||
            break;
 | 
			
		||||
        case 'openCamera':
 | 
			
		||||
            openCamera();
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -17,75 +25,301 @@ function showLicense(data) {
 | 
			
		|||
    const container = document.getElementById('license-container');
 | 
			
		||||
    const card = document.getElementById('license-card');
 | 
			
		||||
    
 | 
			
		||||
    // Lizenztyp-spezifische Klasse hinzufügen
 | 
			
		||||
    card.className = 'license-card ' + data.license.license_type;
 | 
			
		||||
    // Loading anzeigen
 | 
			
		||||
    showLoading();
 | 
			
		||||
    
 | 
			
		||||
    // Header befüllen
 | 
			
		||||
    document.querySelector('.license-title').textContent = data.config.label;
 | 
			
		||||
    document.querySelector('.license-icon').className = 'license-icon ' + data.config.icon;
 | 
			
		||||
    
 | 
			
		||||
    // Lizenzinformationen befüllen
 | 
			
		||||
    document.getElementById('license-name').textContent = data.license.name || 'N/A';
 | 
			
		||||
    document.getElementById('license-birthday').textContent = data.license.birthday || 'N/A';
 | 
			
		||||
    document.getElementById('license-gender').textContent = formatGender(data.license.gender) || 'N/A';
 | 
			
		||||
    document.getElementById('license-issue').textContent = data.license.issue_date || 'N/A';
 | 
			
		||||
    document.getElementById('license-expire').textContent = data.license.expire_date || 'N/A';
 | 
			
		||||
    
 | 
			
		||||
    // Klassen anzeigen (nur bei Führerschein)
 | 
			
		||||
    const classesRow = document.getElementById('license-classes-row');
 | 
			
		||||
    if (data.license.classes && data.license.classes !== '[]') {
 | 
			
		||||
        try {
 | 
			
		||||
            const classes = JSON.parse(data.license.classes);
 | 
			
		||||
            if (classes && classes.length > 0) {
 | 
			
		||||
                document.getElementById('license-classes').textContent = classes.join(', ');
 | 
			
		||||
                classesRow.style.display = 'flex';
 | 
			
		||||
            } else {
 | 
			
		||||
                classesRow.style.display = 'none';
 | 
			
		||||
            }
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            classesRow.style.display = 'none';
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        classesRow.style.display = 'none';
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Status anzeigen
 | 
			
		||||
    const statusElement = document.getElementById('license-status');
 | 
			
		||||
    if (data.license.is_active) {
 | 
			
		||||
        statusElement.textContent = '✅ Gültig';
 | 
			
		||||
        statusElement.className = 'license-status active';
 | 
			
		||||
    } else {
 | 
			
		||||
        statusElement.textContent = '❌ Ungültig';
 | 
			
		||||
        statusElement.className = 'license-status inactive';
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Container anzeigen
 | 
			
		||||
    container.classList.remove('hidden');
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        // Lizenztyp-spezifische Klasse hinzufügen
 | 
			
		||||
        card.className = 'license-card ' + data.license.license_type;
 | 
			
		||||
        
 | 
			
		||||
        // Header befüllen
 | 
			
		||||
        document.getElementById('license-title').textContent = data.config.label;
 | 
			
		||||
        document.getElementById('license-icon').className = 'license-icon ' + data.config.icon;
 | 
			
		||||
        
 | 
			
		||||
        // Persönliche Daten
 | 
			
		||||
        document.getElementById('license-name').textContent = data.license.name || 'N/A';
 | 
			
		||||
        document.getElementById('license-birthday').textContent = formatDate(data.license.birthday) || 'N/A';
 | 
			
		||||
        document.getElementById('license-gender').textContent = formatGender(data.license.gender) || 'N/A';
 | 
			
		||||
        
 | 
			
		||||
        // Dokument-Informationen
 | 
			
		||||
        document.getElementById('license-issue').textContent = formatDate(data.license.issue_date) || 'N/A';
 | 
			
		||||
        document.getElementById('license-expire').textContent = formatDate(data.license.expire_date) || 'N/A';
 | 
			
		||||
        document.getElementById('license-id').textContent = '#' + (data.license.id || '000000').toString().padStart(6, '0');
 | 
			
		||||
        document.getElementById('license-issuer').textContent = data.license.issued_by_name || 'Behörde';
 | 
			
		||||
        
 | 
			
		||||
        // Foto anzeigen
 | 
			
		||||
        displayPlayerPhoto(data.license);
 | 
			
		||||
        
 | 
			
		||||
        // Klassen anzeigen (nur bei Führerschein)
 | 
			
		||||
        displayLicenseClasses(data.license);
 | 
			
		||||
        
 | 
			
		||||
        // Status und Gültigkeit
 | 
			
		||||
        displayLicenseStatus(data.license);
 | 
			
		||||
        displayValidityIndicator(data.license);
 | 
			
		||||
        
 | 
			
		||||
        // Rückseite vorbereiten
 | 
			
		||||
        prepareBackSide(data.license);
 | 
			
		||||
        
 | 
			
		||||
        // Container anzeigen
 | 
			
		||||
        hideLoading();
 | 
			
		||||
        container.classList.remove('hidden');
 | 
			
		||||
        
 | 
			
		||||
        // Sound-Effekt
 | 
			
		||||
        playSound('card-flip-sound');
 | 
			
		||||
        
 | 
			
		||||
        // Notification
 | 
			
		||||
        showNotification('Lizenz geladen', 'success');
 | 
			
		||||
        
 | 
			
		||||
    }, 500);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    // Foto anzeigen
 | 
			
		||||
// Spieler-Foto anzeigen
 | 
			
		||||
function displayPlayerPhoto(license) {
 | 
			
		||||
    const photoImg = document.getElementById('player-photo');
 | 
			
		||||
    const photoPlaceholder = document.getElementById('photo-placeholder');
 | 
			
		||||
    
 | 
			
		||||
    if (data.license.photo_url && data.license.photo_url !== '') {
 | 
			
		||||
        // Echtes Foto anzeigen
 | 
			
		||||
        photoImg.src = data.license.photo_url;
 | 
			
		||||
        photoImg.classList.remove('hidden');
 | 
			
		||||
        photoPlaceholder.classList.add('hidden');
 | 
			
		||||
    if (license.photo_url && license.photo_url !== '') {
 | 
			
		||||
        photoImg.src = license.photo_url;
 | 
			
		||||
        photoImg.onload = function() {
 | 
			
		||||
            photoImg.classList.remove('hidden');
 | 
			
		||||
            photoPlaceholder.classList.add('hidden');
 | 
			
		||||
        };
 | 
			
		||||
        photoImg.onerror = function() {
 | 
			
		||||
            photoImg.classList.add('hidden');
 | 
			
		||||
            photoPlaceholder.classList.remove('hidden');
 | 
			
		||||
        };
 | 
			
		||||
    } else {
 | 
			
		||||
        // Platzhalter anzeigen
 | 
			
		||||
        photoImg.classList.add('hidden');
 | 
			
		||||
        photoPlaceholder.classList.remove('hidden');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Geschlecht formatieren
 | 
			
		||||
function formatGender(gender) {
 | 
			
		||||
    const genderMap = {
 | 
			
		||||
        'male': 'Männlich',
 | 
			
		||||
        'female': 'Weiblich',
 | 
			
		||||
        'other': 'Divers'
 | 
			
		||||
    };
 | 
			
		||||
    return genderMap[gender] || gender;
 | 
			
		||||
// Lizenz-Klassen anzeigen
 | 
			
		||||
function displayLicenseClasses(license) {
 | 
			
		||||
    const classesRow = document.getElementById('license-classes-row');
 | 
			
		||||
    const classesElement = document.getElementById('license-classes');
 | 
			
		||||
    
 | 
			
		||||
    if (license.license_type === 'drivers_license' && license.classes && license.classes !== '[]') {
 | 
			
		||||
        try {
 | 
			
		||||
            const classes = JSON.parse(license.classes);
 | 
			
		||||
            if (classes && classes.length > 0) {
 | 
			
		||||
                classesElement.textContent = classes.join(', ');
 | 
			
		||||
                classesRow.style.display = 'flex';
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            console.error('Fehler beim Parsen der Klassen:', e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    classesRow.style.display = 'none';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Lizenz-Status anzeigen
 | 
			
		||||
function displayLicenseStatus(license) {
 | 
			
		||||
    const statusElement = document.getElementById('license-status');
 | 
			
		||||
    const statusIcon = statusElement.querySelector('.status-icon');
 | 
			
		||||
    const statusText = statusElement.querySelector('.status-text');
 | 
			
		||||
    
 | 
			
		||||
    // Ablaufdatum prüfen
 | 
			
		||||
    let isExpired = false;
 | 
			
		||||
    let isExpiringSoon = false;
 | 
			
		||||
    
 | 
			
		||||
    if (license.expire_date) {
 | 
			
		||||
        const expireDate = new Date(license.expire_date);
 | 
			
		||||
        const today = new Date();
 | 
			
		||||
        const daysUntilExpire = Math.ceil((expireDate - today) / (1000 * 60 * 60 * 24));
 | 
			
		||||
        
 | 
			
		||||
        isExpired = daysUntilExpire < 0;
 | 
			
		||||
        isExpiringSoon = daysUntilExpire <= 30 && daysUntilExpire >= 0;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Status setzen
 | 
			
		||||
    if (!license.is_active || isExpired) {
 | 
			
		||||
        statusElement.className = 'license-status inactive';
 | 
			
		||||
        statusIcon.className = 'status-icon fas fa-times-circle';
 | 
			
		||||
        statusText.textContent = isExpired ? 'Abgelaufen' : 'Ungültig';
 | 
			
		||||
    } else if (isExpiringSoon) {
 | 
			
		||||
        statusElement.className = 'license-status warning';
 | 
			
		||||
        statusIcon.className = 'status-icon fas fa-exclamation-triangle';
 | 
			
		||||
        statusText.textContent = 'Läuft bald ab';
 | 
			
		||||
    } else {
 | 
			
		||||
        statusElement.className = 'license-status active';
 | 
			
		||||
        statusIcon.className = 'status-icon fas fa-check-circle';
 | 
			
		||||
        statusText.textContent = 'Gültig';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Gültigkeits-Indikator anzeigen
 | 
			
		||||
function displayValidityIndicator(license) {
 | 
			
		||||
    const validityFill = document.getElementById('validity-fill');
 | 
			
		||||
    const validityText = document.getElementById('validity-text');
 | 
			
		||||
    
 | 
			
		||||
    if (!license.expire_date) {
 | 
			
		||||
        validityText.textContent = 'Unbegrenzt gültig';
 | 
			
		||||
        validityFill.style.width = '100%';
 | 
			
		||||
        validityFill.style.backgroundColor = '#4CAF50';
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    const issueDate = new Date(license.issue_date);
 | 
			
		||||
    const expireDate = new Date(license.expire_date);
 | 
			
		||||
    const today = new Date();
 | 
			
		||||
    
 | 
			
		||||
    const totalDays = Math.ceil((expireDate - issueDate) / (1000 * 60 * 60 * 24));
 | 
			
		||||
    const remainingDays = Math.ceil((expireDate - today) / (1000 * 60 * 60 * 24));
 | 
			
		||||
    const percentage = Math.max(0, Math.min(100, (remainingDays / totalDays) * 100));
 | 
			
		||||
    
 | 
			
		||||
    validityFill.style.width = percentage + '%';
 | 
			
		||||
    
 | 
			
		||||
    if (remainingDays < 0) {
 | 
			
		||||
        validityText.textContent = 'Abgelaufen';
 | 
			
		||||
        validityFill.style.backgroundColor = '#f44336';
 | 
			
		||||
    } else if (remainingDays <= 30) {
 | 
			
		||||
        validityText.textContent = `Noch ${remainingDays} Tage gültig`;
 | 
			
		||||
        validityFill.style.backgroundColor = '#ff9800';
 | 
			
		||||
    } else {
 | 
			
		||||
        validityText.textContent = `Noch ${remainingDays} Tage gültig`;
 | 
			
		||||
        validityFill.style.backgroundColor = '#4CAF50';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Rückseite vorbereiten
 | 
			
		||||
function prepareBackSide(license) {
 | 
			
		||||
    const classesGrid = document.getElementById('classes-grid');
 | 
			
		||||
    const restrictionsList = document.getElementById('restrictions-list');
 | 
			
		||||
    const notesText = document.getElementById('notes-text');
 | 
			
		||||
    
 | 
			
		||||
    // Klassen-Grid für Führerschein
 | 
			
		||||
    if (license.license_type === 'drivers_license' && license.classes) {
 | 
			
		||||
        try {
 | 
			
		||||
            const classes = JSON.parse(license.classes);
 | 
			
		||||
            classesGrid.innerHTML = '';
 | 
			
		||||
            
 | 
			
		||||
            const classDescriptions = {
 | 
			
		||||
                'A': 'Motorräder',
 | 
			
		||||
                'A1': 'Leichte Motorräder',
 | 
			
		||||
                'A2': 'Mittlere Motorräder',
 | 
			
		||||
                'B': 'PKW',
 | 
			
		||||
                'BE': 'PKW mit Anhänger',
 | 
			
		||||
                'C': 'LKW',
 | 
			
		||||
                'CE': 'LKW mit Anhänger',
 | 
			
		||||
                'D': 'Bus',
 | 
			
		||||
                'DE': 'Bus mit Anhänger'
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
            classes.forEach(cls => {
 | 
			
		||||
                const classItem = document.createElement('div');
 | 
			
		||||
                classItem.className = 'class-item';
 | 
			
		||||
                classItem.innerHTML = `
 | 
			
		||||
                    <div class="class-letter">${cls}</div>
 | 
			
		||||
                    <div class="class-description">${classDescriptions[cls] || 'Unbekannt'}</div>
 | 
			
		||||
                `;
 | 
			
		||||
                classesGrid.appendChild(classItem);
 | 
			
		||||
            });
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            classesGrid.innerHTML = '<p>Keine Klassen verfügbar</p>';
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        classesGrid.innerHTML = '<p>Nicht zutreffend</p>';
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Beschränkungen (Beispiel)
 | 
			
		||||
    restrictionsList.innerHTML = '<li>Keine besonderen Beschränkungen</li>';
 | 
			
		||||
    
 | 
			
		||||
    // Bemerkungen
 | 
			
		||||
    notesText.textContent = license.notes || 'Keine besonderen Bemerkungen';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Karte drehen
 | 
			
		||||
function flipCard() {
 | 
			
		||||
    const frontSide = document.querySelector('.license-content, .license-footer');
 | 
			
		||||
    const backSide = document.getElementById('license-back');
 | 
			
		||||
    
 | 
			
		||||
    isCardFlipped = !isCardFlipped;
 | 
			
		||||
    
 | 
			
		||||
    if (isCardFlipped) {
 | 
			
		||||
        // Zur Rückseite
 | 
			
		||||
        document.querySelector('.license-content').classList.add('hidden');
 | 
			
		||||
        document.querySelector('.license-footer').classList.add('hidden');
 | 
			
		||||
        backSide.classList.remove('hidden');
 | 
			
		||||
    } else {
 | 
			
		||||
        // Zur Vorderseite
 | 
			
		||||
        document.querySelector('.license-content').classList.remove('hidden');
 | 
			
		||||
        document.querySelector('.license-footer').classList.remove('hidden');
 | 
			
		||||
        backSide.classList.add('hidden');
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    playSound('card-flip-sound');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Kamera öffnen
 | 
			
		||||
async function openCamera() {
 | 
			
		||||
    const container = document.getElementById('camera-container');
 | 
			
		||||
    const video = document.getElementById('camera-video');
 | 
			
		||||
    
 | 
			
		||||
    try {
 | 
			
		||||
        cameraStream = await navigator.mediaDevices.getUserMedia({ 
 | 
			
		||||
            video: { 
 | 
			
		||||
                width: 640, 
 | 
			
		||||
                height: 480,
 | 
			
		||||
                facingMode: 'user'
 | 
			
		||||
            } 
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
        video.srcObject = cameraStream;
 | 
			
		||||
        container.classList.remove('hidden');
 | 
			
		||||
        
 | 
			
		||||
        showNotification('Kamera geöffnet', 'info');
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
        console.error('Kamera-Zugriff fehlgeschlagen:', err);
 | 
			
		||||
        showNotification('Kamera-Zugriff fehlgeschlagen', 'error');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Foto aufnehmen
 | 
			
		||||
function takePhoto() {
 | 
			
		||||
    const video = document.getElementById('camera-video');
 | 
			
		||||
    const canvas = document.getElementById('camera-canvas');
 | 
			
		||||
    const ctx = canvas.getContext('2d');
 | 
			
		||||
    
 | 
			
		||||
    canvas.width = video.videoWidth;
 | 
			
		||||
    canvas.height = video.videoHeight;
 | 
			
		||||
    ctx.drawImage(video, 0, 0);
 | 
			
		||||
    
 | 
			
		||||
    const photoData = canvas.toDataURL('image/jpeg', 0.8);
 | 
			
		||||
    
 | 
			
		||||
    // Sound-Effekt
 | 
			
		||||
    playSound('camera-shutter-sound');
 | 
			
		||||
    
 | 
			
		||||
    // An FiveM senden
 | 
			
		||||
    fetch(`https://${GetParentResourceName()}/savePhoto`, {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        headers: {
 | 
			
		||||
            'Content-Type': 'application/json',
 | 
			
		||||
        },
 | 
			
		||||
        body: JSON.stringify({ 
 | 
			
		||||
            photo: photoData,
 | 
			
		||||
            citizenid: currentLicense?.license?.citizenid
 | 
			
		||||
        })
 | 
			
		||||
    }).then(() => {
 | 
			
		||||
        showNotification('Foto gespeichert', 'success');
 | 
			
		||||
        closeCamera();
 | 
			
		||||
    }).catch(err => {
 | 
			
		||||
        console.error('Fehler beim Speichern:', err);
 | 
			
		||||
        showNotification('Fehler beim Speichern', 'error');
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Kamera schließen
 | 
			
		||||
function closeCamera() {
 | 
			
		||||
    const container = document.getElementById('camera-container');
 | 
			
		||||
    
 | 
			
		||||
    if (cameraStream) {
 | 
			
		||||
        cameraStream.getTracks().forEach(track => track.stop());
 | 
			
		||||
        cameraStream = null;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    container.classList.add('hidden');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Lizenz schließen
 | 
			
		||||
| 
						 | 
				
			
			@ -93,26 +327,121 @@ function closeLicense() {
 | 
			
		|||
    const container = document.getElementById('license-container');
 | 
			
		||||
    container.classList.add('hidden');
 | 
			
		||||
    
 | 
			
		||||
    // Callback an FiveM senden
 | 
			
		||||
    // Karte zurücksetzen
 | 
			
		||||
    isCardFlipped = false;
 | 
			
		||||
    document.querySelector('.license-content').classList.remove('hidden');
 | 
			
		||||
    document.querySelector('.license-footer').classList.remove('hidden');
 | 
			
		||||
    document.getElementById('license-back').classList.add('hidden');
 | 
			
		||||
    
 | 
			
		||||
    // Callback an FiveM
 | 
			
		||||
    fetch(`https://${GetParentResourceName()}/closeLicense`, {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        headers: {
 | 
			
		||||
            'Content-Type': 'application/json; charset=UTF-8',
 | 
			
		||||
            'Content-Type': 'application/json',
 | 
			
		||||
        },
 | 
			
		||||
        body: JSON.stringify({})
 | 
			
		||||
    });
 | 
			
		||||
    }).catch(() => {}); // Fehler ignorieren
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ESC-Taste zum Schließen
 | 
			
		||||
// Hilfsfunktionen
 | 
			
		||||
function formatGender(gender) {
 | 
			
		||||
    const genderMap = {
 | 
			
		||||
        'male': 'Männlich',
 | 
			
		||||
        'female': 'Weiblich',
 | 
			
		||||
        'other': 'Divers',
 | 
			
		||||
        'm': 'Männlich',
 | 
			
		||||
        'f': 'Weiblich'
 | 
			
		||||
    };
 | 
			
		||||
    return genderMap[gender?.toLowerCase()] || gender || 'Unbekannt';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function formatDate(dateString) {
 | 
			
		||||
    if (!dateString) return null;
 | 
			
		||||
    
 | 
			
		||||
    try {
 | 
			
		||||
        const date = new Date(dateString);
 | 
			
		||||
        return date.toLocaleDateString('de-DE');
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        return dateString;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function showLoading() {
 | 
			
		||||
    document.getElementById('loading-overlay').classList.remove('hidden');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function hideLoading() {
 | 
			
		||||
    document.getElementById('loading-overlay').classList.add('hidden');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function playSound(soundId) {
 | 
			
		||||
    const audio = document.getElementById(soundId);
 | 
			
		||||
    if (audio) {
 | 
			
		||||
        audio.volume = 0.3;
 | 
			
		||||
        audio.play().catch(() => {}); // Fehler ignorieren
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function showNotification(message, type = 'info') {
 | 
			
		||||
    const container = document.getElementById('notification-container');
 | 
			
		||||
    const notification = document.createElement('div');
 | 
			
		||||
    
 | 
			
		||||
    notification.className = `notification ${type}`;
 | 
			
		||||
    notification.innerHTML = `
 | 
			
		||||
        <div class="notification-content">
 | 
			
		||||
            <i class="notification-icon fas ${getNotificationIcon(type)}"></i>
 | 
			
		||||
            <span class="notification-text">${message}</span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <button class="notification-close" onclick="this.parentElement.remove()">
 | 
			
		||||
            <i class="fas fa-times"></i>
 | 
			
		||||
        </button>
 | 
			
		||||
    `;
 | 
			
		||||
    
 | 
			
		||||
    container.appendChild(notification);
 | 
			
		||||
    
 | 
			
		||||
    // Auto-remove nach 5 Sekunden
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        if (notification.parentElement) {
 | 
			
		||||
            notification.remove();
 | 
			
		||||
        }
 | 
			
		||||
    }, 5000);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getNotificationIcon(type) {
 | 
			
		||||
    const icons = {
 | 
			
		||||
        'success': 'fa-check-circle',
 | 
			
		||||
        'error': 'fa-exclamation-circle',
 | 
			
		||||
        'warning': 'fa-exclamation-triangle',
 | 
			
		||||
        'info': 'fa-info-circle'
 | 
			
		||||
    };
 | 
			
		||||
    return icons[type] || icons.info;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Event Listeners
 | 
			
		||||
document.addEventListener('keydown', function(event) {
 | 
			
		||||
    if (event.key === 'Escape') {
 | 
			
		||||
        if (!document.getElementById('camera-container').classList.contains('hidden')) {
 | 
			
		||||
            closeCamera();
 | 
			
		||||
        } else if (!document.getElementById('license-container').classList.contains('hidden')) {
 | 
			
		||||
            closeLicense();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if (event.key === 'f' || event.key === 'F') {
 | 
			
		||||
        if (!document.getElementById('license-container').classList.contains('hidden')) {
 | 
			
		||||
            flipCard();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Klick außerhalb zum Schließen
 | 
			
		||||
document.getElementById('license-container').addEventListener('click', function(event) {
 | 
			
		||||
    if (event.target.classList.contains('license-overlay')) {
 | 
			
		||||
        closeLicense();
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Klick außerhalb der Karte zum Schließen
 | 
			
		||||
document.getElementById('license-container').addEventListener('click', function(event) {
 | 
			
		||||
    if (event.target === this) {
 | 
			
		||||
        closeLicense();
 | 
			
		||||
    }
 | 
			
		||||
// Initialisierung
 | 
			
		||||
document.addEventListener('DOMContentLoaded', function() {
 | 
			
		||||
    console.log('License System UI geladen');
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -235,3 +235,134 @@ body {
 | 
			
		|||
.license-card {
 | 
			
		||||
    animation: slideIn 0.3s ease-out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Notification System */
 | 
			
		||||
#notification-container {
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    top: 20px;
 | 
			
		||||
    right: 20px;
 | 
			
		||||
    z-index: 10000;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    gap: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.notification {
 | 
			
		||||
    background: rgba(0, 0, 0, 0.9);
 | 
			
		||||
    color: white;
 | 
			
		||||
    padding: 15px;
 | 
			
		||||
    border-radius: 8px;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    min-width: 300px;
 | 
			
		||||
    animation: slideInRight 0.3s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.notification.success { border-left: 4px solid #4CAF50; }
 | 
			
		||||
.notification.error { border-left: 4px solid #f44336; }
 | 
			
		||||
.notification.warning { border-left: 4px solid #ff9800; }
 | 
			
		||||
.notification.info { border-left: 4px solid #2196F3; }
 | 
			
		||||
 | 
			
		||||
.notification-content {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    gap: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.notification-close {
 | 
			
		||||
    background: none;
 | 
			
		||||
    border: none;
 | 
			
		||||
    color: white;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    padding: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Loading Overlay */
 | 
			
		||||
#loading-overlay {
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    background: rgba(0, 0, 0, 0.8);
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    z-index: 9999;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.loading-spinner {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.spinner {
 | 
			
		||||
    width: 50px;
 | 
			
		||||
    height: 50px;
 | 
			
		||||
    border: 3px solid rgba(255, 255, 255, 0.3);
 | 
			
		||||
    border-top: 3px solid white;
 | 
			
		||||
    border-radius: 50%;
 | 
			
		||||
    animation: spin 1s linear infinite;
 | 
			
		||||
    margin: 0 auto 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Kamera Interface */
 | 
			
		||||
#camera-container {
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    background: rgba(0, 0, 0, 0.9);
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    z-index: 9998;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.camera-interface {
 | 
			
		||||
    background: white;
 | 
			
		||||
    border-radius: 15px;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    max-width: 600px;
 | 
			
		||||
    width: 90%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.camera-preview {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    margin: 20px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#camera-video {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    border-radius: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.face-guide {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 50%;
 | 
			
		||||
    left: 50%;
 | 
			
		||||
    transform: translate(-50%, -50%);
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.guide-circle {
 | 
			
		||||
    width: 200px;
 | 
			
		||||
    height: 200px;
 | 
			
		||||
    border: 3px solid rgba(255, 255, 255, 0.8);
 | 
			
		||||
    border-radius: 50%;
 | 
			
		||||
    margin: 0 auto 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Animationen */
 | 
			
		||||
@keyframes slideInRight {
 | 
			
		||||
    from { transform: translateX(100%); opacity: 0; }
 | 
			
		||||
    to { transform: translateX(0); opacity: 1; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes spin {
 | 
			
		||||
    0% { transform: rotate(0deg); }
 | 
			
		||||
    100% { transform: rotate(360deg); }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue