// ==UserScript== // @author Heistergand // @name Fan Fields 2 // @id fanfields@heistergand // @category Layer // @version 2.6.2.20240630 // @description Calculate how to link the portals to create the largest tidy set of nested fields. Enable from the layer chooser. // @downloadURL https://raw.githubusercontent.com/IITC-CE/Community-plugins/master/dist/heistergand/fanfields.user.js // @updateURL https://raw.githubusercontent.com/IITC-CE/Community-plugins/master/dist/heistergand/fanfields.meta.js // @icon https://raw.githubusercontent.com/Heistergand/fanfields2/master/fanfields2-32.png // @icon64 https://raw.githubusercontent.com/Heistergand/fanfields2/master/fanfields2-64.png // @supportURL https://github.com/Heistergand/fanfields2/issues // @namespace https://github.com/Heistergand/fanfields2 // @issueTracker https://github.com/Heistergand/fanfields2/issues // @homepageURL https://github.com/Heistergand/fanfields2/ // @depends draw-tools@breunigs // @recommends bookmarks@ZasoGD|draw-tools-plus@zaso|liveInventory@DanielOnDiordna|keys@xelio // @preview https://raw.githubusercontent.com/Heistergand/fanfields2/master/FanFields2.png // @match https://intel.ingress.com/* // @include https://intel.ingress.com/* // @grant none // ==/UserScript== /* Version History: -- Version History moved into the code. Todo: Add a kind of system to have a cluster of Fanfields Calculate distance to walk for the plan (crow / streets) Calculate the most efficient possible plan based on ways to walk and keys to farm Export to Tasks Bookmarks saving works, but let it also save into a Bookmarks Folder Calculate amount of possible rebuilds after flippinig the center portal Click on a link to flip it's direction */ function wrapper(plugin_info) { // ensure plugin framework is there, even if iitc is not yet loaded if(typeof window.plugin !== 'function') window.plugin = function() {}; plugin_info.buildName = 'main'; plugin_info.dateTimeVersion = '2024-04-10-233042'; plugin_info.pluginId = 'fanfields'; /* global L -- eslint */ /* exported setup, changelog --eslint */ let arcname = window.PLAYER.team === 'ENLIGHTENED' ? 'Arc' : '***'; var changelog = [ { version: '2.6.2', changes: [ 'NEW: Task list now contains a single navigation link for each portal.', ], }, { version: '2.6.1', changes: [ 'FIX: Counts of outgoing links and sbul are now correct when respecting intel and using outbounding mode.', ], }, { version: '2.6.0', changes: [ 'NEW: Add control buttons for better ux on mobile.', ], }, { version: '2.5.6', changes: [ 'NEW: Implementing link details in show-as-list dialog.', ], }, { version: '2.5.5', changes: [ 'FIX: Plugin did not work on IITC-Mobile.', ], }, { version: '2.5.4', changes: [ 'NEW: Option to only use bookmarked portals within the Fanfields (Toggle-Button)', ], }, { version: '2.5.3', changes: [ 'NEW: Saving to Bookmarks now creates a folder in the Bookmarks list.', ], }, { version: '2.5.2', changes: [ 'FIX: Prefer LiveInventory Plugin over Keys Plugin (hotfix)', ], }, { version: '2.5.1', changes: [ 'FIX: Prefer LiveInventory Plugin over Keys Plugin', ], }, { version: '2.5.0', changes: [ 'NEW: Integrate key counts from LiveInventory plugin.', ], }, { version: '2.4.1', changes: [ 'FIX: "Show as List" without having the Keys Plugin did not show any Keys.', ], }, { version: '2.4.0', changes: [ 'NEW: Integrate functionality with Key Plugin.', 'NEW: Replace fieldset box design with a separated sidebar box.', ], }, { version: '2.3.2', changes: [ 'NEW: Introducing code for upcoming multiple fanfields by Drawtools Colors', 'FIX: some code refactorings', 'FIX: SBUL defaults to 2 now, assuming most fields are done solo.', 'FIX: If a marker is not actually snapped onto a portal it does not act as fan point anymore.', 'FIX: When adding a marker, it\'s now selected as start portal.', ], }, { version: '2.3.1', changes: [ 'FIX: Portals were difficult to select underneath the fanfileds plan.', ], }, { version: '2.3.0', changes: [ 'NEW: Added '+arcname+' support.', ], }, { version: '2.2.9', changes: [ 'FIX: Link direction indicator did not work anymore.', 'NEW: Link direction indicator is now optional.', 'NEW: New plugin icon showing a hand fan.', ], }, { version: '2.2.8', changes: [ 'FIX: minor changes', ], }, { version: '2.2.7', changes: [ 'FIX: Menue Buttons in Mobile version are now actually buttons.', ], }, { version: '2.2.6', changes: [ 'NEW: Google Maps Portal Routing', ], }, { version: '2.2.5', changes: [ 'NEW: Set how many SBUL you plan to use.', 'FIX: Anchor shift button design changed', ], }, { version: '2.2.4', changes: [ 'FIX: Fixed what should have been fixed in 2.2.4', ], }, { version: '2.2.4', changes: [ 'FIX: Width of dialog boxes did extend screen size', ], }, { version: '2.2.3', changes: [ 'FIX: Made Bookmark Plugin optional', 'NEW: Anchor shifting ("Cycle Start") is now bidirectional.', 'FIX: Some minor fixes and code formatting.', ], }, { version: '2.2.2', changes: [ 'NEW: Added favicon.ico to script header.', ], }, { version: '2.2.1', changes: [ 'FIX: Merged from Jormund fork (2.1.7): Fixed L.LatLng extension', ], }, { version: '2.2.0', changes: [ 'FIX: Reintroducing the marker function which was removed in 2.1.7 so that a Drawtools Marker can be used to force a portal inside (or outside) the hull to be the anchor.', ], }, { version: '2.1.10', changes: [ 'FIX: minor fixes', ], }, { version: '2.1.9', changes: [ 'FIX: minor fixes', ], }, { version: '2.1.9', changes: [ 'FIX: Fixed blank in header for compatibility with IITC-CE Button.', ], }, { version: '2.1.9', changes: [ 'FIX: Fix for missing constants in leaflet verion 1.6.0.', ], }, { version: '2.1.8', changes: [ 'NEW: Added starting portal advance button to select among the list of perimeter portals.', ], }, { version: '2.1.7', changes: [ 'DEL: Removed marker and random selection of starting point portal.', 'NEW: Replaced with use of first outer hull portal. This ensures maximum fields will be generated.', ], }, { version: '2.1.5', changes: [ 'FIX: Minor syntax issue affecting potentially more strict runtimes', ], }, { version: '2.1.4', changes: [ 'FIX: Make the clockwise button change its label to "Counterclockwise" when toggled', ], }, { version: '2.1.3', changes: [ 'FIX: added id tags to menu button elements, ...just because.', ], }, { version: '2.1.2', changes: [ 'FIX: Minor issues', ], }, { version: '2.1.1', changes: [ 'FIX: changed List export format to display as a table', ], }, { version: '2.1.0', changes: [ 'NEW: Added save to DrawTools functionality', 'NEW: Added fanfield statistics', 'FIX: Changed some menu texts', 'VER: Increased Minor Version due to DrawTools Milestone', ], }, { version: '2.0.9', changes: [ 'NEW: Added the number of outgoing links to the simple list export', ], }, { version: '2.0.8', changes: [ 'NEW: Toggle the direction of the star-links (Inbound/Outbound) and calculate number of SBUL', 'FIX: Despite crosslinks, respecting the current intel did not handle done links', ], }, { version: '2.0.7', changes: [ 'FIX: Sorting of the portals was not accurate for far distance anchors when the angle was too equal.', 'NEW: Added option to respect current intel and not crossing lines.', ], }, { version: '2.0.6', changes: [ 'FIX: Plan messed up on multiple polygons.', ], }, { version: '2.0.5', changes: [ 'FIX: fan links abandoned when Marker was outside the polygon', 'BUG: Issue found where plan messes up when using more than one polygon (fixed in 2.0.6)', ], }, { version: '2.0.4', changes: [ 'NEW: Added Lock/Unlock button to freeze the plan and prevent recalculation on any events.', 'NEW: Added a simple text export (in a dialog box)', 'FIX: Several changes to the algorithm', 'BUG: Issue found where links are closing fields on top of portals that are successors in the list once you got around the startportal', ], }, { version: '2.0.3', changes: [ 'FIX: Counterclockwise did not work properly', 'NEW: Save as Bookmarks', ], }, { version: '2.0.2', changes: [ 'NEW: Added Menu', 'NEW: Added counterclockwise option', 'FIX: Minor Bugfixes', ], }, { version: '2.0.1', changes: [ 'NEW: Count keys to farm', 'NEW: Count total fields', 'NEW: Added labels to portals', 'FIX: Links were drawn in random order', 'FIX: Only fields to the center portal were drawn', ], }, ]; // PLUGIN START //////////////////////////////////////////////////////// // use own namespace for plugin /* jshint shadow:true */ window.plugin.fanfields = function() {}; var thisplugin = window.plugin.fanfields; // const values // zoom level used for projecting points between latLng and pixel coordinates. may affect precision of triangulation thisplugin.PROJECT_ZOOM = 16; thisplugin.LABEL_WIDTH = 100; thisplugin.LABEL_HEIGHT = 49; // constants no longer present in leaflet 1.6.0 thisplugin.DEG_TO_RAD = Math.PI / 180; thisplugin.RAD_TO_DEG = 180 / Math.PI; thisplugin.labelLayers = {}; thisplugin.startingpoint = undefined; thisplugin.availableSBUL = 2; thisplugin.locations = []; thisplugin.fanpoints = []; thisplugin.sortedFanpoints = []; thisplugin.perimeterpoints = []; thisplugin.startingpointIndex = 0; thisplugin.links = []; thisplugin.linksLayerGroup = null; thisplugin.fieldsLayerGroup = null; thisplugin.numbersLayerGroup = null; thisplugin.selectPolygon = function() {}; thisplugin.saveBookmarks = function() { // loop thru portals and UN-Select them for bkmrks var bkmrkData, list; thisplugin.sortedFanpoints.forEach(function(point, index) { bkmrkData = window.plugin.bookmarks.findByGuid(point.guid); if(bkmrkData) { list = window.plugin.bookmarks.bkmrksObj.portals; delete list[bkmrkData.id_folder].bkmrk[bkmrkData.id_bookmark]; $('.bkmrk#'+bkmrkData.id_bookmark + '').remove(); window.plugin.bookmarks.saveStorage(); window.plugin.bookmarks.updateStarPortal(); window.runHooks('pluginBkmrksEdit', {"target": "portal", "action": "remove", "folder": bkmrkData.id_folder, "id": bkmrkData.id_bookmark, "guid":point.guid}); console.log('Fanfields2: removed BOOKMARKS portal ('+bkmrkData.id_bookmark+' situated in '+bkmrkData.id_folder+' folder)'); } }); let type = "folder"; let label = 'Fanfields2'; // Add new folder in the localStorage let folder_ID = window.plugin.bookmarks.generateID(); window.plugin.bookmarks.bkmrksObj.portals[folder_ID] = {"label":label,"state":1,"bkmrk":{}}; window.plugin.bookmarks.saveStorage(); window.plugin.bookmarks.refreshBkmrks(); window.runHooks('pluginBkmrksEdit', {"target": type, "action": "add", "id": folder_ID}); console.log('Fanfields2: added BOOKMARKS '+type+' '+folder_ID); thisplugin.addPortalBookmark = function(guid, latlng, label, folder_ID) { var bookmark_ID = window.plugin.bookmarks.generateID(); // Add bookmark in the localStorage window.plugin.bookmarks.bkmrksObj.portals[folder_ID].bkmrk[bookmark_ID] = {"guid":guid,"latlng":latlng,"label":label}; window.plugin.bookmarks.saveStorage(); window.plugin.bookmarks.refreshBkmrks(); window.runHooks('pluginBkmrksEdit', {"target": "portal", "action": "add", "id": bookmark_ID, "guid": guid}); console.log('Fanfields2: added BOOKMARKS portal '+bookmark_ID); } // loop again: ordered(!) to add them as bookmarks thisplugin.sortedFanpoints.forEach(function(point, index) { if (point.guid) { var p = window.portals[point.guid]; var ll = p.getLatLng(); //plugin.bookmarks.addPortalBookmark(point.guid, ll.lat+','+ll.lng, p.options.data.title); thisplugin.addPortalBookmark(point.guid, ll.lat+','+ll.lng, p.options.data.title, folder_ID) } }); }; thisplugin.updateStartingPoint = function(i) { thisplugin.startingpointIndex = i; thisplugin.startingpointGUID = thisplugin.perimeterpoints[thisplugin.startingpointIndex][0]; thisplugin.startingpoint = this.fanpoints[thisplugin.startingpointGUID]; thisplugin.updateLayer(); } // cycle to next starting point on the convex hull list of portals thisplugin.nextStartingPoint = function() { // *** startingpoint handling is duplicated in updateLayer(). var i = thisplugin.startingpointIndex + 1; if (i >= thisplugin.perimeterpoints.length) { i = 0; } thisplugin.updateStartingPoint(i); }; thisplugin.previousStartingPoint = function() { var i = thisplugin.startingpointIndex - 1; if (i < 0) { i = thisplugin.perimeterpoints.length -1; } thisplugin.updateStartingPoint(i); }; thisplugin.generateTasks = function() {}; thisplugin.reset = function() {}; thisplugin.helpDialogWidth = 650; thisplugin.help = function() { var width = thisplugin.helpDialogWidth; if (thisplugin.MaxDialogWidth < thisplugin.helpDialogWidth) { width = thisplugin.MaxDialogWidth; } dialog({ html: '

Using Drawtools, draw one or more polygons around the portals you want to work with. '+ 'The Polygons can overlap each other or be completely seperated. All portals within the polygons '+ 'count to your planned fanfield.

'+ '

From the layer selector, enable the 3 Fanfields layer for links, fields and numbers. '+ 'The fanfield will be calculated and shown in red links on the intel. Link directions are indicated '+ 'by dashed links at the portal to lnk from.

'+ '

The script selects an anchor portal from the hull of all selected portals. Use the Cycle Start '+ 'Button to select another hull portal as anchor.

'+ '

If you want to use portal as anchor, which is inside the hull, (which is totally legitimate), '+ 'place a marker on a portal to enforce it to be a possible anchor. Again, use the Cycle Start '+ 'Button until the Start Portal is where you want it to be.

'+ '

A Fanfield can be done inbounding by farming many keys at a portal and then link to it by all '+ 'the other portals. It can also be done outbounding by star-linking from the start portal until the maximum '+ 'number of outgoing links is reached. You can toggle that for planning accordingly.

'+ '

You might need to plan your field around links you cannot or do not want to destroy. This is where the '+ 'Respect Intel button comes into play. Toggle this to plan your fanfield avoiding crosslinks.

'+ '

Use the Lock function to prevent the script from recalculating anything. This is useful '+ 'if you have a large area and want to zoom into details.

'+ '

Try to switch your plan to counterclockwise direction. Your route might be easier or harder '+ 'if you change directions. Also try different anchors to get one more field out of some portal '+ 'constellations.

'+ '

Copy your fanfield portals to bookmarks or drawtools to extend your possibilites to work '+ 'with the information.

'+ '
'+ '

Found a bug? Post your issues at GitHub:
https://github.com/Heistergand/fanfields2/issues

'+ '', id: 'plugin_fanfields_alert_help', title: 'Fan Fields - Help', width: width, closeOnEscape: true }); }; thisplugin.showStatistics = function() { var text = ""; if (this.sortedFanpoints.length > 3) { text = "" + "" + "" + "" + "" + //"" + "
FanPortals:" + (thisplugin.n-1) + "
CenterKeys:" + thisplugin.centerKeys +"
Total links / keys:" + thisplugin.donelinks.length.toString() +"
Fields:" + thisplugin.triangles.length.toString() +"
Build AP (links and fields):" + (thisplugin.donelinks.length*313 + thisplugin.triangles.length*1250).toString() +"
Destroy AP (links and fields):" + (thisplugin.sortedFanpoints.length*187 + thisplugin.triangles.length*750).toString() + "
"; var width = 400; if (thisplugin.MaxDialogWidth < width) { width = thisplugin.MaxDialogWidth; } dialog({ html: text, id: 'plugin_fanfields_alert_statistics', title: '== Fan Field Statistics == ', width: width, closeOnEscape: true }); } } thisplugin.exportDrawtools = function() { var alatlng, blatlng, layer; $.each(thisplugin.sortedFanpoints, function(index, portal) { $.each(portal.outgoing, function(targetIndex, targetPortal) { alatlng = map.unproject(portal.point, thisplugin.PROJECT_ZOOM); blatlng = map.unproject(targetPortal.point, thisplugin.PROJECT_ZOOM); layer = L.geodesicPolyline([alatlng, blatlng], window.plugin.drawTools.lineOptions); window.plugin.drawTools.drawnItems.addLayer(layer); window.plugin.drawTools.save(); }); }); } thisplugin.exportArcs = function() { if (window.PLAYER.team === 'RESISTANCE') { // sorry return; }; var alatlng, blatlng, layer; $.each(thisplugin.sortedFanpoints, function(index, portal) { $.each(portal.outgoing, function(targetIndex, targetPortal) { window.selectedPortal = portal.guid; window.plugin.arcs.draw(); window.selectedPortal = targetPortal.guid; window.plugin.arcs.draw(); }); }); window.plugin.arcs.list(); } thisplugin.exportTasks = function() { //todo... } // Show as list thisplugin.exportText = function() { var text = ""; text+=""; text+=""; text+=""; text+=""; text+=""; text+=""; let linkDetailText = ''; var gmnav='http://maps.google.com/maps/dir/'; thisplugin.sortedFanpoints.forEach(function(portal, index) { var p, title, lat, lng; var latlng = map.unproject(portal.point, thisplugin.PROJECT_ZOOM); lat = Math.round(latlng.lat * 10000000) / 10000000 lng = Math.round(latlng.lng * 10000000) / 10000000 gmnav+=`${lat},${lng}/`; p = portal.portal; // window.portals[portal.guid]; title = "unknown title"; if (p !== undefined) { title = p.options.data.title; } let availableKeysText = ''; let availableKeys = 0; if (window.plugin.keys || window.plugin.LiveInventory) { if (window.plugin.LiveInventory) { availableKeys = window.plugin.LiveInventory.keyGuidCount[portal.guid] || 0; } else { availableKeys = window.plugin.keys.keys[portal.guid] || 0; } let keyColorAttribute = ''; if (availableKeys >= portal.incoming.length) { keyColorAttribute = 'plugin_fanfields_enoughKeys'; } else { keyColorAttribute = 'plugin_fanfields_notEnoughKeys'; }; availableKeysText = keyColorAttribute + '>' + availableKeys + '/'; } else { availableKeysText = '>'; }; // Row start text+=''; // List Item Index (Pos.) text+=''; // Action text+=''; // Portal Name // text+=''; let uriTitle=encodeURIComponent(title); text+=''; // Keys text+=''; // other //text+=''; // Row End text+=''; text+='\n'; if (portal.outgoing.length > 0) { // DetailBlock Start text+=''; portal.outgoing.forEach(function(outPortal, outIndex) { let distance = thisplugin.distanceTo(portal.point, outPortal.point); let measure = 'm'; // Row start let linkDetailText=''; // List Item Index (Pos.) linkDetailText+=''; // Action linkDetailText+=''; let outPortalTitle = 'unknown title'; if (outPortal.portal !== undefined) { outPortalTitle = outPortal.portal.options.data.title; } // Portal Name linkDetailText+=''; // Distance linkDetailText+=''; // Links // linkDetailText+=''; // other //linkDetailText+=''; // Row End linkDetailText+='\n'; text+=linkDetailText; }); text+='\n'; } // end if portal.outgoing.length > 0 }); text+='
Pos.ActionPortal NameKeysLinks
' + (index) + ''; text+=' '; text+=' '; text+=''+ title + ''; text+=` ${title}`; text+=''; // Links text+='' + portal.outgoing.length + ''; //text+=''; //text+='
'; if (window.plugin.keys || window.plugin.LiveInventory) { text+='
Adjust available keys using your keys plugin.
'; }; text+='
'; gmnav+='&nav=1'; text+='Navigate with Google Maps'; thisplugin.exportDialogWidth = 500; var width = thisplugin.exportDialogWidth; if (thisplugin.MaxDialogWidth < thisplugin.exportDialogWidth) { width = thisplugin.MaxDialogWidth; } const toggleFunction = function() { $('[plugin_fanfields_exportText_toggle="toggle"]').each(function() { const $toggle = $(this); const $label = $toggle.prev('.plugin_fanfields_exportText_Label'); const $details = $toggle.parents().next('.plugin_fanfields_exportText_LinkDetails'); if ($details.length) { $label.addClass('has-children'); } else { $toggle.remove(); // Entferne die Checkbox, wenn keine Kind-Elemente vorhanden sind $label.css('cursor', 'default'); // Ändere den Cursor zurück auf Standard } }); $('[plugin_fanfields_exportText_toggle="toggle"]').change(function(){ const isChecked = $(this).is(':checked'); $(this).parents().next('.plugin_fanfields_exportText_LinkDetails').toggle(); $(this).prev('.plugin_fanfields_exportText_Label').attr('aria-expanded', isChecked); }); }; dialog({ html: text, id: 'plugin_fanfields_alert_textExport', title: 'Fan Fields', width: width, closeOnEscape: true }); toggleFunction(); }; thisplugin.respectCurrentLinks = false; thisplugin.toggleRespectCurrentLinks = function() { thisplugin.respectCurrentLinks = !thisplugin.respectCurrentLinks; if (thisplugin.respectCurrentLinks) { $('#plugin_fanfields_respectbtn').html('Respect Intel: ON'); } else { $('#plugin_fanfields_respectbtn').html('Respect Intel: OFF'); } thisplugin.delayedUpdateLayer(0.2); }; thisplugin.indicateLinkDirection = true; thisplugin.toggleLinkDirIndicator = function() { thisplugin.indicateLinkDirection = !thisplugin.indicateLinkDirection; if (thisplugin.indicateLinkDirection) { $('#plugin_fanfields_direction_indicator_btn').html('Show link dir: ON'); } else { $('#plugin_fanfields_direction_indicator_btn').html('Show link dir: OFF'); } thisplugin.delayedUpdateLayer(0.2); }; thisplugin.is_locked = false; thisplugin.lock = function() { thisplugin.is_locked = !thisplugin.is_locked; if (thisplugin.is_locked) { $('#plugin_fanfields_lockbtn').html('🔒 Locked'); // 🔒 } else { $('#plugin_fanfields_lockbtn').html('🔓 Unlocked'); // 🔓 } }; thisplugin.use_bookmarks_only = false; thisplugin.useBookmarksOnly = function () { thisplugin.use_bookmarks_only = !thisplugin.use_bookmarks_only; if (thisplugin.use_bookmarks_only) { $('#plugin_fanfields_bookarks_only_btn').html( '🔖 Bookmarks only' ); } else { $('#plugin_fanfields_bookarks_only_btn').html( '🔖 All Portals' ); } thisplugin.delayedUpdateLayer(0.2); }; thisplugin.is_clockwise = true; thisplugin.toggleclockwise = function() { thisplugin.is_clockwise = !thisplugin.is_clockwise; var clockwiseSymbol="", clockwiseWord=""; if (thisplugin.is_clockwise) clockwiseSymbol = "↻", clockwiseWord = "Clockwise"; else clockwiseSymbol = "↺", clockwiseWord = "Counterclockwise"; $('#plugin_fanfields_clckwsbtn').html(clockwiseWord+' '+clockwiseSymbol+''); thisplugin.delayedUpdateLayer(0.2); }; thisplugin.starDirENUM = {CENTRALIZING:-1, RADIATING: 1}; thisplugin.stardirection = thisplugin.starDirENUM.CENTRALIZING; thisplugin.toggleStarDirection = function() { thisplugin.stardirection *= -1; var html = "Outbounding"; if (thisplugin.stardirection == thisplugin.starDirENUM.CENTRALIZING) { html = "Inbounding"; $('#plugin_fanfields_availablesbul').hide(); } else { $('#plugin_fanfields_availablesbul').show(); } $('#plugin_fanfields_stardirbtn').html(html); thisplugin.delayedUpdateLayer(0.2); }; thisplugin.increaseSBUL = function() { if (thisplugin.availableSBUL < 4) { thisplugin.availableSBUL++; $('#plugin_fanfields_availablesbul_count').html(''+(thisplugin.availableSBUL)+''); thisplugin.delayedUpdateLayer(0.2); } } thisplugin.decreaseSBUL = function() { if (thisplugin.availableSBUL > 0) { thisplugin.availableSBUL--; $('#plugin_fanfields_availablesbul_count').html(''+(thisplugin.availableSBUL)+''); thisplugin.delayedUpdateLayer(0.2); } } thisplugin.setupCSS = function() { if (L.Browser.mobile) { // alert('this is mobile') $("