{"id":10282,"date":"2026-07-31T18:33:08","date_gmt":"2026-08-01T00:33:08","guid":{"rendered":"https:\/\/www.lebuinco.com\/jupiterx-codes\/globo-3d-js\/"},"modified":"2026-07-31T18:33:08","modified_gmt":"2026-08-01T00:33:08","slug":"globo-3d-js","status":"publish","type":"jupiterx-codes","link":"https:\/\/www.lebuinco.com\/eng\/jupiterx-codes\/globo-3d-js\/","title":{"rendered":"Globo 3D &#8211; JS"},"content":{"rendered":"<p><script>\n    \/**\n * Globo Terr\u00e1queo 3D Canvas (Coincidencia 100% Exacta con Imagen 2)\n * - Proyecci\u00f3n exacta r = 100\u00b0, c = 20\u00b0\n * - Puntos continentales delicados y definidos (sin bandas gruesas)\n * - Arcos elegantes sutiles en Azul Navy (#0a2540)\n * - Fondo 100% Transparente\n * - Rotaci\u00f3n y l\u00edneas fijas\n *\/<\/p>\n<p>(function () {\n    \"use strict\";<\/p>\n<p>    \/\/ M\u00e1scara real de continentes (costas de Natural Earth 110m, dominio p\u00fablico),\n    \/\/ muestreada en una cuadr\u00edcula de 1\u00b0 x 1\u00b0 (181 filas x 360 columnas) y empaquetada\n    \/\/ en bits + base64 para poder consultar \"\u00bfhay tierra en (lat, lon)?\" con la forma real\n    \/\/ de cada continente, en vez de \u00f3valos aproximados.\n    const LAND_MASK_ROWS = 181;\n    const LAND_MASK_COLS = 360;\n    const LAND_MASK_B64 =\n        \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\" +\n        \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\" +\n        \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH\/\/gAAH\/\/\/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB\/\/\/\/x\/\/v\/\/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM\/\/\/+f\/\/\/\/\/\/\/8AAAAAAAAAAAAAAAAADgAAAAAAAAAAAAA\" +\n        \"AAAAAAAAAAAAAA\/\/\/\/h\/\/\/\/\/\/\/\/AAAAAC\/4AAAAAAAAAAH8AAAAAAAAAAAAAAAAAAAAAAAAAHAP5\/wH\/\/\/\/\/\/\/+AAAAB\/4AAAAAAAAAAAB9wAAAAAAAAAAAAAAAAAAAAAAAcATAD\/h\/\/\/\/\/\/\/\/8AAAAAfnAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAB8AABwf9Af\/\" +\n        \"\/\/\/\/\/\/\/AAAAAGAAAAAAAAAAAAAB+AAAAAAAAAAAAAAAAAAAAADHzB8GAAAAB\/\/\/\/\/\/8AAAAAAAAAAAAA\/gAAAHf\/\/AAAA8AAAAAAAAAAAAAAAAAfwN3\/8AAAP\/\/\/\/\/8AAAAAAAAAAAAPgAAAH\/\/\/+AAAA\/HAAAAAAAAAAAAAAP4AAAcAAAAAH\/\/\/\/\/4AAAAAAAAAAAA8\" +\n        \"AAAAH\/\/\/4AAAAAAAAAAAAAAAAAAAAf3A4c897wAAB\/\/\/\/\/gAAAAAAAAAAABwAAAH\/\/\/\/\/\/34AAAAAAAAAAAAAAAAA\/f\/4eA\/\/4AAB\/\/\/\/\/gAAAAAAAAAAAHwAHn\/\/\/\/\/\/\/\/wAf\/AAAAAwAADgAAAAAP\/8A8f\/\/gAAf\/\/\/\/wAAAAAAA+AAAA4Afv\/\/\/\/\/\/\/\/9\/\/\/wgAAB\" +\n        \"AAB\/\/\/gAaQH\/\/g8B\/\/8AB\/\/\/\/\/gAAAAAAP\/AAAAAAfv\/\/\/\/\/\/\/\/\/\/\/\/\/8AMAAAD\/\/\/\/P\/\/w\/HFOj4f8AAH\/\/\/8AAAAAAB\/\/\/gAAAfPn\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/n\/4AH\/\/\/\/\/\/\/+A+D\/zwZ\/AAf\/\/8AAAAAAAH\/\/\/8OP\/\/\/n\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/gB\/\/\/\/\/\/\/\/\/\/\/\/\/wB\/w\" +\n        \"A\/\/\/wAAAAAAAP\/\/\/+M\/\/\/\/P\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/8f\/\/\/\/\/\/\/\/\/\/\/\/8AD88A\/\/\/AA74AAAAf\/\/+B\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/Dgf\/\/\/\/\/\/\/\/\/\/\/\/7A\/+AAP\/wAAf8AAAB\/4f+T\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/AAAf\/\/\/\/\/\/\/\/\/\/\/DwB\/gAP\/gAAfwAAAD\/5\/\/\/\/\/\/\" +\n        \"\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/8ACH\/\/\/\/\/\/\/\/\/\/\/+AAAcgAH\/AAAAAAAAf\/H\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/+AAP\/\/\/\/\/\/\/\/\/\/\/4AC+BAAD\/AAAAAAAB\/+H\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/v\/gAAH\/+\/\/\/\/\/\/\/\/\/wAA\/wAAB+AAAAAAAB\/+H\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/Cf8A\" +\n        \"AAT\/7wP\/\/\/\/\/\/\/gAAfwQAAAAAAAAAAB\/\/hB\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/+B6AAAAAv4AB\/\/\/\/\/\/\/gAA\/44AAAAAAAAAAA9\/B\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/8AcDgAAAAADgAAD\/\/\/\/\/\/4AAf\/8AAAAAAAAAwAB+A\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/wAAPAAAAAAGYAAB\/\/\/\/\/\/\/AAf\/+\" +\n        \"AAAAAAAAA8AM8H\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/gAA\/gAAAAAcAAAA\/\/\/\/\/\/\/4Af\/+AAAAAAAAA4AMYH\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/+AAA\/AAAAADAAAAAf\/\/\/\/\/\/\/x\/\/\/wAAAAAAADeAMAP\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/8AAA\/AAAAAAAAAABP\/\/\/\/\/\/\/x\/\/\/4AAAAAAAHHAH3\/\/\/\/\/\/\/\" +\n        \"\/\/\/\/\/\/\/\/\/\/\/\/+yAA8AAAAAAAAAAAH\/\/\/\/\/\/\/x\/\/\/8AAAAAAAHfB\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/6AA4AAAAAAAAAAAD\/\/\/\/\/\/\/5\/\/\/8AAAAAAAMfj\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/6AAwAAAAAAAAAAAD\/\/\/\/\/\/\/\/\/\/\/kAAAAAAAAfv\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/7AAAAAA\" +\n        \"AAAAAAAAD\/\/\/\/\/\/\/\/\/+AIAAAAAAAAAf\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/7AAAAAAAAAAAAAAA3\/\/\/\/\/\/\/\/5gfAAAAAAAAD\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/yAAAAAAAAAAAAAAAb\/\/\/\/\/\/\/\/3gfgAAAAAAAP\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/yAAAAAAAAAAAAAAAP\/\/\/\/\/\/\/\/vg\" +\n        \"CgAAAAAAAD\/\/\/\/\/\/n\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/iAAAAAAAAAAAAAAAP\/\/\/\/\/\/\/\/\/xAAAAAAAAAB\/\/\/\/\/GP\/h\/\/\/\/\/\/\/\/\/\/\/\/\/\/AAAAAAAAAAAAAAAAP\/\/\/\/\/\/\/\/+cAAAAAAAAAB\/\/H\/\/Df+H\/\/\/\/\/\/\/\/\/\/\/\/\/+CAAAAAAAAAAAAAAAP\/\/\/\/\/\/\/\/4gAAAAAAAAAB\/zn\/+AH+H\/\" +\n        \"\/\/\/\/\/\/\/\/\/\/\/\/8DAAAAAAAAAAAAAAAP\/\/\/\/\/\/\/\/gAAAAAAAAAH\/4Dx\/8AB+D\/\/\/\/\/\/\/\/\/\/\/\/\/QPAAAAAAAAAAAAAAAP\/\/\/\/\/\/\/\/gAAAAAAAAAH\/4A4f8GA\/C\/\/\/\/\/\/\/\/\/\/\/\/8AIAAAAAAAAAAAAAAAP\/\/\/\/\/\/\/9AAAAAAAAAAH\/gAOf9\/3\/gf\/\/\/\/\/\/\/\/\/\/\/8AMAAAAAA\" +\n        \"AAAAAAAAAP\/\/\/\/\/\/\/8AAAAAAAAAAH\/AMGPH\/\/\/x\/\/\/\/\/\/\/\/\/\/\/PwAMAAAAAAAAAAAAAAAP\/\/\/\/\/\/\/4AAAAAAAAAAH\/AACGH\/\/\/g\/\/\/\/\/\/\/\/\/\/8BwAMAAAAAAAAAAAAAAAH\/\/\/\/\/\/\/4AAAAAAAAAAH+AAYHH\/\/\/g\/\/\/\/\/\/\/\/\/\/8B4AYAAAAAAAAAAAAAAAD\/\/\/\/\/\/\/wAA\" +\n        \"AAAAAAAAF8A0ICH\/\/\/4\/\/\/\/\/\/\/\/\/\/\/wYA4AAAAAAAAAAAAAAAD\/\/\/\/\/\/\/wAAAAAAAAAAAg\/+AAAA\/\/\/\/\/\/\/\/\/\/\/\/\/\/A4D4AAAAAAAAAAAAAAAB\/\/\/\/\/\/\/wAAAAAAAAAAA\/\/+AAQM\/\/\/\/\/\/\/\/\/\/\/\/\/+A4\/wAAAAAAAAAAAAAAAAf\/\/\/\/\/\/AAAAAAAAAAAB\/\/8AAAA\/\/\/\/\" +\n        \"\/\/\/\/\/\/\/\/\/\/ABMAAAAAAAAAAAAAAAAAH\/\/\/\/\/+AAAAAAAAAAAH\/\/\/AAAB\/\/\/\/\/\/\/\/\/\/\/\/\/\/gDgAAAAAAAAAAAAAAAAAH\/\/\/\/\/4AAAAAAAAAAAH\/\/\/4PAB\/\/\/\/\/\/\/\/\/\/\/\/\/\/wDAAAAAAAAAAAAAAAAAACf\/\/\/\/4AAAAAAAAAAAP\/\/\/+P\/7\/\/\/\/\/\/\/\/\/\/\/\/\/\/wAAAAAAAAA\" +\n        \"AAAAAAAAAACf\/\/\/j4AAAAAAAAAAAP\/\/\/\/\/\/\/\/\/v\/\/\/\/\/\/\/\/\/\/\/wAAAAAAAAAAAAAAAAAAABP\/\/gAYAAAAAAAAAAAf\/\/\/\/\/\/\/\/\/H\/\/\/\/\/\/\/\/\/\/\/wAAAAAAAAAAAAAAAAAAAAn\/+AAYAAAAAAAAAAB\/\/\/\/\/\/\/5\/\/n\/\/\/\/\/\/\/\/\/\/\/gAAAAAAAAAAAAAAAAAAAAz\/+AAMAAA\" +\n        \"AAAAAAAD\/\/\/\/\/\/\/8\/\/x3\/\/\/\/\/\/\/\/\/\/AAAAAAAAAAAAAAAAAAAAAJ\/+AAMAAAAAAAAAAH\/\/\/\/\/\/\/8f\/wH\/\/\/\/\/\/\/\/\/\/AAAAAAAAAAAAAAAAAAAAAI\/8AAAAAAAAAAAAAH\/\/\/\/\/\/\/+f\/8QAf\/\/\/\/\/\/\/+QAAAAAAAAAAAAAAAAAAAAEf8AAAAAAAAAAAAAP\/\/\/\/\/\/\/\/P\/\/4\" +\n        \"Af\/\/\/\/\/\/\/8wAAAAAAAAAAAAAAAAAAAAAP8AAcAAAAAAAAAAP\/\/\/\/\/\/\/\/H\/\/+AH\/\/\/\/\/\/\/wgAAAAAAAAAAAAIAAAAAAAAP8ABDAAAAAAAAAAf\/\/\/\/\/\/\/\/n\/\/\/AH\/\/\/P\/\/+AAAAAAAAAAAAAAAAAAAAAAAH+A4AwAAAAAAAAAf\/\/\/\/\/\/\/\/n\/\/+ADf\/4P\/+IAAAAAAAAAAA\" +\n        \"AAAAgAAAAAAAP+B4A4AAAAAAAAAP\/\/\/\/\/\/\/\/j\/\/8AAf\/wH\/8IAAAAAAAAAAAAAAAAAAAAAAAD\/BwAB4AAAAAAAAP\/\/\/\/\/\/\/\/h\/\/8AAf\/gD\/8QAAAAAAAAAAAAAAAAAAAAAAAB\/\/wAwhAAAAAAAAP\/\/\/\/\/\/\/\/w\/\/wAAf\/AD\/8AAQAAAAAAAAAAAAAAAAAAAAAAP\/wAAAA\" +\n        \"AAAAAAAP\/\/\/\/\/\/\/\/4\/\/AAAf8AD\/+AAwAAAAAAAAAAAAAAAAAAAAAADPgAAAAAAAAAAAf\/\/\/\/\/\/\/\/4f8AAAP4ADP\/AAwAAAAAAAAAAAAAAAAAAAAAAAH\/gAAAAAAAAAAf\/\/\/\/\/\/\/\/8f4AAAPwAAP\/gAwAAAAAAAAAAAAAAAAAAAAAAAB\/gAAAAAAAAAAf\/\/\/\/\/\/\/\/\/fgA\" +\n        \"AAHwAAP\/gAYAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAAAAAf\/\/\/\/\/\/\/\/\/4AAAAHwAAF\/gAUAAAAAAAAAAAAAAAAAAAAAAAAHAAgAAAAAAAAP\/\/\/\/\/\/\/\/\/gAAAAHwAAMfgABAAAAAAAAAAAAAAAAAAAAAAAADAHcAAAAAAAAH\/\/\/\/\/\/\/\/\/x4AAADwAAEfABKAAAAAAAAA\" +\n        \"AAAAAAAAAAAAAAADgPf8AAAAAAAD\/\/\/\/\/\/\/\/\/\/4AAADgAAAGAAEAAAAAAAAAAAAAAAAAAAAAAAAA3P\/+AAAAAAAB\/\/\/\/\/\/\/\/\/\/wAAABIAAMEAABAAAAAAAAAAAAAAAAAAAAAAAAAM\/\/\/gAAAAAAB\/\/\/\/\/\/\/\/\/\/wAAAAMAAEAAAPAAAAAAAAAAAAAAAAAAAAAAAAAA\/\/\/\" +\n        \"wAAAAAAA\/\/\/\/\/\/\/\/\/\/wAAAAMAACAAADAAAAAAAAAAAAAAAAAAAAAAAAAAf\/\/4AAAAAAAf\/h\/\/\/\/\/\/\/gAAAAAAADAAMBAAAAAAAAAAAAAAAAAAAAAAAAAAf\/\/\/wAAAAAAHGB\/\/\/\/\/\/\/AAAAAAABzgAeAAAAAAAAAAAAAAAAAAAAAAAAAAAf\/\/\/4AAAAAAAAAH\/\/\/\/\/\/AA\" +\n        \"AAAAAA5gA8AAAAAAAAAAAAAAAAAAAAAAAAAAA\/\/\/\/4AAAAAAAAAD\/\/\/\/\/+AAAAAAAAdgD4AAAAAAAAAAAAAAAAAAAAAAAAAAB\/\/\/\/4AAAAAAAAAD\/\/\/\/\/4AAAAAAAAPQH8AAAAAAAAAAAAAAAAAAAAAAAAAAD\/\/\/\/8AAAAAAAAAH\/\/\/\/\/wAAAAAAAAHgf+SYAAAAAAAA\" +\n        \"AAAAAAAAAAAAAAAAD\/\/\/\/8AAAAAAAAAH\/\/\/\/\/gAAAAAAAAHwf4AAAAAAAAAAAAAAAAAAAAAAAAAAH\/\/\/\/+wAAAAAAAAH\/\/\/\/\/AAAAAAAAADwf54BwAAAAAAAAAAAAAAAAAAAAAAAH\/\/\/\/\/4AAAAAAAAH\/\/\/\/+AAAAAAAAAB4P5wAxgAAAAAAAAAAAAAAAAAAAAAAD\/\/\/\" +\n        \"\/\/\/wAAAAAAAD\/\/\/\/8AAAAAAAAAA8HxQM78AQAAAAAAAAAAAAAAAAAAAAH\/\/\/\/\/\/8AAAAAAAB\/\/\/\/8AAAAAAAAAA8AAYAP\/gAAAAAAAAAAAAAAAAAAAAAH\/\/\/\/\/\/+AAAAAAAA\/\/\/\/4AAAAAAAAAAMABIAB\/wQAAAAAAAAAAAAAAAAAAAAH\/\/\/\/\/\/\/gAAAAAAA\/\/\/\/4AAA\" +\n        \"AAAAAAACAAAAI\/5gAAAAAAAAAAAAAAAAAAAAD\/\/\/\/\/\/\/gAAAAAAAf\/\/\/4AAAAAAAAAAD+AAAAf4AAAAAAAAAAAAAAAAAAAAAB\/\/\/\/\/\/\/gAAAAAAAf\/\/\/4AAAAAAAAAAAPgAAA\/MAEAAAAAAAAAAAAAAAAAAAB\/\/\/\/\/\/\/gAAAAAAAf\/\/\/4AAAAAAAAAAAAABAAGGAAAAA\" +\n        \"AAAAAAAAAAAAAAAAA\/\/\/\/\/\/\/AAAAAAAAf\/\/\/8AAAAAAAAAAAAAiAAADAAAAAAAAAAAAAAAAAAAAAA\/\/\/\/\/\/+AAAAAAAAP\/\/\/8AAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAf\/\/\/\/\/8AAAAAAAAP\/\/\/8AAAAAAAAAAAAAAASCAAAAAAAAAAAAAAAAAAAAAAAf\/\/\" +\n        \"\/\/\/8AAAAAAAAf\/\/\/+AQAAAAAAAAAAAAD+DAAAAAAAAAAAAAAAAAAAAAAAP\/\/\/\/\/4AAAAAAAA\/\/\/\/+AwAAAAAAAAAAAAj8DAAAAAAAAAAAAAAAAAAAAAAAP\/\/\/\/\/4AAAAAAAA\/\/\/\/+BwAAAAAAAAAAAB\/8DgAAAAAAAAAAAAAAAAAAAAAAD\/\/\/\/\/4AAAAAAAA\/\/\/\/8HwA\" +\n        \"AAAAAAAAAAD\/+HgAAAAAAAAAAAAAAAAAAAAAAA\/\/\/\/\/4AAAAAAAA\/\/\/\/4PwAAAAAAAAAAAD\/\/nwAAAAAAAAAAAAAAAAAAAAAAAf\/\/\/\/4AAAAAAAA\/\/\/\/gPgAAAAAAAAAAAP\/\/\/wAAAAGAAAAAAAAAAAAAAAAAAP\/\/\/\/wAAAAAAAAf\/\/\/APgAAAAAAAAAAAP\/\/\/wAAAAA\" +\n        \"AAAAAAAAAAAAAAAAAAP\/\/\/\/wAAAAAAAAf\/\/+APgAAAAAAAAAAB\/\/\/\/8AAAAAAAAAAAAAAAAAAAAAAAP\/\/\/\/gAAAAAAAAf\/\/+APAAAAAAAAAAAP\/\/\/\/+AAEAAAAAAAAAAAAAAAAAAAAP\/\/\/\/gAAAAAAAAP\/\/+AfAAAAAAAAAAAf\/\/\/\/\/AACAAAAAAAAAAAAAAAAAAAAP\/\" +\n        \"\/\/+AAAAAAAAAP\/\/\/AfAAAAAAAAAAA\/\/\/\/\/\/gAAAAAAAAAAAAAAAAAAAAAAP\/\/\/wAAAAAAAAAP\/\/\/AOAAAAAAAAAAB\/\/\/\/\/\/wAAAAAAAAAAAAAAAAAAAAAAf\/\/\/AAAAAAAAAAH\/\/8AOAAAAAAAAAAA\/\/\/\/\/\/4AAAAAAAAAAAAAAAAAAAAAAf\/\/+AAAAAAAAAAH\/\/4AAAA\" +\n        \"AAAAAAAAA\/\/\/\/\/\/4AAAAAAAAAAAAAAAAAAAAAAf\/\/+AAAAAAAAAAH\/\/4AAAAAAAAAAAAA\/\/\/\/\/\/4AAAAAAAAAAAAAAAAAAAAAAf\/\/+AAAAAAAAAAD\/\/4AAAAAAAAAAAAA\/\/\/\/\/\/8AAAAAAAAAAAAAAAAAAAAAAf\/\/+AAAAAAAAAAB\/\/wAAAAAAAAAAAAAf\/\/\/\/\/8AAAA\" +\n        \"AAAAAAAAAAAAAAAAAAf\/\/8AAAAAAAAAAB\/\/gAAAAAAAAAAAAAf\/\/\/\/\/4AAAAAAAAAAAAAAAAAAAAAA\/\/\/4AAAAAAAAAAA\/\/AAAAAAAAAAAAAAf\/\/\/\/\/4AAAAAAAAAAAAAAAAAAAAAA\/\/\/wAAAAAAAAAAA\/+AAAAAAAAAAAAAAP\/wf\/\/4AAAAAAAAAAAAAAAAAAAAAA\/\/\" +\n        \"\/gAAAAAAAAAAA\/8AAAAAAAAAAAAAAP8AP\/\/wAAAAAAAAAAAAAAAAAAAAAA\/\/\/AAAAAAAAAAAA8AAAAAAAAAAAAAAAfAAG\/\/gAAAAAAAAAAAAAAAAAAAAAA\/\/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAB\/\/gAABAAAAAAAAAAAAAAAAAAB\/\/4AAAAAAAAAAAAAAAAAAA\" +\n        \"AAAAAAAAAAAAAf\/AAAAgAAAAAAAAAAAAAAAAAB\/\/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP\/AAAAwAAAAAAAAAAAAAAAAAD\/\/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH8AAAAcAAAAAAAAAAAAAAAAAB\/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8\" +\n        \"AAAAAAAAAAAAAAAAAD\/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAD\/oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAACQAAAAAAAAAAAAAAAAAD\/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAHAAAAAAAAAAAAAAAAAAB\/g\" +\n        \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAGAAAAAAAAAAAAAAAAAAB\/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAAAAAAAAAAAAH\/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAAAAAAAAAAAAAAAAH+AAAAAAAAAAAAAAAAAAAAA\" +\n        \"AAAAAAAAAAAAAAAAABwAAAAAAAAAAAAAAAAAAD+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH\/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAA\" +\n        \"AAAAAAAAAAAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABsA\" +\n        \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\" +\n        \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\" +\n        \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\" +\n        \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAAAAAAAAAAABw\" +\n        \"AAAAAAAwBAAAEAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAAAP8AAAAGAD\/\/8f\/\/\/wAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAH\/\/\/4AP\/\/\/\/\/\/\/\/\/\/4AAAAAAAAAAAAAAAAAAAAAAATwAAAAAAAAAAAAAAAGB\/\/\/\/8A\/\/\/\/\/\/\/\/\/\/\/\/\/AAAA\" +\n        \"AAAAAAAAAAAAAAAAAA\/4AAAAAAAAAAAATwA\/\/\/\/\/\/wP\/\/\/\/\/\/\/\/\/\/\/\/\/8AAAAAAAAAAAAAAAAAAAAA\/8AAAAAAAABAH\/\/\/\/\/\/\/\/\/\/4\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/gAAAAAAAAAAAAAA8AAAH5+AAAAAAAAf\/\/\/\/\/\/\/\/\/\/\/\/x\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/4AAAAAAAAAAAAAD4AwAAH+\" +\n        \"AAAAAAAH\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/wAAAAAAAAAADBgB\/\/\/\/\/\/+AAAAAAAP\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/AAAAAAAAB\/\/\/\/\/GP\/\/\/\/\/wAAAAAAAf\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/4AAAAAAAD\/\/\/\/\/\/\/\/\/\/\/\/8AAAAAAAP\/\/\/\/\/\/\/\/\/\/\/\/\/\" +\n        \"\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/wAAAAAAAf\/\/\/\/\/\/\/\/\/\/\/gAAAAAAA\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/gAAAAAD\/\/\/\/\/\/\/\/\/\/\/\/\/8AAAA4Af\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/4AAAABwf\/\/\/\/\/\/\/\/\/\/\/\/AAAAD8A\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/4AA\" +\n        \"AAAAAH\/\/\/\/\/\/\/\/\/\/\/wABAP+AAf\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/+AAAAAAAAf\/\/\/\/\/\/\/\/\/\/\/\/wAAMAAf\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/8AAAAAAAH\/\/\/\/\/\/\/\/\/\/\/\/\/\/8AP9\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/AAAAAAAH\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\" +\n        \"n\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/8AAAOAAB\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/8A\/\/+AAAf\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\" +\n        \"\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\" +\n        \"\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\";<\/p>\n<p>    const HUB_CDMX = { name: \"Ciudad de M\u00e9xico\", lat: 19.43, lon: -99.13, home: true };<\/p>\n<p>    const DESTINATIONS = [\n        { name: \"Pachuca\", lat: 20.1011, lon: -98.7591 },\n        { name: \"Baja California\", lat: 32.5149, lon: -117.0382 },\n        { name: \"Baja California los cabos\", lat: 22.9335373, lon: -109.9717902 },\n        { name: \"M\u00e9rida\", lat: 20.9674, lon: -89.5926 },\n        { name: \"Colima\", lat: 19.2452, lon: -103.7241 },\n        { name: \"Monterrey\", lat: 25.6866, lon: -100.3161 },\n        { name: \"Estado de M\u00e9xico\", lat: 19.2826, lon: -99.6557 },\n        { name: \"Los \u00c1ngeles\", lat: 34.0522, lon: -118.2437 },\n        { name: \"Nueva York\", lat: 40.7128, lon: -74.0060 },\n        { name: \"Madrid\", lat: 40.4168, lon: -3.7038 },\n        { name: \"Lima\", lat: -12.0464, lon: -77.0428 },\n        { name: \"Toronto\", lat: 43.6532, lon: -79.3832 },\n        { name: \"Tampa\", lat: 27.972447, lon: -82.5169846 }\n    ];<\/p>\n<p>    const ALL_CITIES = [HUB_CDMX, ...DESTINATIONS];\n    const DEG2RAD = Math.PI \/ 180;<\/p>\n<p>    const CONFIG = {\n        initialRotX: 100 * DEG2RAD, \/\/ 100\u00b0 (Visi\u00f3n exacta Imagen 2)\n        tiltY: 20 * DEG2RAD,        \/\/ 20\u00b0 Inclinaci\u00f3n\n        autoRotate: true,\n        \/\/ ==== VELOCIDAD DEL GIRO AUTOM\u00c1TICO ====\n        \/\/ Cambia solo este n\u00famero para acelerar\/frenar el giro (en radianes por segundo).\n        \/\/ M\u00e1s alto = gira m\u00e1s r\u00e1pido. Ej: 0.05 lento, 0.1 normal, 0.3 r\u00e1pido.\n        autoRotateSpeed: 0.3,\n        allowDrag: true,\n        arcPointsCount: 48\n    };<\/p>\n<p>    let cachedMapPoints = null;<\/p>\n<p>    function latLonToVector(lat, lon) {\n        let phi = lat * DEG2RAD;\n        let lambda = lon * DEG2RAD;\n        return [\n            Math.cos(phi) * Math.sin(lambda),\n            Math.sin(phi),\n            Math.cos(phi) * Math.cos(lambda)\n        ];\n    }<\/p>\n<p>    function projectVector(v, rotX, cosTilt, sinTilt) {\n        let cosX = Math.cos(rotX);\n        let sinX = Math.sin(rotX);\n        let x1 = v[0] * cosX + v[2] * sinX;\n        let z1 = -v[0] * sinX + v[2] * cosX;\n        return [\n            x1,\n            v[1] * cosTilt - z1 * sinTilt,\n            v[1] * sinTilt + z1 * cosTilt\n        ];\n    }<\/p>\n<p>    function decodeLandMask(b64) {\n        let binary = atob(b64);\n        let bytes = new Uint8Array(binary.length);\n        for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n        return bytes;\n    }\n\n    const landMaskBytes = decodeLandMask(LAND_MASK_B64);\n\n    function isOnLand(lat, lon) {\n        let row = Math.round(90 - lat);\n        if (row < 0) row = 0;\n        if (row > LAND_MASK_ROWS - 1) row = LAND_MASK_ROWS - 1;\n        let col = Math.round(lon + 180) % LAND_MASK_COLS;\n        if (col < 0) col += LAND_MASK_COLS;\n        let bitIndex = row * LAND_MASK_COLS + col;\n        let byteIndex = bitIndex >> 3;\n        let bitOffset = 7 - (bitIndex & 7);\n        return ((landMaskBytes[byteIndex] >> bitOffset) & 1) === 1;\n    }<\/p>\n<p>    function getMapPoints() {\n        \/\/ Los vectores 3D de cada punto son est\u00e1ticos (no dependen de la rotaci\u00f3n),\n        \/\/ as\u00ed que se calculan una sola vez aqu\u00ed en vez de en cada frame de render.\n        if (cachedMapPoints) return cachedMapPoints;\n        let points = [];\n        const latStep = 1.15;\n        const baseLonStep = 1.15;\n        for (let lat = -80; lat <= 80; lat += latStep) {\n            let lonStep = Math.min(4, baseLonStep \/ Math.max(0.16, Math.cos(lat * DEG2RAD)));\n            for (let lon = -180; lon < 180; lon += lonStep) {\n                if (isOnLand(lat, lon)) points.push({ v: latLonToVector(lat, lon) });\n            }\n        }\n        cachedMapPoints = points;\n        return points;\n    }\n\n    function generateAllArcs(hubLoc, destinations, count = 48) {\n        let startVec = latLonToVector(hubLoc.lat, hubLoc.lon);\n\n        return destinations.map(dest => {\n            let endVec = latLonToVector(dest.lat, dest.lon);\n            let dot = startVec[0] * endVec[0] + startVec[1] * endVec[1] + startVec[2] * endVec[2];\n            let angle = Math.acos(Math.min(1, Math.max(-1, dot)));<\/p>\n<p>            let pts = [];\n            for (let i = 0; i <= count; i++) {\n                let t = i \/ count;\n                let sinTotal = Math.sin(angle);\n                let wStart = sinTotal === 0 ? 1 - t : Math.sin((1 - t) * angle) \/ sinTotal;\n                let wEnd = sinTotal === 0 ? t : Math.sin(t * angle) \/ sinTotal;\n\n                let vec = [\n                    wStart * startVec[0] + wEnd * endVec[0],\n                    wStart * startVec[1] + wEnd * endVec[1],\n                    wStart * startVec[2] + wEnd * endVec[2]\n                ];\n                \/\/ Altura sutil y elegante del arco\n                let alt = 1 + 0.05 * Math.sin(Math.PI * t);\n                pts.push({ v: vec, alt: alt });\n            }\n            return pts;\n        });\n    }\n\n    function getCssColor(element, varName, fallbackHex) {\n        return getComputedStyle(element).getPropertyValue(varName).trim() || fallbackHex;\n    }\n\n    const DOT_BUCKETS = 8; \/\/ cubetas de profundidad para agrupar los fill() de los puntos\n    const FRAME_INTERVAL_MS = 1000 \/ 30; \/\/ limita el repintado a ~30fps (es un elemento decorativo)\n\n    function initGlobeInstance(container) {\n        let canvas = container.querySelector(\"canvas\");\n        if (!canvas) {\n            canvas = document.createElement(\"canvas\");\n            canvas.className = \"block cursor-grab select-none\";\n            canvas.style.touchAction = \"pan-y\";\n            container.appendChild(canvas);\n        }\n\n        const ctx = canvas.getContext(\"2d\");\n        if (!ctx) return;\n\n        const mapPoints = getMapPoints();\n        const cityMarkers = ALL_CITIES.map(c => ({ ...c, v: latLonToVector(c.lat, c.lon) }));\n        const listOfArcs = generateAllArcs(HUB_CDMX, DESTINATIONS, CONFIG.arcPointsCount);<\/p>\n<p>        const cosTilt = Math.cos(CONFIG.tiltY);\n        const sinTilt = Math.sin(CONFIG.tiltY);<\/p>\n<p>        \/\/ Estilos finos y sutiles id\u00e9nticos a Imagen 2\n        const colorNavyAccent = getCssColor(container, \"--globe-navy-accent\", \"#0a2540\");\n        const colorDotDark = getCssColor(container, \"--globe-dot-dark\", \"#475569\");\n        const colorDotLight = getCssColor(container, \"--globe-dot-light\", \"#94a3b8\");\n        const colorBorder = getCssColor(container, \"--globe-border\", \"#e2e8f0\");<\/p>\n<p>        \/\/ Paths reutilizados cada frame para dibujar todos los puntos de una misma\n        \/\/ \"cubeta\" de profundidad con un solo fill(), en vez de un fill() por punto.\n        const dotBucketsDark = [];\n        const dotBucketsLight = [];\n        for (let i = 0; i < DOT_BUCKETS; i++) {\n            dotBucketsDark.push(new Path2D());\n            dotBucketsLight.push(new Path2D());\n        }\n\n        const prefersReducedMotion = window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n\n        let currentRotX = CONFIG.initialRotX;\n        let rotVelocity = 0;\n        let isDragging = false;\n        let startX = 0;\n        let lastTime = 0;\n        let animFrameId = null;\n        let isVisible = true;\n        let isLoopRunning = false;\n\n        function resizeCanvas() {\n            let rect = container.getBoundingClientRect();\n            let size = Math.min(rect.width, rect.height) || rect.width || 450;\n            let dpr = Math.min(window.devicePixelRatio || 1, 2);\n\n            canvas.width = Math.round(size * dpr);\n            canvas.height = Math.round(size * dpr);\n            canvas.style.width = size + \"px\";\n            canvas.style.height = size + \"px\";\n        }\n\n        function render(time) {\n            let dpr = window.devicePixelRatio ? Math.min(window.devicePixelRatio, 2) : 1;\n            let width = canvas.width;\n            if (width === 0) return;\n\n            let centerX = width \/ 2;\n            let centerY = canvas.height \/ 2;\n            let radius = 0.46 * width;\n\n            \/\/ 1. Fondo 100% Transparente\n            ctx.clearRect(0, 0, width, canvas.height);\n\n            \/\/ 2. Anillo Borde Exterior ultra-fino\n            ctx.beginPath();\n            ctx.arc(centerX, centerY, 1.02 * radius, 0, 2 * Math.PI);\n            ctx.strokeStyle = colorBorder;\n            ctx.lineWidth = 1.0 * dpr;\n            ctx.stroke();\n\n            \/\/ 3. Puntos de los continentes (densos, para que la silueta se distinga completa).\n            \/\/ Se agrupan en \"cubetas\" de profundidad y se pintan con un solo fill() por\n            \/\/ cubeta en vez de un fill() por punto (pueden ser miles de puntos por frame).\n            for (let i = 0; i < DOT_BUCKETS; i++) {\n                dotBucketsDark[i] = new Path2D();\n                dotBucketsLight[i] = new Path2D();\n            }\n\n            for (let pt of mapPoints) {\n                let [x, y, z] = projectVector(pt.v, currentRotX, cosTilt, sinTilt);\n                if (z <= 0.02) continue;\n\n                let px = centerX + x * radius;\n                let py = centerY - y * radius;\n                let depth = (z - 0.02) \/ 0.98;\n                let dotRadius = (0.55 + 0.4 * depth) * dpr;\n                let bucketIdx = Math.min(DOT_BUCKETS - 1, (depth * DOT_BUCKETS) | 0);\n                let path = depth > 0.55 ? dotBucketsDark[bucketIdx] : dotBucketsLight[bucketIdx];<\/p>\n<p>                path.moveTo(px + dotRadius, py);\n                path.arc(px, py, dotRadius, 0, 2 * Math.PI);\n            }<\/p>\n<p>            for (let i = 0; i < DOT_BUCKETS; i++) {\n                let depthMid = (i + 0.5) \/ DOT_BUCKETS;\n                ctx.globalAlpha = 0.35 + 0.55 * depthMid;\n                ctx.fillStyle = colorDotLight;\n                ctx.fill(dotBucketsLight[i]);\n                ctx.fillStyle = colorDotDark;\n                ctx.fill(dotBucketsDark[i]);\n            }\n\n            \/\/ 4. Arcos de Direcci\u00f3n (Azul Navy, trazo suave con fundido en los extremos)\n            ctx.strokeStyle = colorNavyAccent;\n            ctx.lineCap = \"round\";\n            ctx.lineJoin = \"round\";\n\n            for (let arcPts of listOfArcs) {\n                let prev = null;\n                for (let i = 0; i < arcPts.length; i++) {\n                    let pt = arcPts[i];\n                    let [x, y, z] = projectVector(pt.v, currentRotX, cosTilt, sinTilt);\n                    if (z <= 0.02) {\n                        prev = null;\n                        continue;\n                    }\n                    let px = centerX + x * pt.alt * radius;\n                    let py = centerY - y * pt.alt * radius;\n\n                    if (prev) {\n                        let t = i \/ (arcPts.length - 1);\n                        \/\/ Fundido suave solo en las puntas (salida del hub y llegada al destino);\n                        \/\/ el cuerpo del arco mantiene una opacidad pareja y elegante.\n                        let edgeFade = Math.min(1, Math.sin(Math.PI * t) * 3.2);\n                        ctx.globalAlpha = 0.2 + 0.65 * edgeFade;\n                        ctx.lineWidth = (1.0 + 0.15 * edgeFade) * dpr;\n\n                        ctx.beginPath();\n                        ctx.moveTo(prev.x, prev.y);\n                        ctx.lineTo(px, py);\n                        ctx.stroke();\n                    }\n                    prev = { x: px, y: py };\n                }\n            }\n            ctx.globalAlpha = 1;\n\n            \/\/ 5. Marcadores de Ciudades\n            for (let city of cityMarkers) {\n                let [x, y, z] = projectVector(city.v, currentRotX, cosTilt, sinTilt);\n                if (z <= 0.05) continue;\n\n                let px = centerX + x * radius;\n                let py = centerY - y * radius;\n                let markerRadius = (city.home ? 2.4 : 1.6) * dpr;\n\n                ctx.fillStyle = colorNavyAccent;\n                ctx.beginPath();\n                ctx.arc(px, py, markerRadius, 0, 2 * Math.PI);\n                ctx.fill();\n\n                if (city.home &#038;&#038; !prefersReducedMotion) {\n                    let pulseProgress = (time \/ 1000 % 2.4) \/ 2.4;\n                    ctx.globalAlpha = (1 - pulseProgress) * 0.5;\n                    ctx.strokeStyle = colorNavyAccent;\n                    ctx.lineWidth = 0.9 * dpr;\n                    ctx.beginPath();\n                    ctx.arc(px, py, markerRadius + 7 * pulseProgress * dpr, 0, 2 * Math.PI);\n                    ctx.stroke();\n                    ctx.globalAlpha = 1;\n                }\n            }\n        }\n\n        let renderAccumulatorMs = 0;\n\n        function animationLoop(timestamp) {\n            let delta = Math.min((timestamp - lastTime) \/ 1000, 0.05);\n            lastTime = timestamp;\n\n            if (!isDragging) {\n                let autoSpeed = (CONFIG.autoRotate &#038;&#038; !prefersReducedMotion) ? CONFIG.autoRotateSpeed : 0;\n                currentRotX += (autoSpeed + rotVelocity) * delta;\n                rotVelocity *= Math.exp(-2.4 * delta);\n                if (Math.abs(rotVelocity) < 0.01) rotVelocity = 0;\n            }\n\n            \/\/ El giro se actualiza cada frame para que sea fluido, pero el repintado\n            \/\/ del canvas se limita a ~30fps: es un elemento decorativo y as\u00ed se\n            \/\/ reduce a la mitad el trabajo de la GPU\/CPU en la p\u00e1gina de WordPress.\n            renderAccumulatorMs += delta * 1000;\n            if (isDragging || renderAccumulatorMs >= FRAME_INTERVAL_MS) {\n                render(timestamp);\n                renderAccumulatorMs = 0;\n            }<\/p>\n<p>            if (isVisible) {\n                animFrameId = requestAnimationFrame(animationLoop);\n            } else {\n                isLoopRunning = false;\n            }\n        }<\/p>\n<p>        function startLoop() {\n            if (!isLoopRunning) {\n                isLoopRunning = true;\n                lastTime = performance.now();\n                animFrameId = requestAnimationFrame(animationLoop);\n            }\n        }<\/p>\n<p>        if (CONFIG.allowDrag) {\n            canvas.addEventListener(\"pointerdown\", function (e) {\n                isDragging = true;\n                rotVelocity = 0;\n                startX = e.clientX;\n                canvas.setPointerCapture(e.pointerId);\n                canvas.style.cursor = \"grabbing\";\n                startLoop();\n            });<\/p>\n<p>            canvas.addEventListener(\"pointermove\", function (e) {\n                if (!isDragging) return;\n                let deltaX = e.clientX - startX;\n                startX = e.clientX;\n                let rect = canvas.getBoundingClientRect();\n                let factor = rect.width > 0 ? 2.4 \/ rect.width : 0.006;\n                currentRotX += deltaX * factor;\n            });<\/p>\n<p>            function endDrag() {\n                if (isDragging) {\n                    isDragging = false;\n                    canvas.style.cursor = \"grab\";\n                    startLoop();\n                }\n            }<\/p>\n<p>            canvas.addEventListener(\"pointerup\", endDrag);\n            canvas.addEventListener(\"pointercancel\", endDrag);\n        }<\/p>\n<p>        resizeCanvas();\n        startLoop();<\/p>\n<p>        const resizeObserver = new ResizeObserver(() => {\n            resizeCanvas();\n            render(performance.now());\n        });\n        resizeObserver.observe(container);<\/p>\n<p>        const intersectionObserver = new IntersectionObserver(([entry]) => {\n            isVisible = entry.isIntersecting && !document.hidden;\n            if (isVisible) {\n                startLoop();\n            } else if (animFrameId) {\n                cancelAnimationFrame(animFrameId);\n                isLoopRunning = false;\n            }\n        });\n        intersectionObserver.observe(container);<\/p>\n<p>        \/\/ Si el usuario cambia de pesta\u00f1a, no seguir gastando CPU\/bater\u00eda en un\n        \/\/ globo que no se ve (rAF ya se pausa solo en background, esto es un refuerzo).\n        document.addEventListener(\"visibilitychange\", () => {\n            let inViewport = container.getBoundingClientRect().top < window.innerHeight;\n            isVisible = !document.hidden &#038;&#038; inViewport;\n            if (isVisible) {\n                startLoop();\n            } else if (animFrameId) {\n                cancelAnimationFrame(animFrameId);\n                isLoopRunning = false;\n            }\n        });\n    }\n\n    function initGlobe() {\n        document.querySelectorAll(\".rg-globe3d\").forEach(initGlobeInstance);\n    }\n\n    if (document.readyState === \"loading\") {\n        document.addEventListener(\"DOMContentLoaded\", initGlobe);\n    } else {\n        initGlobe();\n    }\n})();\n\n<\/script><\/p>\n","protected":false},"author":1,"template":"","meta":{"_acf_changed":false},"class_list":["post-10282","jupiterx-codes","type-jupiterx-codes","status-publish","hentry"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.2 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Globo 3D - JS - Lebuinco<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lebuinco.com\/eng\/jupiterx-codes\/globo-3d-js\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Globo 3D - JS - Lebuinco\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lebuinco.com\/eng\/jupiterx-codes\/globo-3d-js\/\" \/>\n<meta property=\"og:site_name\" content=\"Lebuinco\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lebuinco.com\\\/jupiterx-codes\\\/globo-3d-js\\\/\",\"url\":\"https:\\\/\\\/www.lebuinco.com\\\/jupiterx-codes\\\/globo-3d-js\\\/\",\"name\":\"Globo 3D - JS - Lebuinco\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lebuinco.com\\\/#website\"},\"datePublished\":\"2026-08-01T00:33:08+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lebuinco.com\\\/jupiterx-codes\\\/globo-3d-js\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lebuinco.com\\\/jupiterx-codes\\\/globo-3d-js\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lebuinco.com\\\/jupiterx-codes\\\/globo-3d-js\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Portada\",\"item\":\"https:\\\/\\\/www.lebuinco.com\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Globo 3D &#8211; JS\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lebuinco.com\\\/#website\",\"url\":\"https:\\\/\\\/www.lebuinco.com\\\/\",\"name\":\"Lebuinco\",\"description\":\"Agencia de desarrollo web\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lebuinco.com\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lebuinco.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.lebuinco.com\\\/#organization\",\"name\":\"Lebuinco\",\"url\":\"https:\\\/\\\/www.lebuinco.com\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lebuinco.com\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.lebuinco.com\\\/wp-content\\\/uploads\\\/2024\\\/10\\\/logo-Lebuinco-night.svg\",\"contentUrl\":\"https:\\\/\\\/www.lebuinco.com\\\/wp-content\\\/uploads\\\/2024\\\/10\\\/logo-Lebuinco-night.svg\",\"width\":147,\"height\":26,\"caption\":\"Lebuinco\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lebuinco.com\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.instagram.com\\\/lebuinco\\\/\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Globo 3D - JS - Lebuinco","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lebuinco.com\/eng\/jupiterx-codes\/globo-3d-js\/","og_locale":"en_US","og_type":"article","og_title":"Globo 3D - JS - Lebuinco","og_url":"https:\/\/www.lebuinco.com\/eng\/jupiterx-codes\/globo-3d-js\/","og_site_name":"Lebuinco","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.lebuinco.com\/jupiterx-codes\/globo-3d-js\/","url":"https:\/\/www.lebuinco.com\/jupiterx-codes\/globo-3d-js\/","name":"Globo 3D - JS - Lebuinco","isPartOf":{"@id":"https:\/\/www.lebuinco.com\/#website"},"datePublished":"2026-08-01T00:33:08+00:00","breadcrumb":{"@id":"https:\/\/www.lebuinco.com\/jupiterx-codes\/globo-3d-js\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lebuinco.com\/jupiterx-codes\/globo-3d-js\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.lebuinco.com\/jupiterx-codes\/globo-3d-js\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Portada","item":"https:\/\/www.lebuinco.com\/"},{"@type":"ListItem","position":2,"name":"Globo 3D &#8211; JS"}]},{"@type":"WebSite","@id":"https:\/\/www.lebuinco.com\/#website","url":"https:\/\/www.lebuinco.com\/","name":"Lebuinco","description":"Website development agency","publisher":{"@id":"https:\/\/www.lebuinco.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lebuinco.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.lebuinco.com\/#organization","name":"Lebuinco","url":"https:\/\/www.lebuinco.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lebuinco.com\/#\/schema\/logo\/image\/","url":"https:\/\/www.lebuinco.com\/wp-content\/uploads\/2024\/10\/logo-Lebuinco-night.svg","contentUrl":"https:\/\/www.lebuinco.com\/wp-content\/uploads\/2024\/10\/logo-Lebuinco-night.svg","width":147,"height":26,"caption":"Lebuinco"},"image":{"@id":"https:\/\/www.lebuinco.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.instagram.com\/lebuinco\/"]}]}},"_links":{"self":[{"href":"https:\/\/www.lebuinco.com\/eng\/wp-json\/wp\/v2\/jupiterx-codes\/10282","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lebuinco.com\/eng\/wp-json\/wp\/v2\/jupiterx-codes"}],"about":[{"href":"https:\/\/www.lebuinco.com\/eng\/wp-json\/wp\/v2\/types\/jupiterx-codes"}],"author":[{"embeddable":true,"href":"https:\/\/www.lebuinco.com\/eng\/wp-json\/wp\/v2\/users\/1"}],"wp:attachment":[{"href":"https:\/\/www.lebuinco.com\/eng\/wp-json\/wp\/v2\/media?parent=10282"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}