Initial commit - docs.artetui.de SPAs (ohne Subprojekte)
This commit is contained in:
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Editor files
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Node modules (falls vorhanden)
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Temp files
|
||||||
|
*.tmp
|
||||||
|
*.bak
|
||||||
|
symbols/
|
||||||
|
kostenschaetzung/
|
||||||
|
schadendokumentation/
|
||||||
|
zeitwert/
|
||||||
5492
angebotsdatenbank/Pos.json
Normal file
5492
angebotsdatenbank/Pos.json
Normal file
File diff suppressed because it is too large
Load Diff
4071
angebotsdatenbank/index.html
Normal file
4071
angebotsdatenbank/index.html
Normal file
File diff suppressed because it is too large
Load Diff
115
api/backup.php
Normal file
115
api/backup.php
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* SPA Backup API - Speichert SPA-Backups in Nextcloud
|
||||||
|
* URL: https://docs.artetui.de/api/backup.php
|
||||||
|
*/
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
header('Access-Control-Allow-Origin: https://docs.artetui.de');
|
||||||
|
header('Access-Control-Allow-Methods: POST, OPTIONS');
|
||||||
|
header('Access-Control-Allow-Headers: Content-Type');
|
||||||
|
|
||||||
|
// Preflight für CORS
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||||
|
http_response_code(200);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nur POST erlauben
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['error' => 'Method not allowed']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Konfiguration aus .env laden
|
||||||
|
$envFile = __DIR__ . '/spa-backup-api.env';
|
||||||
|
if (file_exists($envFile)) {
|
||||||
|
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
if (strpos(trim($line), '#') === 0) continue;
|
||||||
|
list($key, $value) = explode('=', $line, 2);
|
||||||
|
putenv(trim($key) . '=' . trim($value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$NEXTCLOUD_URL = 'https://cloud.artetui.de';
|
||||||
|
$NEXTCLOUD_USER = getenv('NEXTCLOUD_USER') ?: 'lars.munkes';
|
||||||
|
$NEXTCLOUD_PASS = getenv('NEXTCLOUD_PASS');
|
||||||
|
|
||||||
|
// Prüfen ob Credentials konfiguriert sind
|
||||||
|
if (!$NEXTCLOUD_PASS) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Nextcloud credentials not configured. Create spa-backup-api.env file.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request-Daten lesen
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
if (!$input || !isset($input['app']) || !isset($input['data'])) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Missing app or data parameter']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$app = $input['app'];
|
||||||
|
$backupData = $input['data'];
|
||||||
|
|
||||||
|
// Validierung: Nur erlaubte Apps
|
||||||
|
$allowedApps = ['schadenprofi', 'angebotsdatenbank', 'kostenschaetzung', 'zeitwert'];
|
||||||
|
if (!in_array($app, $allowedApps)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Invalid app name']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup-Dateiname mit Timestamp
|
||||||
|
$timestamp = date('Y-m-d_H-i-s');
|
||||||
|
$filename = "{$app}-backup-{$timestamp}.json";
|
||||||
|
|
||||||
|
// Nextcloud WebDAV Upload
|
||||||
|
$webdavUrl = "{$NEXTCLOUD_URL}/remote.php/dav/files/{$NEXTCLOUD_USER}/Backups/{$app}/{$filename}";
|
||||||
|
|
||||||
|
// Verzeichnis erstellen falls nicht existiert
|
||||||
|
$dirUrl = "{$NEXTCLOUD_URL}/remote.php/dav/files/{$NEXTCLOUD_USER}/Backups/{$app}";
|
||||||
|
$ch = curl_init($dirUrl);
|
||||||
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'MKCOL');
|
||||||
|
curl_setopt($ch, CURLOPT_USERPWD, "{$NEXTCLOUD_USER}:{$NEXTCLOUD_PASS}");
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_exec($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
// Backup hochladen
|
||||||
|
$ch = curl_init($webdavUrl);
|
||||||
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
|
||||||
|
curl_setopt($ch, CURLOPT_USERPWD, "{$NEXTCLOUD_USER}:{$NEXTCLOUD_PASS}");
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($backupData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
'Content-Type: application/json',
|
||||||
|
]);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
// Erfolg prüfen
|
||||||
|
if ($httpCode >= 200 && $httpCode < 300) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Backup erfolgreich gespeichert',
|
||||||
|
'filename' => $filename,
|
||||||
|
'path' => "/Backups/{$app}/{$filename}",
|
||||||
|
'timestamp' => $timestamp,
|
||||||
|
'url' => "{$NEXTCLOUD_URL}/f/" // Nextcloud sharing link base
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode([
|
||||||
|
'error' => 'Nextcloud upload failed',
|
||||||
|
'http_code' => $httpCode
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
11
api/spa-backup-api.env.example
Normal file
11
api/spa-backup-api.env.example
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Nextcloud Credentials für SPA Backup API
|
||||||
|
# Kopiere diese Datei zu spa-backup-api.env und fülle die Werte aus
|
||||||
|
|
||||||
|
# Nextcloud Username
|
||||||
|
NEXTCLOUD_USER=lars.munkes
|
||||||
|
|
||||||
|
# Nextcloud App Password (NICHT dein Login-Passwort!)
|
||||||
|
# Erstelle ein App-Password unter: https://cloud.artetui.de/settings/user/security
|
||||||
|
# -> Geräte & Sitzungen -> Neues App-Passwort erstellen
|
||||||
|
# Name: "SPA Backup API"
|
||||||
|
NEXTCLOUD_PASS=xxxx-xxxx-xxxx-xxxx
|
||||||
1367
dictation/index.html
Normal file
1367
dictation/index.html
Normal file
File diff suppressed because it is too large
Load Diff
855
fotoupload/index.html
Normal file
855
fotoupload/index.html
Normal file
@@ -0,0 +1,855 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
|
<title>PowerToolsX - Foto-Upload</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg-primary: #1E1E1E;
|
||||||
|
--bg-secondary: #2D2D30;
|
||||||
|
--bg-tertiary: #3C3C3C;
|
||||||
|
--text-primary: #E0E0E0;
|
||||||
|
--text-secondary: #A0A0A0;
|
||||||
|
--accent: #0E639C;
|
||||||
|
--accent-hover: #1177BB;
|
||||||
|
--success: #00B454;
|
||||||
|
--warning: #FFB900;
|
||||||
|
--error: #E81123;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .projekt-name {
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* IFC-Hierarchie Dropdowns */
|
||||||
|
.ifc-select-group {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ifc-select-group label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ifc-select-group select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid #555;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ifc-select-group select:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ifc-path {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PropertySets */
|
||||||
|
.pset-group {
|
||||||
|
border: 1px solid #444;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pset-group h4 {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pset-property {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pset-property label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pset-property select,
|
||||||
|
.pset-property input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid #555;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Foto-Capture */
|
||||||
|
.capture-area {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-preview {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 300px;
|
||||||
|
background: #000;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-image {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 300px;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 14px 24px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--accent);
|
||||||
|
color: white;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: var(--accent-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:disabled {
|
||||||
|
background: #555;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
background: var(--success);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-row .btn {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Beschreibung */
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid #555;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 1rem;
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status */
|
||||||
|
.status {
|
||||||
|
text-align: center;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.success {
|
||||||
|
background: rgba(0, 180, 84, 0.2);
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.error {
|
||||||
|
background: rgba(232, 17, 35, 0.2);
|
||||||
|
color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.info {
|
||||||
|
background: rgba(14, 99, 156, 0.2);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Upload Counter */
|
||||||
|
.upload-counter {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-counter strong {
|
||||||
|
color: var(--success);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hidden file input */
|
||||||
|
#fileInput {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading Overlay */
|
||||||
|
.loading-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-overlay.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border: 4px solid var(--bg-tertiary);
|
||||||
|
border-top-color: var(--accent);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No Session */
|
||||||
|
.no-session {
|
||||||
|
text-align: center;
|
||||||
|
padding: 48px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-session h2 {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<h1>PowerToolsX Foto-Upload</h1>
|
||||||
|
<div class="projekt-name" id="projektName">Wird geladen...</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div id="noSession" class="no-session" style="display: none;">
|
||||||
|
<h2>Keine aktive Session</h2>
|
||||||
|
<p>Bitte scannen Sie den QR-Code in PowerToolsX um eine Upload-Session zu starten.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="mainContent" style="display: none;">
|
||||||
|
<!-- IFC-Struktur Auswahl -->
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">Ort im Bauwerk</div>
|
||||||
|
|
||||||
|
<div class="ifc-select-group">
|
||||||
|
<label>Gebaude</label>
|
||||||
|
<select id="selectBuilding" onchange="onBuildingChanged()">
|
||||||
|
<option value="">-- Gebaude wahlen --</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ifc-select-group">
|
||||||
|
<label>Geschoss</label>
|
||||||
|
<select id="selectStorey" onchange="onStoreyChanged()" disabled>
|
||||||
|
<option value="">-- Geschoss wahlen --</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ifc-select-group">
|
||||||
|
<label>Raum</label>
|
||||||
|
<select id="selectSpace" onchange="onSpaceChanged()" disabled>
|
||||||
|
<option value="">-- Raum wahlen --</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ifc-path" id="ifcPath" style="display: none;">
|
||||||
|
<strong>Pfad:</strong> <span id="pathText"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PropertySets -->
|
||||||
|
<div class="section" id="propertySetsSection">
|
||||||
|
<div class="section-title">Eigenschaften</div>
|
||||||
|
<div id="propertySetsContainer"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Beschreibung -->
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">Beschreibung</div>
|
||||||
|
<textarea id="description" placeholder="Optionale Beschreibung zum Foto..."></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Foto aufnehmen -->
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">Foto</div>
|
||||||
|
<div class="capture-area">
|
||||||
|
<video id="cameraPreview" class="camera-preview" autoplay playsinline></video>
|
||||||
|
<img id="previewImage" class="preview-image" alt="Vorschau">
|
||||||
|
|
||||||
|
<input type="file" id="fileInput" accept="image/*" capture="environment" onchange="onFileSelected(event)">
|
||||||
|
|
||||||
|
<button class="btn btn-primary" id="btnCapture" onclick="openCamera()">
|
||||||
|
<span>Foto aufnehmen</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="btn-row" id="previewButtons" style="display: none;">
|
||||||
|
<button class="btn btn-secondary" onclick="resetCapture()">Neu</button>
|
||||||
|
<button class="btn btn-success" onclick="uploadPhoto()">Hochladen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Status -->
|
||||||
|
<div id="statusMessage" class="status info" style="display: none;"></div>
|
||||||
|
|
||||||
|
<!-- Upload Counter -->
|
||||||
|
<div class="upload-counter">
|
||||||
|
Hochgeladene Fotos: <strong id="uploadCount">0</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Loading Overlay -->
|
||||||
|
<div class="loading-overlay" id="loadingOverlay">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Konfiguration
|
||||||
|
const DATA_URL = 'https://docs.artetui.de/backups/fotoupload';
|
||||||
|
|
||||||
|
// Session-Daten
|
||||||
|
let sessionId = null;
|
||||||
|
let strukturData = null;
|
||||||
|
let selectedElement = null;
|
||||||
|
let capturedImageData = null;
|
||||||
|
let uploadCount = 0;
|
||||||
|
|
||||||
|
// Initialisierung
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
// Session-ID aus URL holen
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
sessionId = params.get('session');
|
||||||
|
|
||||||
|
if (!sessionId) {
|
||||||
|
document.getElementById('noSession').style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Struktur laden
|
||||||
|
await loadStruktur();
|
||||||
|
});
|
||||||
|
|
||||||
|
// IFC-Struktur vom Server laden
|
||||||
|
async function loadStruktur() {
|
||||||
|
showLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${DATA_URL}/struktur_${sessionId}.json`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Struktur nicht gefunden');
|
||||||
|
}
|
||||||
|
|
||||||
|
strukturData = await response.json();
|
||||||
|
|
||||||
|
// UI aktualisieren
|
||||||
|
document.getElementById('projektName').textContent = strukturData.ProjektName || 'Projekt';
|
||||||
|
document.getElementById('mainContent').style.display = 'block';
|
||||||
|
|
||||||
|
// Gebaude-Dropdown fullen
|
||||||
|
populateBuildingSelect();
|
||||||
|
|
||||||
|
// PropertySets generieren
|
||||||
|
generatePropertySets();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Laden der Struktur:', error);
|
||||||
|
document.getElementById('projektName').textContent = 'Fehler beim Laden';
|
||||||
|
showStatus('Struktur konnte nicht geladen werden. Bitte erneut versuchen.', 'error');
|
||||||
|
document.getElementById('mainContent').style.display = 'block';
|
||||||
|
} finally {
|
||||||
|
showLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gebaude-Dropdown fullen
|
||||||
|
function populateBuildingSelect() {
|
||||||
|
const select = document.getElementById('selectBuilding');
|
||||||
|
select.innerHTML = '<option value="">-- Gebaude wahlen --</option>';
|
||||||
|
|
||||||
|
if (!strukturData || !strukturData.Elemente) return;
|
||||||
|
|
||||||
|
// Finde alle IfcBuilding Elemente
|
||||||
|
const buildings = findElementsByType(strukturData.Elemente, 'IfcBuilding');
|
||||||
|
|
||||||
|
buildings.forEach(b => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = b.GlobalId;
|
||||||
|
option.textContent = `${b.Icon || ''} ${b.Name}`;
|
||||||
|
option.dataset.element = JSON.stringify(b);
|
||||||
|
select.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rekursiv Elemente nach Typ finden
|
||||||
|
function findElementsByType(elements, type) {
|
||||||
|
let result = [];
|
||||||
|
for (const el of elements) {
|
||||||
|
if (el.IfcType === type) {
|
||||||
|
result.push(el);
|
||||||
|
}
|
||||||
|
if (el.Children && el.Children.length > 0) {
|
||||||
|
result = result.concat(findElementsByType(el.Children, type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Element nach GlobalId finden
|
||||||
|
function findElementById(elements, globalId) {
|
||||||
|
for (const el of elements) {
|
||||||
|
if (el.GlobalId === globalId) {
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
if (el.Children && el.Children.length > 0) {
|
||||||
|
const found = findElementById(el.Children, globalId);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gebaude geandert
|
||||||
|
function onBuildingChanged() {
|
||||||
|
const select = document.getElementById('selectBuilding');
|
||||||
|
const storeySelect = document.getElementById('selectStorey');
|
||||||
|
const spaceSelect = document.getElementById('selectSpace');
|
||||||
|
|
||||||
|
// Geschoss und Raum zurucksetzen
|
||||||
|
storeySelect.innerHTML = '<option value="">-- Geschoss wahlen --</option>';
|
||||||
|
storeySelect.disabled = true;
|
||||||
|
spaceSelect.innerHTML = '<option value="">-- Raum wahlen --</option>';
|
||||||
|
spaceSelect.disabled = true;
|
||||||
|
selectedElement = null;
|
||||||
|
updatePath();
|
||||||
|
|
||||||
|
if (!select.value) return;
|
||||||
|
|
||||||
|
// Gebaude finden
|
||||||
|
const building = findElementById(strukturData.Elemente, select.value);
|
||||||
|
if (!building) return;
|
||||||
|
|
||||||
|
// Geschosse dieses Gebaudes finden
|
||||||
|
const storeys = findElementsByType(building.Children || [], 'IfcBuildingStorey');
|
||||||
|
|
||||||
|
storeys.forEach(s => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = s.GlobalId;
|
||||||
|
option.textContent = `${s.Icon || ''} ${s.Name}`;
|
||||||
|
option.dataset.element = JSON.stringify(s);
|
||||||
|
storeySelect.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
storeySelect.disabled = storeys.length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Geschoss geandert
|
||||||
|
function onStoreyChanged() {
|
||||||
|
const buildingSelect = document.getElementById('selectBuilding');
|
||||||
|
const storeySelect = document.getElementById('selectStorey');
|
||||||
|
const spaceSelect = document.getElementById('selectSpace');
|
||||||
|
|
||||||
|
// Raum zurucksetzen
|
||||||
|
spaceSelect.innerHTML = '<option value="">-- Raum wahlen --</option>';
|
||||||
|
spaceSelect.disabled = true;
|
||||||
|
|
||||||
|
if (!storeySelect.value) {
|
||||||
|
selectedElement = null;
|
||||||
|
updatePath();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gebaude und Geschoss finden
|
||||||
|
const building = findElementById(strukturData.Elemente, buildingSelect.value);
|
||||||
|
const storey = findElementById(building?.Children || [], storeySelect.value);
|
||||||
|
|
||||||
|
if (!storey) {
|
||||||
|
selectedElement = null;
|
||||||
|
updatePath();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raume dieses Geschosses finden
|
||||||
|
const spaces = findElementsByType(storey.Children || [], 'IfcSpace');
|
||||||
|
|
||||||
|
spaces.forEach(s => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = s.GlobalId;
|
||||||
|
option.textContent = `${s.Icon || ''} ${s.Name}`;
|
||||||
|
option.dataset.element = JSON.stringify(s);
|
||||||
|
spaceSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
spaceSelect.disabled = spaces.length === 0;
|
||||||
|
|
||||||
|
// Geschoss als ausgewahltes Element setzen (falls kein Raum gewahlt)
|
||||||
|
selectedElement = storey;
|
||||||
|
updatePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raum geandert
|
||||||
|
function onSpaceChanged() {
|
||||||
|
const buildingSelect = document.getElementById('selectBuilding');
|
||||||
|
const storeySelect = document.getElementById('selectStorey');
|
||||||
|
const spaceSelect = document.getElementById('selectSpace');
|
||||||
|
|
||||||
|
if (!spaceSelect.value) {
|
||||||
|
// Zurück zum Geschoss
|
||||||
|
const building = findElementById(strukturData.Elemente, buildingSelect.value);
|
||||||
|
selectedElement = findElementById(building?.Children || [], storeySelect.value);
|
||||||
|
} else {
|
||||||
|
// Raum finden
|
||||||
|
const building = findElementById(strukturData.Elemente, buildingSelect.value);
|
||||||
|
const storey = findElementById(building?.Children || [], storeySelect.value);
|
||||||
|
selectedElement = findElementById(storey?.Children || [], spaceSelect.value);
|
||||||
|
}
|
||||||
|
updatePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pfad-Anzeige aktualisieren
|
||||||
|
function updatePath() {
|
||||||
|
const pathDiv = document.getElementById('ifcPath');
|
||||||
|
const pathText = document.getElementById('pathText');
|
||||||
|
|
||||||
|
if (!selectedElement) {
|
||||||
|
pathDiv.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pfad aus den Dropdowns bauen
|
||||||
|
const parts = [];
|
||||||
|
const buildingSelect = document.getElementById('selectBuilding');
|
||||||
|
const storeySelect = document.getElementById('selectStorey');
|
||||||
|
const spaceSelect = document.getElementById('selectSpace');
|
||||||
|
|
||||||
|
if (buildingSelect.value) {
|
||||||
|
const opt = buildingSelect.selectedOptions[0];
|
||||||
|
parts.push(opt.textContent.trim());
|
||||||
|
}
|
||||||
|
if (storeySelect.value) {
|
||||||
|
const opt = storeySelect.selectedOptions[0];
|
||||||
|
parts.push(opt.textContent.trim());
|
||||||
|
}
|
||||||
|
if (spaceSelect.value) {
|
||||||
|
const opt = spaceSelect.selectedOptions[0];
|
||||||
|
parts.push(opt.textContent.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
pathText.textContent = parts.join(' > ');
|
||||||
|
pathDiv.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// PropertySets generieren
|
||||||
|
function generatePropertySets() {
|
||||||
|
const container = document.getElementById('propertySetsContainer');
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
if (!strukturData || !strukturData.PropertySets) {
|
||||||
|
document.getElementById('propertySetsSection').style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
strukturData.PropertySets.forEach(pset => {
|
||||||
|
const group = document.createElement('div');
|
||||||
|
group.className = 'pset-group';
|
||||||
|
group.innerHTML = `<h4>${pset.Label}</h4>`;
|
||||||
|
|
||||||
|
pset.Properties.forEach(prop => {
|
||||||
|
const propDiv = document.createElement('div');
|
||||||
|
propDiv.className = 'pset-property';
|
||||||
|
|
||||||
|
let inputHtml = '';
|
||||||
|
if (prop.Type === 'select' && prop.Options) {
|
||||||
|
inputHtml = `<select id="pset_${pset.Name}_${prop.Name}">
|
||||||
|
<option value="">-- Wahlen --</option>
|
||||||
|
${prop.Options.map(o => `<option value="${o}">${o}</option>`).join('')}
|
||||||
|
</select>`;
|
||||||
|
} else {
|
||||||
|
inputHtml = `<input type="text" id="pset_${pset.Name}_${prop.Name}" placeholder="${prop.Label}">`;
|
||||||
|
}
|
||||||
|
|
||||||
|
propDiv.innerHTML = `
|
||||||
|
<label>${prop.Label}</label>
|
||||||
|
${inputHtml}
|
||||||
|
`;
|
||||||
|
group.appendChild(propDiv);
|
||||||
|
});
|
||||||
|
|
||||||
|
container.appendChild(group);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('propertySetsSection').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// PropertySet-Werte sammeln
|
||||||
|
function collectPropertyValues() {
|
||||||
|
const values = {};
|
||||||
|
if (!strukturData || !strukturData.PropertySets) return values;
|
||||||
|
|
||||||
|
strukturData.PropertySets.forEach(pset => {
|
||||||
|
pset.Properties.forEach(prop => {
|
||||||
|
const input = document.getElementById(`pset_${pset.Name}_${prop.Name}`);
|
||||||
|
if (input && input.value) {
|
||||||
|
values[`${pset.Name}.${prop.Name}`] = input.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kamera offnen
|
||||||
|
function openCamera() {
|
||||||
|
document.getElementById('fileInput').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Datei ausgewahlt
|
||||||
|
function onFileSelected(event) {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
capturedImageData = e.target.result;
|
||||||
|
|
||||||
|
// Vorschau anzeigen
|
||||||
|
const img = document.getElementById('previewImage');
|
||||||
|
img.src = capturedImageData;
|
||||||
|
img.style.display = 'block';
|
||||||
|
|
||||||
|
// Buttons umschalten
|
||||||
|
document.getElementById('btnCapture').style.display = 'none';
|
||||||
|
document.getElementById('previewButtons').style.display = 'flex';
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aufnahme zurucksetzen
|
||||||
|
function resetCapture() {
|
||||||
|
capturedImageData = null;
|
||||||
|
document.getElementById('previewImage').style.display = 'none';
|
||||||
|
document.getElementById('previewImage').src = '';
|
||||||
|
document.getElementById('btnCapture').style.display = 'block';
|
||||||
|
document.getElementById('previewButtons').style.display = 'none';
|
||||||
|
document.getElementById('fileInput').value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foto hochladen
|
||||||
|
async function uploadPhoto() {
|
||||||
|
if (!capturedImageData) {
|
||||||
|
showStatus('Bitte erst ein Foto aufnehmen', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoading(true);
|
||||||
|
try {
|
||||||
|
// Upload-Daten zusammenstellen
|
||||||
|
const timestamp = Date.now().toString(16);
|
||||||
|
const fileName = `${sessionId}_${timestamp}.json`;
|
||||||
|
|
||||||
|
// Pfad bauen
|
||||||
|
const pathParts = [];
|
||||||
|
const buildingSelect = document.getElementById('selectBuilding');
|
||||||
|
const storeySelect = document.getElementById('selectStorey');
|
||||||
|
const spaceSelect = document.getElementById('selectSpace');
|
||||||
|
|
||||||
|
if (buildingSelect.value) pathParts.push(buildingSelect.selectedOptions[0].textContent.trim());
|
||||||
|
if (storeySelect.value) pathParts.push(storeySelect.selectedOptions[0].textContent.trim());
|
||||||
|
if (spaceSelect.value) pathParts.push(spaceSelect.selectedOptions[0].textContent.trim());
|
||||||
|
|
||||||
|
const uploadData = {
|
||||||
|
Timestamp: new Date().toISOString(),
|
||||||
|
Filename: `foto_${Date.now()}.jpg`,
|
||||||
|
Description: document.getElementById('description').value,
|
||||||
|
Base64: capturedImageData,
|
||||||
|
Size: capturedImageData.length,
|
||||||
|
Type: 'image/jpeg',
|
||||||
|
ElementGlobalId: selectedElement?.GlobalId || null,
|
||||||
|
ElementPath: pathParts.join(' > '),
|
||||||
|
Properties: collectPropertyValues()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Per WebDAV PUT hochladen
|
||||||
|
const response = await fetch(`${DATA_URL}/${fileName}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(uploadData)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Upload fehlgeschlagen: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erfolg
|
||||||
|
uploadCount++;
|
||||||
|
document.getElementById('uploadCount').textContent = uploadCount;
|
||||||
|
showStatus('Foto erfolgreich hochgeladen!', 'success');
|
||||||
|
|
||||||
|
// Zurucksetzen
|
||||||
|
resetCapture();
|
||||||
|
document.getElementById('description').value = '';
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Upload-Fehler:', error);
|
||||||
|
showStatus('Upload fehlgeschlagen: ' + error.message, 'error');
|
||||||
|
} finally {
|
||||||
|
showLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status-Meldung anzeigen
|
||||||
|
function showStatus(message, type) {
|
||||||
|
const statusDiv = document.getElementById('statusMessage');
|
||||||
|
statusDiv.textContent = message;
|
||||||
|
statusDiv.className = `status ${type}`;
|
||||||
|
statusDiv.style.display = 'block';
|
||||||
|
|
||||||
|
// Nach 5 Sekunden ausblenden
|
||||||
|
setTimeout(() => {
|
||||||
|
statusDiv.style.display = 'none';
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading-Overlay
|
||||||
|
function showLoading(show) {
|
||||||
|
document.getElementById('loadingOverlay').classList.toggle('active', show);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<footer style="text-align:center;padding:1rem;margin-top:2rem;border-top:1px solid #e5e7eb;font-size:0.85rem;color:#6b7280;">
|
||||||
|
<a href="#" onclick="openImpressum();return false;" style="color:#6b7280;text-decoration:none;">Impressum</a>
|
||||||
|
<span style="color:#d1d5db;margin:0 0.5rem;">|</span>
|
||||||
|
<a href="#" onclick="openDatenschutz();return false;" style="color:#6b7280;text-decoration:none;">Datenschutz</a>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- IMPRESSUM MODAL -->
|
||||||
|
<div class="legal-modal" id="impressumModal" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);z-index:9999;align-items:center;justify-content:center;">
|
||||||
|
<div style="background:white;width:90%;max-width:900px;height:90vh;border-radius:12px;margin:20px;overflow:hidden;display:flex;flex-direction:column;">
|
||||||
|
<div style="padding:0.75rem 1.5rem;border-bottom:1px solid #e5e7eb;display:flex;justify-content:space-between;align-items:center;flex-shrink:0;">
|
||||||
|
<h2 style="margin:0;font-size:1.25rem;color:#1f2937;">Impressum</h2>
|
||||||
|
<button onclick="closeImpressum()" style="background:none;border:none;font-size:1.5rem;cursor:pointer;color:#6b7280;line-height:1;">×</button>
|
||||||
|
</div>
|
||||||
|
<iframe src="/legal/impressum.html" style="flex:1;width:100%;border:none;"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- DATENSCHUTZ MODAL -->
|
||||||
|
<div class="legal-modal" id="datenschutzModal" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);z-index:9999;align-items:center;justify-content:center;">
|
||||||
|
<div style="background:white;width:90%;max-width:900px;height:90vh;border-radius:12px;margin:20px;overflow:hidden;display:flex;flex-direction:column;">
|
||||||
|
<div style="padding:0.75rem 1.5rem;border-bottom:1px solid #e5e7eb;display:flex;justify-content:space-between;align-items:center;flex-shrink:0;">
|
||||||
|
<h2 style="margin:0;font-size:1.25rem;color:#1f2937;">Datenschutz</h2>
|
||||||
|
<button onclick="closeDatenschutz()" style="background:none;border:none;font-size:1.5rem;cursor:pointer;color:#6b7280;line-height:1;">×</button>
|
||||||
|
</div>
|
||||||
|
<iframe src="/legal/datenschutz.html" style="flex:1;width:100%;border:none;"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function openImpressum(){document.getElementById("impressumModal").style.display="flex";}
|
||||||
|
function closeImpressum(){document.getElementById("impressumModal").style.display="none";}
|
||||||
|
function openDatenschutz(){document.getElementById("datenschutzModal").style.display="flex";}
|
||||||
|
function closeDatenschutz(){document.getElementById("datenschutzModal").style.display="none";}
|
||||||
|
document.addEventListener("keydown",function(e){if(e.key==="Escape"){closeImpressum();closeDatenschutz();}});
|
||||||
|
["impressumModal","datenschutzModal"].forEach(function(id){
|
||||||
|
var el=document.getElementById(id);
|
||||||
|
if(el)el.addEventListener("click",function(e){if(e.target===this)this.style.display="none";});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
geo-calc/dokumentation.pdf
Normal file
BIN
geo-calc/dokumentation.pdf
Normal file
Binary file not shown.
2908
geo-calc/index.html
Normal file
2908
geo-calc/index.html
Normal file
File diff suppressed because it is too large
Load Diff
337
index.html
Normal file
337
index.html
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de" data-theme="light">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>SPA Platform - artetui.de</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg-primary: #f5f1e8;
|
||||||
|
--bg-secondary: #ffffff;
|
||||||
|
--bg-card: #ffffff;
|
||||||
|
--text-primary: #3e3126;
|
||||||
|
--text-secondary: #6b5d4f;
|
||||||
|
--text-muted: #9d8b7a;
|
||||||
|
--accent-primary: #c47b5a;
|
||||||
|
--accent-secondary: #d4a574;
|
||||||
|
--accent-hover: #b36848;
|
||||||
|
--border: #e6dfd5;
|
||||||
|
--shadow: rgba(62, 49, 38, 0.08);
|
||||||
|
--shadow-hover: rgba(62, 49, 38, 0.15);
|
||||||
|
}
|
||||||
|
[data-theme="dark"] {
|
||||||
|
--bg-primary: #1a1612;
|
||||||
|
--bg-secondary: #2a2319;
|
||||||
|
--bg-card: #2a2319;
|
||||||
|
--text-primary: #f5f1e8;
|
||||||
|
--text-secondary: #c9bfb3;
|
||||||
|
--text-muted: #9d8b7a;
|
||||||
|
--accent-primary: #d4a574;
|
||||||
|
--accent-secondary: #c47b5a;
|
||||||
|
--accent-hover: #e6b889;
|
||||||
|
--border: #3e3126;
|
||||||
|
--shadow: rgba(0, 0, 0, 0.3);
|
||||||
|
--shadow-hover: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
line-height: 1.6;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
box-shadow: 0 2px 8px var(--shadow);
|
||||||
|
}
|
||||||
|
.header-content {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.logo-section h1 { font-size: 2rem; font-weight: 700; }
|
||||||
|
.logo-section p { color: var(--text-secondary); font-size: 0.95rem; }
|
||||||
|
.theme-toggle {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border: 2px solid var(--border);
|
||||||
|
border-radius: 2rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.container { max-width: 1400px; margin: 0 auto; padding: 3rem 2rem; }
|
||||||
|
.apps-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
.app-card {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
padding: 2rem;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--text-primary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 2px 8px var(--shadow);
|
||||||
|
}
|
||||||
|
.app-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 8px 24px var(--shadow-hover);
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
.app-header { display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem; }
|
||||||
|
.app-icon {
|
||||||
|
width: 3rem; height: 3rem;
|
||||||
|
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
.app-card h2 { font-size: 1.5rem; font-weight: 600; }
|
||||||
|
.app-card p { color: var(--text-secondary); line-height: 1.7; margin: 1rem 0; flex-grow: 1; }
|
||||||
|
.app-meta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
.app-status {
|
||||||
|
background: linear-gradient(135deg, #4ade80, #22c55e);
|
||||||
|
color: white;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.app-version { color: var(--text-muted); }
|
||||||
|
.doc-link {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
background: #007acc;
|
||||||
|
color: white !important;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.doc-link:hover { background: #005fa3; }
|
||||||
|
.info-section {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
padding: 2rem;
|
||||||
|
box-shadow: 0 2px 8px var(--shadow);
|
||||||
|
}
|
||||||
|
.info-section h3 {
|
||||||
|
color: var(--accent-primary);
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
.info-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
.info-item {
|
||||||
|
padding: 1rem;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.info-item strong { display: block; margin-bottom: 0.5rem; }
|
||||||
|
.info-item p { color: var(--text-secondary); font-size: 0.9rem; }
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="header">
|
||||||
|
<div class="header-content">
|
||||||
|
<div class="logo-section">
|
||||||
|
<h1>SPA Platform</h1>
|
||||||
|
<p>Zentrale Anwendungsplattform</p>
|
||||||
|
</div>
|
||||||
|
<button class="theme-toggle" onclick="toggleTheme()">🌓 Theme</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main class="container">
|
||||||
|
<div class="apps-grid">
|
||||||
|
<a href="/schadendokumentation/" class="app-card">
|
||||||
|
<div class="app-header">
|
||||||
|
<div class="app-icon">📋</div>
|
||||||
|
<h2>Schadendokumentation</h2>
|
||||||
|
</div>
|
||||||
|
<p>Erfassung und Verwaltung von Schadenfällen mit umfassender Dokumentation, Bildverwaltung und Exportfunktionen.</p>
|
||||||
|
<div class="app-meta">
|
||||||
|
<span class="app-status">Aktiv</span>
|
||||||
|
<span class="app-version">Build 24.11.2025</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="/kostenschaetzung/" class="app-card">
|
||||||
|
<div class="app-header">
|
||||||
|
<div class="app-icon">💰</div>
|
||||||
|
<h2>Kostenschätzung</h2>
|
||||||
|
</div>
|
||||||
|
<p>Professionelles Tool zur Erstellung und Verwaltung von Kostenschätzungen für Bauprojekte und Sanierungen.</p>
|
||||||
|
<div class="app-meta">
|
||||||
|
<span class="app-status">Aktiv</span>
|
||||||
|
<span class="app-version">Build 09.11.2025</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="/zeitwert/" class="app-card">
|
||||||
|
<div class="app-header">
|
||||||
|
<div class="app-icon">⏱️</div>
|
||||||
|
<h2>Zeitwert</h2>
|
||||||
|
</div>
|
||||||
|
<p>Berechnung und Dokumentation von Zeitwerten für Versicherungen und Gutachten mit automatischer Wertermittlung.</p>
|
||||||
|
<div class="app-meta">
|
||||||
|
<span class="app-status">Aktiv</span>
|
||||||
|
<span class="app-version">Build 22.11.2025</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="/angebotsdatenbank/" class="app-card">
|
||||||
|
<div class="app-header">
|
||||||
|
<div class="app-icon">💼</div>
|
||||||
|
<h2>Angebotsdatenbank</h2>
|
||||||
|
</div>
|
||||||
|
<p>Intelligente Positionsverwaltung mit STLB-Codes, DIN 276 Klassifikation und umfassender Suchfunktion für Bauleistungen.</p>
|
||||||
|
<div class="app-meta">
|
||||||
|
<span class="app-status">Aktiv</span>
|
||||||
|
<span class="app-version">Build 25.11.2025</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="/zeitutility/" class="app-card">
|
||||||
|
<div class="app-header">
|
||||||
|
<div class="app-icon">🕐</div>
|
||||||
|
<h2>ZeitUtility</h2>
|
||||||
|
</div>
|
||||||
|
<p>Zeit- und Datumsmanagement mit Zeitstempel-Generator, Zeitzonen-Konverter und Datumsrechner für präzise Zeitberechnungen.</p>
|
||||||
|
<div class="app-meta">
|
||||||
|
<span class="app-status">Aktiv</span>
|
||||||
|
<span class="app-version">Build 25.11.2025</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<div class="app-card" style="cursor:default;">
|
||||||
|
<a href="/geo-calc/" style="text-decoration:none;color:inherit;">
|
||||||
|
<div class="app-header">
|
||||||
|
<div class="app-icon">📐</div>
|
||||||
|
<h2>GEO-Calc</h2>
|
||||||
|
</div>
|
||||||
|
<p>3D Geometrie-Rechner für rechtwinklige Dreiecke mit interaktiver Visualisierung und automatischer Berechnung aller Maße.</p>
|
||||||
|
<div class="app-meta">
|
||||||
|
<span class="app-status">Aktiv</span>
|
||||||
|
<span class="app-version">V7</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="/geo-calc/dokumentation.pdf" target="_blank" class="doc-link">📄 Anleitung (PDF)</a>
|
||||||
|
</div>
|
||||||
|
<a href="/symbols/" class="app-card">
|
||||||
|
<div class="app-header">
|
||||||
|
<div class="app-icon">🔣</div>
|
||||||
|
<h2>Symbolbibliothek</h2>
|
||||||
|
</div>
|
||||||
|
<p>148+ SVG & DXF Symbole für Schadensgutachten und Vermessung. Kategorien: Schäden, Werkzeuge, Bauteile, Möbel, Vermessung und mehr.</p>
|
||||||
|
<div class="app-meta">
|
||||||
|
<span class="app-status">Aktiv</span>
|
||||||
|
<span class="app-version">v2.0</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<section class="info-section">
|
||||||
|
<h3>📦 Backup & Datenverwaltung</h3>
|
||||||
|
<div class="info-grid">
|
||||||
|
<div class="info-item">
|
||||||
|
<strong>Lokale Speicherung</strong>
|
||||||
|
<p>Alle Daten werden sicher in deinem Browser gespeichert (IndexedDB). Niemand außer dir hat Zugriff auf deine Daten.</p>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<strong>Export & Backup</strong>
|
||||||
|
<p>Nutze die Export-Funktion in jeder App, um Backups herunterzuladen. Daten als JSON oder CSV exportierbar.</p>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<strong>Import & Restore</strong>
|
||||||
|
<p>Lade Backups direkt in der App hoch, um Daten wiederherzustellen. Vollständiger Datenimport möglich.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<footer class="footer">
|
||||||
|
<p>artetui.de · SPA Platform · Alle Daten werden lokal gespeichert</p>
|
||||||
|
<p style="text-align:center;margin-top:0.5rem;font-size:0.85rem;">
|
||||||
|
<a href="#" onclick="openImpressum();return false;" style="color:#6b7280;text-decoration:none;">Impressum</a>
|
||||||
|
<span style="color:#d1d5db;margin:0 0.5rem;">|</span>
|
||||||
|
<a href="#" onclick="openDatenschutz();return false;" style="color:#6b7280;text-decoration:none;">Datenschutz</a>
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
<script>
|
||||||
|
function toggleTheme() {
|
||||||
|
const html = document.documentElement;
|
||||||
|
const newTheme = html.getAttribute("data-theme") === "light" ? "dark" : "light";
|
||||||
|
html.setAttribute("data-theme", newTheme);
|
||||||
|
localStorage.setItem("theme", newTheme);
|
||||||
|
}
|
||||||
|
(function() {
|
||||||
|
const savedTheme = localStorage.getItem("theme") || "light";
|
||||||
|
document.documentElement.setAttribute("data-theme", savedTheme);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- IMPRESSUM MODAL -->
|
||||||
|
<div class="legal-modal" id="impressumModal" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);z-index:9999;align-items:center;justify-content:center;">
|
||||||
|
<div style="background:white;width:90%;max-width:900px;height:90vh;border-radius:12px;margin:20px;overflow:hidden;display:flex;flex-direction:column;">
|
||||||
|
<div style="padding:0.75rem 1.5rem;border-bottom:1px solid #e5e7eb;display:flex;justify-content:space-between;align-items:center;flex-shrink:0;">
|
||||||
|
<h2 style="margin:0;font-size:1.25rem;color:#1f2937;">Impressum</h2>
|
||||||
|
<button onclick="closeImpressum()" style="background:none;border:none;font-size:1.5rem;cursor:pointer;color:#6b7280;line-height:1;">×</button>
|
||||||
|
</div>
|
||||||
|
<iframe src="/legal/impressum.html" style="flex:1;width:100%;border:none;"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- DATENSCHUTZ MODAL -->
|
||||||
|
<div class="legal-modal" id="datenschutzModal" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);z-index:9999;align-items:center;justify-content:center;">
|
||||||
|
<div style="background:white;width:90%;max-width:900px;height:90vh;border-radius:12px;margin:20px;overflow:hidden;display:flex;flex-direction:column;">
|
||||||
|
<div style="padding:0.75rem 1.5rem;border-bottom:1px solid #e5e7eb;display:flex;justify-content:space-between;align-items:center;flex-shrink:0;">
|
||||||
|
<h2 style="margin:0;font-size:1.25rem;color:#1f2937;">Datenschutz</h2>
|
||||||
|
<button onclick="closeDatenschutz()" style="background:none;border:none;font-size:1.5rem;cursor:pointer;color:#6b7280;line-height:1;">×</button>
|
||||||
|
</div>
|
||||||
|
<iframe src="/legal/datenschutz.html" style="flex:1;width:100%;border:none;"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function openImpressum(){document.getElementById("impressumModal").style.display="flex";}
|
||||||
|
function closeImpressum(){document.getElementById("impressumModal").style.display="none";}
|
||||||
|
function openDatenschutz(){document.getElementById("datenschutzModal").style.display="flex";}
|
||||||
|
function closeDatenschutz(){document.getElementById("datenschutzModal").style.display="none";}
|
||||||
|
document.addEventListener("keydown",function(e){if(e.key==="Escape"){closeImpressum();closeDatenschutz();}});
|
||||||
|
["impressumModal","datenschutzModal"].forEach(function(id){
|
||||||
|
var el=document.getElementById(id);
|
||||||
|
if(el)el.addEventListener("click",function(e){if(e.target===this)this.style.display="none";});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
83
legal/datenschutz.html
Normal file
83
legal/datenschutz.html
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Datenschutz - Architekturbuero Lars Munkes</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
|
line-height: 1.7;
|
||||||
|
color: #374151;
|
||||||
|
background: #fff;
|
||||||
|
padding: 2rem;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
h1 { color: #1f2937; margin-bottom: 1.5rem; font-size: 2rem; }
|
||||||
|
h2 { color: #2563eb; margin: 2rem 0 1rem 0; font-size: 1.25rem; }
|
||||||
|
h3 { color: #2563eb; margin: 1.5rem 0 0.75rem 0; font-size: 1.1rem; }
|
||||||
|
p { margin-bottom: 1rem; }
|
||||||
|
ul { margin: 0 0 1rem 1.5rem; }
|
||||||
|
li { margin-bottom: 0.5rem; }
|
||||||
|
a { color: #2563eb; text-decoration: none; }
|
||||||
|
a:hover { text-decoration: underline; }
|
||||||
|
hr { border: none; border-top: 1px solid #e5e7eb; margin: 2rem 0; }
|
||||||
|
.updated { text-align: center; color: #9ca3af; font-size: 0.9rem; margin-top: 2rem; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Datenschutzerklaerung</h1>
|
||||||
|
|
||||||
|
<h2>1. Verantwortlicher</h2>
|
||||||
|
<p>Verantwortlich fuer die Datenverarbeitung auf dieser Website ist:</p>
|
||||||
|
<p>Architekturbuero Lars Munkes<br>
|
||||||
|
Oldenstorf 3<br>
|
||||||
|
18276 Lohmen<br>
|
||||||
|
Deutschland<br>
|
||||||
|
E-Mail: <a href="mailto:info@artetui.de">info@artetui.de</a></p>
|
||||||
|
|
||||||
|
<h2>2. Erhebung und Speicherung personenbezogener Daten</h2>
|
||||||
|
|
||||||
|
<h3>Server-Logfiles</h3>
|
||||||
|
<p>Bei jedem Zugriff auf diese Website werden durch den Webserver automatisch Daten erfasst und in Server-Logfiles gespeichert:</p>
|
||||||
|
<ul>
|
||||||
|
<li>IP-Adresse des anfragenden Rechners</li>
|
||||||
|
<li>Datum und Uhrzeit des Zugriffs</li>
|
||||||
|
<li>Name und URL der abgerufenen Datei</li>
|
||||||
|
<li>Website, von der aus der Zugriff erfolgt (Referrer-URL)</li>
|
||||||
|
<li>Verwendeter Browser und ggf. das Betriebssystem</li>
|
||||||
|
</ul>
|
||||||
|
<p>Diese Daten werden zur Gewaehrleistung eines reibungslosen Verbindungsaufbaus und zur Systemsicherheit verarbeitet. Rechtsgrundlage ist Art. 6 Abs. 1 lit. f DSGVO.</p>
|
||||||
|
|
||||||
|
<h3>Lokale Speicherung (localStorage)</h3>
|
||||||
|
<p>Diese Webanwendung speichert Daten ausschliesslich lokal in Ihrem Browser (localStorage). Es findet keine Uebertragung dieser Daten an unsere Server oder Dritte statt. Sie koennen die lokal gespeicherten Daten jederzeit in Ihren Browsereinstellungen loeschen.</p>
|
||||||
|
|
||||||
|
<h2>3. Cookies</h2>
|
||||||
|
<p>Diese Website verwendet keine Tracking-Cookies. Es werden ausschliesslich technisch notwendige Browser-Funktionen genutzt.</p>
|
||||||
|
|
||||||
|
<h2>4. Weitergabe von Daten</h2>
|
||||||
|
<p>Eine Uebermittlung Ihrer persoenlichen Daten an Dritte zu anderen als den im Folgenden aufgefuehrten Zwecken findet nicht statt.</p>
|
||||||
|
|
||||||
|
<h2>5. Ihre Rechte</h2>
|
||||||
|
<p>Sie haben gegenueber uns folgende Rechte hinsichtlich der Sie betreffenden personenbezogenen Daten:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Recht auf Auskunft (Art. 15 DSGVO)</li>
|
||||||
|
<li>Recht auf Berichtigung (Art. 16 DSGVO)</li>
|
||||||
|
<li>Recht auf Loeschung (Art. 17 DSGVO)</li>
|
||||||
|
<li>Recht auf Einschraenkung der Verarbeitung (Art. 18 DSGVO)</li>
|
||||||
|
<li>Recht auf Datenuebertragbarkeit (Art. 20 DSGVO)</li>
|
||||||
|
<li>Recht auf Widerspruch gegen die Verarbeitung (Art. 21 DSGVO)</li>
|
||||||
|
</ul>
|
||||||
|
<p>Sie haben zudem das Recht, sich bei einer Datenschutz-Aufsichtsbehoerde ueber die Verarbeitung Ihrer personenbezogenen Daten zu beschweren.</p>
|
||||||
|
|
||||||
|
<h2>6. Datensicherheit</h2>
|
||||||
|
<p>Diese Website nutzt aus Sicherheitsgruenden eine SSL- bzw. TLS-Verschluesselung. Eine verschluesselte Verbindung erkennen Sie daran, dass die Adresszeile des Browsers von "http://" auf "https://" wechselt und an dem Schloss-Symbol in Ihrer Browserzeile.</p>
|
||||||
|
|
||||||
|
<h2>7. Aktualitaet und Aenderung dieser Datenschutzerklaerung</h2>
|
||||||
|
<p>Diese Datenschutzerklaerung ist aktuell gueltig und hat den Stand Dezember 2024. Durch die Weiterentwicklung unserer Website oder aufgrund geaenderter gesetzlicher bzw. behoerdlicher Vorgaben kann es notwendig werden, diese Datenschutzerklaerung zu aendern.</p>
|
||||||
|
|
||||||
|
<p class="updated">Stand: Dezember 2024</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
76
legal/impressum.html
Normal file
76
legal/impressum.html
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Impressum - Architekturbuero Lars Munkes</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
|
line-height: 1.7;
|
||||||
|
color: #374151;
|
||||||
|
background: #fff;
|
||||||
|
padding: 2rem;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
h1 { color: #1f2937; margin-bottom: 1.5rem; font-size: 2rem; }
|
||||||
|
h2 { color: #2563eb; margin: 2rem 0 1rem 0; font-size: 1.25rem; }
|
||||||
|
h3 { color: #2563eb; margin: 1.5rem 0 0.75rem 0; font-size: 1.1rem; }
|
||||||
|
p { margin-bottom: 1rem; }
|
||||||
|
a { color: #2563eb; text-decoration: none; }
|
||||||
|
a:hover { text-decoration: underline; }
|
||||||
|
hr { border: none; border-top: 1px solid #e5e7eb; margin: 2rem 0; }
|
||||||
|
.company { font-size: 1.25rem; font-weight: 600; color: #1f2937; margin-bottom: 1.5rem; }
|
||||||
|
.section { margin-bottom: 1.5rem; }
|
||||||
|
.label { font-weight: 600; color: #1f2937; }
|
||||||
|
.copyright { text-align: center; color: #9ca3af; font-size: 0.9rem; margin-top: 2rem; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Impressum</h1>
|
||||||
|
|
||||||
|
<p class="company">Architekturbuero Lars Munkes</p>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<p class="label">Anschrift:</p>
|
||||||
|
<p>Oldenstorf 3<br>18276 Lohmen<br>Deutschland</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<p class="label">Kontakt:</p>
|
||||||
|
<p>E-Mail: <a href="mailto:info@artetui.de">info@artetui.de</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<p class="label">Umsatzsteuer-Identifikationsnummer:</p>
|
||||||
|
<p>DE233951802</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<p class="label">Berufsbezeichnung:</p>
|
||||||
|
<p>Architekt<br>Verliehen in Deutschland</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<p class="label">Zustaendige Kammer:</p>
|
||||||
|
<p>Architektenkammer Mecklenburg-Vorpommern</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2>Haftungsausschluss</h2>
|
||||||
|
|
||||||
|
<h3>Haftung fuer Inhalte</h3>
|
||||||
|
<p>Die Inhalte dieser Seiten wurden mit groesster Sorgfalt erstellt. Fuer die Richtigkeit, Vollstaendigkeit und Aktualitaet der Inhalte kann jedoch keine Gewaehr uebernommen werden. Als Diensteanbieter sind wir gemaess § 7 Abs. 1 TMG fuer eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich.</p>
|
||||||
|
|
||||||
|
<h3>Haftung fuer Links</h3>
|
||||||
|
<p>Diese Seiten enthalten Links zu externen Webseiten Dritter, auf deren Inhalte wir keinen Einfluss haben. Deshalb koennen wir fuer diese fremden Inhalte auch keine Gewaehr uebernehmen. Fuer die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber der Seiten verantwortlich.</p>
|
||||||
|
|
||||||
|
<h3>Urheberrecht</h3>
|
||||||
|
<p>Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht. Die Vervielfaeltigung, Bearbeitung, Verbreitung und jede Art der Verwertung ausserhalb der Grenzen des Urheberrechtes beduerfen der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers.</p>
|
||||||
|
|
||||||
|
<p class="copyright">© 2024 Architekturbuero Lars Munkes</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
2660
zeitutility/index.html
Normal file
2660
zeitutility/index.html
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user