Commit 18535aa9 authored by Alfakhori's avatar Alfakhori
Browse files

new test

parent d69bf271
Pipeline #12491 passed with stage
in 23 seconds
// Debug-Konsole aktivieren
const debugConsoleEnabled = true;
if (debugConsoleEnabled) {
const debugConsole = document.getElementById('debug-console');
const debugToggleBtn = document.getElementById('debug-toggle-btn');
debugConsole.style.display = 'block';
debugToggleBtn.style.display = 'block';
// Toggle-Funktion hinzufügen
let isDebugVisible = true;
debugToggleBtn.addEventListener('click', () => {
toggleDebugConsole();
});
// Konsole überschreiben
(function() {
const oldConsoleLog = console.log;
console.log = function(...args) {
oldConsoleLog.apply(console, args); // Normale Konsole weiterhin nutzen
// Logs in das Debug-Element schreiben
debugConsole.innerHTML += args.join(' ') + '<br>';
debugConsole.scrollTop = debugConsole.scrollHeight; // Automatisch nach unten scrollen
};
const oldConsoleError = console.error;
console.error = function(...args) {
oldConsoleError.apply(console, args);
debugConsole.innerHTML += '<span style="color: red;">' + args.join(' ') + '</span><br>';
debugConsole.scrollTop = debugConsole.scrollHeight;
};
// JavaScript-Fehler global abfangen
window.onerror = function(message, source, lineno, colno, error) {
const errorMsg = `[ERROR] ${message} at ${source}:${lineno}:${colno}`;
debugConsole.innerHTML += `<span style="color: red;">${errorMsg}</span><br>`;
debugConsole.scrollTop = debugConsole.scrollHeight;
// Auch in der normalen Konsole anzeigen
oldConsoleError(errorMsg);
};
// Unhandled Promise Rejections abfangen
window.addEventListener('unhandledrejection', function(event) {
const errorMsg = `[PROMISE ERROR] ${event.reason}`;
debugConsole.innerHTML += `<span style="color: orange;">${errorMsg}</span><br>`;
debugConsole.scrollTop = debugConsole.scrollHeight;
oldConsoleError(errorMsg);
});
})();
function toggleDebugConsole() {
isDebugVisible = !isDebugVisible;
debugConsole.style.display = isDebugVisible ? 'block' : 'none';
debugToggleBtn.innerText = isDebugVisible ? 'Close' : 'Open Console';
}
toggleDebugConsole();
console.log("Debug Console enabled");
}
function downloadScene() {
const placedModels = getAllPlacedModels().map(model => {
const { lat, lng } = threeToLeaflet(model.position.x, model.position.z);
return {
name: model.modelConfig.name,
position: model.position,
rotation: model.rotation,
scale: model.scale,
lat: lat,
lng: lng,
}
});
console.log("Modelle: " + placedModels.length)
if (placedModels.length === 0) {
showInfoDialog("Szene ist leer");
return;
}
if (!geoLocation) {
console.log("Standort nicht geladen");
showInfoDialog("Ihr Standort wurde noch nicht geladen");
return;
}
const data = {
lat: geoLocation.latitude,
lng: geoLocation.longitude,
models: placedModels,
}
const sceneData = JSON.stringify(data, null, 1);
const blob = new Blob([sceneData], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "scene.json";
a.style.display = "none";
document.body.appendChild(a);
a.click();
URL.revokeObjectURL(url);
document.body.removeChild(a);
}
function loadSceneFromFile(loadedData) {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'application/json';
fileInput.addEventListener('change', function(event) {
const file = event.target.files[0];
const fileName = file ? file.name : "Keine Datei ausgewählt"
if (!file) {
console.log("Keine Datei ausgewählt");
return;
}
const reader = new FileReader();
reader.onload = function(e) {
try {
const sceneData = JSON.parse(e.target.result);
loadedData(sceneData, fileName);
} catch (error) {
console.error("Fehler beim Verarbeiten der Datei:", error);
}
};
reader.readAsText(file);
});
fileInput.click();
}
\ No newline at end of file
This diff is collapsed.
let init = false;
let mymap;
let userIcon;
let intervalId;
let modeMarkers = [];
// PERFORMANCE: Reuse one Vector3 instead of allocating a new
// Vector3 every time updateMap() runs.
let cameraPosition = new THREE.Vector3();
// PERFORMANCE: Cache the map window element once.
let mapWindow = document.getElementById("map-window");
// PERFORMANCE: Remember the last mapped camera position so Leaflet
// is updated only after the user has actually moved.
let lastMapX = null;
let lastMapZ = null;
// ============================================================
// Proposed plan overlay
// ============================================================
let planOverlay = null;
let planVisible = false;
// Proposed plan geographic bounds
// DHDN / Gauss-Krüger Zone 3
// Converted to WGS84
//
// Southwest:
// Latitude = 48.405858385
// Longitude = 7.997080070
//
// Northeast:
// Latitude = 48.414966828
// Longitude = 8.010410216
const planBounds = [
[48.405858385, 7.99708007],
[48.414966828, 8.010410216],
];
const earthRadius = 6378137;
// ============================================================
// Initialize map
// ============================================================
function init_map() {
const mapContainer = document.getElementById("map-container");
if (mapContainer) {
if (!init) {
mymap = L.map(mapContainer);
var OpenStreetMap_DE = L.tileLayer(
"https://tile.openstreetmap.de/{z}/{x}/{y}.png",
{
maxZoom: 25,
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
},
);
OpenStreetMap_DE.addTo(mymap);
userIcon = L.icon({
iconUrl: "assets/previewImages/map-user.png",
iconSize: [32, 32],
iconAnchor: [16, 32],
popupAnchor: [0, -32],
});
mymap.fitBounds(planBounds, {
padding: [20, 20],
});
userIcon = L.marker([geoLocation.latitude, geoLocation.longitude], {
icon: userIcon,
})
.bindPopup("Ihre Position")
.addTo(mymap);
init = true;
}
// ========================================================
// Remove old model markers
// ========================================================
modeMarkers.forEach((marker) => {
marker.remove();
});
modeMarkers = [];
// ========================================================
// Add currently placed models to map
// ========================================================
const models = getAllPlacedModels();
models.forEach((model) => {
const { lat, lng } = threeToLeaflet(model.position.x, model.position.z);
const modelMarker = L.marker([lat, lng], {
icon: L.icon({
iconUrl: model.modelConfig.image,
iconSize: [16, 16],
iconAnchor: [16, 32],
popupAnchor: [0, -32],
}),
})
.addTo(mymap)
.bindPopup(model.modelConfig?.name || "Unbenannt");
modeMarkers.push(modelMarker);
});
// ========================================================
// Add proposed plan
// ========================================================
addPlanOverlay();
// ========================================================
// Make sure Leaflet knows the map container dimensions
// ========================================================
setTimeout(() => {
centerOnPlan();
}, 100);
// ========================================================
// Update map position continuously
// ========================================================
// PERFORMANCE: Start only one map update interval.
if (!intervalId) {
intervalId = setInterval(updateMap, 500);
}
}
}
// ============================================================
// Center map on proposed plan
// ============================================================
function centerOnPlan() {
if (!mymap) {
console.error("Map ist noch nicht initialisiert.");
return;
}
mymap.invalidateSize();
mymap.fitBounds(planBounds, {
padding: [20, 20],
});
}
// ============================================================
// Add georeferenced proposed plan
// ============================================================
function addPlanOverlay() {
if (!mymap) {
console.error("Map ist noch nicht initialisiert.");
return;
}
if (planOverlay) {
planOverlay.addTo(mymap);
planVisible = true;
return;
}
planOverlay = L.imageOverlay("assets/plans/proposed-plan8k.png", planBounds, {
opacity: 0.7,
interactive: false,
});
planOverlay.addTo(mymap);
planVisible = true;
}
// ============================================================
// Update map
// ============================================================
function updateMap() {
// The interval is normally stopped by closeMap().
// This guard also handles cases where the map is hidden
// by another UI action.
if (mapWindow && mapWindow.style.display === "none" && intervalId) {
clearInterval(intervalId);
intervalId = null;
return;
}
if (!camera || !userIcon) {
return;
}
// PERFORMANCE: Reuse the same Vector3 object.
camera.getWorldPosition(cameraPosition);
// PERFORMANCE: Do not update Leaflet if the user
// has not moved.
if (
lastMapX !== null &&
Math.abs(cameraPosition.x - lastMapX) < 0.1 &&
Math.abs(cameraPosition.z - lastMapZ) < 0.1
) {
return;
}
lastMapX = cameraPosition.x;
lastMapZ = cameraPosition.z;
const { lat, lng } = threeToLeaflet(cameraPosition.x, cameraPosition.z);
userIcon.setLatLng([lat, lng]);
}
// ============================================================
// Convert Three.js coordinates to Leaflet coordinates
// ============================================================
function threeToLeaflet(threeX, threeZ) {
if (!geoLocation) {
console.error("GeoLocation ist noch nicht verfügbar");
return;
}
const originLat = geoLocation.latitude;
const originLng = geoLocation.longitude;
const deltaLat = (threeZ / earthRadius) * (180 / Math.PI);
const deltaLng =
(threeX / (earthRadius * Math.cos((Math.PI * originLat) / 180))) *
(180 / Math.PI);
const globalLat = originLat + deltaLat;
const globalLng = originLng + deltaLng;
return {
lat: globalLat,
lng: globalLng,
};
}
// ============================================================
// Convert Leaflet coordinates to Three.js coordinates
// ============================================================
function leafletToThree(lat, lng) {
if (!geoLocation) {
console.error("GeoLocation ist noch nicht verfügbar");
return;
}
const originLat = geoLocation.latitude;
const originLng = geoLocation.longitude;
const deltaLat = lat - originLat;
const deltaLng = lng - originLng;
const threeZ = deltaLat * (Math.PI / 180) * earthRadius;
const threeX =
deltaLng *
(Math.PI / 180) *
earthRadius *
Math.cos((Math.PI * originLat) / 180);
return {
x: threeX,
z: threeZ,
};
}
// ============================================================
// Round number
// ============================================================
function roundTo(n, digits) {
var negative = false;
if (digits === undefined) {
digits = 0;
}
if (n < 0) {
negative = true;
n = n * -1;
}
var multiplicator = Math.pow(10, digits);
n = parseFloat((n * multiplicator).toFixed(11));
n = (Math.round(n) / multiplicator).toFixed(digits);
if (negative) {
n = (n * -1).toFixed(digits);
}
return parseFloat(n);
}
/* ========================= */
/* DEBUGGING UND START */
/* ========================= */
let sceneData = null;
let sceneFileName = null;
let startScreen = null;
let startButton = null;
let loadButton = null;
let predefinedButton1 = null;
let predefinedButton2 = null;
/* ========================= */
/* PREDEFINED SCENE */
/* ========================= */
async function loadPredefinedScene(filePath, fileName) {
try {
const response = await fetch(filePath);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
sceneData = data;
sceneFileName = fileName;
console.log("Vordefinierte Szene geladen:", fileName);
return true;
} catch (error) {
console.error("Fehler beim Laden der vordefinierten Szene:", error);
return false;
}
}
/* ============================================================ */
/* Create the Start Screen */
/* ============================================================ */
function showStartScreen() {
// Prevent duplicate start screens
if (
startScreen ||
startButton ||
loadButton ||
predefinedButton1 ||
predefinedButton2
) {
return;
}
/* ==========================================================
START SCREEN CONTAINER
========================================================== */
startScreen = document.createElement("div");
startScreen.classList.add("start-screen");
document.body.appendChild(startScreen);
/* ==========================================================
HFT LOGO
========================================================== */
const hftLogo = document.createElement("img");
hftLogo.src = "assets/images/logo-HFT.svg";
hftLogo.alt = "HFT Stuttgart";
hftLogo.classList.add("start-screen-logo");
startScreen.appendChild(hftLogo);
/* ==========================================================
TITLE
========================================================== */
const title = document.createElement("h1");
title.textContent = "GENGENBACH";
title.classList.add("start-screen-title");
startScreen.appendChild(title);
/* ==========================================================
SUBTITLE
========================================================== */
const subtitle = document.createElement("div");
subtitle.textContent = "AR Urban Planning";
subtitle.classList.add("start-screen-subtitle");
startScreen.appendChild(subtitle);
/* ==========================================================
DIVIDER
========================================================== */
const divider = document.createElement("div");
divider.classList.add("start-screen-divider");
startScreen.appendChild(divider);
/* ==========================================================
START AR
========================================================== */
startButton = document.createElement("button");
startButton.textContent = "Start AR";
startButton.classList.add("start-screen-button", "start-ar-button");
startScreen.appendChild(startButton);
/* ==========================================================
LOAD SCENE
========================================================== */
loadButton = document.createElement("button");
loadButton.textContent = sceneFileName || "Umgebung Laden";
loadButton.classList.add(
"start-screen-button",
"load-scene-button",
"scene-button",
);
startScreen.appendChild(loadButton);
/* ==========================================================
PREDEFINED SCENE 1
========================================================== */
predefinedButton1 = document.createElement("button");
predefinedButton1.textContent = "Quartiersplatz 1";
predefinedButton1.classList.add(
"start-screen-button",
"predefined-scene-button-1",
"scene-button",
);
startScreen.appendChild(predefinedButton1);
/* ==========================================================
PREDEFINED SCENE 2
========================================================== */
predefinedButton2 = document.createElement("button");
predefinedButton2.textContent = "Quartiersplatz 2";
predefinedButton2.classList.add(
"start-screen-button",
"predefined-scene-button-2",
"scene-button",
);
startScreen.appendChild(predefinedButton2);
/* ==========================================================
START AR CLICK
========================================================== */
startButton.onclick = () => {
// Remove complete start screen
startScreen.remove();
// Reset references
startScreen = null;
startButton = null;
loadButton = null;
predefinedButton1 = null;
predefinedButton2 = null;
// Start AR
activateXR(sceneData);
};
/* ==========================================================
LOAD SCENE CLICK
========================================================== */
loadButton.onclick = () => {
loadSceneFromFile((data, fileName) => {
if (data && fileName) {
sceneData = data;
sceneFileName = fileName;
loadButton.textContent = fileName;
// Select uploaded scene
loadButton.classList.add("selected");
// Deselect predefined scenes
predefinedButton1.classList.remove("selected");
predefinedButton2.classList.remove("selected");
} else {
loadButton.textContent = sceneFileName || "Umgebung Laden";
}
});
};
/* ==========================================================
PREDEFINED SCENE 1
========================================================== */
predefinedButton1.onclick = async () => {
const success = await loadPredefinedScene(
"scenes/gengenbach-plaza.json",
"Quartiersplatz 1",
);
if (success) {
// Select Plaza
predefinedButton1.classList.add("selected");
// Deselect Street
predefinedButton2.classList.remove("selected");
// Deselect uploaded scene
loadButton.classList.remove("selected");
}
};
/* ==========================================================
PREDEFINED SCENE 2
========================================================== */
predefinedButton2.onclick = async () => {
const success = await loadPredefinedScene(
"scenes/gengenbach-street.json",
"Quartiersplatz 2",
);
if (success) {
// Select Street
predefinedButton2.classList.add("selected");
// Deselect Plaza
predefinedButton1.classList.remove("selected");
// Deselect uploaded scene
loadButton.classList.remove("selected");
}
};
}
/* ============================================================ */
/* INITIAL START */
/* ============================================================ */
if (navigator.xr) {
showStartScreen();
} else {
alert("WebXR wird nicht unterstützt.");
}
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gengenbach AR Projekt</title>
<link rel="stylesheet" href="style.css">
<script src="https://unpkg.com/three@0.126.0/build/three.js"></script>
<script src="https://unpkg.com/three@0.126.0/examples/js/loaders/GLTFLoader.js"></script>
</head>
<body>
<!-- ============================================================
DEBUG
============================================================ -->
<div id="debug-container">
<div id="debug-console"></div>
<button id="debug-toggle-btn"></button>
</div>
<!-- ============================================================
STANDARDMENÜ
============================================================ -->
<div id="menu-bar">
<div
class="menu-item"
id="add-section"
onclick="showMenu('add-menu')">
<img
src="assets/icons/add-icon.png"
alt="Hinzufügen" />
</div>
<div
class="menu-item"
onclick="showMenu('map-window')">
<img
src="assets/icons/map-icon.png"
alt="Karte" />
</div>
<div
class="menu-item"
id="options-section"
onclick="showMenu('options-menu')">
<img
src="assets/icons/options-icon.png"
alt="Optionen" />
</div>
</div>
<!-- ============================================================
HINZUFÜGEN-MENÜ
============================================================ -->
<div
id="add-menu"
class="menu-placeholder">
</div>
<!-- ============================================================
BEARBEITEN-MENÜ
============================================================ -->
<div
id="edit-menu"
class="menu-placeholder">
<div
class="menu-item"
onclick="openRotationMenu()">
<img
src="assets/icons/rotate-icon.png"
alt="Rotation" />
</div>
<div
class="menu-item"
onclick="openScaleMenu()">
<img
src="assets/icons/scale-icon.png"
alt="Skalierung" />
</div>
<div
class="menu-item"
onclick="openMoveMenu()">
<img
src="assets/icons/move-icon.png"
alt="Bewegen" />
</div>
<div
class="menu-item"
onclick="deleteModel()">
<img
src="assets/icons/delete-icon.png"
alt="Löschen" />
</div>
<div
class="menu-item"
onclick="completeEditing()">
<img
src="assets/icons/check-icon.png"
alt="Fertigstellen" />
</div>
</div>
<!-- ============================================================
OPTIONEN-MENÜ
============================================================ -->
<div
id="options-menu"
class="menu-placeholder">
<div
class="menu-item"
onclick="exitAR()">
<img
src="assets/icons/exit-icon.png"
alt="Beenden" />
</div>
<div
class="menu-item"
onclick="downloadScene()">
<img
src="assets/icons/download-icon.png"
alt="Download" />
</div>
<div
class="menu-item"
onclick="showMenu('menu-bar')">
<img
src="assets/icons/back-icon.png"
alt="Zurück" />
</div>
</div>
<!-- ============================================================
DYNAMISCHES MENÜ
============================================================ -->
<div id="dynamic-menu"></div>
<!-- ============================================================
BESTÄTIGUNGSDIALOG LÖSCHEN
============================================================ -->
<div id="delete-confirmation-dialog">
<div class="dialog-overlay"></div>
<div class="dialog-box">
<p id="delete-confirmation-text">
Möchten Sie das Modell wirklich löschen?
</p>
<button onclick="confirmDelete(true)">
Ja
</button>
<button onclick="confirmDelete(false)">
Nein
</button>
</div>
</div>
<!-- ============================================================
BESTÄTIGUNGSDIALOG BEENDEN
============================================================ -->
<div id="confirmation-dialog">
<div class="dialog-overlay"></div>
<div class="dialog-box">
<p>
Möchten Sie die AR-Session wirklich verlassen?
</p>
<button onclick="confirmExit(true)">
Ja
</button>
<button onclick="confirmExit(false)">
Nein
</button>
</div>
</div>
<!-- ============================================================
INFORMATIONS-DIALOG
============================================================ -->
<div id="info-dialog">
<div class="dialog-overlay"></div>
<div class="dialog-box">
<p id="info-text">
Hier kommt die Nachricht hin
</p>
<button onclick="closeInfoDialog()">
OK
</button>
</div>
</div>
<!-- ============================================================
MAP WINDOW
============================================================ -->
<div
id="map-window"
class="menu-placeholder">
<div id="map-container"></div>
<div
class="menu-item map-back-button"
onclick="showMenu('menu-bar')">
<img
src="assets/icons/back-icon.png"
alt="Zurück" />
</div>
</div>
<!-- ============================================================
APP VERSION
Change this manually when needed.
============================================================ -->
<div id="app-version">
v0.2.0
</div>
<!-- ============================================================
JAVASCRIPT
============================================================ -->
<script src="ar_start.js"></script>
<script src="ar_main.js"></script>
<script src="ar_debug_console.js"></script>
<link
rel="stylesheet"
href="https://unpkg.com/leaflet/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script src="ar_overviewmap.js"></script>
<script src="ar_download.js"></script>
<!-- ============================================================
BUTTON SOUND
============================================================ -->
<audio
id="button-sound"
src="assets/sounds/button-sound.mp3"
preload="auto">
</audio>
</body>
</html>
\ No newline at end of file
/* ============================================================
GLOBAL / START SCREEN
============================================================ */
body {
margin: 0;
font-family: Arial, sans-serif;
color: white;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
overflow: hidden;
background-image:
linear-gradient(
rgba(0, 0, 0, 0.35),
rgba(0, 0, 0, 0.50)
),
url('assets/images/gengenbach.png');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
/* ============================================================
GENERAL CONTAINER
============================================================ */
.container {
text-align: center;
background: rgba(20, 20, 20, 0.55);
padding: 40px 30px;
border-radius: 20px;
color: white;
max-width: 80%;
box-shadow:
0 10px 40px rgba(0, 0, 0, 0.35);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.15);
}
/* ============================================================
GENERAL HEADINGS / TEXT
============================================================ */
h1 {
font-size: 2.5em;
margin-bottom: 10px;
color: white;
}
p {
font-size: 1.1em;
margin-bottom: 30px;
color: rgba(255, 255, 255, 0.85);
}
/* ============================================================
GENERAL BUTTONS
============================================================ */
button {
background-color: #4CAF50;
color: white;
font-size: 1em;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
transition:
background-color 0.3s ease-in-out,
transform 0.2s;
box-shadow:
0 2px 4px rgba(0, 0, 0, 0.1);
margin: 10px;
touch-action: manipulation;
}
button:hover {
background-color: #45a049;
}
button:active {
background-color: #387a39;
}
/* ============================================================
START SCREEN
============================================================ */
.start-screen {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: min(340px, 82vw);
display: flex;
flex-direction: column;
align-items: center;
gap: 11px;
z-index: 10;
text-align: center;
}
/* ==========================================================
HFT LOGO
========================================================== */
.start-screen-logo {
width: 110px;
height: auto;
display: block;
margin: 0 auto 18px auto;
object-fit: contain;
}
/* ============================================================
START SCREEN TITLE
============================================================ */
.start-screen-title {
margin: 0;
color: white;
font-size: clamp(28px, 7vw, 42px);
font-weight: 700;
letter-spacing: 3px;
line-height: 1.1;
text-shadow:
0 3px 12px rgba(0, 0, 0, 0.45);
}
.start-screen-subtitle {
margin: 4px 0 4px;
color: rgba(255, 255, 255, 0.88);
font-size: 15px;
font-weight: 400;
letter-spacing: 1px;
text-shadow:
0 2px 8px rgba(0, 0, 0, 0.45);
}
/* ============================================================
START SCREEN DIVIDER
============================================================ */
.start-screen-divider {
width: 55px;
height: 2px;
margin: 8px 0 12px;
background: rgba(255, 255, 255, 0.75);
border-radius: 2px;
}
/* ============================================================
START SCREEN BUTTONS
============================================================ */
.start-screen-button {
position: relative;
width: 100%;
box-sizing: border-box;
min-height: 54px;
padding: 15px 48px 15px 48px;
font-size: 16px;
font-weight: 500;
letter-spacing: 0.2px;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.18);
color: white;
cursor: pointer;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
box-shadow:
0 6px 20px rgba(0, 0, 0, 0.25);
margin: 0;
touch-action: manipulation;
transition:
background-color 0.2s ease,
transform 0.15s ease,
box-shadow 0.2s ease;
}
/* ============================================================
BUTTON ICONS
============================================================ */
.start-screen-button::before {
position: absolute;
left: 18px;
top: 50%;
transform: translateY(-50%);
font-size: 19px;
opacity: 0.9;
}
/* Start AR */
.start-ar-button::before {
content: "◎";
}
/* Load scene */
.load-scene-button::before {
content: "↥";
}
/* Plaza */
.predefined-scene-button-1::before {
content: "⌂";
}
/* Street */
.predefined-scene-button-2::before {
content: "▥";
}
/* ============================================================
SELECTED SCENE CHECKMARK
============================================================ */
.scene-button.selected::after {
content: "✓";
position: absolute;
right: 18px;
top: 50%;
transform: translateY(-50%);
font-size: 18px;
font-weight: 700;
color: white;
}
/* ============================================================
START AR BUTTON
============================================================ */
.start-ar-button {
background: #4CAF50;
min-height: 60px;
font-size: 19px;
font-weight: 600;
letter-spacing: 0.3px;
border-color:
rgba(255, 255, 255, 0.28);
box-shadow:
0 8px 25px rgba(0, 0, 0, 0.30),
0 3px 12px rgba(76, 175, 80, 0.20);
margin-bottom: 7px;
}
.start-ar-button:hover {
background: #45a049;
transform: scale(1.03);
box-shadow:
0 10px 30px rgba(0, 0, 0, 0.35),
0 4px 16px rgba(76, 175, 80, 0.25);
}
.start-ar-button:active {
background: #387a39;
transform: scale(0.98);
}
/* ============================================================
SCENE BUTTONS
============================================================ */
.scene-button {
background: rgba(45, 45, 45, 0.78);
}
.scene-button:hover {
background: rgba(70, 70, 70, 0.88);
transform: scale(1.02);
box-shadow:
0 8px 24px rgba(0, 0, 0, 0.30);
}
.scene-button.selected {
background: #4CAF50;
border-color:
rgba(255, 255, 255, 0.30);
box-shadow:
0 7px 24px rgba(0, 0, 0, 0.30),
0 3px 14px rgba(76, 175, 80, 0.25);
}
.scene-button.selected:hover {
background: #45a049;
transform: scale(1.02);
}
.scene-button:active {
transform: scale(0.98);
}
/* ============================================================
STANDARD MENÜ
============================================================ */
#menu-bar,
.menu-placeholder {
position: absolute;
bottom: 0;
width: 100%;
height: 80px;
display: flex;
justify-content: space-around;
align-items: center;
background: #121212;
padding: 10px;
box-shadow:
0 -2px 10px rgba(0, 0, 0, 0.8);
color: white;
z-index: 10;
border-top: 1px solid #2a2a2a;
}
/* ============================================================
MENU INITIAL STATE
============================================================ */
#menu-bar,
#add-menu,
#edit-menu,
#options-menu {
display: none;
}
/* ============================================================
MENU ITEMS
============================================================ */
.menu-item {
background: #2c2c2c;
border-radius: 20px;
padding: 8px;
transition:
background-color 0.3s,
transform 0.2s;
}
.menu-item:hover {
background-color: #3a3a3a;
transform: scale(1.1);
}
.menu-item img {
width: 50px;
height: 50px;
}
.menu-placeholder .menu-item img {
width: 50px;
height: 50px;
}
/* ============================================================
ADD MENU
============================================================ */
#add-menu {
overflow-y: auto;
}
/* ============================================================
DYNAMIC MENU
============================================================ */
#dynamic-menu {
position: absolute;
bottom: 90px;
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
background: #1e1e1e;
box-shadow:
0 -2px 6px rgba(0, 0, 0, 0.8);
color: white;
width: 100vw;
height: 250px;
z-index: 20;
overflow-y: auto;
border-radius: 8px;
}
/* ============================================================
RANGE SLIDER
============================================================ */
#dynamic-menu input[type="range"] {
width: 100%;
margin: 10px auto;
background: #3a3a3a;
border-radius: 5px;
}
#dynamic-menu input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
background: #fff;
border: 2px solid #555;
border-radius: 50%;
cursor: pointer;
}
/* ============================================================
CONFIRMATION / INFORMATION DIALOGS
============================================================ */
#confirmation-dialog,
#delete-confirmation-dialog,
#info-dialog {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: none;
justify-content: center;
align-items: center;
z-index: 20;
}
.dialog-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
}
.dialog-box {
position: relative;
background: #2c2c2c;
padding: 20px;
border-radius: 10px;
text-align: center;
color: white;
box-shadow:
0 4px 6px rgba(0, 0, 0, 0.8);
}
.dialog-box p {
margin-bottom: 20px;
font-size: 1.2em;
color: #f0f0f0;
}
.dialog-box button {
margin: 5px;
padding: 10px 20px;
font-size: 16px;
border: none;
border-radius: 5px;
cursor: pointer;
transition:
background-color 0.3s ease,
transform 0.2s;
}
.dialog-box button:first-child {
background-color: #e74c3c;
color: white;
}
.dialog-box button:first-child:hover {
background-color: #c0392b;
transform: scale(1.05);
}
.dialog-box button:last-child {
background-color: #3498db;
color: white;
}
.dialog-box button:last-child:hover {
background-color: #2980b9;
transform: scale(1.05);
}
/* ============================================================
MAP WINDOW
============================================================ */
#map-window {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: none;
flex-direction: column;
background: #ffffff;
z-index: 100;
box-sizing: border-box;
padding: 10px;
}
/* ============================================================
MAP CONTAINER
============================================================ */
#map-container {
position: relative;
z-index: 1500;
flex: 15;
width: 100%;
height: 100%;
}
/* ============================================================
MAP BACK BUTTON
============================================================ */
.map-back-button {
cursor: pointer;
flex: 1;
align-items: center;
justify-content: center;
}
.map-back-button img {
width: 30px;
height: 30px;
}
/* ============================================================
LEAFLET MAP
============================================================ */
#map-container .leaflet-container {
width: 100%;
height: 100%;
}
/* ============================================================
DEBUG CONSOLE
============================================================ */
#debug-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 9999;
font-family: monospace;
font-size: 12px;
display: flex;
flex-direction: column;
align-items: flex-start;
}
#debug-console {
width: 100%;
max-height: 200px;
overflow-y: auto;
background: rgba(0, 0, 0, 0.7);
color: #00ff00;
padding: 5px;
display: none;
}
#debug-toggle-btn {
background: rgba(0, 0, 0, 0.7);
color: #00ff00;
font-size: 14px;
border: none;
padding: 5px 10px;
cursor: pointer;
display: none;
}
/* ============================================================
MOVE MENU
============================================================ */
.button-row {
display: flex;
gap: 15px;
justify-content: center;
align-items: center;
}
.control-button {
flex: 0 0 auto;
border: none;
border-radius: 20px;
padding: 8px;
background-color: #007BFF;
cursor: pointer;
transition:
background-color 0.3s,
transform 0.2s;
}
.control-button img {
width: 30px;
height: 30px;
}
.control-button:active {
background-color: #0056b3;
transform: scale(0.95);
}
/* ============================================================
APP VERSION
Manual version number for development
============================================================ */
#app-version {
position: fixed;
right: 8px;
bottom: 6px;
z-index: 9998;
font-family: monospace;
font-size: 10px;
color: rgba(255, 255, 255, 0.55);
background: rgba(0, 0, 0, 0.25);
padding: 3px 6px;
border-radius: 5px;
pointer-events: none;
user-select: none;
}
/* ============================================================
MOBILE
============================================================ */
@media (max-width: 600px) {
.start-screen {
width: 78vw;
}
.start-screen-title {
font-size: 30px;
letter-spacing: 2.5px;
}
.start-screen-subtitle {
font-size: 14px;
}
.start-screen-button {
min-height: 52px;
padding: 14px 45px;
font-size: 16px;
}
.start-ar-button {
min-height: 58px;
font-size: 18px;
}
}
/* ============================================================
SMALL HEIGHT DEVICES
============================================================ */
@media (max-height: 650px) {
.start-screen {
gap: 7px;
width: min(310px, 76vw);
}
.start-screen-title {
font-size: 27px;
}
.start-screen-subtitle {
font-size: 13px;
}
.start-screen-divider {
margin: 4px 0 6px;
}
.start-screen-button {
min-height: 46px;
padding-top: 11px;
padding-bottom: 11px;
}
.start-ar-button {
min-height: 50px;
}
}
/* ============================================================
VERY SMALL SCREENS / LANDSCAPE
============================================================ */
@media (max-height: 500px) {
.start-screen {
transform:
translate(-50%, -50%)
scale(0.85);
transform-origin: center;
}
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment