Zum Inhalt

Delivery Tracking

Mittel SmartMaps GL Marker Popup flyTo Animation

Diese Anwendungsfall-Demo zeigt eine Live-Sendungsverfolgung, wie sie in modernen E-Commerce- und Logistik-Apps zu finden ist. Sie zeigt den aktuellen Paketstatus mit einem visuellen Fortschrittsbalken, ein voraussichtliches Zustellfenster und eine Live-Fahrzeugposition, die sich auf einer Route auf der Karte in Richtung Zustellziel bewegt.

Funktionen & APIs

  • SmartMaps GL Map -- Rendert eine interaktive Karte mit smartmapsgl.Map und dem Stil essential
  • Custom Markers -- Verwendet smartmapsgl.Marker mit benutzerdefinierten HTML-Elementen sowohl für das Lieferfahrzeug als auch für das Zielsymbol (Haus)
  • Animierte Fahrzeugbewegung -- Simuliert den Live-Zustellfortschritt, indem die Position des Fahrzeug-Markers alle 5 Sekunden entlang einer Reihe von Wegpunkten aktualisiert wird
  • Fly-To-Animation -- Schwenkt und zoomt die Karte sanft, um dem Fahrzeug mit map.flyTo() zu folgen
  • Pop-up bei Ankunft -- Zeigt eine smartmapsgl.Popup-Meldung an, wenn das Fahrzeug das Ziel erreicht
  • Zustellfortschrittsbalken -- Eine visuelle Fortschrittsanzeige, die synchron mit der Fahrzeugbewegung fortschreitet und dabei die Bestellphasen durchläuft (Bestellt, Versendet, In Zustellung, Zugestellt)
  • Maki-Icon-Integration -- Lädt SVG-Symbole vom SmartMaps-CDN (cdn.smartmaps.cloud/packages/maki/icons/) für die Marker-Gestaltung

Wie es funktioniert

Die Demo simuliert den Zustellfortschritt, indem sie eine vordefinierte Liste von GPS-Wegpunkten durchläuft. Alle 5 Sekunden springt der Fahrzeug-Marker zum nächsten Wegpunkt, die Kamera schwenkt mit flyTo sanft mit, und der Fortschrittsbalken schreitet fort. Wenn der letzte Wegpunkt erreicht ist, wechselt die Benutzeroberfläche in den Zustand „zugestellt" mit einer Bestätigung per Pop-up.

Code

// =============================================================
// DELIVERY TRACKING DEMO
// Simulates a real-time delivery experience with a vehicle marker
// that moves through a series of waypoints toward a destination.
// The progress bar and status text update in sync.
//
// APIs used:
//   - SmartMaps GL JS (Map, Marker, Popup, flyTo)
// =============================================================

// --- CONFIGURATION & CONSTANTS ---
const TRACKING_ZOOM = 15;           // Zoom level when following the vehicle
const FLY_TO_SPEED = 0.8;           // flyTo animation speed
const UPDATE_INTERVAL_MS = 5000;    // Milliseconds between location updates
const PROGRESS_INCREMENT = 5;       // Percentage points per update step
const INITIAL_PROGRESS = 75;        // Starting progress (3 of 4 stages complete)

// --- MAP INITIALIZATION ---
const map = new smartmapsgl.Map({
  apiKey:
    "[INSERT API-KEY]",
  container: "map",
  style: "essential",
  center: [8.438, 49.018], // Karlsruhe city center
  zoom: 13,
});

// --- ROUTE DATA ---
// Simulated waypoints the delivery vehicle will pass through
const locations = [
  [8.42982, 49.01394],
  [8.4354, 49.01523],
  [8.44042, 49.02009],
  [8.43828, 49.02101],
  [8.43933, 49.02166], // Final destination
];

// --- HELPER FUNCTIONS ---

/** Update all UI elements to show the delivery-complete state. */
function showDeliveryComplete(progressBar) {
  document.getElementById("update-banner").innerText =
    "Zustellung erfolgt!";
  document.getElementById("delivery-status-heading").innerText =
    "Erfolgreich zugestellt";
  progressBar.style.width = "100%";

  // Activate the final progress dot
  const deliveredDot = document.getElementById("status-dot-delivered");
  deliveredDot.classList.remove("bg-gray-300");
  deliveredDot.classList.add("bg-blue-600");

  // Show arrival popup at the destination
  new smartmapsgl.Popup({ closeButton: false, offset: 35 })
    .setText("Sie sind da!")
    .setLngLat(locations[locations.length - 1])
    .addTo(map);
}

/** Move the vehicle marker and camera to the given location. */
function moveVehicleTo(vehicleMarker, location) {
  vehicleMarker.setLngLat(location);
  map.flyTo({
    center: location,
    zoom: TRACKING_ZOOM,
    speed: FLY_TO_SPEED,
  });
}

// --- MAP SETUP ---
let vehicleMarker;

map.on("load", () => {
  // Create destination marker (red border, home icon)
  const destinationEl = document.createElement("div");
  destinationEl.className = "destination-marker";
  const destinationIcon = document.createElement("img");
  destinationIcon.src =
    "https://cdn.smartmaps.cloud/packages/maki/icons/home.svg";
  destinationEl.appendChild(destinationIcon);

  new smartmapsgl.Marker({ element: destinationEl })
    .setLngLat(locations[locations.length - 1])
    .addTo(map);

  // Create vehicle marker (green border, bus icon)
  const vehicleEl = document.createElement("div");
  vehicleEl.className = "vehicle-marker";
  const vehicleIcon = document.createElement("img");
  vehicleIcon.src =
    "https://cdn.smartmaps.cloud/packages/maki/icons/bus.svg";
  vehicleEl.appendChild(vehicleIcon);

  vehicleMarker = new smartmapsgl.Marker({ element: vehicleEl })
    .setLngLat(locations[0])
    .addTo(map);

  // --- LOCATION UPDATE LOOP ---
  const progressBar = document.getElementById("progress-bar-fill");
  let locationIndex = 0;
  let progressPercent = INITIAL_PROGRESS;
  progressBar.style.width = `${progressPercent}%`;

  const intervalId = setInterval(() => {
    locationIndex++;

    // Check if the vehicle has reached the destination
    if (locationIndex >= locations.length) {
      clearInterval(intervalId);
      showDeliveryComplete(progressBar);
      return;
    }

    // Move vehicle and update progress bar
    moveVehicleTo(vehicleMarker, locations[locationIndex]);
    progressPercent += PROGRESS_INCREMENT;
    progressBar.style.width = `${progressPercent}%`;
  }, UPDATE_INTERVAL_MS);
});
<body class="bg-gray-100 h-screen flex justify-center">
  <div class="w-full max-w-md h-full bg-white flex flex-col shadow-lg">
    <header class="p-4 border-b border-gray-200">
      <div class="flex items-center justify-between text-gray-700">
        <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
        </svg>
        <div class="flex-grow mx-4 relative">
          <input type="text" placeholder="Suchen oder eine Frage stellen"
            class="w-full bg-gray-100 border-none rounded-lg py-2 px-4 text-sm focus:ring-2 focus:ring-blue-500" />
        </div>
        <div class="flex items-center space-x-4">
          <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
              d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
              d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
          </svg>
          <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
              d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z" />
          </svg>
        </div>
      </div>
      <div class="mt-4 flex justify-between items-center">
        <div>
          <h1 class="text-xl font-bold text-gray-900">Zustellung heute</h1>
          <p class="text-gray-600 font-medium">
            16h - 19h
            <span
              class="inline-block bg-gray-200 text-gray-600 text-xs font-bold rounded-full h-4 w-4 text-center leading-4 ml-1">i</span>
          </p>
        </div>
        <a href="#" class="text-sm font-medium text-blue-600">Alle Bestellungen anzeigen</a>
      </div>
    </header>

    <section class="p-4 border-b border-gray-200">
      <div class="flex items-center space-x-4">
        <img src="/assets/examples/img/ph-shoe.svg" alt="Product Image 1"
          class="w-16 h-16 rounded-lg object-cover bg-gray-200" />
        <img src="/assets/examples/img/ph-pants.svg" alt="Product Image 2"
          class="w-16 h-16 rounded-lg object-cover bg-gray-200" />
        <img src="/assets/examples/img/ph-pants.svg" alt="Product Image 3"
          class="w-16 h-16 rounded-lg object-cover bg-gray-200" />
      </div>
    </section>

    <main class="flex-grow relative">
      <div id="map"></div>
      <!-- Update banner with solid background for visibility -->
      <div id="update-banner"
        class="absolute top-4 left-1/2 -translate-x-1/2 text-sm text-gray-700 bg-white px-3 py-1 rounded-full shadow">
        Wird alle 5s aktualisiert
      </div>
    </main>

    <section class="p-4 bg-white border-t border-gray-200">
      <h2 id="delivery-status-heading" class="text-lg font-bold text-center mb-4">
        In Zustellung
      </h2>
      <div class="relative w-full">
        <div class="h-2 bg-gray-200 rounded-full"></div>
        <div id="progress-bar-fill"
          class="absolute top-0 left-0 h-2 bg-blue-600 rounded-full transition-all duration-500 ease-linear"
          style="width: 75%"></div>
        <div class="absolute flex justify-between w-full -top-1">
          <span class="h-4 w-4 bg-blue-600 border-2 border-white rounded-full"></span>
          <span class="h-4 w-4 bg-blue-600 border-2 border-white rounded-full"></span>
          <span class="h-4 w-4 bg-blue-600 border-2 border-white rounded-full"></span>
          <span id="status-dot-delivered" class="h-4 w-4 bg-gray-300 border-2 border-white rounded-full"></span>
        </div>
      </div>
      <div class="flex justify-between text-xs mt-2 text-gray-500">
        <span>Bestellt</span>
        <span>Versendet</span>
        <span>In Zustellung</span>
        <span>Zugestellt</span>
      </div>
    </section>

    <nav class="flex justify-around items-center p-2 border-t border-gray-200 text-gray-600">
      <a href="#" class="text-center hover:text-blue-600">
        <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mx-auto" fill="none" viewBox="0 0 24 24"
          stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
            d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
        </svg>
        <span class="text-xs">Home</span>
      </a>
      <a href="#" class="text-center hover:text-blue-600">
        <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mx-auto" fill="none" viewBox="0 0 24 24"
          stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
            d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
        </svg>
        <span class="text-xs">Profile</span>
      </a>
      <a href="#" class="text-center hover:text-blue-600">
        <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mx-auto" fill="none" viewBox="0 0 24 24"
          stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
            d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4z" />
        </svg>
        <span class="text-xs">Cart</span>
      </a>
      <a href="#" class="text-center hover:text-blue-600">
        <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mx-auto" fill="none" viewBox="0 0 24 24"
          stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
        </svg>
        <span class="text-xs">Menu</span>
      </a>
    </nav>
  </div>
</body>
@font-face {
  font-family: 'Inter';
  src: url('../fonts/Inter-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Inter';
  src: url('../fonts/Inter-Medium.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Inter';
  src: url('../fonts/Inter-Bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Material Icons';
  font-style: normal;
  font-weight: 400;
  src: url(../fonts/MaterialIcons-Regular.woff2) format('woff2');
}

body {
  font-family: "Inter", sans-serif;
  overscroll-behavior: none;
}

/* Ensure map fills its container */
#map {
  width: 100%;
  height: 100%;
}

/* Base style for the circular markers */
.destination-marker,
.vehicle-marker {
  width: 40px;
  height: 40px;
  background-color: white;
  border-radius: 50%;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

/* Specific border colors to distinguish markers */
.destination-marker {
  border: 3px solid #ef4444;
  /* red-500 */
}

.vehicle-marker {
  border: 3px solid #10b981;
  /* emerald-500 */
}

/* Styling for the SVG icons within the markers */
.destination-marker img,
.vehicle-marker img {
  width: 22px;
  height: 22px;
}

/* Custom popup styling */
.maplibregl-popup-content {
  background-color: white;
  border-radius: 9999px;
  /* rounded-full */
  padding: 8px 16px;
  font-weight: 500;
  color: #1f2937;
  /* gray-800 */
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.maplibregl-popup-tip {
  display: none;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Delivery Tracking</title>

  <script src="https://cdn.smartmaps.cloud/packages/smartmaps/smartmaps-gl/v2/umd/smartmaps-gl.min.js"></script>

  <link rel="stylesheet" href="css/tailwind.min.css" />
  <link rel="stylesheet" href="css/smartmaps-demo-styles.css" />

  <style>
    @font-face {
      font-family: 'Inter';
      src: url('../fonts/Inter-Regular.woff2') format('woff2');
      font-weight: 400;
      font-style: normal;
      font-display: swap;
    }

    @font-face {
      font-family: 'Inter';
      src: url('../fonts/Inter-Medium.woff2') format('woff2');
      font-weight: 500;
      font-style: normal;
      font-display: swap;
    }

    @font-face {
      font-family: 'Inter';
      src: url('../fonts/Inter-Bold.woff2') format('woff2');
      font-weight: 700;
      font-style: normal;
      font-display: swap;
    }

    @font-face {
      font-family: 'Material Icons';
      font-style: normal;
      font-weight: 400;
      src: url(../fonts/MaterialIcons-Regular.woff2) format('woff2');
    }

    body {
      font-family: "Inter", sans-serif;
      overscroll-behavior: none;
    }

    /* Ensure map fills its container */
    #map {
      width: 100%;
      height: 100%;
    }

    /* Base style for the circular markers */
    .destination-marker,
    .vehicle-marker {
      width: 40px;
      height: 40px;
      background-color: white;
      border-radius: 50%;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
    }

    /* Specific border colors to distinguish markers */
    .destination-marker {
      border: 3px solid #ef4444;
      /* red-500 */
    }

    .vehicle-marker {
      border: 3px solid #10b981;
      /* emerald-500 */
    }

    /* Styling for the SVG icons within the markers */
    .destination-marker img,
    .vehicle-marker img {
      width: 22px;
      height: 22px;
    }

    /* Custom popup styling */
    .maplibregl-popup-content {
      background-color: white;
      border-radius: 9999px;
      /* rounded-full */
      padding: 8px 16px;
      font-weight: 500;
      color: #1f2937;
      /* gray-800 */
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    }

    .maplibregl-popup-tip {
      display: none;
    }
  </style>
</head>

<body class="bg-gray-100 h-screen flex justify-center">
  <div class="w-full max-w-md h-full bg-white flex flex-col shadow-lg">
    <header class="p-4 border-b border-gray-200">
      <div class="flex items-center justify-between text-gray-700">
        <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
        </svg>
        <div class="flex-grow mx-4 relative">
          <input type="text" placeholder="Suchen oder eine Frage stellen"
            class="w-full bg-gray-100 border-none rounded-lg py-2 px-4 text-sm focus:ring-2 focus:ring-blue-500" />
        </div>
        <div class="flex items-center space-x-4">
          <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
              d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
              d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
          </svg>
          <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
              d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z" />
          </svg>
        </div>
      </div>
      <div class="mt-4 flex justify-between items-center">
        <div>
          <h1 class="text-xl font-bold text-gray-900">Zustellung heute</h1>
          <p class="text-gray-600 font-medium">
            16h - 19h
            <span
              class="inline-block bg-gray-200 text-gray-600 text-xs font-bold rounded-full h-4 w-4 text-center leading-4 ml-1">i</span>
          </p>
        </div>
        <a href="#" class="text-sm font-medium text-blue-600">Alle Bestellungen anzeigen</a>
      </div>
    </header>

    <section class="p-4 border-b border-gray-200">
      <div class="flex items-center space-x-4">
        <img src="/assets/examples/img/ph-shoe.svg" alt="Product Image 1"
          class="w-16 h-16 rounded-lg object-cover bg-gray-200" />
        <img src="/assets/examples/img/ph-pants.svg" alt="Product Image 2"
          class="w-16 h-16 rounded-lg object-cover bg-gray-200" />
        <img src="/assets/examples/img/ph-pants.svg" alt="Product Image 3"
          class="w-16 h-16 rounded-lg object-cover bg-gray-200" />
      </div>
    </section>

    <main class="flex-grow relative">
      <div id="map"></div>
      <!-- Update banner with solid background for visibility -->
      <div id="update-banner"
        class="absolute top-4 left-1/2 -translate-x-1/2 text-sm text-gray-700 bg-white px-3 py-1 rounded-full shadow">
        Wird alle 5s aktualisiert
      </div>
    </main>

    <section class="p-4 bg-white border-t border-gray-200">
      <h2 id="delivery-status-heading" class="text-lg font-bold text-center mb-4">
        In Zustellung
      </h2>
      <div class="relative w-full">
        <div class="h-2 bg-gray-200 rounded-full"></div>
        <div id="progress-bar-fill"
          class="absolute top-0 left-0 h-2 bg-blue-600 rounded-full transition-all duration-500 ease-linear"
          style="width: 75%"></div>
        <div class="absolute flex justify-between w-full -top-1">
          <span class="h-4 w-4 bg-blue-600 border-2 border-white rounded-full"></span>
          <span class="h-4 w-4 bg-blue-600 border-2 border-white rounded-full"></span>
          <span class="h-4 w-4 bg-blue-600 border-2 border-white rounded-full"></span>
          <span id="status-dot-delivered" class="h-4 w-4 bg-gray-300 border-2 border-white rounded-full"></span>
        </div>
      </div>
      <div class="flex justify-between text-xs mt-2 text-gray-500">
        <span>Bestellt</span>
        <span>Versendet</span>
        <span>In Zustellung</span>
        <span>Zugestellt</span>
      </div>
    </section>

    <nav class="flex justify-around items-center p-2 border-t border-gray-200 text-gray-600">
      <a href="#" class="text-center hover:text-blue-600">
        <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mx-auto" fill="none" viewBox="0 0 24 24"
          stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
            d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
        </svg>
        <span class="text-xs">Home</span>
      </a>
      <a href="#" class="text-center hover:text-blue-600">
        <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mx-auto" fill="none" viewBox="0 0 24 24"
          stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
            d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
        </svg>
        <span class="text-xs">Profile</span>
      </a>
      <a href="#" class="text-center hover:text-blue-600">
        <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mx-auto" fill="none" viewBox="0 0 24 24"
          stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
            d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4z" />
        </svg>
        <span class="text-xs">Cart</span>
      </a>
      <a href="#" class="text-center hover:text-blue-600">
        <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mx-auto" fill="none" viewBox="0 0 24 24"
          stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
        </svg>
        <span class="text-xs">Menu</span>
      </a>
    </nav>
  </div>

  <script>
    // =============================================================
    // DELIVERY TRACKING DEMO
    // Simulates a real-time delivery experience with a vehicle marker
    // that moves through a series of waypoints toward a destination.
    // The progress bar and status text update in sync.
    //
    // APIs used:
    //   - SmartMaps GL JS (Map, Marker, Popup, flyTo)
    // =============================================================

    // --- CONFIGURATION & CONSTANTS ---
    const TRACKING_ZOOM = 15;           // Zoom level when following the vehicle
    const FLY_TO_SPEED = 0.8;           // flyTo animation speed
    const UPDATE_INTERVAL_MS = 5000;    // Milliseconds between location updates
    const PROGRESS_INCREMENT = 5;       // Percentage points per update step
    const INITIAL_PROGRESS = 75;        // Starting progress (3 of 4 stages complete)

    // --- MAP INITIALIZATION ---
    const map = new smartmapsgl.Map({
      apiKey:
        "[INSERT API-KEY]",
      container: "map",
      style: "essential",
      center: [8.438, 49.018], // Karlsruhe city center
      zoom: 13,
    });

    // --- ROUTE DATA ---
    // Simulated waypoints the delivery vehicle will pass through
    const locations = [
      [8.42982, 49.01394],
      [8.4354, 49.01523],
      [8.44042, 49.02009],
      [8.43828, 49.02101],
      [8.43933, 49.02166], // Final destination
    ];

    // --- HELPER FUNCTIONS ---

    /** Update all UI elements to show the delivery-complete state. */
    function showDeliveryComplete(progressBar) {
      document.getElementById("update-banner").innerText =
        "Zustellung erfolgt!";
      document.getElementById("delivery-status-heading").innerText =
        "Erfolgreich zugestellt";
      progressBar.style.width = "100%";

      // Activate the final progress dot
      const deliveredDot = document.getElementById("status-dot-delivered");
      deliveredDot.classList.remove("bg-gray-300");
      deliveredDot.classList.add("bg-blue-600");

      // Show arrival popup at the destination
      new smartmapsgl.Popup({ closeButton: false, offset: 35 })
        .setText("Sie sind da!")
        .setLngLat(locations[locations.length - 1])
        .addTo(map);
    }

    /** Move the vehicle marker and camera to the given location. */
    function moveVehicleTo(vehicleMarker, location) {
      vehicleMarker.setLngLat(location);
      map.flyTo({
        center: location,
        zoom: TRACKING_ZOOM,
        speed: FLY_TO_SPEED,
      });
    }

    // --- MAP SETUP ---
    let vehicleMarker;

    map.on("load", () => {
      // Create destination marker (red border, home icon)
      const destinationEl = document.createElement("div");
      destinationEl.className = "destination-marker";
      const destinationIcon = document.createElement("img");
      destinationIcon.src =
        "https://cdn.smartmaps.cloud/packages/maki/icons/home.svg";
      destinationEl.appendChild(destinationIcon);

      new smartmapsgl.Marker({ element: destinationEl })
        .setLngLat(locations[locations.length - 1])
        .addTo(map);

      // Create vehicle marker (green border, bus icon)
      const vehicleEl = document.createElement("div");
      vehicleEl.className = "vehicle-marker";
      const vehicleIcon = document.createElement("img");
      vehicleIcon.src =
        "https://cdn.smartmaps.cloud/packages/maki/icons/bus.svg";
      vehicleEl.appendChild(vehicleIcon);

      vehicleMarker = new smartmapsgl.Marker({ element: vehicleEl })
        .setLngLat(locations[0])
        .addTo(map);

      // --- LOCATION UPDATE LOOP ---
      const progressBar = document.getElementById("progress-bar-fill");
      let locationIndex = 0;
      let progressPercent = INITIAL_PROGRESS;
      progressBar.style.width = `${progressPercent}%`;

      const intervalId = setInterval(() => {
        locationIndex++;

        // Check if the vehicle has reached the destination
        if (locationIndex >= locations.length) {
          clearInterval(intervalId);
          showDeliveryComplete(progressBar);
          return;
        }

        // Move vehicle and update progress bar
        moveVehicleTo(vehicleMarker, locations[locationIndex]);
        progressPercent += PROGRESS_INCREMENT;
        progressBar.style.width = `${progressPercent}%`;
      }, UPDATE_INTERVAL_MS);
    });
  </script>
</body>

</html>