Toggle menu
611
110
57
5.1K
Landrace.Wiki - The Landrace Cannabis Wiki
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

MediaWiki:Common.js: Difference between revisions

MediaWiki interface page
No edit summary
No edit summary
Line 1: Line 1:
// <nowiki>
// <nowiki>
/* Infobox map expand toggle */
/* Infobox map expand toggle */
$(function() {
$(function(){
  var $overlay = $('<div class="lw-map-overlay"></div>').appendTo('body');
var $overlay=$('<div class="lw-map-overlay"></div>').appendTo('body');
 
 
  $('.lw-infobox__map-expand').on('click', function() {
$('.lw-infobox__map-expand').on('click',function(){
    var $mapContainer = $(this).closest('.lw-infobox__map-container');
var $mapContainer=$(this).closest('.lw-infobox__map-container');
    var $map = $mapContainer.find('.lw-infobox__map');
var $map=$mapContainer.find('.lw-infobox__map');
   
 
    if ($overlay.hasClass('lw-map-overlay--active')) {
if($overlay.hasClass('lw-map-overlay--active')){
      $map.appendTo($mapContainer);
$map.appendTo($mapContainer);
      $overlay.removeClass('lw-map-overlay--active');
$overlay.removeClass('lw-map-overlay--active');
      $('body').css('overflow', '');
$('body').css('overflow','');
    } else {
}else{
      $map.appendTo($overlay);
$map.appendTo($overlay);
      $overlay.addClass('lw-map-overlay--active');
$overlay.addClass('lw-map-overlay--active');
      $('body').css('overflow', 'hidden');
$('body').css('overflow','hidden');
    }
}
   
 
    setTimeout(function() {
setTimeout(function(){
      $map.each(function() {
$map.each(function(){
        if (this._leaflet_map) {
if(this._leaflet_map){
          this._leaflet_map.invalidateSize();
this._leaflet_map.invalidateSize();
        }
}
      });
});
    }, 100);
},100);
  });
});
 
 
  $(document).on('keydown', function(e) {
$(document).on('keydown',function(e){
    if (e.key === 'Escape' && $overlay.hasClass('lw-map-overlay--active')) {
if(e.key==='Escape' && $overlay.hasClass('lw-map-overlay--active')){
      $('.lw-infobox__map-expand').trigger('click');
$('.lw-infobox__map-expand').trigger('click');
    }
}
  });
});
 
 
  $overlay.on('click', function(e) {
$overlay.on('click',function(e){
    if (e.target === this) {
if(e.target===this){
      $('.lw-infobox__map-expand').trigger('click');
$('.lw-infobox__map-expand').trigger('click');
    }
}
  });
});
});
});


/* Landrace.wiki – Leaflet map w/ SMW CSV export, Clustering, Labels & Hulls */
/* Landrace.wiki – Leaflet map w/ SMW CSV export, Clustering, Labels & Hulls */
(function () {
(function(){
  var LVER = '1.9.4';
var LVER='1.9.4';
  var CDN = 'https://unpkg.com/leaflet@' + LVER + '/dist/';
var CDN='https://unpkg.com/leaflet@'+LVER+'/dist/';
  var CLUSTER_CDN = 'https://unpkg.com/leaflet.markercluster@1.4.1/dist/';
var CLUSTER_CDN='https://unpkg.com/leaflet.markercluster@1.4.1/dist/';
  var TURF_CDN = 'https://unpkg.com/@turf/turf@6/turf.min.js';
var TURF_CDN='https://unpkg.com/@turf/turf@6/turf.min.js';


  var BRAND = {
var BRAND={
    primary: '#2d6a4f',
primary:'#2d6a4f',
    primaryLight: '#40916c',
primaryLight:'#40916c',
    accent: '#74c69d',
accent:'#74c69d',
    dark: '#1b4332',
dark:'#1b4332',
    neutral: '#495057',
neutral:'#495057',
    light: '#f8f9fa',
light:'#f8f9fa',
    white: '#ffffff'
white:'#ffffff'
  };
};


  var STATUS_COLORS = {
var STATUS_COLORS={
    'Critical': '#c92a2a',
'Critical':'#c92a2a',
    'High': '#d9480f',
'High':'#d9480f',
    'Medium': '#e67700',
'Medium':'#e67700',
    'Low': '#2b8a3e'
'Low':'#2b8a3e'
  };
};


  var STATUS_PRIORITY = ['Critical', 'High', 'Medium', 'Low'];
var STATUS_PRIORITY=['Critical','High','Medium','Low'];


  function addCSS(href, id) {
/* Event maps (News Items / eradications) */
    if (id && document.getElementById(id)) return;
var EVENT_COLORS={
    var l = document.createElement('link');
'Enforcement':'#c92a2a',
    l.rel = 'stylesheet';
'Trafficking':'#d9480f',
    l.href = href;
'Policy':'#1c7ed6',
    if (id) l.id = id;
'Research':BRAND.primary,
    document.head.appendChild(l);
'Fieldwork':BRAND.primaryLight,
  }
'Community':BRAND.accent,
'Report':BRAND.neutral
};


  function addJS(src, cb, id) {
var EVENT_PRIORITY=['Enforcement','Trafficking','Policy','Research','Fieldwork','Community','Report'];
    var existing = id ? document.getElementById(id) : document.querySelector('script[src="' + src + '"]');
    if (existing) {
      if (existing.dataset.loaded === '1') return cb();
      existing.addEventListener('load', cb);
      return;
    }
    var s = document.createElement('script');
    s.src = src;
    if (id) s.id = id;
    s.onload = function() {
      s.dataset.loaded = '1';
      cb();
    };
    s.onerror = function () { console.error('[lw-map] Script failed to load:', src); };
    document.head.appendChild(s);
  }


  function getWorstStatus(markers) {
function addCSS(href,id){
    var worst = 'Low';
if(id && document.getElementById(id)) return;
    var worstIndex = STATUS_PRIORITY.indexOf(worst);
var l=document.createElement('link');
   
l.rel='stylesheet';
    markers.forEach(function(marker) {
l.href=href;
      var status = marker.options.status || 'Low';
if(id) l.id=id;
      var index = STATUS_PRIORITY.indexOf(status);
document.head.appendChild(l);
      if (index !== -1 && index < worstIndex) {
}
        worst = status;
 
        worstIndex = index;
function addJS(src,cb,id){
      }
var existing=id ? document.getElementById(id) : document.querySelector('script[src="'+src+'"]');
    });
if(existing){
   
if(existing.dataset.loaded==='1') return cb();
    return worst;
existing.addEventListener('load',cb);
  }
return;
}
var s=document.createElement('script');
s.src=src;
if(id) s.id=id;
s.onload=function(){
s.dataset.loaded='1';
cb();
};
s.onerror=function(){console.error('[lw-map] Script failed to load:',src);};
document.head.appendChild(s);
}


  function getWorstStatusFromFeatures(features) {
function getWorstStatus(markers){
    var worst = 'Low';
var worst='Low';
    var worstIndex = STATUS_PRIORITY.indexOf(worst);
var worstIndex=STATUS_PRIORITY.indexOf(worst);
   
markers.forEach(function(marker){
    features.forEach(function(f) {
var status=marker.options.status || 'Low';
      var status = f.properties.status || 'Low';
var index=STATUS_PRIORITY.indexOf(status);
      var index = STATUS_PRIORITY.indexOf(status);
if(index!==-1 && index<worstIndex){
      if (index !== -1 && index < worstIndex) {
worst=status;
        worst = status;
worstIndex=index;
        worstIndex = index;
}
      }
});
    });
return worst;
   
}
    return worst;
  }


  function injectStyles() {
function getWorstStatusFromFeatures(features){
    if (document.getElementById('lw-map-styles')) return;
var worst='Low';
    var style = document.createElement('style');
var worstIndex=STATUS_PRIORITY.indexOf(worst);
    style.id = 'lw-map-styles';
features.forEach(function(f){
    style.textContent = [
var status=f.properties.status || 'Low';
      '.lw-popup .leaflet-popup-content-wrapper {',
var index=STATUS_PRIORITY.indexOf(status);
      '  background: ' + BRAND.white + ';',
if(index!==-1 && index<worstIndex){
      '  border-radius: 12px;',
worst=status;
      '  box-shadow: 0 4px 20px rgba(0,0,0,0.12), 0 2px 6px rgba(0,0,0,0.08);',
worstIndex=index;
      '  padding: 0;',
}
      '  overflow: hidden;',
});
      '}',
return worst;
      '.lw-popup .leaflet-popup-content {',
}
      '  margin: 0;',
      '  min-width: 220px;',
      '  max-width: 280px;',
      '}',
      '.lw-popup .leaflet-popup-tip {',
      '  background: ' + BRAND.white + ';',
      '  box-shadow: 0 2px 6px rgba(0,0,0,0.1);',
      '}',
      '.lw-popup .leaflet-popup-close-button {',
      '  color: ' + BRAND.neutral + ' !important;',
      '  font-size: 20px !important;',
      '  padding: 8px 10px !important;',
      '  right: 4px !important;',
      '  top: 4px !important;',
      '  transition: color 0.2s ease;',
      '}',
      '.lw-popup .leaflet-popup-close-button:hover {',
      '  color: ' + BRAND.dark + ' !important;',
      '}',
      '.lw-popup-inner {',
      '  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;',
      '}',
      '.lw-popup-header {',
      '  background: linear-gradient(135deg, ' + BRAND.primary + ' 0%, ' + BRAND.dark + ' 100%);',
      '  padding: 16px 18px;',
      '  color: ' + BRAND.white + ';',
      '}',
      '.lw-popup-id {',
      '  font-size: 10px;',
      '  font-weight: 600;',
      '  letter-spacing: 0.5px;',
      '  text-transform: uppercase;',
      '  opacity: 0.85;',
      '  margin-bottom: 4px;',
      '}',
      '.lw-popup-name {',
      '  font-size: 15px;',
      '  font-weight: 600;',
      '  line-height: 1.3;',
      '}',
      '.lw-popup-body {',
      '  padding: 14px 18px;',
      '}',
      '.lw-popup-meta {',
      '  display: flex;',
      '  flex-direction: column;',
      '  align-items: flex-start;',
      '  gap: 6px;',
      '  margin-bottom: 8px;',
      '}',
      '.lw-popup-label {',
      '  font-size: 11px;',
      '  color: ' + BRAND.neutral + ';',
      '  font-weight: 500;',
      '  text-transform: uppercase;',
      '  letter-spacing: 0.3px;',
      '}',
      '.lw-popup-status {',
      '  display: inline-flex;',
      '  align-items: center;',
      '  padding: 4px 10px;',
      '  border-radius: 20px;',
      '  font-size: 11px;',
      '  font-weight: 600;',
      '  background: ' + BRAND.light + ';',
      '  color: ' + BRAND.neutral + ';',
      '}',
      '.lw-popup-status::before {',
      ' content: "";',
      '  width: 6px;',
      '  height: 6px;',
      '  border-radius: 50%;',
      '  margin-right: 6px;',
      '  background: currentColor;',
      '}',
      '.lw-popup-status--critical { color: #c92a2a; background: #fff5f5; }',
      '.lw-popup-status--high { color: #d9480f; background: #fff4e6; }',
      '.lw-popup-status--medium { color: #e67700; background: #fff9db; }',
      '.lw-popup-status--low { color: #2b8a3e; background: #ebfbee; }',
      '.lw-popup-link {',
      '  display: block;',
      '  text-align: center;',
      '  padding: 10px 18px;',
      '  background: ' + BRAND.light + ';',
      '  color: ' + BRAND.primary + ';',
      '  text-decoration: none;',
      '  font-size: 13px;',
      '  font-weight: 600;',
      '  transition: background 0.2s ease, color 0.2s ease;',
      '  border-top: 1px solid #e9ecef;',
      '}',
      '.lw-popup-link:hover {',
      '  background: ' + BRAND.primary + ';',
      '  color: ' + BRAND.white + ';',
      '}',
      '.lw-map-tooltip {',
      '  background: ' + BRAND.dark + ' !important;',
      '  border: none !important;',
      '  border-radius: 6px !important;',
      '  color: ' + BRAND.white + ' !important;',
      '  font-size: 12px !important;',
      '  font-weight: 500 !important;',
      '  padding: 6px 10px !important;',
      '  box-shadow: 0 2px 8px rgba(0,0,0,0.2) !important;',
      '}',
      '.lw-map-tooltip::before {',
      '  border-top-color: ' + BRAND.dark + ' !important;',
      '}',
      '.lw-layer-toggle {',
      '  display: flex;',
      '  background: ' + BRAND.white + ';',
      '  border-radius: 8px;',
      '  box-shadow: 0 2px 8px rgba(0,0,0,0.12);',
      '  overflow: hidden;',
      '}',
      '.lw-layer-btn {',
      '  display: flex;',
      '  align-items: center;',
      '  justify-content: center;',
      '  width: 36px;',
      '  height: 36px;',
      '  border: none;',
      '  background: ' + BRAND.white + ';',
      '  color: ' + BRAND.neutral + ';',
      '  cursor: pointer;',
      '  transition: all 0.2s ease;',
      '}',
      '.lw-layer-btn:hover {',
      '  background: ' + BRAND.light + ';',
      '  color: ' + BRAND.primary + ';',
      '}',
      '.lw-layer-btn--active {',
      '  background: ' + BRAND.primary + ' !important;',
      '  color: ' + BRAND.white + ' !important;',
      '}',
      '.lw-layer-btn:not(:last-child) {',
      '  border-right: 1px solid #e9ecef;',
      '}',
      '.lw-layer-btn svg {',
      '  pointer-events: none;',
      '}',
      '/* Cluster styles */',
      '.lw-cluster-icon {',
      '  background: transparent !important;',
      '}',
      '.lw-cluster {',
      '  width: 40px;',
      '  height: 40px;',
      '  border-radius: 50%;',
      '  color: #fff;',
      '  font-weight: 700;',
      '  font-size: 14px;',
      '  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;',
      '  display: flex;',
      '  align-items: center;',
      '  justify-content: center;',
      '  box-shadow: 0 3px 10px rgba(0,0,0,0.25);',
      '  border: 3px solid rgba(255,255,255,0.9);',
      '  transition: transform 0.2s ease;',
      '}',
      '.lw-cluster:hover {',
      '  transform: scale(1.1);',
      '}',
      '.lw-cluster--small {',
      '  width: 36px;',
      '  height: 36px;',
      '  font-size: 13px;',
      '}',
      '.lw-cluster--large {',
      '  width: 48px;',
      '  height: 48px;',
      '  font-size: 15px;',
      '}',
      '.marker-cluster {',
      '  background: transparent !important;',
      '}',
      '.marker-cluster div {',
      '  background: transparent !important;',
      '}',
      '/* Hull polygons */',
      '.lw-hull-tooltip {',
      '  background: ' + BRAND.white + ' !important;',
      '  border: 1px solid ' + BRAND.primary + ' !important;',
      '  border-radius: 6px !important;',
      '  color: ' + BRAND.dark + ' !important;',
      '  font-size: 12px !important;',
      '  font-weight: 600 !important;',
      '  padding: 6px 10px !important;',
      '  box-shadow: 0 2px 8px rgba(0,0,0,0.15) !important;',
      '}'
    ].join('\n');
    document.head.appendChild(style);
  }


  function esc(s) {
function getWorstCategory(markers){
    return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
var worst=EVENT_PRIORITY[EVENT_PRIORITY.length-1];
      return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]);
var worstIndex=EVENT_PRIORITY.indexOf(worst);
    });
markers.forEach(function(marker){
  }
var cat=marker.options.event_category || '';
var idx=EVENT_PRIORITY.indexOf(cat);
if(idx!==-1 && idx<worstIndex){
worst=cat;
worstIndex=idx;
}
});
return worst;
}


  function statusClass(s) {
function injectStyles(){
    var status = String(s || '').toLowerCase();
if(document.getElementById('lw-map-styles')) return;
    if (status === 'critical') return 'lw-popup-status--critical';
var style=document.createElement('style');
    if (status === 'high') return 'lw-popup-status--high';
style.id='lw-map-styles';
    if (status === 'medium') return 'lw-popup-status--medium';
style.textContent=[
    if (status === 'low') return 'lw-popup-status--low';
'.lw-popup .leaflet-popup-content-wrapper {',
    return '';
'  background: '+BRAND.white+';',
  }
'  border-radius: 12px;',
'  box-shadow: 0 4px 20px rgba(0,0,0,0.12), 0 2px 6px rgba(0,0,0,0.08);',
'  padding: 0;',
'  overflow: hidden;',
'}',
'.lw-popup .leaflet-popup-content {',
'  margin: 0;',
'  min-width: 220px;',
'  max-width: 280px;',
'}',
'.lw-popup .leaflet-popup-tip {',
'  background: '+BRAND.white+';',
'  box-shadow: 0 2px 6px rgba(0,0,0,0.1);',
'}',
'.lw-popup .leaflet-popup-close-button {',
'  color: '+BRAND.neutral+' !important;',
'  font-size: 20px !important;',
'  padding: 8px 10px !important;',
'  right: 4px !important;',
'  top: 4px !important;',
'  transition: color 0.2s ease;',
'}',
'.lw-popup .leaflet-popup-close-button:hover {',
'  color: '+BRAND.dark+' !important;',
'}',
'.lw-popup-inner {',
'  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;',
'}',
'.lw-popup-header {',
'  background: linear-gradient(135deg, '+BRAND.primary+' 0%, '+BRAND.dark+' 100%);',
'  padding: 16px 18px;',
'  color: '+BRAND.white+';',
'}',
'.lw-popup-id {',
'  font-size: 10px;',
'  font-weight: 600;',
'  letter-spacing: 0.5px;',
'  text-transform: uppercase;',
'  opacity: 0.85;',
'  margin-bottom: 4px;',
'}',
'.lw-popup-name {',
'  font-size: 15px;',
'  font-weight: 600;',
'  line-height: 1.3;',
'}',
'.lw-popup-body {',
'  padding: 14px 18px;',
'}',
'.lw-popup-meta {',
'  display: flex;',
'  flex-direction: column;',
'  align-items: flex-start;',
'  gap: 6px;',
'  margin-bottom: 8px;',
'}',
'.lw-popup-label {',
'  font-size: 11px;',
'  color: '+BRAND.neutral+';',
'  font-weight: 500;',
'  text-transform: uppercase;',
'  letter-spacing: 0.3px;',
'}',
'.lw-popup-status {',
'  display: inline-flex;',
'  align-items: center;',
'  padding: 4px 10px;',
'  border-radius: 20px;',
'  font-size: 11px;',
'  font-weight: 600;',
'  background: '+BRAND.light+';',
'  color: '+BRAND.neutral+';',
'}',
'.lw-popup-status::before {',
'  content: "";',
'  width: 6px;',
'  height: 6px;',
'  border-radius: 50%;',
'  margin-right: 6px;',
' background: currentColor;',
'}',
'.lw-popup-status--critical { color: #c92a2a; background: #fff5f5; }',
'.lw-popup-status--high { color: #d9480f; background: #fff4e6; }',
'.lw-popup-status--medium { color: #e67700; background: #fff9db; }',
'.lw-popup-status--low { color: #2b8a3e; background: #ebfbee; }',
'.lw-popup-link {',
'  display: block;',
'  text-align: center;',
'  padding: 10px 18px;',
'  background: '+BRAND.light+';',
'  color: '+BRAND.primary+';',
'  text-decoration: none;',
'  font-size: 13px;',
'  font-weight: 600;',
'  transition: background 0.2s ease, color 0.2s ease;',
'  border-top: 1px solid #e9ecef;',
'}',
'.lw-popup-link:hover {',
'  background: '+BRAND.primary+';',
'  color: '+BRAND.white+';',
'}',
'.lw-map-tooltip {',
'  background: '+BRAND.dark+' !important;',
'  border: none !important;',
'  border-radius: 6px !important;',
'  color: '+BRAND.white+' !important;',
'  font-size: 12px !important;',
'  font-weight: 500 !important;',
'  padding: 6px 10px !important;',
'  box-shadow: 0 2px 8px rgba(0,0,0,0.2) !important;',
'}',
'.lw-map-tooltip::before {',
'  border-top-color: '+BRAND.dark+' !important;',
'}',
'.lw-layer-toggle {',
'  display: flex;',
'  background: '+BRAND.white+';',
'  border-radius: 8px;',
'  box-shadow: 0 2px 8px rgba(0,0,0,0.12);',
'  overflow: hidden;',
'}',
'.lw-layer-btn {',
'  display: flex;',
'  align-items: center;',
'  justify-content: center;',
'  width: 36px;',
'  height: 36px;',
'  border: none;',
'  background: '+BRAND.white+';',
'  color: '+BRAND.neutral+';',
'  cursor: pointer;',
'  transition: all 0.2s ease;',
'}',
'.lw-layer-btn:hover {',
'  background: '+BRAND.light+';',
'  color: '+BRAND.primary+';',
'}',
'.lw-layer-btn--active {',
'  background: '+BRAND.primary+' !important;',
'  color: '+BRAND.white+' !important;',
'}',
'.lw-layer-btn:not(:last-child) {',
'  border-right: 1px solid #e9ecef;',
'}',
'.lw-layer-btn svg {',
'  pointer-events: none;',
'}',
'/* Cluster styles */',
'.lw-cluster-icon {',
'  background: transparent !important;',
'}',
'.lw-cluster {',
'  width: 40px;',
'  height: 40px;',
'  border-radius: 50%;',
'  color: #fff;',
'  font-weight: 700;',
'  font-size: 14px;',
'  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;',
'  display: flex;',
'  align-items: center;',
' justify-content: center;',
'  box-shadow: 0 3px 10px rgba(0,0,0,0.25);',
'  border: 3px solid rgba(255,255,255,0.9);',
'  transition: transform 0.2s ease;',
'}',
'.lw-cluster:hover {',
' transform: scale(1.1);',
'}',
'.lw-cluster--small {',
'  width: 36px;',
'  height: 36px;',
'  font-size: 13px;',
'}',
'.lw-cluster--large {',
'  width: 48px;',
'  height: 48px;',
'  font-size: 15px;',
'}',
'.marker-cluster {',
'  background: transparent !important;',
'}',
'.marker-cluster div {',
'  background: transparent !important;',
'}',
'/* Hull polygons */',
'.lw-hull-tooltip {',
'  background: '+BRAND.white+' !important;',
'  border: 1px solid '+BRAND.primary+' !important;',
'  border-radius: 6px !important;',
'  color: '+BRAND.dark+' !important;',
'  font-size: 12px !important;',
'  font-weight: 600 !important;',
'  padding: 6px 10px !important;',
' box-shadow: 0 2px 8px rgba(0,0,0,0.15) !important;',
'}'
].join('\n');
document.head.appendChild(style);
}


  function popupForAccession(p) {
function esc(s){
    var id = p.id || '';
return String(s==null ? '' : s).replace(/[&<>"']/g,function(c){
    var name = p.name || 'Unknown';
return({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]);
    var status = p.status || '';
});
    var url = p.page_url || '#';
}
   
    var statusHtml = '';
    if (status) {
      statusHtml = '<div class="lw-popup-meta">' +
        '<span class="lw-popup-label">Conservation Priority</span>' +
        '<span class="lw-popup-status ' + statusClass(status) + '">' + esc(status) + '</span>' +
      '</div>';
    }
   
    return '<div class="lw-popup-inner">' +
      '<div class="lw-popup-header">' +
        (id ? '<div class="lw-popup-id">' + esc(id) + '</div>' : '') +
        '<div class="lw-popup-name">' + esc(name) + '</div>' +
      '</div>' +
      '<div class="lw-popup-body">' +
        statusHtml +
      '</div>' +
      '<a href="' + esc(url) + '" class="lw-popup-link">View Accession →</a>' +
    '</div>';
  }


  function popupForArea(name, count, worstStatus, url) {
function statusClass(s){
    var statusHtml = '';
var status=String(s || '').toLowerCase();
    if (worstStatus) {
if(status==='critical') return 'lw-popup-status--critical';
      statusHtml = '<div class="lw-popup-meta">' +
if(status==='high') return 'lw-popup-status--high';
        '<span class="lw-popup-label">Worst Status in Area</span>' +
if(status==='medium') return 'lw-popup-status--medium';
        '<span class="lw-popup-status ' + statusClass(worstStatus) + '">' + esc(worstStatus) + '</span>' +
if(status==='low') return 'lw-popup-status--low';
      '</div>';
return '';
    }
}
   
    return '<div class="lw-popup-inner">' +
      '<div class="lw-popup-header">' +
        '<div class="lw-popup-id">Growing Area</div>' +
        '<div class="lw-popup-name">' + esc(name) + '</div>' +
      '</div>' +
      '<div class="lw-popup-body">' +
        '<div class="lw-popup-meta">' +
          '<span class="lw-popup-label">Accessions</span>' +
          '<span style="font-size:18px;font-weight:700;color:' + BRAND.dark + '">' + count + '</span>' +
        '</div>' +
        statusHtml +
      '</div>' +
      '<a href="' + esc(url) + '" class="lw-popup-link">View Growing Area →</a>' +
    '</div>';
  }


  function parseCoords(coordStr) {
function popupForAccession(p){
    if (!coordStr) return null;
var id=p.id || '';
   
var name=p.name || 'Unknown';
    var decimalMatch = coordStr.match(/(-?\d+\.?\d*)\s*,\s*(-?\d+\.?\d*)/);
var status=p.status || '';
    if (decimalMatch) {
var url=p.page_url || '#';
      var lat = parseFloat(decimalMatch[1]);
      var lon = parseFloat(decimalMatch[2]);
      if (!isNaN(lat) && !isNaN(lon)) return { lat: lat, lon: lon };
    }
   
    var dmsRegex = /(\d+)[°]\s*(\d+)[′']\s*(\d+\.?\d*)[″"]?\s*([NSEW])/gi;
    var matches = [];
    var m;
    while ((m = dmsRegex.exec(coordStr)) !== null) {
      matches.push(m);
      if (matches.length >= 2) break;
    }
   
    if (matches.length >= 2) {
      var lat = dmsToDecimal(matches[0][1], matches[0][2], matches[0][3], matches[0][4]);
      var lon = dmsToDecimal(matches[1][1], matches[1][2], matches[1][3], matches[1][4]);
     
      var h0 = matches[0][4].toUpperCase();
      if (h0 === 'E' || h0 === 'W') {
        var tmp = lat; lat = lon; lon = tmp;
      }
     
      if (lat !== null && lon !== null) return { lat: lat, lon: lon };
    }
   
    return null;
  }
 
  function dmsToDecimal(deg, min, sec, hemi) {
    var d = parseFloat(deg);
    var m = parseFloat(min) || 0;
    var s = parseFloat(sec) || 0;
    if (isNaN(d)) return null;
    var dec = Math.abs(d) + (m / 60) + (s / 3600);
    hemi = String(hemi || '').toUpperCase();
    if (hemi === 'S' || hemi === 'W') dec = -dec;
    return dec;
  }


  function parseCSV(text) {
var statusHtml='';
    var lines = text.trim().split('\n');
if(status){
    if (lines.length < 2) return [];
statusHtml='<div class="lw-popup-meta">'+
   
'<span class="lw-popup-label">Conservation Priority</span>'+
    var headers = parseCSVLine(lines[0]);
'<span class="lw-popup-status '+statusClass(status)+'">'+esc(status)+'</span>'+
   
'</div>';
    var results = [];
}
    for (var i = 1; i < lines.length; i++) {
      var values = parseCSVLine(lines[i]);
      var obj = {};
      for (var j = 0; j < headers.length; j++) {
        obj[headers[j]] = values[j] || '';
      }
      results.push(obj);
    }
    return results;
  }
 
  function parseCSVLine(line) {
    var result = [];
    var current = '';
    var inQuotes = false;
   
    for (var i = 0; i < line.length; i++) {
      var c = line[i];
      var next = line[i + 1];
     
      if (inQuotes) {
        if (c === '"' && next === '"') {
          current += '"';
          i++;
        } else if (c === '"') {
          inQuotes = false;
        } else {
          current += c;
        }
      } else {
        if (c === '"') {
          inQuotes = true;
        } else if (c === ',') {
          result.push(current.trim());
          current = '';
        } else {
          current += c;
        }
      }
    }
    result.push(current.trim());
    return result;
  }


  function csvToGeoJSON(data) {
return '<div class="lw-popup-inner">'+
    var features = [];
'<div class="lw-popup-header">'+
   
(id ? '<div class="lw-popup-id">'+esc(id)+'</div>' : '')+
    data.forEach(function(row) {
'<div class="lw-popup-name">'+esc(name)+'</div>'+
var coordStr = row['Has coordinates'] || row['Has GPS coordinates'] || row['GPS coordinates'] || '';
'</div>'+
      var coords = parseCoords(coordStr);
'<div class="lw-popup-body">'+
      if (!coords) {
statusHtml+
        console.log('[lw-map] Skipping row, no valid coords:', coordStr);
'</div>'+
        return;
'<a href="'+esc(url)+'" class="lw-popup-link">View Accession →</a>'+
      }
'</div>';
     
}
      var pageName = row[''] || Object.values(row)[0] || '';
      var name = row['Has descriptive name'] || row['descriptive name'] || pageName;
      var status = row['Has conservation priority'] || row['conservation priority'] || '';
      var accessionId = row['Has accession ID'] || row['accession ID'] || '';
      var growingArea = row['Has growing area'] || row['growing area'] || '';
      var growingRegion = row['Has growing region'] || row['growing region'] || '';
     
      features.push({
        type: 'Feature',
        properties: {
          id: accessionId,
          name: name,
          status: status,
          growing_area: growingArea,
          growing_region: growingRegion,
          page_url: '/wiki/' + encodeURIComponent(pageName.replace(/ /g, '_'))
        },
        geometry: {
          type: 'Point',
          coordinates: [coords.lon, coords.lat]
        }
      });
    });
   
    console.log('[lw-map] Converted', features.length, 'features from CSV');
    return { type: 'FeatureCollection', features: features };
  }


  function buildCSVUrl(query) {
function popupForNewsItem(p){
    var parts = query.split('|');
var title=p.name || 'News Item';
    var encodedParts = parts.map(function(part) {
var date=p.event_date || '';
      return part
var cat=p.event_category || '';
        .replace(/-/g, '-2D')
var loc=[p.locality,p.admin2,p.admin1].filter(Boolean).join(', ');
        .replace(/\[/g, '-5B')
var url=p.page_url || '#';
        .replace(/\]/g, '-5D')
var src=p.source_url || '';
        .replace(/\?/g, '-3F')
        .replace(/:/g, '-3A')
        .replace(/ /g, '-20');
    });
   
    var baseUrl = mw.config.get('wgScriptPath') || '';
    return baseUrl + '/wiki/Special:Ask/' + encodedParts.join('/') + '/format=csv';
  }


  function fetchCSV(query, cb) {
var blocks='';
    var url = buildCSVUrl(query);
if(date){
    console.log('[lw-map] Fetching CSV from:', url);
blocks+='<div class="lw-popup-meta"><span class="lw-popup-label">Date</span><span style="font-size:14px;font-weight:700;color:'+BRAND.dark+'">'+esc(date)+'</span></div>';
   
}
    fetch(url)
if(loc){
      .then(function(r) {
blocks+='<div class="lw-popup-meta"><span class="lw-popup-label">Location</span><span style="font-size:13px;color:'+BRAND.neutral+'">'+esc(loc)+'</span></div>';
        if (!r.ok) throw new Error('CSV fetch error: ' + r.status);
}
        return r.text();
if(cat){
      })
blocks+='<div class="lw-popup-meta"><span class="lw-popup-label">Category</span><span style="font-size:13px;color:'+BRAND.neutral+'">'+esc(cat)+'</span></div>';
      .then(function(text) {
}
        console.log('[lw-map] CSV response length:', text.length);
        var data = parseCSV(text);
        console.log('[lw-map] Parsed', data.length, 'rows');
        var geojson = csvToGeoJSON(data);
        cb(null, geojson);
      })
      .catch(function(err) {
        console.error('[lw-map] CSV fetch error:', err);
        cb(err, null);
      });
  }


  // Generate convex hull for a set of points using Turf.js
var sourceBtn=src ? ('<a href="'+esc(src)+'" target="_blank" rel="noreferrer" class="lw-popup-link" style="border-top:0">Source ↗</a>') : '';
  function generateHull(features) {
    if (!window.turf || features.length < 3) return null;
   
    try {
      var points = turf.featureCollection(
        features.map(function(f) {
          return turf.point(f.geometry.coordinates);
        })
      );
     
      // Try concave hull first for tighter fit
      var hull;
      if (features.length >= 4) {
        try {
          hull = turf.concave(points, { maxEdge: 100, units: 'kilometers' });
        } catch (e) {
          console.log('[lw-map] Concave hull failed, falling back to convex');
        }
      }
     
      // Fall back to convex hull
      if (!hull) {
        hull = turf.convex(points);
      }
     
      // Add buffer to prevent thin/linear hulls looking weird
      if (hull) {
        hull = turf.buffer(hull, 3, { units: 'kilometers' });
      }
     
      return hull;
    } catch (e) {
      console.error('[lw-map] Hull generation error:', e);
      return null;
    }
  }


  // Group features by a property
return '<div class="lw-popup-inner">'+
  function groupBy(features, prop) {
'<div class="lw-popup-header">'+
    var groups = {};
'<div class="lw-popup-id">News Item</div>'+
    features.forEach(function(f) {
'<div class="lw-popup-name">'+esc(title)+'</div>'+
      var key = f.properties[prop] || 'Unknown';
'</div>'+
      if (!groups[key]) groups[key] = [];
'<div class="lw-popup-body">'+
      groups[key].push(f);
blocks+
    });
'</div>'+
    return groups;
'<a href="'+esc(url)+'" class="lw-popup-link">View Article →</a>'+
  }
sourceBtn+
'</div>';
}


  function initOne(el) {
function popupForArea(name,count,worstStatus,url){
    if (el.dataset.init) return;
var statusHtml='';
    el.dataset.init = '1';
if(worstStatus){
    if (el.clientHeight < 100) el.style.height = '70vh';
statusHtml='<div class="lw-popup-meta">'+
'<span class="lw-popup-label">Worst Status in Area</span>'+
'<span class="lw-popup-status '+statusClass(worstStatus)+'">'+esc(worstStatus)+'</span>'+
'</div>';
}


    injectStyles();
return '<div class="lw-popup-inner">'+
'<div class="lw-popup-header">'+
'<div class="lw-popup-id">Growing Area</div>'+
'<div class="lw-popup-name">'+esc(name)+'</div>'+
'</div>'+
'<div class="lw-popup-body">'+
'<div class="lw-popup-meta">'+
'<span class="lw-popup-label">Accessions</span>'+
'<span style="font-size:18px;font-weight:700;color:'+BRAND.dark+'">'+count+'</span>'+
'</div>'+
statusHtml+
'</div>'+
'<a href="'+esc(url)+'" class="lw-popup-link">View Growing Area →</a>'+
'</div>';
}


    var map = L.map(el, {
function parseCoords(coordStr){
      minZoom: 2,
if(!coordStr) return null;
      maxZoom: 17,
      zoomControl: false
    }).setView([26.4, 89.5], 8);
   
    el._leaflet_map = map;
   
    L.control.zoom({ position: 'topright' }).addTo(map);
   
    var baseLayers = {
      basic: L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
        attribution: '© <a href="https://www.openstreetmap.org/copyright">OSM</a> © <a href="https://carto.com/attributions">CARTO</a>',
        subdomains: 'abcd',
        maxZoom: 19
      }),
      terrain: L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}', {
        attribution: '© <a href="https://www.esri.com">Esri</a>',
        maxZoom: 18
      }),
      satellite: L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
        attribution: '© <a href="https://www.esri.com">Esri</a>',
        maxZoom: 18
      })
    };
   
    baseLayers.basic.addTo(map);
    var currentLayer = 'basic';
   
    var layerToggle = L.control({ position: 'topleft' });
    layerToggle.onAdd = function() {
      var container = L.DomUtil.create('div', 'lw-layer-toggle');
      container.innerHTML =
        '<button class="lw-layer-btn lw-layer-btn--active" data-layer="basic" title="Basic view">' +
          '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>' +
        '</button>' +
        '<button class="lw-layer-btn" data-layer="terrain" title="Terrain view">' +
          '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 21l4-10 4 10M12 11V3M4 21l4.5-9L12 17l3.5-5L20 21"/></svg>' +
        '</button>' +
        '<button class="lw-layer-btn" data-layer="satellite" title="Satellite view">' +
          '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20M2 12h20"/></svg>' +
        '</button>';
     
      L.DomEvent.disableClickPropagation(container);
     
      container.querySelectorAll('.lw-layer-btn').forEach(function(btn) {
        btn.addEventListener('click', function() {
          var layer = this.dataset.layer;
          if (layer === currentLayer) return;
         
          map.removeLayer(baseLayers[currentLayer]);
          baseLayers[layer].addTo(map);
          currentLayer = layer;
         
          container.querySelectorAll('.lw-layer-btn').forEach(function(b) {
            b.classList.remove('lw-layer-btn--active');
          });
          this.classList.add('lw-layer-btn--active');
        });
      });
     
      return container;
    };
    layerToggle.addTo(map);


    var query = el.dataset.smwQuery;
var decimalMatch=coordStr.match(/(-?\d+\.?\d*)\s*,\s*(-?\d+\.?\d*)/);
    if (!query) {
if(decimalMatch){
      console.log('[lw-map] No data-smw-query attribute found');
var lat=parseFloat(decimalMatch[1]);
      return;
var lon=parseFloat(decimalMatch[2]);
    }
if(!isNaN(lat) && !isNaN(lon)) return {lat:lat,lon:lon};
}


    // Check if this is an infobox map
var dmsRegex=/(\d+)[°]\s*(\d+)[′']\s*(\d+\.?\d*)[″"]?\s*([NSEW])/gi;
    var isInfoboxMap = el.closest('.lw-infobox') !== null;
var matches=[];
var m;
while((m=dmsRegex.exec(coordStr))!==null){
matches.push(m);
if(matches.length>=2) break;
}


    // Layer group for area hulls
if(matches.length>=2){
    var areaHullsLayer = L.layerGroup();
var lat2=dmsToDecimal(matches[0][1],matches[0][2],matches[0][3],matches[0][4]);
   
var lon2=dmsToDecimal(matches[1][1],matches[1][2],matches[1][3],matches[1][4]);
    // Update visibility based on zoom
    function updateLayerVisibility() {
      var zoom = map.getZoom();
     
      // Area hulls: show at zoom 6-12
      if (zoom >= 6 && zoom <= 12) {
        if (!map.hasLayer(areaHullsLayer)) map.addLayer(areaHullsLayer);
      } else {
        if (map.hasLayer(areaHullsLayer)) map.removeLayer(areaHullsLayer);
      }
    }


    // For main map, add growing area to query to enable grouping
var h0=matches[0][4].toUpperCase();
    var fullQuery = query;
if(h0==='E' || h0==='W'){
if (!isInfoboxMap && query.indexOf('Category:Accessions') !== -1) {
var tmp=lat2; lat2=lon2; lon2=tmp;
  fullQuery = '[[Category:Accessions]]|?Has coordinates|?Has descriptive name|?Has conservation priority|?Has accession ID|?Has growing area|?Has growing region';
}
}


    fetchCSV(fullQuery, function (err, geojson) {
if(lat2!==null && lon2!==null) return {lat:lat2,lon:lon2};
      if (err || !geojson || !geojson.features || !geojson.features.length) {
}
        var msg = L.control({ position: 'bottomleft' });
        msg.onAdd = function () {
          var d = L.DomUtil.create('div');
          d.style.cssText = 'background:white;padding:12px 16px;border-radius:8px;font-size:13px;color:#495057;box-shadow:0 2px 8px rgba(0,0,0,0.1);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;';
          d.innerHTML = '<strong>No accessions found</strong><br><span style="opacity:0.7;">No mappable results for this query.</span>';
          return d;
        };
        msg.addTo(map);
        return;
      }


      // Generate area hulls for main map
return null;
      if (!isInfoboxMap && window.turf) {
}
        var areaGroups = groupBy(geojson.features, 'growing_area');
       
        Object.keys(areaGroups).forEach(function(areaName) {
          if (areaName === 'Unknown' || areaName === '') return;
         
          var areaFeatures = areaGroups[areaName];
          if (areaFeatures.length < 3) return; // Need at least 3 points for hull
         
          var hull = generateHull(areaFeatures);
          if (!hull) return;
         
          var worstStatus = getWorstStatusFromFeatures(areaFeatures);
          var color = STATUS_COLORS[worstStatus] || BRAND.primary;
         
          var hullLayer = L.geoJSON(hull, {
            style: {
              fillColor: color,
              fillOpacity: 0.12,
              color: color,
              weight: 2,
              dashArray: '6, 4',
              lineCap: 'round'
            }
          });
         
          // Add popup for hull
          hullLayer.bindPopup(
            popupForArea(areaName, areaFeatures.length, worstStatus, '/wiki/' + encodeURIComponent(areaName.replace(/ /g, '_'))),
            { className: 'lw-popup', maxWidth: 300 }
          );
         
          // Add tooltip
          hullLayer.bindTooltip(areaName + ' (' + areaFeatures.length + ')', {
            className: 'lw-hull-tooltip',
            direction: 'center',
            permanent: false
          });
         
          // Hover effect
          hullLayer.on('mouseover', function(e) {
            e.target.setStyle({
              fillOpacity: 0.25,
              weight: 3
            });
          });
          hullLayer.on('mouseout', function(e) {
            e.target.setStyle({
              fillOpacity: 0.12,
              weight: 2
            });
          });
         
          areaHullsLayer.addLayer(hullLayer);
        });
       
        // Set up zoom listener
        map.on('zoomend', updateLayerVisibility);
        updateLayerVisibility();
      }


      // Add markers (with or without clustering)
function dmsToDecimal(deg,min,sec,hemi){
      if (isInfoboxMap || !window.L.markerClusterGroup) {
var d=parseFloat(deg);
        var layer = L.geoJSON(geojson, {
var m=parseFloat(min) || 0;
          pointToLayer: function (f, ll) {
var s=parseFloat(sec) || 0;
            var status = f.properties.status || '';
if(isNaN(d)) return null;
            var color = STATUS_COLORS[status] || BRAND.primary;
var dec=Math.abs(d)+(m/60)+(s/3600);
            return L.circleMarker(ll, {
hemi=String(hemi || '').toUpperCase();
              radius: 8,
if(hemi==='S' || hemi==='W') dec=-dec;
              color: BRAND.white,
return dec;
              weight: 2.5,
}
              fillColor: color,
              fillOpacity: 0.9,
              status: status
            });
          },
          onEachFeature: function (f, ly) {
            var p = f.properties || {};
           
            ly.bindPopup(popupForAccession(p), {
              className: 'lw-popup',
              closeButton: true,
              maxWidth: 300,
              offset: [0, -5]
            });
           
            var tip = p.name || p.id || '';
            if (tip) {
              ly.bindTooltip(esc(tip), {
                direction: 'top',
                offset: [0, -10],
                opacity: 1,
                className: 'lw-map-tooltip'
              });
            }
           
            ly.on('mouseover', function() {
              this.setStyle({
                fillColor: BRAND.primaryLight,
                radius: 10
              });
            });
            ly.on('mouseout', function() {
              var status = f.properties.status || '';
              var color = STATUS_COLORS[status] || BRAND.primary;
              this.setStyle({
                fillColor: color,
                radius: 8
              });
            });
          }
        }).addTo(map);


        var b = layer.getBounds();
function parseCSV(text){
        if (b && b.isValid()) map.fitBounds(b.pad(0.3), { maxZoom: 11 });
var lines=text.trim().split('\n');
      } else {
if(lines.length<2) return [];
        var markers = L.markerClusterGroup({
var headers=parseCSVLine(lines[0]);
          maxClusterRadius: 50,
          spiderfyOnMaxZoom: true,
          showCoverageOnHover: false,
          zoomToBoundsOnClick: true,
          iconCreateFunction: function(cluster) {
            var children = cluster.getAllChildMarkers();
            var count = children.length;
            var worstStatus = getWorstStatus(children);
            var color = STATUS_COLORS[worstStatus] || BRAND.primary;
           
            var sizeClass = 'lw-cluster';
            if (count < 10) sizeClass += ' lw-cluster--small';
            else if (count >= 50) sizeClass += ' lw-cluster--large';
           
            return L.divIcon({
              html: '<div class="' + sizeClass + '" style="background:' + color + '">' + count + '</div>',
              className: 'lw-cluster-icon',
              iconSize: [40, 40]
            });
          }
        });


        geojson.features.forEach(function(f) {
var results=[];
          var coords = f.geometry.coordinates;
for(var i=1;i<lines.length;i++){
          var p = f.properties;
var values=parseCSVLine(lines[i]);
          var status = p.status || '';
var obj={};
          var color = STATUS_COLORS[status] || BRAND.primary;
for(var j=0;j<headers.length;j++){
         
obj[headers[j]]=values[j] || '';
          var marker = L.circleMarker([coords[1], coords[0]], {  
}
            radius: 8,
results.push(obj);
            color: BRAND.white,
}
            weight: 2.5,
return results;
            fillColor: color,
}
            fillOpacity: 0.9,
            status: status
          });
         
          marker.bindPopup(popupForAccession(p), {
            className: 'lw-popup',
            closeButton: true,
            maxWidth: 300,
            offset: [0, -5]
          });
         
          var tip = p.name || p.id || '';
          if (tip) {
            marker.bindTooltip(esc(tip), {
              direction: 'top',
              offset: [0, -10],
              opacity: 1,
              className: 'lw-map-tooltip'
            });
          }
         
          marker.on('mouseover', function() {
            this.setStyle({
              fillColor: BRAND.primaryLight,
              radius: 10
            });
          });
          marker.on('mouseout', function() {
            this.setStyle({
              fillColor: color,
              radius: 8
            });
          });
         
          markers.addLayer(marker);
        });


        map.addLayer(markers);
function parseCSVLine(line){
       
var result=[];
        var b = markers.getBounds();
var current='';
        if (b && b.isValid()) map.fitBounds(b.pad(0.3), { maxZoom: 11 });
var inQuotes=false;
      }
     
      setTimeout(function () { map.invalidateSize(true); }, 100);
    });
  }


  function init(root) {
for(var i=0;i<line.length;i++){
    (root || document).querySelectorAll('.lw-map').forEach(initOne);
var c=line[i];
  }
var next=line[i+1];


  // Load CSS
if(inQuotes){
  addCSS(CDN + 'leaflet.css', 'leaflet-css');
if(c === '"' && next === '"'){
  addCSS(CLUSTER_CDN + 'MarkerCluster.css', 'markercluster-css');
current+='"';
  addCSS(CLUSTER_CDN + 'MarkerCluster.Default.css', 'markercluster-default-css');
i++;
 
}else if(c === '"'){
  // Load JS: Leaflet → MarkerCluster → Turf → init
inQuotes=false;
  addJS(CDN + 'leaflet.js', function () {
}else{
    addJS(CLUSTER_CDN + 'leaflet.markercluster.js', function() {
current+=c;
      addJS(TURF_CDN, function() {
}
        console.log('[lw-map] All libraries loaded, initializing maps');
}else{
        init();
if(c === '"'){
        if (window.mw && mw.hook) {
inQuotes=true;
          mw.hook('wikipage.content').add(function ($c) {  
}else if(c === ','){
            init($c && $c[0] ? $c[0] : document);  
result.push(current.trim());
          });
current='';
        }
}else{
      }, 'turf-js');
current+=c;
    }, 'markercluster-js');
}
  }, 'leaflet-js');
}
}
result.push(current.trim());
return result;
}
 
function csvToGeoJSON(data){
var features=[];
 
data.forEach(function(row){
var coordStr=row['Has coordinates'] || row['Has GPS coordinates'] || row['GPS coordinates'] || '';
var coords=parseCoords(coordStr);
if(!coords) return;
 
var pageName=row[''] || Object.values(row)[0] || '';
var pageUrl='/wiki/' + encodeURIComponent(pageName.replace(/ /g,'_'));
 
var isNews=!!(row['Has event headline'] || row['Has event date'] || row['Has event category']);
 
if(isNews){
var headline=row['Has event headline'] || pageName;
var eventDate=row['Has event date'] || '';
var eventCat=row['Has event category'] || '';
var admin1=row['Has admin subdivision 1'] || '';
var admin2=row['Has admin subdivision 2'] || '';
var locality=row['Has locality'] || '';
var sourceUrl=row['Has source'] || '';
 
features.push({
type:'Feature',
properties:{
name:headline,
event_date:eventDate,
event_category:eventCat,
admin1:admin1,
admin2:admin2,
locality:locality,
source_url:sourceUrl,
page_url:pageUrl
},
geometry:{type:'Point',coordinates:[coords.lon,coords.lat]}
});
return;
}
 
/* Default: Accessions */
var name=row['Has descriptive name'] || row['descriptive name'] || pageName;
var status=row['Has conservation priority'] || row['conservation priority'] || '';
var accessionId=row['Has accession ID'] || row['accession ID'] || '';
var growingArea=row['Has growing area'] || row['growing area'] || '';
var growingRegion=row['Has growing region'] || row['growing region'] || '';
 
features.push({
type:'Feature',
properties:{
id:accessionId,
name:name,
status:status,
growing_area:growingArea,
growing_region:growingRegion,
page_url:pageUrl
},
geometry:{type:'Point',coordinates:[coords.lon,coords.lat]}
});
});
 
console.log('[lw-map] Converted',features.length,'features from CSV');
return {type:'FeatureCollection',features:features};
}
 
function buildCSVUrl(query){
var parts=query.split('|');
var encodedParts=parts.map(function(part){
return part
.replace(/-/g,'-2D')
.replace(/\[/g,'-5B')
.replace(/\]/g,'-5D')
.replace(/\?/g,'-3F')
.replace(/:/g,'-3A')
.replace(/ /g,'-20');
});
 
var baseUrl=mw.config.get('wgScriptPath') || '';
return baseUrl+'/wiki/Special:Ask/'+encodedParts.join('/')+'/format=csv';
}
 
function fetchCSV(query,cb){
var url=buildCSVUrl(query);
console.log('[lw-map] Fetching CSV from:',url);
 
fetch(url)
.then(function(r){
if(!r.ok) throw new Error('CSV fetch error: '+r.status);
return r.text();
})
.then(function(text){
console.log('[lw-map] CSV response length:',text.length);
var data=parseCSV(text);
console.log('[lw-map] Parsed',data.length,'rows');
var geojson=csvToGeoJSON(data);
cb(null,geojson);
})
.catch(function(err){
console.error('[lw-map] CSV fetch error:',err);
cb(err,null);
});
}
 
/* Generate hull for a set of points using Turf.js */
function generateHull(features){
if(!window.turf || features.length<3) return null;
 
try{
var points=turf.featureCollection(
features.map(function(f){ return turf.point(f.geometry.coordinates); })
);
 
var hull;
if(features.length>=4){
try{
hull=turf.concave(points,{maxEdge:100,units:'kilometers'});
}catch(e){
console.log('[lw-map] Concave hull failed, falling back to convex');
}
}
 
if(!hull){
hull=turf.convex(points);
}
 
if(hull){
hull=turf.buffer(hull,3,{units:'kilometers'});
}
 
return hull;
}catch(e){
console.error('[lw-map] Hull generation error:',e);
return null;
}
}
 
function groupBy(features,prop){
var groups={};
features.forEach(function(f){
var key=f.properties[prop] || 'Unknown';
if(!groups[key]) groups[key]=[];
groups[key].push(f);
});
return groups;
}
 
function initOne(el){
if(el.dataset.init) return;
el.dataset.init='1';
if(el.clientHeight<100) el.style.height='70vh';
 
injectStyles();
 
var map=L.map(el,{
minZoom:2,
maxZoom:17,
zoomControl:false
}).setView([26.4,89.5],8);
 
el._leaflet_map=map;
 
L.control.zoom({position:'topright'}).addTo(map);
 
var baseLayers={
basic:L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png',{
attribution:'© <a href="https://www.openstreetmap.org/copyright">OSM</a> © <a href="https://carto.com/attributions">CARTO</a>',
subdomains:'abcd',
maxZoom:19
}),
terrain:L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}',{
attribution:'© <a href="https://www.esri.com">Esri</a>',
maxZoom:18
}),
satellite:L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',{
attribution:'© <a href="https://www.esri.com">Esri</a>',
maxZoom:18
})
};
 
baseLayers.basic.addTo(map);
var currentLayer='basic';
 
var layerToggle=L.control({position:'topleft'});
layerToggle.onAdd=function(){
var container=L.DomUtil.create('div','lw-layer-toggle');
container.innerHTML=
'<button class="lw-layer-btn lw-layer-btn--active" data-layer="basic" title="Basic view">'+
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>'+
'</button>'+
'<button class="lw-layer-btn" data-layer="terrain" title="Terrain view">'+
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 21l4-10 4 10M12 11V3M4 21l4.5-9L12 17l3.5-5L20 21"/></svg>'+
'</button>'+
'<button class="lw-layer-btn" data-layer="satellite" title="Satellite view">'+
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20M2 12h20"/></svg>'+
'</button>';
 
L.DomEvent.disableClickPropagation(container);
 
container.querySelectorAll('.lw-layer-btn').forEach(function(btn){
btn.addEventListener('click',function(){
var layer=this.dataset.layer;
if(layer===currentLayer) return;
 
map.removeLayer(baseLayers[currentLayer]);
baseLayers[layer].addTo(map);
currentLayer=layer;
 
container.querySelectorAll('.lw-layer-btn').forEach(function(b){
b.classList.remove('lw-layer-btn--active');
});
this.classList.add('lw-layer-btn--active');
});
});
 
return container;
};
layerToggle.addTo(map);
 
var query=el.dataset.smwQuery;
if(!query){
console.log('[lw-map] No data-smw-query attribute found');
return;
}
 
var mapType=el.dataset.mapType || '';
 
/* Check if this is an infobox map */
var isInfoboxMap=el.closest('.lw-infobox') !== null;
 
/* Layer group for area hulls */
var areaHullsLayer=L.layerGroup();
 
/* Update visibility based on zoom */
function updateLayerVisibility(){
var zoom=map.getZoom();
if(zoom>=6 && zoom<=12){
if(!map.hasLayer(areaHullsLayer)) map.addLayer(areaHullsLayer);
}else{
if(map.hasLayer(areaHullsLayer)) map.removeLayer(areaHullsLayer);
}
}
 
/* For main map, add growing area to query to enable grouping (accessions only) */
var fullQuery=query;
if(!isInfoboxMap && query.indexOf('Category:Accessions') !== -1){
fullQuery='[[Category:Accessions]]|?Has coordinates|?Has descriptive name|?Has conservation priority|?Has accession ID|?Has growing area|?Has growing region';
}
 
fetchCSV(fullQuery,function(err,geojson){
if(err || !geojson || !geojson.features || !geojson.features.length){
var msg=L.control({position:'bottomleft'});
msg.onAdd=function(){
var d=L.DomUtil.create('div');
d.style.cssText='background:white;padding:12px 16px;border-radius:8px;font-size:13px;color:#495057;box-shadow:0 2px 8px rgba(0,0,0,0.1);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;';
d.innerHTML='<strong>No results found</strong><br><span style="opacity:0.7;">No mappable results for this query.</span>';
return d;
};
msg.addTo(map);
return;
}
 
/* Generate area hulls for main map (accessions only) */
if(!isInfoboxMap && window.turf && mapType!=='eradication'){
var areaGroups=groupBy(geojson.features,'growing_area');
 
Object.keys(areaGroups).forEach(function(areaName){
if(areaName==='Unknown' || areaName==='') return;
 
var areaFeatures=areaGroups[areaName];
if(areaFeatures.length<3) return;
 
var hull=generateHull(areaFeatures);
if(!hull) return;
 
var worstStatus=getWorstStatusFromFeatures(areaFeatures);
var color=STATUS_COLORS[worstStatus] || BRAND.primary;
 
var hullLayer=L.geoJSON(hull,{
style:{
fillColor:color,
fillOpacity:0.12,
color:color,
weight:2,
dashArray:'6, 4',
lineCap:'round'
}
});
 
hullLayer.bindPopup(
popupForArea(areaName,areaFeatures.length,worstStatus,'/wiki/'+encodeURIComponent(areaName.replace(/ /g,'_'))),
{className:'lw-popup',maxWidth:300}
);
 
hullLayer.bindTooltip(areaName+' ('+areaFeatures.length+')',{
className:'lw-hull-tooltip',
direction:'center',
permanent:false
});
 
hullLayer.on('mouseover',function(e){
e.target.setStyle({fillOpacity:0.25,weight:3});
});
hullLayer.on('mouseout',function(e){
e.target.setStyle({fillOpacity:0.12,weight:2});
});
 
areaHullsLayer.addLayer(hullLayer);
});
 
map.on('zoomend',updateLayerVisibility);
updateLayerVisibility();
}
 
/* Add markers */
if(isInfoboxMap || !window.L.markerClusterGroup){
var layer=L.geoJSON(geojson,{
pointToLayer:function(f,ll){
var p=f.properties || {};
var status=p.status || '';
var cat=p.event_category || '';
var color=(mapType==='eradication') ? (EVENT_COLORS[cat] || BRAND.primary) : (STATUS_COLORS[status] || BRAND.primary);
return L.circleMarker(ll,{
radius:8,
color:BRAND.white,
weight:2.5,
fillColor:color,
fillOpacity:0.9,
status:status,
event_category:cat
});
},
onEachFeature:function(f,ly){
var p=f.properties || {};
 
ly.bindPopup((mapType==='eradication') ? popupForNewsItem(p) : popupForAccession(p),{
className:'lw-popup',
closeButton:true,
maxWidth:300,
offset:[0,-5]
});
 
var tip=(mapType==='eradication') ? (p.name || '') : (p.name || p.id || '');
if(tip){
ly.bindTooltip(esc(tip),{
direction:'top',
offset:[0,-10],
opacity:1,
className:'lw-map-tooltip'
});
}
 
ly.on('mouseover',function(){
this.setStyle({fillColor:BRAND.primaryLight,radius:10});
});
ly.on('mouseout',function(){
var pp=f.properties || {};
var status2=pp.status || '';
var cat2=pp.event_category || '';
var color2=(mapType==='eradication') ? (EVENT_COLORS[cat2] || BRAND.primary) : (STATUS_COLORS[status2] || BRAND.primary);
this.setStyle({fillColor:color2,radius:8});
});
}
}).addTo(map);
 
var b=layer.getBounds();
if(b && b.isValid()) map.fitBounds(b.pad(0.3),{maxZoom:11});
}else{
var markers=L.markerClusterGroup({
maxClusterRadius:50,
spiderfyOnMaxZoom:true,
showCoverageOnHover:false,
zoomToBoundsOnClick:true,
iconCreateFunction:function(cluster){
var children=cluster.getAllChildMarkers();
var count=children.length;
var color;
if(mapType==='eradication'){
var worstCat=getWorstCategory(children);
color=EVENT_COLORS[worstCat] || BRAND.primary;
}else{
var worstStatus=getWorstStatus(children);
color=STATUS_COLORS[worstStatus] || BRAND.primary;
}
 
var sizeClass='lw-cluster';
if(count<10) sizeClass+=' lw-cluster--small';
else if(count>=50) sizeClass+=' lw-cluster--large';
 
return L.divIcon({
html:'<div class="'+sizeClass+'" style="background:'+color+'">'+count+'</div>',
className:'lw-cluster-icon',
iconSize:[40,40]
});
}
});
 
geojson.features.forEach(function(f){
var coords=f.geometry.coordinates;
var p=f.properties || {};
var status=p.status || '';
var cat=p.event_category || '';
var color=(mapType==='eradication') ? (EVENT_COLORS[cat] || BRAND.primary) : (STATUS_COLORS[status] || BRAND.primary);
 
var marker=L.circleMarker([coords[1],coords[0]],{
radius:8,
color:BRAND.white,
weight:2.5,
fillColor:color,
fillOpacity:0.9,
status:status,
event_category:cat
});
 
marker.bindPopup((mapType==='eradication') ? popupForNewsItem(p) : popupForAccession(p),{
className:'lw-popup',
closeButton:true,
maxWidth:300,
offset:[0,-5]
});
 
var tip=(mapType==='eradication') ? (p.name || '') : (p.name || p.id || '');
if(tip){
marker.bindTooltip(esc(tip),{
direction:'top',
offset:[0,-10],
opacity:1,
className:'lw-map-tooltip'
});
}
 
marker.on('mouseover',function(){
this.setStyle({fillColor:BRAND.primaryLight,radius:10});
});
marker.on('mouseout',function(){
this.setStyle({fillColor:color,radius:8});
});
 
markers.addLayer(marker);
});
 
map.addLayer(markers);
 
var b2=markers.getBounds();
if(b2 && b2.isValid()) map.fitBounds(b2.pad(0.3),{maxZoom:11});
}
 
setTimeout(function(){ map.invalidateSize(true); },100);
});
}
 
function init(root){
(root || document).querySelectorAll('.lw-map').forEach(initOne);
}
 
/* Load CSS */
addCSS(CDN+'leaflet.css','leaflet-css');
addCSS(CLUSTER_CDN+'MarkerCluster.css','markercluster-css');
addCSS(CLUSTER_CDN+'MarkerCluster.Default.css','markercluster-default-css');
 
/* Load JS: Leaflet → MarkerCluster → Turf → init */
addJS(CDN+'leaflet.js',function(){
addJS(CLUSTER_CDN+'leaflet.markercluster.js',function(){
addJS(TURF_CDN,function(){
console.log('[lw-map] All libraries loaded, initializing maps');
init();
if(window.mw && mw.hook){
mw.hook('wikipage.content').add(function($c){
init($c && $c[0] ? $c[0] : document);
});
}
},'turf-js');
},'markercluster-js');
},'leaflet-js');
})();
})();


Line 961: Line 1,027:
  * Detects the category of the subject page and loads the appropriate talk template
  * Detects the category of the subject page and loads the appropriate talk template
  */
  */
(function() {
(function(){
    'use strict';
'use strict';
   
 
    // Only run on edit action for new pages in Talk namespace
/* Only run on edit action for new pages in Talk namespace */
    if (mw.config.get('wgAction') !== 'edit') return;
if(mw.config.get('wgAction')!=='edit') return;
    if (mw.config.get('wgArticleId') !== 0) return; // Page already exists
if(mw.config.get('wgArticleId')!==0) return; /* Page already exists */
    if (mw.config.get('wgNamespaceNumber') !== 1) return; // Not Talk namespace
if(mw.config.get('wgNamespaceNumber')!==1) return; /* Not Talk namespace */
   
 
    var mainPageTitle = mw.config.get('wgTitle'); // Subject page name
var mainPageTitle=mw.config.get('wgTitle'); /* Subject page name */
   
 
    // Map categories to preload templates (checked in order)
/* Map categories to preload templates (checked in order) */
    var categoryToPreload = {
var categoryToPreload={
        'Category:Countries': 'Template:Talk preload/Country',
'Category:Countries':'Template:Talk preload/Country',
        'Category:States': 'Template:Talk preload/State',
'Category:States':'Template:Talk preload/State',
        'Category:Growing Regions': 'Template:Talk preload/Growing region',
'Category:Growing Regions':'Template:Talk preload/Growing region',
        'Category:Growing Areas': 'Template:Talk preload/Growing area',
'Category:Growing Areas':'Template:Talk preload/Growing area',
        'Category:Accessions': 'Template:Talk preload/Accession'
'Category:Accessions':'Template:Talk preload/Accession'
    };
};
   
 
    // Default fallback template
/* Default fallback template */
    var defaultPreload = 'Template:Talk preload/Default';
var defaultPreload='Template:Talk preload/Default';
   
 
    mw.loader.using(['mediawiki.api'], function() {
mw.loader.using(['mediawiki.api'],function(){
        var api = new mw.Api();
var api=new mw.Api();
       
 
        $(function() {
$(function(){
            var $textbox = $('#wpTextbox1');
var $textbox=$('#wpTextbox1');
            if ($textbox.val().trim()) return; // Already has content
if($textbox.val().trim()) return; /* Already has content */
           
 
            // Get categories of the subject page
/* Get categories of the subject page */
            api.get({
api.get({
                action: 'query',
action:'query',
                titles: mainPageTitle,
titles:mainPageTitle,
                prop: 'categories',
prop:'categories',
                cllimit: 50,
cllimit:50,
                format: 'json'
format:'json'
            }).done(function(data) {
}).done(function(data){
                var pages = data.query.pages;
var pages=data.query.pages;
                var categories = [];
var categories=[];
               
 
                for (var id in pages) {
for(var id in pages){
                    if (pages[id].categories) {
if(pages[id].categories){
                        categories = pages[id].categories.map(function(c) {
categories=pages[id].categories.map(function(c){ return c.title; });
                            return c.title;
}
                        });
}
                    }
 
                }
/* Find matching preload template */
               
var preloadTemplate=defaultPreload;
                // Find matching preload template
for(var cat in categoryToPreload){
                var preloadTemplate = defaultPreload;
if(categories.indexOf(cat)!==-1){
                for (var cat in categoryToPreload) {
preloadTemplate=categoryToPreload[cat];
                    if (categories.indexOf(cat) !== -1) {
break;
                        preloadTemplate = categoryToPreload[cat];
}
                        break;
}
                    }
 
                }
/* Fetch and insert the preload template content */
               
api.get({
                // Fetch and insert the preload template content
action:'query',
                api.get({
titles:preloadTemplate,
                    action: 'query',
prop:'revisions',
                    titles: preloadTemplate,
rvprop:'content',
                    prop: 'revisions',
rvslots:'main',
                    rvprop: 'content',
format:'json'
                    rvslots: 'main',
}).done(function(data){
                    format: 'json'
var pages=data.query.pages;
                }).done(function(data) {
for(var id in pages){
                    var pages = data.query.pages;
if(pages[id].revisions){
                    for (var id in pages) {
var content=pages[id].revisions[0].slots.main['*'];
                        if (pages[id].revisions) {
content=content.replace(/<noinclude>[\s\S]*?<\/noinclude>/g,'');
                            var content = pages[id].revisions[0].slots.main['*'];
content=content.trim();
                            // Strip noinclude tags
$textbox.val(content);
                            content = content.replace(/<noinclude>[\s\S]*?<\/noinclude>/g, '');
}
                            content = content.trim();
}
                            $textbox.val(content);
}).fail(function(){
                        }
$textbox.val('{{TalkHeader}}\n\n== Discussion ==\n');
                    }
});
                }).fail(function() {
});
                    // Fallback if template doesn't exist
});
                    $textbox.val('{{TalkHeader}}\n\n== Discussion ==\n');
});
                });
            });
        });
    });
})();
})();
// <nowiki>
// </nowiki>

Revision as of 14:19, 22 February 2026

// <nowiki>
/* Infobox map expand toggle */
$(function(){
var $overlay=$('<div class="lw-map-overlay"></div>').appendTo('body');

$('.lw-infobox__map-expand').on('click',function(){
var $mapContainer=$(this).closest('.lw-infobox__map-container');
var $map=$mapContainer.find('.lw-infobox__map');

if($overlay.hasClass('lw-map-overlay--active')){
$map.appendTo($mapContainer);
$overlay.removeClass('lw-map-overlay--active');
$('body').css('overflow','');
}else{
$map.appendTo($overlay);
$overlay.addClass('lw-map-overlay--active');
$('body').css('overflow','hidden');
}

setTimeout(function(){
$map.each(function(){
if(this._leaflet_map){
this._leaflet_map.invalidateSize();
}
});
},100);
});

$(document).on('keydown',function(e){
if(e.key==='Escape' && $overlay.hasClass('lw-map-overlay--active')){
$('.lw-infobox__map-expand').trigger('click');
}
});

$overlay.on('click',function(e){
if(e.target===this){
$('.lw-infobox__map-expand').trigger('click');
}
});
});

/* Landrace.wiki – Leaflet map w/ SMW CSV export, Clustering, Labels & Hulls */
(function(){
var LVER='1.9.4';
var CDN='https://unpkg.com/leaflet@'+LVER+'/dist/';
var CLUSTER_CDN='https://unpkg.com/leaflet.markercluster@1.4.1/dist/';
var TURF_CDN='https://unpkg.com/@turf/turf@6/turf.min.js';

var BRAND={
primary:'#2d6a4f',
primaryLight:'#40916c',
accent:'#74c69d',
dark:'#1b4332',
neutral:'#495057',
light:'#f8f9fa',
white:'#ffffff'
};

var STATUS_COLORS={
'Critical':'#c92a2a',
'High':'#d9480f',
'Medium':'#e67700',
'Low':'#2b8a3e'
};

var STATUS_PRIORITY=['Critical','High','Medium','Low'];

/* Event maps (News Items / eradications) */
var EVENT_COLORS={
'Enforcement':'#c92a2a',
'Trafficking':'#d9480f',
'Policy':'#1c7ed6',
'Research':BRAND.primary,
'Fieldwork':BRAND.primaryLight,
'Community':BRAND.accent,
'Report':BRAND.neutral
};

var EVENT_PRIORITY=['Enforcement','Trafficking','Policy','Research','Fieldwork','Community','Report'];

function addCSS(href,id){
if(id && document.getElementById(id)) return;
var l=document.createElement('link');
l.rel='stylesheet';
l.href=href;
if(id) l.id=id;
document.head.appendChild(l);
}

function addJS(src,cb,id){
var existing=id ? document.getElementById(id) : document.querySelector('script[src="'+src+'"]');
if(existing){
if(existing.dataset.loaded==='1') return cb();
existing.addEventListener('load',cb);
return;
}
var s=document.createElement('script');
s.src=src;
if(id) s.id=id;
s.onload=function(){
s.dataset.loaded='1';
cb();
};
s.onerror=function(){console.error('[lw-map] Script failed to load:',src);};
document.head.appendChild(s);
}

function getWorstStatus(markers){
var worst='Low';
var worstIndex=STATUS_PRIORITY.indexOf(worst);
markers.forEach(function(marker){
var status=marker.options.status || 'Low';
var index=STATUS_PRIORITY.indexOf(status);
if(index!==-1 && index<worstIndex){
worst=status;
worstIndex=index;
}
});
return worst;
}

function getWorstStatusFromFeatures(features){
var worst='Low';
var worstIndex=STATUS_PRIORITY.indexOf(worst);
features.forEach(function(f){
var status=f.properties.status || 'Low';
var index=STATUS_PRIORITY.indexOf(status);
if(index!==-1 && index<worstIndex){
worst=status;
worstIndex=index;
}
});
return worst;
}

function getWorstCategory(markers){
var worst=EVENT_PRIORITY[EVENT_PRIORITY.length-1];
var worstIndex=EVENT_PRIORITY.indexOf(worst);
markers.forEach(function(marker){
var cat=marker.options.event_category || '';
var idx=EVENT_PRIORITY.indexOf(cat);
if(idx!==-1 && idx<worstIndex){
worst=cat;
worstIndex=idx;
}
});
return worst;
}

function injectStyles(){
if(document.getElementById('lw-map-styles')) return;
var style=document.createElement('style');
style.id='lw-map-styles';
style.textContent=[
'.lw-popup .leaflet-popup-content-wrapper {',
'  background: '+BRAND.white+';',
'  border-radius: 12px;',
'  box-shadow: 0 4px 20px rgba(0,0,0,0.12), 0 2px 6px rgba(0,0,0,0.08);',
'  padding: 0;',
'  overflow: hidden;',
'}',
'.lw-popup .leaflet-popup-content {',
'  margin: 0;',
'  min-width: 220px;',
'  max-width: 280px;',
'}',
'.lw-popup .leaflet-popup-tip {',
'  background: '+BRAND.white+';',
'  box-shadow: 0 2px 6px rgba(0,0,0,0.1);',
'}',
'.lw-popup .leaflet-popup-close-button {',
'  color: '+BRAND.neutral+' !important;',
'  font-size: 20px !important;',
'  padding: 8px 10px !important;',
'  right: 4px !important;',
'  top: 4px !important;',
'  transition: color 0.2s ease;',
'}',
'.lw-popup .leaflet-popup-close-button:hover {',
'  color: '+BRAND.dark+' !important;',
'}',
'.lw-popup-inner {',
'  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;',
'}',
'.lw-popup-header {',
'  background: linear-gradient(135deg, '+BRAND.primary+' 0%, '+BRAND.dark+' 100%);',
'  padding: 16px 18px;',
'  color: '+BRAND.white+';',
'}',
'.lw-popup-id {',
'  font-size: 10px;',
'  font-weight: 600;',
'  letter-spacing: 0.5px;',
'  text-transform: uppercase;',
'  opacity: 0.85;',
'  margin-bottom: 4px;',
'}',
'.lw-popup-name {',
'  font-size: 15px;',
'  font-weight: 600;',
'  line-height: 1.3;',
'}',
'.lw-popup-body {',
'  padding: 14px 18px;',
'}',
'.lw-popup-meta {',
'  display: flex;',
'  flex-direction: column;',
'  align-items: flex-start;',
'  gap: 6px;',
'  margin-bottom: 8px;',
'}',
'.lw-popup-label {',
'  font-size: 11px;',
'  color: '+BRAND.neutral+';',
'  font-weight: 500;',
'  text-transform: uppercase;',
'  letter-spacing: 0.3px;',
'}',
'.lw-popup-status {',
'  display: inline-flex;',
'  align-items: center;',
'  padding: 4px 10px;',
'  border-radius: 20px;',
'  font-size: 11px;',
'  font-weight: 600;',
'  background: '+BRAND.light+';',
'  color: '+BRAND.neutral+';',
'}',
'.lw-popup-status::before {',
'  content: "";',
'  width: 6px;',
'  height: 6px;',
'  border-radius: 50%;',
'  margin-right: 6px;',
'  background: currentColor;',
'}',
'.lw-popup-status--critical { color: #c92a2a; background: #fff5f5; }',
'.lw-popup-status--high { color: #d9480f; background: #fff4e6; }',
'.lw-popup-status--medium { color: #e67700; background: #fff9db; }',
'.lw-popup-status--low { color: #2b8a3e; background: #ebfbee; }',
'.lw-popup-link {',
'  display: block;',
'  text-align: center;',
'  padding: 10px 18px;',
'  background: '+BRAND.light+';',
'  color: '+BRAND.primary+';',
'  text-decoration: none;',
'  font-size: 13px;',
'  font-weight: 600;',
'  transition: background 0.2s ease, color 0.2s ease;',
'  border-top: 1px solid #e9ecef;',
'}',
'.lw-popup-link:hover {',
'  background: '+BRAND.primary+';',
'  color: '+BRAND.white+';',
'}',
'.lw-map-tooltip {',
'  background: '+BRAND.dark+' !important;',
'  border: none !important;',
'  border-radius: 6px !important;',
'  color: '+BRAND.white+' !important;',
'  font-size: 12px !important;',
'  font-weight: 500 !important;',
'  padding: 6px 10px !important;',
'  box-shadow: 0 2px 8px rgba(0,0,0,0.2) !important;',
'}',
'.lw-map-tooltip::before {',
'  border-top-color: '+BRAND.dark+' !important;',
'}',
'.lw-layer-toggle {',
'  display: flex;',
'  background: '+BRAND.white+';',
'  border-radius: 8px;',
'  box-shadow: 0 2px 8px rgba(0,0,0,0.12);',
'  overflow: hidden;',
'}',
'.lw-layer-btn {',
'  display: flex;',
'  align-items: center;',
'  justify-content: center;',
'  width: 36px;',
'  height: 36px;',
'  border: none;',
'  background: '+BRAND.white+';',
'  color: '+BRAND.neutral+';',
'  cursor: pointer;',
'  transition: all 0.2s ease;',
'}',
'.lw-layer-btn:hover {',
'  background: '+BRAND.light+';',
'  color: '+BRAND.primary+';',
'}',
'.lw-layer-btn--active {',
'  background: '+BRAND.primary+' !important;',
'  color: '+BRAND.white+' !important;',
'}',
'.lw-layer-btn:not(:last-child) {',
'  border-right: 1px solid #e9ecef;',
'}',
'.lw-layer-btn svg {',
'  pointer-events: none;',
'}',
'/* Cluster styles */',
'.lw-cluster-icon {',
'  background: transparent !important;',
'}',
'.lw-cluster {',
'  width: 40px;',
'  height: 40px;',
'  border-radius: 50%;',
'  color: #fff;',
'  font-weight: 700;',
'  font-size: 14px;',
'  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;',
'  display: flex;',
'  align-items: center;',
'  justify-content: center;',
'  box-shadow: 0 3px 10px rgba(0,0,0,0.25);',
'  border: 3px solid rgba(255,255,255,0.9);',
'  transition: transform 0.2s ease;',
'}',
'.lw-cluster:hover {',
'  transform: scale(1.1);',
'}',
'.lw-cluster--small {',
'  width: 36px;',
'  height: 36px;',
'  font-size: 13px;',
'}',
'.lw-cluster--large {',
'  width: 48px;',
'  height: 48px;',
'  font-size: 15px;',
'}',
'.marker-cluster {',
'  background: transparent !important;',
'}',
'.marker-cluster div {',
'  background: transparent !important;',
'}',
'/* Hull polygons */',
'.lw-hull-tooltip {',
'  background: '+BRAND.white+' !important;',
'  border: 1px solid '+BRAND.primary+' !important;',
'  border-radius: 6px !important;',
'  color: '+BRAND.dark+' !important;',
'  font-size: 12px !important;',
'  font-weight: 600 !important;',
'  padding: 6px 10px !important;',
'  box-shadow: 0 2px 8px rgba(0,0,0,0.15) !important;',
'}'
].join('\n');
document.head.appendChild(style);
}

function esc(s){
return String(s==null ? '' : s).replace(/[&<>"']/g,function(c){
return({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]);
});
}

function statusClass(s){
var status=String(s || '').toLowerCase();
if(status==='critical') return 'lw-popup-status--critical';
if(status==='high') return 'lw-popup-status--high';
if(status==='medium') return 'lw-popup-status--medium';
if(status==='low') return 'lw-popup-status--low';
return '';
}

function popupForAccession(p){
var id=p.id || '';
var name=p.name || 'Unknown';
var status=p.status || '';
var url=p.page_url || '#';

var statusHtml='';
if(status){
statusHtml='<div class="lw-popup-meta">'+
'<span class="lw-popup-label">Conservation Priority</span>'+
'<span class="lw-popup-status '+statusClass(status)+'">'+esc(status)+'</span>'+
'</div>';
}

return '<div class="lw-popup-inner">'+
'<div class="lw-popup-header">'+
(id ? '<div class="lw-popup-id">'+esc(id)+'</div>' : '')+
'<div class="lw-popup-name">'+esc(name)+'</div>'+
'</div>'+
'<div class="lw-popup-body">'+
statusHtml+
'</div>'+
'<a href="'+esc(url)+'" class="lw-popup-link">View Accession →</a>'+
'</div>';
}

function popupForNewsItem(p){
var title=p.name || 'News Item';
var date=p.event_date || '';
var cat=p.event_category || '';
var loc=[p.locality,p.admin2,p.admin1].filter(Boolean).join(', ');
var url=p.page_url || '#';
var src=p.source_url || '';

var blocks='';
if(date){
blocks+='<div class="lw-popup-meta"><span class="lw-popup-label">Date</span><span style="font-size:14px;font-weight:700;color:'+BRAND.dark+'">'+esc(date)+'</span></div>';
}
if(loc){
blocks+='<div class="lw-popup-meta"><span class="lw-popup-label">Location</span><span style="font-size:13px;color:'+BRAND.neutral+'">'+esc(loc)+'</span></div>';
}
if(cat){
blocks+='<div class="lw-popup-meta"><span class="lw-popup-label">Category</span><span style="font-size:13px;color:'+BRAND.neutral+'">'+esc(cat)+'</span></div>';
}

var sourceBtn=src ? ('<a href="'+esc(src)+'" target="_blank" rel="noreferrer" class="lw-popup-link" style="border-top:0">Source ↗</a>') : '';

return '<div class="lw-popup-inner">'+
'<div class="lw-popup-header">'+
'<div class="lw-popup-id">News Item</div>'+
'<div class="lw-popup-name">'+esc(title)+'</div>'+
'</div>'+
'<div class="lw-popup-body">'+
blocks+
'</div>'+
'<a href="'+esc(url)+'" class="lw-popup-link">View Article →</a>'+
sourceBtn+
'</div>';
}

function popupForArea(name,count,worstStatus,url){
var statusHtml='';
if(worstStatus){
statusHtml='<div class="lw-popup-meta">'+
'<span class="lw-popup-label">Worst Status in Area</span>'+
'<span class="lw-popup-status '+statusClass(worstStatus)+'">'+esc(worstStatus)+'</span>'+
'</div>';
}

return '<div class="lw-popup-inner">'+
'<div class="lw-popup-header">'+
'<div class="lw-popup-id">Growing Area</div>'+
'<div class="lw-popup-name">'+esc(name)+'</div>'+
'</div>'+
'<div class="lw-popup-body">'+
'<div class="lw-popup-meta">'+
'<span class="lw-popup-label">Accessions</span>'+
'<span style="font-size:18px;font-weight:700;color:'+BRAND.dark+'">'+count+'</span>'+
'</div>'+
statusHtml+
'</div>'+
'<a href="'+esc(url)+'" class="lw-popup-link">View Growing Area →</a>'+
'</div>';
}

function parseCoords(coordStr){
if(!coordStr) return null;

var decimalMatch=coordStr.match(/(-?\d+\.?\d*)\s*,\s*(-?\d+\.?\d*)/);
if(decimalMatch){
var lat=parseFloat(decimalMatch[1]);
var lon=parseFloat(decimalMatch[2]);
if(!isNaN(lat) && !isNaN(lon)) return {lat:lat,lon:lon};
}

var dmsRegex=/(\d+)[°]\s*(\d+)[′']\s*(\d+\.?\d*)[″"]?\s*([NSEW])/gi;
var matches=[];
var m;
while((m=dmsRegex.exec(coordStr))!==null){
matches.push(m);
if(matches.length>=2) break;
}

if(matches.length>=2){
var lat2=dmsToDecimal(matches[0][1],matches[0][2],matches[0][3],matches[0][4]);
var lon2=dmsToDecimal(matches[1][1],matches[1][2],matches[1][3],matches[1][4]);

var h0=matches[0][4].toUpperCase();
if(h0==='E' || h0==='W'){
var tmp=lat2; lat2=lon2; lon2=tmp;
}

if(lat2!==null && lon2!==null) return {lat:lat2,lon:lon2};
}

return null;
}

function dmsToDecimal(deg,min,sec,hemi){
var d=parseFloat(deg);
var m=parseFloat(min) || 0;
var s=parseFloat(sec) || 0;
if(isNaN(d)) return null;
var dec=Math.abs(d)+(m/60)+(s/3600);
hemi=String(hemi || '').toUpperCase();
if(hemi==='S' || hemi==='W') dec=-dec;
return dec;
}

function parseCSV(text){
var lines=text.trim().split('\n');
if(lines.length<2) return [];
var headers=parseCSVLine(lines[0]);

var results=[];
for(var i=1;i<lines.length;i++){
var values=parseCSVLine(lines[i]);
var obj={};
for(var j=0;j<headers.length;j++){
obj[headers[j]]=values[j] || '';
}
results.push(obj);
}
return results;
}

function parseCSVLine(line){
var result=[];
var current='';
var inQuotes=false;

for(var i=0;i<line.length;i++){
var c=line[i];
var next=line[i+1];

if(inQuotes){
if(c === '"' && next === '"'){
current+='"';
i++;
}else if(c === '"'){
inQuotes=false;
}else{
current+=c;
}
}else{
if(c === '"'){
inQuotes=true;
}else if(c === ','){
result.push(current.trim());
current='';
}else{
current+=c;
}
}
}
result.push(current.trim());
return result;
}

function csvToGeoJSON(data){
var features=[];

data.forEach(function(row){
var coordStr=row['Has coordinates'] || row['Has GPS coordinates'] || row['GPS coordinates'] || '';
var coords=parseCoords(coordStr);
if(!coords) return;

var pageName=row[''] || Object.values(row)[0] || '';
var pageUrl='/wiki/' + encodeURIComponent(pageName.replace(/ /g,'_'));

var isNews=!!(row['Has event headline'] || row['Has event date'] || row['Has event category']);

if(isNews){
var headline=row['Has event headline'] || pageName;
var eventDate=row['Has event date'] || '';
var eventCat=row['Has event category'] || '';
var admin1=row['Has admin subdivision 1'] || '';
var admin2=row['Has admin subdivision 2'] || '';
var locality=row['Has locality'] || '';
var sourceUrl=row['Has source'] || '';

features.push({
type:'Feature',
properties:{
name:headline,
event_date:eventDate,
event_category:eventCat,
admin1:admin1,
admin2:admin2,
locality:locality,
source_url:sourceUrl,
page_url:pageUrl
},
geometry:{type:'Point',coordinates:[coords.lon,coords.lat]}
});
return;
}

/* Default: Accessions */
var name=row['Has descriptive name'] || row['descriptive name'] || pageName;
var status=row['Has conservation priority'] || row['conservation priority'] || '';
var accessionId=row['Has accession ID'] || row['accession ID'] || '';
var growingArea=row['Has growing area'] || row['growing area'] || '';
var growingRegion=row['Has growing region'] || row['growing region'] || '';

features.push({
type:'Feature',
properties:{
id:accessionId,
name:name,
status:status,
growing_area:growingArea,
growing_region:growingRegion,
page_url:pageUrl
},
geometry:{type:'Point',coordinates:[coords.lon,coords.lat]}
});
});

console.log('[lw-map] Converted',features.length,'features from CSV');
return {type:'FeatureCollection',features:features};
}

function buildCSVUrl(query){
var parts=query.split('|');
var encodedParts=parts.map(function(part){
return part
.replace(/-/g,'-2D')
.replace(/\[/g,'-5B')
.replace(/\]/g,'-5D')
.replace(/\?/g,'-3F')
.replace(/:/g,'-3A')
.replace(/ /g,'-20');
});

var baseUrl=mw.config.get('wgScriptPath') || '';
return baseUrl+'/wiki/Special:Ask/'+encodedParts.join('/')+'/format=csv';
}

function fetchCSV(query,cb){
var url=buildCSVUrl(query);
console.log('[lw-map] Fetching CSV from:',url);

fetch(url)
.then(function(r){
if(!r.ok) throw new Error('CSV fetch error: '+r.status);
return r.text();
})
.then(function(text){
console.log('[lw-map] CSV response length:',text.length);
var data=parseCSV(text);
console.log('[lw-map] Parsed',data.length,'rows');
var geojson=csvToGeoJSON(data);
cb(null,geojson);
})
.catch(function(err){
console.error('[lw-map] CSV fetch error:',err);
cb(err,null);
});
}

/* Generate hull for a set of points using Turf.js */
function generateHull(features){
if(!window.turf || features.length<3) return null;

try{
var points=turf.featureCollection(
features.map(function(f){ return turf.point(f.geometry.coordinates); })
);

var hull;
if(features.length>=4){
try{
hull=turf.concave(points,{maxEdge:100,units:'kilometers'});
}catch(e){
console.log('[lw-map] Concave hull failed, falling back to convex');
}
}

if(!hull){
hull=turf.convex(points);
}

if(hull){
hull=turf.buffer(hull,3,{units:'kilometers'});
}

return hull;
}catch(e){
console.error('[lw-map] Hull generation error:',e);
return null;
}
}

function groupBy(features,prop){
var groups={};
features.forEach(function(f){
var key=f.properties[prop] || 'Unknown';
if(!groups[key]) groups[key]=[];
groups[key].push(f);
});
return groups;
}

function initOne(el){
if(el.dataset.init) return;
el.dataset.init='1';
if(el.clientHeight<100) el.style.height='70vh';

injectStyles();

var map=L.map(el,{
minZoom:2,
maxZoom:17,
zoomControl:false
}).setView([26.4,89.5],8);

el._leaflet_map=map;

L.control.zoom({position:'topright'}).addTo(map);

var baseLayers={
basic:L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png',{
attribution:'© <a href="https://www.openstreetmap.org/copyright">OSM</a> © <a href="https://carto.com/attributions">CARTO</a>',
subdomains:'abcd',
maxZoom:19
}),
terrain:L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}',{
attribution:'© <a href="https://www.esri.com">Esri</a>',
maxZoom:18
}),
satellite:L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',{
attribution:'© <a href="https://www.esri.com">Esri</a>',
maxZoom:18
})
};

baseLayers.basic.addTo(map);
var currentLayer='basic';

var layerToggle=L.control({position:'topleft'});
layerToggle.onAdd=function(){
var container=L.DomUtil.create('div','lw-layer-toggle');
container.innerHTML=
'<button class="lw-layer-btn lw-layer-btn--active" data-layer="basic" title="Basic view">'+
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>'+
'</button>'+
'<button class="lw-layer-btn" data-layer="terrain" title="Terrain view">'+
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 21l4-10 4 10M12 11V3M4 21l4.5-9L12 17l3.5-5L20 21"/></svg>'+
'</button>'+
'<button class="lw-layer-btn" data-layer="satellite" title="Satellite view">'+
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20M2 12h20"/></svg>'+
'</button>';

L.DomEvent.disableClickPropagation(container);

container.querySelectorAll('.lw-layer-btn').forEach(function(btn){
btn.addEventListener('click',function(){
var layer=this.dataset.layer;
if(layer===currentLayer) return;

map.removeLayer(baseLayers[currentLayer]);
baseLayers[layer].addTo(map);
currentLayer=layer;

container.querySelectorAll('.lw-layer-btn').forEach(function(b){
b.classList.remove('lw-layer-btn--active');
});
this.classList.add('lw-layer-btn--active');
});
});

return container;
};
layerToggle.addTo(map);

var query=el.dataset.smwQuery;
if(!query){
console.log('[lw-map] No data-smw-query attribute found');
return;
}

var mapType=el.dataset.mapType || '';

/* Check if this is an infobox map */
var isInfoboxMap=el.closest('.lw-infobox') !== null;

/* Layer group for area hulls */
var areaHullsLayer=L.layerGroup();

/* Update visibility based on zoom */
function updateLayerVisibility(){
var zoom=map.getZoom();
if(zoom>=6 && zoom<=12){
if(!map.hasLayer(areaHullsLayer)) map.addLayer(areaHullsLayer);
}else{
if(map.hasLayer(areaHullsLayer)) map.removeLayer(areaHullsLayer);
}
}

/* For main map, add growing area to query to enable grouping (accessions only) */
var fullQuery=query;
if(!isInfoboxMap && query.indexOf('Category:Accessions') !== -1){
fullQuery='[[Category:Accessions]]|?Has coordinates|?Has descriptive name|?Has conservation priority|?Has accession ID|?Has growing area|?Has growing region';
}

fetchCSV(fullQuery,function(err,geojson){
if(err || !geojson || !geojson.features || !geojson.features.length){
var msg=L.control({position:'bottomleft'});
msg.onAdd=function(){
var d=L.DomUtil.create('div');
d.style.cssText='background:white;padding:12px 16px;border-radius:8px;font-size:13px;color:#495057;box-shadow:0 2px 8px rgba(0,0,0,0.1);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;';
d.innerHTML='<strong>No results found</strong><br><span style="opacity:0.7;">No mappable results for this query.</span>';
return d;
};
msg.addTo(map);
return;
}

/* Generate area hulls for main map (accessions only) */
if(!isInfoboxMap && window.turf && mapType!=='eradication'){
var areaGroups=groupBy(geojson.features,'growing_area');

Object.keys(areaGroups).forEach(function(areaName){
if(areaName==='Unknown' || areaName==='') return;

var areaFeatures=areaGroups[areaName];
if(areaFeatures.length<3) return;

var hull=generateHull(areaFeatures);
if(!hull) return;

var worstStatus=getWorstStatusFromFeatures(areaFeatures);
var color=STATUS_COLORS[worstStatus] || BRAND.primary;

var hullLayer=L.geoJSON(hull,{
style:{
fillColor:color,
fillOpacity:0.12,
color:color,
weight:2,
dashArray:'6, 4',
lineCap:'round'
}
});

hullLayer.bindPopup(
popupForArea(areaName,areaFeatures.length,worstStatus,'/wiki/'+encodeURIComponent(areaName.replace(/ /g,'_'))),
{className:'lw-popup',maxWidth:300}
);

hullLayer.bindTooltip(areaName+' ('+areaFeatures.length+')',{
className:'lw-hull-tooltip',
direction:'center',
permanent:false
});

hullLayer.on('mouseover',function(e){
e.target.setStyle({fillOpacity:0.25,weight:3});
});
hullLayer.on('mouseout',function(e){
e.target.setStyle({fillOpacity:0.12,weight:2});
});

areaHullsLayer.addLayer(hullLayer);
});

map.on('zoomend',updateLayerVisibility);
updateLayerVisibility();
}

/* Add markers */
if(isInfoboxMap || !window.L.markerClusterGroup){
var layer=L.geoJSON(geojson,{
pointToLayer:function(f,ll){
var p=f.properties || {};
var status=p.status || '';
var cat=p.event_category || '';
var color=(mapType==='eradication') ? (EVENT_COLORS[cat] || BRAND.primary) : (STATUS_COLORS[status] || BRAND.primary);
return L.circleMarker(ll,{
radius:8,
color:BRAND.white,
weight:2.5,
fillColor:color,
fillOpacity:0.9,
status:status,
event_category:cat
});
},
onEachFeature:function(f,ly){
var p=f.properties || {};

ly.bindPopup((mapType==='eradication') ? popupForNewsItem(p) : popupForAccession(p),{
className:'lw-popup',
closeButton:true,
maxWidth:300,
offset:[0,-5]
});

var tip=(mapType==='eradication') ? (p.name || '') : (p.name || p.id || '');
if(tip){
ly.bindTooltip(esc(tip),{
direction:'top',
offset:[0,-10],
opacity:1,
className:'lw-map-tooltip'
});
}

ly.on('mouseover',function(){
this.setStyle({fillColor:BRAND.primaryLight,radius:10});
});
ly.on('mouseout',function(){
var pp=f.properties || {};
var status2=pp.status || '';
var cat2=pp.event_category || '';
var color2=(mapType==='eradication') ? (EVENT_COLORS[cat2] || BRAND.primary) : (STATUS_COLORS[status2] || BRAND.primary);
this.setStyle({fillColor:color2,radius:8});
});
}
}).addTo(map);

var b=layer.getBounds();
if(b && b.isValid()) map.fitBounds(b.pad(0.3),{maxZoom:11});
}else{
var markers=L.markerClusterGroup({
maxClusterRadius:50,
spiderfyOnMaxZoom:true,
showCoverageOnHover:false,
zoomToBoundsOnClick:true,
iconCreateFunction:function(cluster){
var children=cluster.getAllChildMarkers();
var count=children.length;
var color;
if(mapType==='eradication'){
var worstCat=getWorstCategory(children);
color=EVENT_COLORS[worstCat] || BRAND.primary;
}else{
var worstStatus=getWorstStatus(children);
color=STATUS_COLORS[worstStatus] || BRAND.primary;
}

var sizeClass='lw-cluster';
if(count<10) sizeClass+=' lw-cluster--small';
else if(count>=50) sizeClass+=' lw-cluster--large';

return L.divIcon({
html:'<div class="'+sizeClass+'" style="background:'+color+'">'+count+'</div>',
className:'lw-cluster-icon',
iconSize:[40,40]
});
}
});

geojson.features.forEach(function(f){
var coords=f.geometry.coordinates;
var p=f.properties || {};
var status=p.status || '';
var cat=p.event_category || '';
var color=(mapType==='eradication') ? (EVENT_COLORS[cat] || BRAND.primary) : (STATUS_COLORS[status] || BRAND.primary);

var marker=L.circleMarker([coords[1],coords[0]],{
radius:8,
color:BRAND.white,
weight:2.5,
fillColor:color,
fillOpacity:0.9,
status:status,
event_category:cat
});

marker.bindPopup((mapType==='eradication') ? popupForNewsItem(p) : popupForAccession(p),{
className:'lw-popup',
closeButton:true,
maxWidth:300,
offset:[0,-5]
});

var tip=(mapType==='eradication') ? (p.name || '') : (p.name || p.id || '');
if(tip){
marker.bindTooltip(esc(tip),{
direction:'top',
offset:[0,-10],
opacity:1,
className:'lw-map-tooltip'
});
}

marker.on('mouseover',function(){
this.setStyle({fillColor:BRAND.primaryLight,radius:10});
});
marker.on('mouseout',function(){
this.setStyle({fillColor:color,radius:8});
});

markers.addLayer(marker);
});

map.addLayer(markers);

var b2=markers.getBounds();
if(b2 && b2.isValid()) map.fitBounds(b2.pad(0.3),{maxZoom:11});
}

setTimeout(function(){ map.invalidateSize(true); },100);
});
}

function init(root){
(root || document).querySelectorAll('.lw-map').forEach(initOne);
}

/* Load CSS */
addCSS(CDN+'leaflet.css','leaflet-css');
addCSS(CLUSTER_CDN+'MarkerCluster.css','markercluster-css');
addCSS(CLUSTER_CDN+'MarkerCluster.Default.css','markercluster-default-css');

/* Load JS: Leaflet → MarkerCluster → Turf → init */
addJS(CDN+'leaflet.js',function(){
addJS(CLUSTER_CDN+'leaflet.markercluster.js',function(){
addJS(TURF_CDN,function(){
console.log('[lw-map] All libraries loaded, initializing maps');
init();
if(window.mw && mw.hook){
mw.hook('wikipage.content').add(function($c){
init($c && $c[0] ? $c[0] : document);
});
}
},'turf-js');
},'markercluster-js');
},'leaflet-js');
})();

/**
 * Auto-preload templates for new talk pages
 * Detects the category of the subject page and loads the appropriate talk template
 */
(function(){
'use strict';

/* Only run on edit action for new pages in Talk namespace */
if(mw.config.get('wgAction')!=='edit') return;
if(mw.config.get('wgArticleId')!==0) return; /* Page already exists */
if(mw.config.get('wgNamespaceNumber')!==1) return; /* Not Talk namespace */

var mainPageTitle=mw.config.get('wgTitle'); /* Subject page name */

/* Map categories to preload templates (checked in order) */
var categoryToPreload={
'Category:Countries':'Template:Talk preload/Country',
'Category:States':'Template:Talk preload/State',
'Category:Growing Regions':'Template:Talk preload/Growing region',
'Category:Growing Areas':'Template:Talk preload/Growing area',
'Category:Accessions':'Template:Talk preload/Accession'
};

/* Default fallback template */
var defaultPreload='Template:Talk preload/Default';

mw.loader.using(['mediawiki.api'],function(){
var api=new mw.Api();

$(function(){
var $textbox=$('#wpTextbox1');
if($textbox.val().trim()) return; /* Already has content */

/* Get categories of the subject page */
api.get({
action:'query',
titles:mainPageTitle,
prop:'categories',
cllimit:50,
format:'json'
}).done(function(data){
var pages=data.query.pages;
var categories=[];

for(var id in pages){
if(pages[id].categories){
categories=pages[id].categories.map(function(c){ return c.title; });
}
}

/* Find matching preload template */
var preloadTemplate=defaultPreload;
for(var cat in categoryToPreload){
if(categories.indexOf(cat)!==-1){
preloadTemplate=categoryToPreload[cat];
break;
}
}

/* Fetch and insert the preload template content */
api.get({
action:'query',
titles:preloadTemplate,
prop:'revisions',
rvprop:'content',
rvslots:'main',
format:'json'
}).done(function(data){
var pages=data.query.pages;
for(var id in pages){
if(pages[id].revisions){
var content=pages[id].revisions[0].slots.main['*'];
content=content.replace(/<noinclude>[\s\S]*?<\/noinclude>/g,'');
content=content.trim();
$textbox.val(content);
}
}
}).fail(function(){
$textbox.val('{{TalkHeader}}\n\n== Discussion ==\n');
});
});
});
});
})();
// </nowiki>