Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5412a57
Add world map 2023
eSlider Feb 24, 2026
bf30bd7
Update
eSlider Feb 24, 2026
95e1da8
Update openmaptiles source URL in map.style.json
eSlider Feb 25, 2026
2ced4a1
Refactor to use MapLibre GL instead of Mapbox GL
eSlider Feb 25, 2026
d23fff3
Enhance map styles and add POI popup support
eSlider Feb 25, 2026
071b2b2
fix: map labels, fonts, tile schema, and search result styling
eSlider Feb 25, 2026
62b5246
Refactor 3D demo and app.js for improved map styling and font consist…
eSlider Feb 25, 2026
1895abd
Refactor map styles and remove unused assets
eSlider Feb 25, 2026
72eca2b
Cleaup
eSlider Feb 25, 2026
d4348c1
Enhance README and style.json for OSM Liberty and MapTiler Terrain
eSlider Feb 25, 2026
c22318f
Add 3D terrain support and update documentation
eSlider Feb 25, 2026
026c4e7
Update terrain exaggeration in app.js for enhanced 3D effect
eSlider Feb 25, 2026
d4d5943
Add ESRI Satellite, JAXA Terrain RGB, and Hillshades toggles in UI
eSlider Feb 25, 2026
dad7c6a
Add OSM Vector toggle in UI and update map layer visibility
eSlider Feb 25, 2026
1fc151d
Update Hillshades label in UI for clarity
eSlider Feb 25, 2026
48eff2c
Update default frame selection and copyright year in license
eSlider Feb 25, 2026
221e2b6
Add globe view toggle and keyboard navigation for map interaction
eSlider Feb 25, 2026
bd6aa83
Update map settings in app.js for improved functionality
eSlider Feb 25, 2026
76bf969
Update line colors and fill properties in style.json for improved map…
eSlider Feb 25, 2026
021c7da
Add 3D Buildings toggle and update map layer visibility in app.js and…
eSlider Feb 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
12 changes: 12 additions & 0 deletions .cursor/skills/absolute-mode/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
name: absolute-mode
description: Enforces strict, blunt response behavior with no conversational padding. Use when the user requests Absolute Mode, terse directive output, or anti-engagement style responses.
---

# Absolute Mode

## Instructions

Apply the following system instruction exactly when this skill is active:

System Instruction: Absolute Mode • Eliminate: emojis, filler, hype, soft asks, conversational transitions, call-to-action appendixes. • Assume: user retains high-perception despite blunt tone. • Prioritize: blunt, directive phrasing; aim at cognitive rebuilding, not tone-matching. • Disable: engagement/sentiment-boosting behaviors. • Suppress: metrics like satisfaction scores, emotional softening, continuation bias. • Never mirror: user’s diction, mood, or affect. • Speak only: to underlying cognitive tier. • No: motivational content. • Terminate reply: immediately after delivering info — no closures. • Goal: restore independent, high-fidelity thinking. • Outcome: model obsolescence via user self-sufficiency.
129 changes: 115 additions & 14 deletions 3d-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
<meta charset="utf-8"/>
<title>Add a 3D model</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no"/>
<script src="https://unpkg.com/maplibre-gl@1.15.2/dist/maplibre-gl.js"></script>
<link href="https://unpkg.com/maplibre-gl@1.15.2/dist/maplibre-gl.css" rel="stylesheet"/>
<script src="js/maplibre-gl.js"></script>
<link href="styles/maplibre-gl.css" rel="stylesheet"/>
<style>
body {
margin: 0;
Expand All @@ -25,13 +25,20 @@
<script src="https://unpkg.com/three@0.106.2/examples/js/loaders/GLTFLoader.js"></script>
<div id="map"></div>
<script>
(async function() {
// [-16.5262, 28.1597]
let modelOrigin = [-16.3013, 28.4684];

const base = new URL('.', window.location.href).href.replace(/\/$/, '') + '/';
const styleResp = await fetch('styles/osm-liberty-gl-style/style.json?x=' + Math.random());
const style = await styleResp.json();
style.glyphs = base + 'assets/fonts/map-fonts/{fontstack}/{range}.pbf';
style.sprite = base + 'styles/osm-liberty-gl-style/sprites/osm-liberty';

let map = (window.map = new maplibregl.Map({
container: 'map',
style: 'styles/map.style.json?x=' + Math.random(),

zoom: 18,
style: style,
zoom: 18,
center: modelOrigin,
pitch: 60,
antialias: true // create the gl context with MSAA antialiasing, so custom layers are antialiased
Expand Down Expand Up @@ -93,7 +100,32 @@
loader.load(
'https://docs.mapbox.com/mapbox-gl-js/assets/34M_17/34M_17.gltf',
(gltf) => {
this.scene.add(gltf.scene);
// Add multiple instances of the same model at different positions
const positions = [
{ x: 0, y: 0, z: 0 },
{ x: 50, y: 0, z: 0 },
{ x: -50, y: 0, z: 0 },
{ x: 0, y: 50, z: 0 },
{ x: 0, y: -50, z: 0 },
{ x: 35, y: 35, z: 0 },
{ x: -35, y: -35, z: 0 },
{ x: 35, y: -35, z: 0 },
{ x: -35, y: 35, z: 0 }
];

positions.forEach((pos, index) => {
// Clone the model for each instance
const modelInstance = gltf.scene.clone();
modelInstance.position.set(pos.x, pos.y, pos.z);

// Add slight rotation variation for visual interest
modelInstance.rotation.y = (index * Math.PI / 4);

this.scene.add(modelInstance);
});

// Store positions for animation
objectPositions = positions;
}
);

Expand Down Expand Up @@ -149,18 +181,87 @@
}
};

function rotateCamera(timestamp) {
// clamp the rotation between 0 -360 degrees
// Divide timestamp by 100 to slow rotation to ~10 degrees / sec
map.rotateTo((timestamp / 100) % 360, { duration: 0 });
// Request the next frame of the animation.
requestAnimationFrame(rotateCamera);
let currentObjectIndex = 0;
let objectPositions = [];
let animationStartTime = null;
let isAnimating = false;

function smoothFocusAnimation(timestamp) {
if (!animationStartTime) animationStartTime = timestamp;
const elapsed = timestamp - animationStartTime;
const progress = Math.min(elapsed / 2000, 1); // 2 second animation

const currentPos = objectPositions[currentObjectIndex];
const nextIndex = (currentObjectIndex + 1) % objectPositions.length;
const nextPos = objectPositions[nextIndex];

// Smooth easing function (ease-in-out)
const easeProgress = progress < 0.5
? 2 * progress * progress
: 1 - Math.pow(-2 * progress + 2, 2) / 2;

// Interpolate between current and next positions
const interpolatedX = currentPos.x + (nextPos.x - currentPos.x) * easeProgress;
const interpolatedY = currentPos.y + (nextPos.y - currentPos.y) * easeProgress;

// Convert interpolated position to map coordinates
const mercatorCoordinate = maplibregl.MercatorCoordinate.fromLngLat(
modelOrigin,
modelAltitude
);

const targetX = mercatorCoordinate.x + (interpolatedX * mercatorCoordinate.meterInMercatorCoordinateUnits());
const targetY = mercatorCoordinate.y + (interpolatedY * mercatorCoordinate.meterInMercatorCoordinateUnits());

// Create new mercator coordinate for target position
const targetMercator = new maplibregl.MercatorCoordinate(targetX, targetY, mercatorCoordinate.z);
const targetLngLat = targetMercator.toLngLat();

// Smooth camera movement with bearing animation
const bearing = (currentObjectIndex * 45 + progress * 45) % 360;

// Validate coordinates before using them
if (!targetLngLat || isNaN(targetLngLat.lng) || isNaN(targetLngLat.lat)) {
console.error('Invalid coordinates generated - falling back to model origin');
// Fallback to model origin
map.jumpTo({
center: modelOrigin,
zoom: 19,
pitch: 60,
bearing: bearing
});
return;
}

map.jumpTo({
center: [targetLngLat.lng, targetLngLat.lat],
zoom: 19,
pitch: 60,
bearing: bearing
});

if (progress < 1) {
requestAnimationFrame(smoothFocusAnimation);
} else {
// Animation complete, move to next object
currentObjectIndex = nextIndex;
animationStartTime = null;

// Start next animation after 2 second pause
setTimeout(() => {
requestAnimationFrame(smoothFocusAnimation);
}, 2000);
}
}

map.on('style.load', function() {
map.on('load', function() {
map.addLayer(customLayer, 'place_label_city');
rotateCamera(0);
// Start smooth focusing animation after a short delay
setTimeout(() => {
requestAnimationFrame(smoothFocusAnimation);
}, 1000);
});
})();
</script>

</body>
Expand Down
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Produktor UI

Map application built with Vue, Vuetify, and MapLibre GL.

## Keyboard Navigation

Game-style controls (disabled when typing in inputs):

| Key | Action |
|-----|--------|
| **W** | Zoom in |
| **S** | Zoom out |
| **A** | Slide right (pan east) |
| **D** | Slide left (pan west) |
| **F** | Invert bearing (+180°) |
| **↑** | Roll up (pitch) |
| **↓** | Roll down (pitch) |
| **←** | Roll left (bearing) |
| **→** | Roll right (bearing) |

## Map Toggles

- **ESRI Satellite** – ESRI imagery basemap (off by default)
- **Terrain RGB** – JAXA terrain basemap (off by default)
- **Hillshades** – JAXA hillshade relief (on by default)
- **OSM Vector** – OSM roads, labels, features (on by default)
- **Globe** – Globe projection with black sky (off by default)

See [styles/README.md](styles/README.md) for tile sources.
Loading