+
+
Loading the map…
+
+
+
+ Open Wine Map ↗
+ ⤢
+
+
+
+
+
+
+
diff --git a/mcp/view/view.js b/mcp/view/view.js
new file mode 100644
index 0000000..d0c27dc
--- /dev/null
+++ b/mcp/view/view.js
@@ -0,0 +1,337 @@
+// The MCP Apps view of `show_on_map`: a MapLibre map of the requested
+// appellations, drawn from the site's own vector tiles over the CARTO basemap.
+// Runs in the host's sandboxed iframe; everything it loads is declared in the
+// resource's `_meta.ui.csp` (server.js). MapLibre 4's UMD build starts its
+// worker from a blob: URL, which the MCP Apps hosts allow.
+//
+// Interaction: the title opens a legend list — one colour per appellation (up
+// to PALETTE.length of them), hover a row or a polygon to highlight the
+// other, click a row to zoom to it (and point "Open ↗" at its page), the
+// colour square toggles it, "Show all" resets.
+import maplibregl from 'maplibre-gl';
+import { Protocol } from 'pmtiles';
+import { App } from '@modelcontextprotocol/ext-apps';
+
+const CONFIG = JSON.parse(document.getElementById('owm-config').textContent);
+const KIND_COLOUR = { IGP: '#6e7546', other: '#934050' };
+// Tableau 10: distinct, readable on both basemaps.
+const PALETTE = ['#4e79a7', '#f28e2b', '#e15759', '#76b7b2', '#59a14f', '#edc948', '#b07aa1', '#ff9da7', '#9c755f', '#bab0ac'];
+const OVERVIEW_MAX_ZOOM = 11.9; // the site's LOD: footprints below, parcels from z11
+const DETAIL_MIN_ZOOM = 11;
+const FILLS = ['fill-overview', 'fill-detail'];
+const LINES = ['line-overview', 'line-detail'];
+
+const $ = id => document.getElementById(id);
+const status = msg => { $('status').textContent = msg || ''; $('status').hidden = !msg; };
+
+let map = null;
+let theme = 'light';
+let data = null; // the tool result: { appellations, bbox, map_url }
+let colours = {}; // slug → fill colour
+const hidden = new Set(); // slugs toggled off in the list
+let pinned = null; // slug the user zoomed to (row or polygon click)
+let hovered = null; // slug under the pointer (row or polygon)
+
+function cartoTiles(style) {
+ const auth = CONFIG.cartoKey ? `?key=${encodeURIComponent(CONFIG.cartoKey)}` : '';
+ return ['a', 'b', 'c'].map(s => `https://${s}.basemaps.cartocdn.com/${style}/{z}/{x}/{y}.png${auth}`);
+}
+
+const lineColour = () => (theme === 'dark' ? '#f2e6d0' : '#2a1014');
+
+function applyTheme(t) {
+ theme = t === 'dark' ? 'dark' : 'light';
+ document.documentElement.classList.toggle('dark', theme === 'dark');
+ if (!map || !map.getLayer('basemap-light')) return;
+ map.setLayoutProperty('basemap-light', 'visibility', theme === 'dark' ? 'none' : 'visible');
+ map.setLayoutProperty('basemap-dark', 'visibility', theme === 'dark' ? 'visible' : 'none');
+ for (const id of LINES) if (map.getLayer(id)) map.setPaintProperty(id, 'line-color', lineColour());
+}
+
+function setFeatureFlag(slug, key, on) {
+ if (!map || !slug) return;
+ for (const source of ['overview', 'detail']) {
+ try { map.setFeatureState({ source, sourceLayer: 'appellations', id: slug }, { [key]: on }); } catch { /* not loaded yet */ }
+ }
+}
+
+function buildMap() {
+ // The site's versioned paths (the files are browser-cached for 30 days, so
+ // a deploy must change the URL); bare paths until the server has them.
+ const tiles = (data && data.tiles) || { overview: '/map-data/appellations-overview.pmtiles', detail: '/map-data/appellations.pmtiles' };
+ const protocol = new Protocol();
+ maplibregl.addProtocol('pmtiles', protocol.tile);
+ const attribution = '©