diff --git a/examples/basemap-browser/README.md b/examples/basemap-browser/README.md
new file mode 100644
index 00000000000..d98dea30ed3
--- /dev/null
+++ b/examples/basemap-browser/README.md
@@ -0,0 +1,142 @@
+# Basemap Browser
+
+A TypeScript/React test application for quickly testing deck.gl with different basemap providers and configurations. Uses React function components throughout.
+
+## Features
+
+- **Multiple Basemap Providers**: Google Maps, Mapbox, MapLibre, and MapLibre Globe
+- **Framework Variants**: Pure JS and React implementations (configurations from get-started examples)
+- **Interleaved Mode Toggle**: Test both interleaved (shared GL context) and overlaid modes
+- **Live Metrics**: Monitor Device Pixel Ratio and canvas dimensions in real-time
+- **TypeScript**: Fully typed for better development experience
+- **Test Matrix Coverage**: Covers all combinations tested in resize/DPR bug fix
+
+## Architecture
+
+The basemap-browser uses TypeScript and React function components with a modular architecture:
+
+```
+src/
+├── types.ts # TypeScript type definitions
+├── constants.ts # Shared constants from get-started examples
+├── layers.ts # Layer configurations (from get-started examples)
+├── examples/
+│ └── index.ts # Example configurations matching get-started
+├── app.tsx # Main app (React function component)
+├── map-container.tsx # Map rendering (React function components)
+└── index.tsx # Entry point
+```
+
+### Key Design Decisions
+
+1. **TypeScript Throughout**: All files use TypeScript for type safety
+2. **React Function Components**: No class components, uses hooks for state management
+3. **Shared Layer Configs**: Layer definitions extracted from get-started examples into `layers.ts`
+4. **Type-Safe Examples**: Example configurations are fully typed via `types.ts`
+
+## Usage
+
+```bash
+# From the examples/basemap-browser directory
+yarn
+yarn start-local
+```
+
+Open http://localhost:8080 in your browser.
+
+## Testing Resize and DPR Changes
+
+1. **Window Resize Test**: Resize your browser window and verify that:
+ - Layers redraw correctly
+ - Canvas dimensions update
+ - No visual artifacts appear
+
+2. **Device Pixel Ratio Test**: Move the browser window between screens with different pixel ratios (e.g., from Retina to non-Retina display) and verify that:
+ - DPR value updates in the control panel
+ - Layers scale correctly without blur
+ - Canvas drawing buffer dimensions adjust
+
+3. **Interleaved vs Overlaid**: Toggle the "Interleaved Mode" checkbox and verify:
+ - Both modes work correctly
+ - Resize and DPR changes work in both modes
+ - Layers render correctly in both modes
+
+## Test Matrix
+
+The basemap browser covers these configurations:
+
+### Google Maps
+- ✅ Pure JS + Interleaved: true
+- ✅ Pure JS + Interleaved: false
+- ✅ React + Interleaved: true
+- ✅ React + Interleaved: false
+
+### Mapbox
+- ✅ Pure JS + Interleaved: true
+- ✅ Pure JS + Interleaved: false
+- ✅ React + Interleaved: true
+- ✅ React + Interleaved: false
+
+### MapLibre
+- ✅ Pure JS + Interleaved: true
+- ✅ Pure JS + Interleaved: false
+- ✅ React + Interleaved: true
+- ✅ React + Interleaved: false
+
+### MapLibre Globe
+- ✅ Pure JS + Interleaved: true
+- ✅ Pure JS + Interleaved: false
+- ✅ React + Interleaved: true
+- ✅ React + Interleaved: false
+
+## Google Maps Setup
+
+To test Google Maps examples, you need to set environment variables:
+
+```bash
+export GoogleMapsAPIKey="your-api-key"
+export GoogleMapsMapId="your-map-id"
+```
+
+Or add them to your `.env` file. The vite config will automatically inject them.
+
+## Adding New Examples
+
+To add a new basemap example:
+
+1. Add layer configuration to `src/layers.ts` if needed
+2. Add example config to `src/examples/index.ts`:
+
+```typescript
+'New Example': {
+ name: 'New Example',
+ mapType: 'maplibre', // or 'mapbox' or 'google-maps'
+ framework: 'react', // or 'pure-js'
+ mapStyle: MAPLIBRE_STYLE,
+ initialViewState: {
+ latitude: 51.47,
+ longitude: 0.45,
+ zoom: 4,
+ bearing: 0,
+ pitch: 30
+ },
+ getLayers: getAirportLayers
+}
+```
+
+## Relation to get-started Examples
+
+This browser extracts the core layer configurations from the get-started examples into reusable functions:
+
+- Layer configs in `src/layers.ts` match those in `examples/get-started/*/app.js`
+- Constants in `src/constants.ts` are shared across all examples
+- Example configurations in `src/examples/index.ts` use the same initial view states
+
+## Known Issues
+
+- Google Maps in overlaid mode (interleaved: false) may show a blank canvas when entering fullscreen - this is a pre-existing issue unrelated to the resize/DPR fix
+
+## Related PRs
+
+- [#9886](https://github.com/visgl/deck.gl/pull/9886) - Canvas resize bug fix (9.2 branch)
+- [#9887](https://github.com/visgl/deck.gl/pull/9887) - DPR fix for interleaved mode
diff --git a/examples/basemap-browser/index.html b/examples/basemap-browser/index.html
new file mode 100644
index 00000000000..88c2cc323d4
--- /dev/null
+++ b/examples/basemap-browser/index.html
@@ -0,0 +1,70 @@
+
+
+
+
+ deck.gl Basemap Browser
+
+
+
+
+
+
+
+
diff --git a/examples/basemap-browser/package.json b/examples/basemap-browser/package.json
new file mode 100644
index 00000000000..b82e3b517b8
--- /dev/null
+++ b/examples/basemap-browser/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "deckgl-examples-basemap-browser",
+ "version": "0.0.0",
+ "private": true,
+ "license": "MIT",
+ "type": "module",
+ "scripts": {
+ "start-local": "vite --config ../vite.config.local.mjs"
+ },
+ "dependencies": {
+ "@deck.gl/core": "*",
+ "@deck.gl/google-maps": "*",
+ "@deck.gl/layers": "*",
+ "@deck.gl/mapbox": "*",
+ "@vis.gl/react-google-maps": "^1.7.1",
+ "deck.gl": "*",
+ "mapbox-gl": "^3.8.0",
+ "maplibre-gl": "^5.0.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-map-gl": "^8.0.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
+ "typescript": "^4.6.0",
+ "vite": "^4.0.0"
+ }
+}
diff --git a/examples/basemap-browser/src/constants.ts b/examples/basemap-browser/src/constants.ts
new file mode 100644
index 00000000000..54d86678b68
--- /dev/null
+++ b/examples/basemap-browser/src/constants.ts
@@ -0,0 +1,12 @@
+// deck.gl
+// SPDX-License-Identifier: MIT
+// Copyright (c) vis.gl contributors
+
+// Shared constants from get-started examples
+export const AIR_PORTS =
+ 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_airports.geojson';
+
+export const MAPBOX_STYLE = 'mapbox://styles/mapbox/light-v9';
+export const MAPLIBRE_STYLE = 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json';
+
+export const LONDON_COORDINATES: [number, number] = [-0.4531566, 51.4709959];
diff --git a/examples/basemap-browser/src/control-panel.tsx b/examples/basemap-browser/src/control-panel.tsx
new file mode 100644
index 00000000000..5780c4908a8
--- /dev/null
+++ b/examples/basemap-browser/src/control-panel.tsx
@@ -0,0 +1,168 @@
+// deck.gl
+// SPDX-License-Identifier: MIT
+// Copyright (c) vis.gl contributors
+
+import React, {useState, useEffect, useCallback} from 'react';
+import BASEMAP_CATEGORIES from './examples';
+import type {BasemapExample} from './types';
+
+type CanvasSize = {
+ width: number;
+ height: number;
+ clientWidth?: number;
+ clientHeight?: number;
+ drawingBufferWidth?: number;
+ drawingBufferHeight?: number;
+};
+
+type ControlPanelProps = {
+ onExampleChange: (example: BasemapExample, interleaved: boolean) => void;
+};
+
+export default function ControlPanel({onExampleChange}: ControlPanelProps) {
+ const [selectedExample, setSelectedExample] = useState('MapLibre Pure JS');
+ const [interleaved, setInterleaved] = useState(true);
+ const [currentDPR, setCurrentDPR] = useState(window.devicePixelRatio);
+ const [canvasSize, setCanvasSize] = useState({width: 0, height: 0});
+
+ const getCurrentExample = useCallback((): BasemapExample | null => {
+ for (const category of Object.values(BASEMAP_CATEGORIES)) {
+ if (category[selectedExample]) {
+ return category[selectedExample];
+ }
+ }
+ return null;
+ }, [selectedExample]);
+
+ const updateCanvasInfo = useCallback(() => {
+ const canvas = document.querySelector('canvas');
+ if (canvas) {
+ // Get WebGL context to read drawing buffer size
+ const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
+
+ setCanvasSize({
+ width: canvas.width,
+ height: canvas.height,
+ clientWidth: canvas.clientWidth,
+ clientHeight: canvas.clientHeight,
+ drawingBufferWidth: gl?.drawingBufferWidth,
+ drawingBufferHeight: gl?.drawingBufferHeight
+ });
+ }
+ }, []);
+
+ useEffect(() => {
+ // Continuously poll for all changes (canvas, DPR, dimensions)
+ const interval = setInterval(() => {
+ const newDPR = window.devicePixelRatio;
+ if (newDPR !== currentDPR) {
+ setCurrentDPR(newDPR);
+ }
+ updateCanvasInfo();
+ }, 100);
+
+ return () => {
+ clearInterval(interval);
+ };
+ }, [currentDPR, updateCanvasInfo]);
+
+ // Load initial example
+ useEffect(() => {
+ const example = getCurrentExample();
+ if (example) {
+ onExampleChange(example, interleaved);
+ }
+ }, []); // Only on mount
+
+ // Handle example or interleaved changes
+ useEffect(() => {
+ const example = getCurrentExample();
+ if (example) {
+ onExampleChange(example, interleaved);
+ }
+ }, [selectedExample, interleaved, getCurrentExample, onExampleChange]);
+
+ const example = getCurrentExample();
+
+ return (
+
+
Basemap Browser
+
+
+
Select Example:
+
setSelectedExample(e.target.value)}>
+ {Object.keys(BASEMAP_CATEGORIES).map(categoryName => (
+
+ {Object.keys(BASEMAP_CATEGORIES[categoryName]).map(exampleName => (
+
+ {exampleName}
+
+ ))}
+
+ ))}
+
+
+
+
+
+ setInterleaved(!interleaved)}
+ />
+ Interleaved Mode
+
+
+
+
+
Current State
+
+ Framework: {example?.framework || 'N/A'}
+
+
+ Map Type: {example?.mapType || 'N/A'}
+
+
+ Interleaved: {interleaved ? 'true' : 'false'}
+
+
+ Device Pixel Ratio: {currentDPR.toFixed(2)}
+
+ {canvasSize.width > 0 && (
+ <>
+
+ Canvas Size: {canvasSize.width} x {canvasSize.height}
+
+
+ Canvas Client Size: {canvasSize.clientWidth} x {canvasSize.clientHeight}
+
+ {canvasSize.drawingBufferWidth && canvasSize.drawingBufferHeight && (
+
+ Drawing Buffer: {canvasSize.drawingBufferWidth} x{' '}
+ {canvasSize.drawingBufferHeight}
+
+ )}
+ >
+ )}
+
+
+
+
Testing Instructions
+
+
+ Test Window Resize: Resize browser window and verify layers redraw correctly.
+ Watch canvas and drawing buffer dimensions update.
+
+
+ Test DPR Change: Move browser window between screens with different pixel ratios
+ and verify layers scale correctly. Drawing buffer should adjust based on DPR.
+
+
+ Test Interleaved vs Overlaid: Toggle interleaved mode and verify both work
+ correctly.
+
+
+
+
+ );
+}
diff --git a/examples/basemap-browser/src/examples-pure-js/google-maps.ts b/examples/basemap-browser/src/examples-pure-js/google-maps.ts
new file mode 100644
index 00000000000..98abb412eb5
--- /dev/null
+++ b/examples/basemap-browser/src/examples-pure-js/google-maps.ts
@@ -0,0 +1,104 @@
+// deck.gl
+// SPDX-License-Identifier: MIT
+// Copyright (c) vis.gl contributors
+
+import {GoogleMapsOverlay} from '@deck.gl/google-maps';
+import type {Layer} from '@deck.gl/core';
+
+// Track if we're loading the API
+let loadingPromise: Promise | null = null;
+
+function loadGoogleMapsAPI(apiKey: string): Promise {
+ // If already loaded, return immediately
+ if ((window as any).google?.maps) {
+ return Promise.resolve((window as any).google.maps);
+ }
+
+ // If already loading, return existing promise
+ if (loadingPromise) {
+ return loadingPromise;
+ }
+
+ // Load the script manually
+ loadingPromise = new Promise((resolve, reject) => {
+ const script = document.createElement('script');
+ script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&v=weekly&libraries=maps`;
+ script.async = true;
+ script.defer = true;
+
+ script.onload = () => {
+ if ((window as any).google?.maps) {
+ resolve((window as any).google.maps);
+ } else {
+ reject(new Error('Google Maps API loaded but google.maps not found'));
+ }
+ };
+
+ script.onerror = () => {
+ loadingPromise = null;
+ reject(new Error('Failed to load Google Maps API script'));
+ };
+
+ document.head.appendChild(script);
+ });
+
+ return loadingPromise;
+}
+
+export function mount(
+ container: HTMLElement,
+ getLayers: (interleaved?: boolean) => Layer[],
+ initialViewState: any,
+ interleaved: boolean
+): () => void {
+ // eslint-disable-next-line no-process-env
+ const apiKey = process.env.GoogleMapsAPIKey;
+ // eslint-disable-next-line no-process-env
+ const mapId = process.env.GoogleMapsMapId;
+
+ if (!apiKey) {
+ container.innerHTML = `
+
+
Google Maps Configuration Required
+
Set GoogleMapsAPIKey and GoogleMapsMapId environment variables.
+
+ `;
+ return () => {};
+ }
+
+ let overlay: GoogleMapsOverlay | null = null;
+
+ // Load the API and create the map
+ loadGoogleMapsAPI(apiKey)
+ .then((googlemaps: any) => {
+ const map = new googlemaps.Map(container, {
+ center: {lat: initialViewState.latitude, lng: initialViewState.longitude},
+ zoom: initialViewState.zoom,
+ heading: initialViewState.bearing || 0,
+ tilt: initialViewState.pitch || 0,
+ mapId
+ });
+
+ overlay = new GoogleMapsOverlay({
+ interleaved,
+ layers: getLayers(interleaved)
+ });
+
+ overlay.setMap(map);
+ })
+ .catch((error: Error) => {
+ container.innerHTML = `
+
+
Google Maps Error
+
${error.message}
+
+ `;
+ });
+
+ return () => {
+ if (overlay) {
+ overlay.finalize();
+ overlay = null;
+ }
+ };
+}
diff --git a/examples/basemap-browser/src/examples-pure-js/index.ts b/examples/basemap-browser/src/examples-pure-js/index.ts
new file mode 100644
index 00000000000..0a62bc7cda4
--- /dev/null
+++ b/examples/basemap-browser/src/examples-pure-js/index.ts
@@ -0,0 +1,9 @@
+// deck.gl
+// SPDX-License-Identifier: MIT
+// Copyright (c) vis.gl contributors
+
+import * as googleMaps from './google-maps';
+import * as mapbox from './mapbox';
+import * as maplibre from './maplibre';
+
+export {googleMaps, mapbox, maplibre};
diff --git a/examples/basemap-browser/src/examples-pure-js/mapbox.ts b/examples/basemap-browser/src/examples-pure-js/mapbox.ts
new file mode 100644
index 00000000000..cc3232f3cc4
--- /dev/null
+++ b/examples/basemap-browser/src/examples-pure-js/mapbox.ts
@@ -0,0 +1,52 @@
+// deck.gl
+// SPDX-License-Identifier: MIT
+// Copyright (c) vis.gl contributors
+
+import {MapboxOverlay} from '@deck.gl/mapbox';
+import mapboxgl from 'mapbox-gl';
+import type {Layer} from '@deck.gl/core';
+
+export function mount(
+ container: HTMLElement,
+ getLayers: (interleaved?: boolean) => Layer[],
+ initialViewState: any,
+ mapStyle: string,
+ interleaved: boolean
+): () => void {
+ // eslint-disable-next-line no-process-env
+ const mapboxToken = process.env.MapboxAccessToken;
+
+ if (!mapboxToken) {
+ container.innerHTML = `
+
+
Mapbox Configuration Required
+
Set MapboxAccessToken environment variable.
+
+ `;
+ return () => {};
+ }
+
+ (mapboxgl as any).accessToken = mapboxToken;
+
+ const map = new mapboxgl.Map({
+ container,
+ style: mapStyle,
+ center: [initialViewState.longitude, initialViewState.latitude],
+ zoom: initialViewState.zoom,
+ bearing: initialViewState.bearing || 0,
+ pitch: initialViewState.pitch || 0
+ });
+
+ const deckOverlay = new MapboxOverlay({
+ interleaved,
+ layers: getLayers(interleaved)
+ });
+
+ map.addControl(deckOverlay as any);
+ map.addControl(new mapboxgl.NavigationControl());
+
+ return () => {
+ deckOverlay.finalize();
+ map.remove();
+ };
+}
diff --git a/examples/basemap-browser/src/examples-pure-js/maplibre.ts b/examples/basemap-browser/src/examples-pure-js/maplibre.ts
new file mode 100644
index 00000000000..ee9fbcdd03c
--- /dev/null
+++ b/examples/basemap-browser/src/examples-pure-js/maplibre.ts
@@ -0,0 +1,45 @@
+// deck.gl
+// SPDX-License-Identifier: MIT
+// Copyright (c) vis.gl contributors
+
+import {MapboxOverlay} from '@deck.gl/mapbox';
+import maplibregl from 'maplibre-gl';
+import type {Layer} from '@deck.gl/core';
+
+// eslint-disable-next-line max-params
+export function mount(
+ container: HTMLElement,
+ getLayers: (interleaved?: boolean) => Layer[],
+ initialViewState: any,
+ mapStyle: string,
+ interleaved: boolean,
+ globe?: boolean
+): () => void {
+ const map = new maplibregl.Map({
+ container,
+ style: mapStyle,
+ center: [initialViewState.longitude, initialViewState.latitude],
+ zoom: initialViewState.zoom,
+ bearing: initialViewState.bearing || 0,
+ pitch: initialViewState.pitch || 0
+ });
+
+ const deckOverlay = new MapboxOverlay({
+ interleaved,
+ layers: getLayers(interleaved)
+ });
+
+ map.on('load', () => {
+ // Set projection before adding overlay (critical for globe + interleaved mode)
+ if (globe) {
+ map.setProjection({type: 'globe'} as any);
+ }
+ map.addControl(deckOverlay as any);
+ map.addControl(new maplibregl.NavigationControl());
+ });
+
+ return () => {
+ deckOverlay.finalize();
+ map.remove();
+ };
+}
diff --git a/examples/basemap-browser/src/examples-react/google-maps-component.tsx b/examples/basemap-browser/src/examples-react/google-maps-component.tsx
new file mode 100644
index 00000000000..5e01c6d2e08
--- /dev/null
+++ b/examples/basemap-browser/src/examples-react/google-maps-component.tsx
@@ -0,0 +1,60 @@
+// deck.gl
+// SPDX-License-Identifier: MIT
+// Copyright (c) vis.gl contributors
+
+import React, {useEffect, useMemo} from 'react';
+import {APIProvider, Map as GoogleMap, useMap} from '@vis.gl/react-google-maps';
+import {GoogleMapsOverlay} from '@deck.gl/google-maps';
+import type {BasemapExample} from '../types';
+
+// Google Maps DeckGL Overlay - from get-started/react/google-maps
+function GoogleMapsDeckOverlay({
+ getLayers,
+ interleaved
+}: {
+ getLayers: (interleaved?: boolean) => any[];
+ interleaved: boolean;
+}) {
+ const map = useMap();
+ const overlay = useMemo(() => new GoogleMapsOverlay({interleaved}), [interleaved]);
+
+ useEffect(() => {
+ if (map) {
+ overlay.setMap(map);
+ }
+ return () => overlay.setMap(null);
+ }, [map, overlay]);
+
+ overlay.setProps({layers: getLayers(interleaved)});
+ return null;
+}
+
+type GoogleMapsComponentProps = {
+ example: BasemapExample;
+ interleaved: boolean;
+};
+
+export default function GoogleMapsComponent({example, interleaved}: GoogleMapsComponentProps) {
+ // eslint-disable-next-line no-process-env
+ const apiKey = process.env.GoogleMapsAPIKey!;
+ // eslint-disable-next-line no-process-env
+ const mapId = process.env.GoogleMapsMapId || 'DEMO_MAP_ID';
+ const {initialViewState, getLayers} = example;
+
+ return (
+
+ );
+}
diff --git a/examples/basemap-browser/src/examples-react/index.ts b/examples/basemap-browser/src/examples-react/index.ts
new file mode 100644
index 00000000000..df817b37a9f
--- /dev/null
+++ b/examples/basemap-browser/src/examples-react/index.ts
@@ -0,0 +1,21 @@
+// deck.gl
+// SPDX-License-Identifier: MIT
+// Copyright (c) vis.gl contributors
+
+import type {MapType} from '../types';
+import GoogleMapsComponent from './google-maps-component';
+import MapboxComponent from './mapbox-component';
+import MapLibreComponent from './maplibre-component';
+
+export function getComponent(mapType: MapType) {
+ switch (mapType) {
+ case 'google-maps':
+ return GoogleMapsComponent;
+ case 'mapbox':
+ return MapboxComponent;
+ case 'maplibre':
+ return MapLibreComponent;
+ default:
+ throw new Error(`Unknown map type: ${mapType}`);
+ }
+}
diff --git a/examples/basemap-browser/src/examples-react/mapbox-component.tsx b/examples/basemap-browser/src/examples-react/mapbox-component.tsx
new file mode 100644
index 00000000000..1859d155804
--- /dev/null
+++ b/examples/basemap-browser/src/examples-react/mapbox-component.tsx
@@ -0,0 +1,44 @@
+// deck.gl
+// SPDX-License-Identifier: MIT
+// Copyright (c) vis.gl contributors
+
+import React from 'react';
+import {Map as MapboxMap, useControl as useMapboxControl} from 'react-map-gl/mapbox';
+import {MapboxOverlay} from '@deck.gl/mapbox';
+import type {BasemapExample} from '../types';
+import type {MapboxOverlayProps} from '@deck.gl/mapbox';
+
+import 'mapbox-gl/dist/mapbox-gl.css';
+
+// Set your Mapbox token here or via environment variable
+// eslint-disable-next-line no-process-env
+const MAPBOX_TOKEN = process.env.MapboxAccessToken;
+
+// Mapbox Overlay wrapper
+function MapboxOverlayWrapper(props: MapboxOverlayProps & {interleaved: boolean}) {
+ const overlay = useMapboxControl(() => new MapboxOverlay(props));
+ overlay.setProps(props);
+ return null;
+}
+
+type MapboxComponentProps = {
+ example: BasemapExample;
+ interleaved: boolean;
+};
+
+export default function MapboxComponent({example, interleaved}: MapboxComponentProps) {
+ const {mapStyle, initialViewState, getLayers} = example;
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/examples/basemap-browser/src/examples-react/maplibre-component.tsx b/examples/basemap-browser/src/examples-react/maplibre-component.tsx
new file mode 100644
index 00000000000..714809d339a
--- /dev/null
+++ b/examples/basemap-browser/src/examples-react/maplibre-component.tsx
@@ -0,0 +1,49 @@
+// deck.gl
+// SPDX-License-Identifier: MIT
+// Copyright (c) vis.gl contributors
+
+import React from 'react';
+import {Map as MapLibreMap, useControl as useMapLibreControl} from 'react-map-gl/maplibre';
+import {MapboxOverlay} from '@deck.gl/mapbox';
+import type {BasemapExample} from '../types';
+import type {MapboxOverlayProps} from '@deck.gl/mapbox';
+
+import 'maplibre-gl/dist/maplibre-gl.css';
+
+// MapLibre Overlay wrapper
+function MapLibreOverlay(props: MapboxOverlayProps & {interleaved: boolean}) {
+ const overlay = useMapLibreControl(() => new MapboxOverlay(props));
+ overlay.setProps(props);
+ return null;
+}
+
+type MapLibreComponentProps = {
+ example: BasemapExample;
+ interleaved: boolean;
+};
+
+export default function MapLibreComponent({example, interleaved}: MapLibreComponentProps) {
+ const {mapStyle, initialViewState, getLayers, globe} = example;
+ const [overlayReady, setOverlayReady] = React.useState(!globe);
+
+ return (
+
+ {
+ if (globe) {
+ // Set projection before rendering overlay (critical for globe + interleaved mode)
+ e.target.setProjection({type: 'globe'});
+ setOverlayReady(true);
+ }
+ }}
+ >
+ {overlayReady && (
+
+ )}
+
+
+ );
+}
diff --git a/examples/basemap-browser/src/examples/index.ts b/examples/basemap-browser/src/examples/index.ts
new file mode 100644
index 00000000000..9617cd79f4a
--- /dev/null
+++ b/examples/basemap-browser/src/examples/index.ts
@@ -0,0 +1,131 @@
+// deck.gl
+// SPDX-License-Identifier: MIT
+// Copyright (c) vis.gl contributors
+
+import type {ExampleCategories} from '../types';
+import {getAirportLayers, getAirportLayersWithGlobe} from '../layers';
+import {MAPBOX_STYLE, MAPLIBRE_STYLE} from '../constants';
+
+// Configuration matching get-started examples
+const EXAMPLES: ExampleCategories = {
+ 'Google Maps': {
+ 'Google Maps Pure JS': {
+ name: 'Google Maps Pure JS',
+ mapType: 'google-maps',
+ framework: 'pure-js',
+ initialViewState: {
+ latitude: 51.47,
+ longitude: 0.45,
+ zoom: 4,
+ bearing: 0,
+ pitch: 30
+ },
+ getLayers: interleaved => getAirportLayers(interleaved, 'google-maps')
+ },
+ 'Google Maps React': {
+ name: 'Google Maps React',
+ mapType: 'google-maps',
+ framework: 'react',
+ initialViewState: {
+ latitude: 51.47,
+ longitude: 0.45,
+ zoom: 4,
+ bearing: 0,
+ pitch: 30
+ },
+ getLayers: interleaved => getAirportLayers(interleaved, 'google-maps')
+ }
+ },
+ Mapbox: {
+ 'Mapbox Pure JS': {
+ name: 'Mapbox Pure JS',
+ mapType: 'mapbox',
+ framework: 'pure-js',
+ mapStyle: MAPBOX_STYLE,
+ initialViewState: {
+ latitude: 51.47,
+ longitude: 0.45,
+ zoom: 4,
+ bearing: 0,
+ pitch: 30
+ },
+ getLayers: interleaved => getAirportLayers(interleaved, 'mapbox')
+ },
+ 'Mapbox React': {
+ name: 'Mapbox React',
+ mapType: 'mapbox',
+ framework: 'react',
+ mapStyle: MAPBOX_STYLE,
+ initialViewState: {
+ latitude: 51.47,
+ longitude: 0.45,
+ zoom: 4,
+ bearing: 0,
+ pitch: 30
+ },
+ getLayers: interleaved => getAirportLayers(interleaved, 'mapbox')
+ }
+ },
+ MapLibre: {
+ 'MapLibre Pure JS': {
+ name: 'MapLibre Pure JS',
+ mapType: 'maplibre',
+ framework: 'pure-js',
+ mapStyle: MAPLIBRE_STYLE,
+ initialViewState: {
+ latitude: 51.47,
+ longitude: 0.45,
+ zoom: 4,
+ bearing: 0,
+ pitch: 30
+ },
+ getLayers: interleaved => getAirportLayers(interleaved, 'maplibre')
+ },
+ 'MapLibre React': {
+ name: 'MapLibre React',
+ mapType: 'maplibre',
+ framework: 'react',
+ mapStyle: MAPLIBRE_STYLE,
+ initialViewState: {
+ latitude: 51.47,
+ longitude: 0.45,
+ zoom: 4,
+ bearing: 0,
+ pitch: 30
+ },
+ getLayers: interleaved => getAirportLayers(interleaved, 'maplibre')
+ },
+ 'MapLibre Globe Pure JS': {
+ name: 'MapLibre Globe Pure JS',
+ mapType: 'maplibre',
+ framework: 'pure-js',
+ mapStyle: MAPLIBRE_STYLE,
+ globe: true,
+ initialViewState: {
+ latitude: 51.47,
+ longitude: 0.45,
+ zoom: 0,
+ bearing: 0,
+ pitch: 0
+ },
+ getLayers: interleaved => getAirportLayersWithGlobe(interleaved, 'maplibre')
+ },
+ 'MapLibre Globe React': {
+ name: 'MapLibre Globe React',
+ mapType: 'maplibre',
+ framework: 'react',
+ mapStyle: MAPLIBRE_STYLE,
+ globe: true,
+ initialViewState: {
+ latitude: 51.47,
+ longitude: 0.45,
+ zoom: 0,
+ bearing: 0,
+ pitch: 0
+ },
+ getLayers: interleaved => getAirportLayersWithGlobe(interleaved, 'maplibre')
+ }
+ }
+};
+
+export default EXAMPLES;
diff --git a/examples/basemap-browser/src/index.tsx b/examples/basemap-browser/src/index.tsx
new file mode 100644
index 00000000000..891dfc038a2
--- /dev/null
+++ b/examples/basemap-browser/src/index.tsx
@@ -0,0 +1,89 @@
+// deck.gl
+// SPDX-License-Identifier: MIT
+// Copyright (c) vis.gl contributors
+
+import React from 'react';
+import {createRoot, type Root} from 'react-dom/client';
+import ControlPanel from './control-panel';
+import type {BasemapExample} from './types';
+import * as pureJSExamples from './examples-pure-js';
+import * as reactExamples from './examples-react';
+
+// Two separate React roots
+const controlsDiv = document.getElementById('controls')!;
+const mapDiv = document.getElementById('map')!;
+
+const controlRoot = createRoot(controlsDiv);
+
+// Track current map state
+let currentMapCleanup: (() => void) | null = null;
+let currentMapRoot: Root | null = null;
+
+// Load an example into the map div
+function loadExample(example: BasemapExample, interleaved: boolean) {
+ // Defer cleanup to avoid synchronous unmount during React render
+ setTimeout(() => {
+ // Clean up previous
+ if (currentMapCleanup) {
+ currentMapCleanup();
+ currentMapCleanup = null;
+ }
+ if (currentMapRoot) {
+ currentMapRoot.unmount();
+ currentMapRoot = null;
+ }
+
+ // Clear the map div
+ mapDiv.innerHTML = '';
+
+ // Mount new example
+ mountExample(example, interleaved);
+ }, 0);
+}
+
+function mountExample(example: BasemapExample, interleaved: boolean) {
+ // Mount new example
+ if (example.framework === 'pure-js') {
+ // Pure JS mounts directly, no React involved
+ switch (example.mapType) {
+ case 'google-maps':
+ currentMapCleanup = pureJSExamples.googleMaps.mount(
+ mapDiv,
+ example.getLayers,
+ example.initialViewState,
+ interleaved
+ );
+ break;
+ case 'mapbox':
+ currentMapCleanup = pureJSExamples.mapbox.mount(
+ mapDiv,
+ example.getLayers,
+ example.initialViewState,
+ example.mapStyle!,
+ interleaved
+ );
+ break;
+ case 'maplibre':
+ currentMapCleanup = pureJSExamples.maplibre.mount(
+ mapDiv,
+ example.getLayers,
+ example.initialViewState,
+ example.mapStyle!,
+ interleaved,
+ example.globe
+ );
+ break;
+ default:
+ // Unknown map type
+ break;
+ }
+ } else {
+ // React mounts to separate root
+ currentMapRoot = createRoot(mapDiv);
+ const Component = reactExamples.getComponent(example.mapType);
+ currentMapRoot.render( );
+ }
+}
+
+// Render control panel (always React)
+controlRoot.render( );
diff --git a/examples/basemap-browser/src/layers.ts b/examples/basemap-browser/src/layers.ts
new file mode 100644
index 00000000000..b01ef5ae0f8
--- /dev/null
+++ b/examples/basemap-browser/src/layers.ts
@@ -0,0 +1,84 @@
+// deck.gl
+// SPDX-License-Identifier: MIT
+// Copyright (c) vis.gl contributors
+
+import {GeoJsonLayer, ArcLayer} from '@deck.gl/layers';
+import type {Layer} from '@deck.gl/core';
+import {AIR_PORTS, LONDON_COORDINATES} from './constants';
+import type {MapType} from './types';
+
+// Layer configurations from get-started examples
+export function getAirportLayers(interleaved?: boolean, mapType?: MapType): Layer[] {
+ // In interleaved mode, render the layers under map labels
+ // Mapbox uses slot, MapLibre uses beforeId
+ const interleavedProps = interleaved
+ ? mapType === 'mapbox'
+ ? {slot: 'middle'}
+ : {beforeId: 'watername_ocean'}
+ : {};
+
+ return [
+ new GeoJsonLayer({
+ id: 'airports',
+ data: AIR_PORTS,
+ filled: true,
+ pointRadiusMinPixels: 2,
+ pointRadiusScale: 2000,
+ getPointRadius: (f: any) => 11 - f.properties.scalerank,
+ getFillColor: [200, 0, 80, 180],
+ pickable: true,
+ autoHighlight: true,
+ ...interleavedProps
+ }),
+ new ArcLayer({
+ id: 'arcs',
+ data: AIR_PORTS,
+ dataTransform: (d: any) => d.features.filter((f: any) => f.properties.scalerank < 4),
+ getSourcePosition: () => LONDON_COORDINATES,
+ getTargetPosition: (f: any) => f.geometry.coordinates,
+ getSourceColor: [0, 128, 200],
+ getTargetColor: [200, 0, 80],
+ getWidth: 1,
+ ...interleavedProps
+ })
+ ];
+}
+
+export function getAirportLayersWithGlobe(interleaved?: boolean, mapType?: MapType): Layer[] {
+ // In interleaved mode, render the layers under map labels
+ // Mapbox uses slot, MapLibre uses beforeId
+ const interleavedProps = interleaved
+ ? mapType === 'mapbox'
+ ? {slot: 'middle'}
+ : {beforeId: 'watername_ocean'}
+ : {};
+
+ return [
+ new GeoJsonLayer({
+ id: 'airports',
+ data: AIR_PORTS,
+ filled: true,
+ pointRadiusMinPixels: 2,
+ pointRadiusScale: 2000,
+ getPointRadius: (f: any) => 11 - f.properties.scalerank,
+ getFillColor: [200, 0, 80, 180],
+ pickable: true,
+ autoHighlight: true,
+ ...interleavedProps
+ }),
+ new ArcLayer({
+ id: 'arcs',
+ data: AIR_PORTS,
+ parameters: {
+ cullMode: 'none'
+ },
+ dataTransform: (d: any) => d.features.filter((f: any) => f.properties.scalerank < 4),
+ getSourcePosition: () => LONDON_COORDINATES,
+ getTargetPosition: (f: any) => f.geometry.coordinates,
+ getSourceColor: [0, 128, 200],
+ getTargetColor: [200, 0, 80],
+ getWidth: 1,
+ ...interleavedProps
+ })
+ ];
+}
diff --git a/examples/basemap-browser/src/types.ts b/examples/basemap-browser/src/types.ts
new file mode 100644
index 00000000000..2b0f391120b
--- /dev/null
+++ b/examples/basemap-browser/src/types.ts
@@ -0,0 +1,35 @@
+// deck.gl
+// SPDX-License-Identifier: MIT
+// Copyright (c) vis.gl contributors
+
+import type {Layer} from '@deck.gl/core';
+
+export type MapType = 'google-maps' | 'mapbox' | 'maplibre';
+
+export type Framework = 'pure-js' | 'react';
+
+export type InitialViewState = {
+ latitude: number;
+ longitude: number;
+ zoom: number;
+ bearing?: number;
+ pitch?: number;
+};
+
+export type BasemapExample = {
+ name: string;
+ mapType: MapType;
+ framework: Framework;
+ mapStyle?: string;
+ initialViewState: InitialViewState;
+ getLayers: (interleaved?: boolean) => Layer[];
+ globe?: boolean;
+};
+
+export type ExampleCategory = {
+ [key: string]: BasemapExample;
+};
+
+export type ExampleCategories = {
+ [category: string]: ExampleCategory;
+};
diff --git a/examples/basemap-browser/tsconfig.json b/examples/basemap-browser/tsconfig.json
new file mode 100644
index 00000000000..26288273867
--- /dev/null
+++ b/examples/basemap-browser/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "jsx": "react",
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+
+ /* Linting */
+ "strict": false,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "noFallthroughCasesInSwitch": true,
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true
+ },
+ "include": ["src"]
+}