Greasy Fork is available in English.

Travel Agency 3.0D

3D version of Torn's world map

// ==UserScript==
// @name         Travel Agency 3.0D
// @namespace    https://greasyfork.org/en/users/3898
// @version      0.25.4
// @description  3D version of Torn's world map
// @author       Xiphias[187717]
// @match        https://www.torn.com/travelagency.php
// @require      https://cdn.jsdelivr.net/npm/three@0.119.1/build/three.min.js
// @require      https://cdn.jsdelivr.net/npm/three@0.119.1/examples/js/controls/OrbitControls.js
// @require      https://cdn.jsdelivr.net/npm/three@0.119.1/examples/js/lines/LineSegmentsGeometry.js
// @require      https://cdn.jsdelivr.net/npm/three@0.119.1/examples/js/lines/LineSegments2.js
// @require      https://cdn.jsdelivr.net/npm/three@0.119.1/examples/js/lines/Line2.js
// @require      https://cdn.jsdelivr.net/npm/three@0.119.1/examples/js/lines/LineMaterial.js
// @require      https://cdn.jsdelivr.net/npm/three@0.119.1/examples/js/lines/LineGeometry.js
// @require      https://cdn.jsdelivr.net/npm/dat.gui@0.7.7/build/dat.gui.min.js
// @require      https://greasyfork.org/scripts/412024-ztext-by-bennett-feely/code/ZText%20by%20Bennett%20Feely.js?version=851816
// @grant          GM_addStyle
// ==/UserScript==


(function() {
    'use strict';

    var captcha_exists = document.getElementsByClassName('captcha').length > 0;
    if (!captcha_exists) {

        let is_firefox = false;
        if(navigator.userAgent.toLowerCase().indexOf('firefox') > -1){
            is_firefox = true;
        }

        GM_addStyle(
            '#earth_location{cursor: grab} \
.invisible{visibility: hidden;transition: visibility 250ms linear} \
.opaque{opacity: .1;transition: opacity 250ms linear} \
.visible{opacity: 1;transition: opacity 250ms linear} \
.argentina{background: url(/images/v2/travel_agency/pinpoints/pinpoints_argentina.svg) left top no-repeat} \
.canada{background: url(/images/v2/travel_agency/pinpoints/pinpoints_canada.svg) left top no-repeat} \
.cayman_islands{background: url(/images/v2/travel_agency/pinpoints/pinpoints_cayman_islands.svg) left top no-repeat} \
.china{background: url(/images/v2/travel_agency/pinpoints/pinpoints_china.svg) left top no-repeat} \
.hawaii{background: url(/images/v2/travel_agency/pinpoints/pinpoints_hawaii.svg) left top no-repeat} \
.japan{background: url(/images/v2/travel_agency/pinpoints/pinpoints_japan.svg) left top no-repeat} \
.mexico{background: url(/images/v2/travel_agency/pinpoints/pinpoints_mexico.svg) left top no-repeat} \
.south_africa{background: url(/images/v2/travel_agency/pinpoints/pinpoints_south_africa.svg) left top no-repeat} \
.switzerland{background: url(/images/v2/travel_agency/pinpoints/pinpoints_switzerland.svg) left top no-repeat} \
.torn{background: url(/images/v2/travel_agency/pinpoints/pinpoints_torn.svg) left top no-repeat} \
.uae{background: url(/images/v2/travel_agency/pinpoints/pinpoints_uae.svg) left top no-repeat} \
.uk{background: url(/images/v2/travel_agency/pinpoints/pinpoints_uk.svg) left top no-repeat} \
.instahide{visibility: hidden}'
        );

        // Style for the controls
        GM_addStyle(
            '#gui { position: absolute; top: 2px; right: 5px } \
.dg .cr.number input[type=text] { \
border-radius: 0 !important; \
height: 13px; \
}'
        );

        // Spline js
        const GLOBE_RADIUS = 250;
        const CURVE_MIN_ALTITUDE = 50;
        const CURVE_MAX_ALTITUDE = GLOBE_RADIUS / 2.5;
        const DEGREE_TO_RADIAN = Math.PI / 180;

        function clamp(num, min, max) {
            return num <= min ? min : (num >= max ? max : num);
        }

        /**
     * @return {number}
     */
        function C(n) {
            return n > 1 ? (Math.PI/2) : n < -1 ? -Math.PI/2 : Math.asin(n);
        }
        /**
     * @return {number}
     */
        function P(n) {
            return (n = Math.sin(n / 2)) * n;
        }

        function geoInterpolate(n, t) {
            const p = 180 / Math.PI
            , h = Math.PI / 180
            , d = Math.atan2
            , E = Math.cos
            , x = Math.sin
            , _ = Math.sign || function (n) { return n > 0 ? 1 : n < 0 ? -1 : 0 }
            , N = Math.sqrt;

            let r = n[0] * h
            , i = n[1] * h
            , e = t[0] * h
            , o = t[1] * h
            , u = E(i)
            , c = x(i)
            , a = E(o)
            , l = x(o)
            , f = u * E(r)
            , s = u * x(r)
            , g = a * E(e)
            , v = a * x(e)
            , y = 2 * C(N(P(o - i) + u * a * P(e - r)))
            , S = x(y)
            , m = y ? function(n) {
                let t = x(n *= y) / S
                , r = x(y - n) / S
                , i = r * f + t * g
                , e = r * s + t * v
                , o = r * c + t * l;
                return [d(e, i) * p, d(o, N(i * i + e * e)) * p]
            }
            : function() {
                return [r * p, i * p]
            }
            ;
            return m.distance = y, m;
        }

        // util function to convert lat/lng to 3D point on globe
        function coordinateToPosition(lat, lng, radius) {
            const phi = (90 - lat) * DEGREE_TO_RADIAN;
            const theta = (lng + 180) * DEGREE_TO_RADIAN;

            return new THREE.Vector3(
                - radius * Math.sin(phi) * Math.cos(theta),
                radius * Math.cos(phi),
                radius * Math.sin(phi) * Math.sin(theta)
            );
        }

        function quartic(x) {
            return -0.000212614*Math.pow(x,4) + 0.00601681*Math.pow(x,3) - 0.0393748*Math.pow(x,2) - 0.0526971*x + 1.10919
        }

        let getSplineFromCoords = function(coords) {
            const startLat = coords[0];
            const startLng = coords[1];
            const endLat = coords[2];
            const endLng = coords[3];

            // start and end points
            const start = coordinateToPosition(startLat, startLng, GLOBE_RADIUS);
            const end = coordinateToPosition(endLat, endLng, GLOBE_RADIUS);

            // altitude
            let distance = start.distanceTo(end);
            let max_distance = 2 * GLOBE_RADIUS; // Yeah yeah
            let max_altitude = CURVE_MAX_ALTITUDE + CURVE_MAX_ALTITUDE*(max_distance / distance);

            // Naive weighted max altitude to make the lines not too high for short flights and low for long flights
            let steps = 15;

            let c = quartic((distance-25)/(max_distance - 25) * steps);
            max_altitude *= c;

            const altitude = clamp(distance * 0.5, CURVE_MIN_ALTITUDE, max_altitude);
            // 2 control points
            const interpolate = geoInterpolate([startLng, startLat], [endLng, endLat]);
            const midCoord1 = interpolate(0.25);
            const midCoord2 = interpolate(0.75);
            const mid1 = coordinateToPosition(midCoord1[1], midCoord1[0], GLOBE_RADIUS + altitude);
            const mid2 = coordinateToPosition(midCoord2[1], midCoord2[0], GLOBE_RADIUS + altitude);

            return {
                start,
                end,
                spline: new THREE.CubicBezierCurve3(start, mid1, mid2, end)
            };
        };

        // ------ Marker object ------------------------------------------------

        function Marker() {
            THREE.Object3D.call(this);

            const radius = 3.005;
            const sphereRadius = 5.02;
            const height = 12.0;

            const material = new THREE.MeshPhongMaterial({ color: 0xe85d64 }); // old color 0x636363

            let cone = new THREE.Mesh(new THREE.ConeBufferGeometry(radius, height, 8, 1, true), material);
            cone.position.y = height * 0.5;
            cone.rotation.x = Math.PI;

            let sphere = new THREE.Mesh(new THREE.SphereBufferGeometry(sphereRadius, 16, 8), material);
            sphere.position.y = height * 0.95 + sphereRadius;
            material.visible = false;

            this.add(cone, sphere);
        }

        Marker.prototype = Object.create(THREE.Object3D.prototype);

        function createMarker(name, radius, lat,lon ) {
            const marker = new Marker();

            marker.lat = lat;
            marker.lon = lon;

            marker.name = name;
            marker.country_flag = $('.raceway.' + name);
            marker.is_visible = false;

            const lonRad = -lon * (Math.PI / 180);
            const latRad = lat * (Math.PI / 180);
            const r = radius;

            marker.position.set(Math.cos(latRad) * Math.cos(lonRad) * r, Math.sin(latRad) * r, Math.cos(latRad) * Math.sin(lonRad) * r);
            marker.rotation.set(0.0, -lonRad, latRad - Math.PI * 0.5);

            return marker;
        }

        let clock = new THREE.Clock();

        let options = {
            rotation_speed: 0.03
        };

        let state = {
            rotation_speed: options.rotation_speed,
            flag_scale: 1.0
        };

        const imgLoc = "https://i.imgur.com/9cupfma.png"; ///images/v2/travel_agency/map.png";
        let travel_map_obj = $('.travel-map');
        let camera = new THREE.PerspectiveCamera(50, travel_map_obj.width() / travel_map_obj.height(), 0.1, 25000);
        let light = new THREE.AmbientLight(0xffffff, 1);

        var zoom_level = 750;
        camera.position.set(zoom_level, 0, 0);
        let scene = new THREE.Scene();

        camera.lookAt(scene.position);
        light.position.set(2000, 2000, 1500);
        scene.add(light);

        let canvas_location;

        const radius = 250;
        let earth = new THREE.SphereGeometry (radius,360,180);
        let material = new THREE.MeshPhongMaterial();
        let mesh = new THREE.Mesh(earth, material);


        let renderer = new THREE.WebGLRenderer( {antialias : true} );
        let maxAnisotropy = renderer.capabilities.getMaxAnisotropy();

        let loader = new THREE.TextureLoader();
        material.map = loader.load(imgLoc);

        material.displacementMap = new THREE.TextureLoader().load("https://i.imgur.com/FccON1I.png");
        material.displacementScale = 4.5;
        material.displacementBias = -4.0;

        //Filters
        material.map.magFilter = THREE.LinearFilter;
        material.map.minFilter = THREE.LinearMipMapLinearFilter;
        material.specular = new THREE.Color('#000000');
        material.map.anisotropy = maxAnisotropy;

        renderer.setSize(travel_map_obj.width(), travel_map_obj.height());

        let earth_location = document.createElement('div');
        earth_location.setAttribute("id", "earth_location");
        earth_location.appendChild(renderer.domElement);
        $(renderer.domElement).css('border-radius', '0 0 5px 5px');

        let china = createMarker("china", radius,  17.0832, 213.3532); // China
        let japan = createMarker("japan", radius,  12.220, 235.5); // Japan
        let hawaii = createMarker("hawaii", radius, -4.921, 301.5); // US
        let mexico = createMarker("mexico", radius, 6, -11); // Mexico
        let torn = createMarker("torn", radius, 4.75, 2.5); // Torn
        let canada = createMarker("canada", radius, 20.75, 17.812); // Canada
        let cayman_islands = createMarker("cayman", radius, -6.75, 16.112); // Cayman
        let argentina = createMarker("argentina", radius, -60.50, 37.812); // Argentina
        let switzerland = createMarker("switzerland", radius, 24.40, 105.812); // switzerland
        let uk = createMarker("uk", radius, 30.30, 95.412); // UK
        let south_africa = createMarker("south-africa", radius, -52.00, 125.412); // SA
        let uae = createMarker("uae", radius, -0.50, 152.012); // UAE

        let tabs = [$('#tab4-1'), $('#tab4-2'), $('#tab4-3'), $('#tab4-4')];

        for (let i = 0; i < tabs.length; ++i)
        {
            let elem = tabs[i];
            if (elem.attr('aria-hidden') === "false") {
                // Found active tab

                let travelmap = elem.find('.travel-map');
                let height = travelmap.height();
                $(earth_location).height(height);
                travelmap.after(earth_location);
                travelmap.hide();
            }
        }

        // Fix for hiding flags behind bottom padding of tabs
        $('.tabs.tabs-order').css('padding', '0 3px 0');
        let d = `<div id="paddington" style="position: relative;height: 5px;background: linear-gradient(0deg,black 10%,#383839,#2e2e2e);z-index: 20;"></div>`;
        $('.tabs.tabs-order').after(d);

        function removeTravelLine() {
            if (group !== undefined)
            {
                if (lineDashed !== null) {
                    group.remove(lineDashed);
                }
                if (lineSolid !== null) {
                    group.remove(lineSolid);
                }
            }
        }

        function tabClicked(a, tab_id) {
            if (a.parent().attr('aria-disabled') === "true") {
                return;
            }
            let travelmap = $(tab_id).find('.travel-map');
            travelmap.after($('#earth_location'));
            travelmap.hide();
            $(renderer.domElement).css('border-radius', '0 0 5px 5px');
            removeTravelLine();
        }

        $('.tabs > .standard > a').click(function () {
            tabClicked($(this), '#tab4-1');
        });

        $('.tabs > .airstrip > a').click(function () {
            tabClicked($(this), '#tab4-2');
        });

        $('.tabs > .private > a').click(function () {
            tabClicked($(this), '#tab4-3');
        });

        $('.tabs > .business > a').click(function () {
            tabClicked($(this), '#tab4-4');
        });

        // remove all pathways
        $('#tab-menu4').find('.path').each(function(i, el) {
            el.remove();
        });

        mesh.add(china);
        mesh.add(japan);
        mesh.add(hawaii);
        mesh.add(mexico);
        mesh.add(torn);
        mesh.add(canada);
        mesh.add(cayman_islands);
        mesh.add(argentina);
        mesh.add(switzerland);
        mesh.add(uk);
        mesh.add(south_africa);
        mesh.add(uae);

        let marker_array= [];
        marker_array.push(china);
        marker_array.push(japan);
        marker_array.push(hawaii);
        marker_array.push(mexico);
        marker_array.push(torn);
        marker_array.push(canada);
        marker_array.push(cayman_islands);
        marker_array.push(argentina);
        marker_array.push(switzerland);
        marker_array.push(uk);
        marker_array.push(south_africa);
        marker_array.push(uae);

        var timeout;

        for (let i = 0; i < marker_array.length; ++i)
        {
            let marker = marker_array[i];
            let class_to_find = '.raceway.' + marker.name;
            $(class_to_find).each(function(i, el) {
                el.marker = marker;
                $(el).addClass('opaque invisible');
                marker.is_visible = false;
            });

            $(class_to_find).click(function(event) {
                let this_marker = $(this)[0].marker;
                if (this_marker.name !== "torn") {
                    setTravelLine(this_marker.lat, this_marker.lon);
                    $(renderer.domElement).css('border-radius', '0 0 0 0');

                    clearTimeout(timeout);
                    let current_rotation_speed = options.rotation_speed;
                    let sign = Math.sign(current_rotation_speed);
                    if (Math.abs(current_rotation_speed) > 0.000001) {

                        let steps = 20;
                        let step = current_rotation_speed / steps;
                        let spin_up_time = 2500;

                        state.rotation_speed = 0.00;

                        function Start(target_speed)
                        {
                            state.rotation_speed += step;

                            if (sign*state.rotation_speed < sign*current_rotation_speed) {
                                timeout = setTimeout(Start, spin_up_time/steps, target_speed);
                            }

                        }

                        let delay = 6000;
                        timeout = setTimeout(Start, delay, current_rotation_speed);
                    }
                }
            });
        }


        let group = new THREE.Group();
        group.add(mesh);
        for (let i = 0; i < marker_array.length; ++i) {
            group.add(marker_array[i]);
        }

        group.position.set(0, 0, 0);
        group.scale.set(1, 1, 1);

        scene.add(group);

        let frustum = new THREE.Frustum();

        let controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.enablePan = false;
        controls.minDistance = 350;
        controls.maxDistance = 15000;
        controls.addEventListener('change', render);

        function createTravelLine(destination_lat, destination_long, isDash) {
            let spline = getSplineFromCoords([destination_lat, destination_long, 4.75, 2.5]).spline;

            let spline_points = spline.getPoints( 50 );

            let linewidth_ = 5;
            let geometry = new THREE.LineGeometry();
            let positions = [];
            for (let i = 0; i < spline_points.length; ++i){
                positions.push(spline_points[i].x, spline_points[i].y, spline_points[i].z );
            }
            geometry.setPositions( positions );

            let spline_material = new THREE.LineMaterial(
                {
                    color : 0xffffff,
                    linewidth: linewidth_-1
                } );

            let spline_dashed = new THREE.LineMaterial(
                {
                    color: 0x49caf5,
                    linewidth: linewidth_,
                    dashSize: 9,
                    gapSize: 6,
                    dashed: true,
                    transparent: true
                } );

            let line;
            if (isDash) {
                line = new THREE.Line2( geometry, spline_dashed );
                line.computeLineDistances();
            } else {
                line = new THREE.Line2( geometry, spline_material );
            }

            line.scale.set( 1, 1, 1 );

            // resolution of the viewport
            spline_dashed.resolution.set( window.innerWidth, window.innerHeight );
            spline_material.resolution.set( window.innerWidth, window.innerHeight );
            spline_dashed.defines.USE_DASH = "";

            return line;
        }

        function findPos(obj) {
            let curleft = 0;
            let curtop = 0;
            if (obj.offsetParent) {
                do {
                    curleft += obj.offsetLeft;
                    curtop += obj.offsetTop;
                    obj = obj.offsetParent
                } while (obj);
            }
            return [curleft,curtop];
        }

        function updateCanvasLocation(){
            canvas_location = findPos(renderer.domElement);
        }


        $('.raceway').css({'top': '0', 'left': '0'});

        function updateByMarker(offsetWidth, offsetHeight, marker, tab_index, flag_scale_normalized) {
            let v = new THREE.Vector3(0, 0, 0);
            v.setFromMatrixPosition(marker.matrixWorld);

            camera.updateMatrixWorld();
            camera.updateProjectionMatrix();
            v.project(camera);

            let boxes = marker.country_flag;
            let box = boxes[tab_index];

            let boundingRect = box.getBoundingClientRect();

            let screenpos = new THREE.Vector3(0, 0, 0);

            screenpos.x = ( v.x + 1 ) * (offsetWidth/2);
            screenpos.y = ( 1 - v.y ) * (offsetHeight/2);

            let scale = flag_scale_normalized;
            let pos_x = screenpos.x - (boundingRect.width / scale)/2;
            let pos_y = screenpos.y - (boundingRect.height / scale) - 7*(scale-1);

            // Scale 1
            //  x - 9
            //  y - 20

            // Scale 2
            //  x - 9
            //  y - 26

            // Scale 3
            //  x - 9
            //  y - 34

            // Scale 6
            //  x - 9
            //  y - 57

            let transform;
            if (is_firefox) {
                transform = 'translate(' + pos_x + 'px, ' + pos_y + 'px) scale(' + scale + ') rotate(0.2deg)'; // Minimize jerky movement
            } else {
                transform = 'translate(' + pos_x + 'px, ' + pos_y + 'px) scale(' + scale + ')';
            }
            $(box).css({'transform': transform});
        }

        function checkPinVisibility(frustum, earth, camera, markers) {
            if (earth == null || earth.boundingSphere == null) {
                return;
            }

            frustum.setFromProjectionMatrix(new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse));

            let cameraToEarth = earth.boundingSphere.center.distanceTo(camera.position);
            zoom_level = cameraToEarth;

            let L = Math.sqrt(Math.pow(cameraToEarth, 2) - Math.pow(earth.boundingSphere.radius, 2));

            let ratio = earth.boundingSphere.radius / L;

            for (let i = 0; i < markers.length; ++i) {
                let classes = markers[i].country_flag;
                let v = new THREE.Vector3();
                v.setFromMatrixPosition(markers[i].matrixWorld);
                let cameraToPin = v.distanceTo(camera.position);

                if (!frustum.containsPoint(v)) {
                    classes.removeClass('visible opaque invisible');
                    classes.addClass('instahide opaque invisible');
                    markers[i].is_visible = false;
                }


                else if(cameraToPin > L* (1 + ratio/4)) { // Try to hide flag if it is actually behind the earth
                    classes.removeClass('instahide');
                    classes.addClass('invisible');
                    markers[i].is_visible = false;
                } else if (cameraToPin > L) {
                    classes.removeClass('instahide visible invisible');
                    classes.addClass('opaque');
                    markers[i].is_visible = true;
                } else {
                    classes.removeClass('instahide opaque invisible');
                    classes.addClass('visible');
                    markers[i].is_visible = true;
                }
            }
        }

        function normalize(value, min, max, t_min, t_max) {
            let a = (t_max - t_min) / (max - min)
            let b = t_max - a * max
            return a * value + b
        }

        let lineDashed = null;
        let lineSolid = null;

        function animate(){
            requestAnimationFrame(animate);

            checkPinVisibility(frustum, earth, camera, marker_array);

            let offsetWidth = renderer.domElement.offsetWidth;
            let offsetHeight = renderer.domElement.offsetHeight;

            let flag_boxes = marker_array[0].country_flag;

            let tab_index = 0;
            for (let i = 0; i < flag_boxes.length; ++i)
            {
                if (flag_boxes[i].parentNode.getAttribute('aria-hidden') === "false")
                {
                    tab_index = i;
                    break;
                }
            }

            let flag_scale_normalized = state.flag_scale;


            for (let i = 0; i < marker_array.length; ++i)
            {
                // Improve by doing batch-read first, then write
                // So first get dimensions of elements and such, then update CSS

                if (marker_array[i].is_visible) {
                    updateByMarker(offsetWidth, offsetHeight, marker_array[i], tab_index, flag_scale_normalized);
                }
            }

            controls.update();
            render();
        }

        function render(){

            let delta = clock.getDelta();

            group.rotateY( state.rotation_speed * delta );

            renderer.clear();
            renderer.render(scene, camera);
        }

        animate();

        updateCanvasLocation();

        earth_location.addEventListener('mousedown', function() {
            earth_location.style.cursor = "-moz-grabbing";
            earth_location.style.cursor = "-webkit-grabbing";
            earth_location.style.cursor = "grabbing";
        });

        earth_location.addEventListener('mouseup', function() {
            earth_location.style.cursor = "-moz-grab";
            earth_location.style.cursor = "-webkit-grab";
            earth_location.style.cursor = "grab";
        });

        renderer.domElement.addEventListener("wheel", function() {
            const max_zoom = 15000;
            const middle_point = 1000;
            const min_zoom = 350;

            const t_min = 0.4;
            const t_middle = 1.0;
            const t_max = 2.0;

            if (zoom_level > middle_point) {
                state.flag_scale = normalize(zoom_level, middle_point, max_zoom, t_middle, t_min);
            } else {
                state.flag_scale = normalize(zoom_level, min_zoom, middle_point, t_max, t_middle);
            }
        });

        function updateFlagScaling() {
            const max_zoom = 15000;
            const middle_point = 1000;
            const min_zoom = 350;

            const t_min = 0.5;
            const t_middle = 1.0;
            const t_max = 2.0;

            if (zoom_level > middle_point) {
                state.flag_scale = normalize(zoom_level, middle_point, max_zoom, t_middle, t_min);
            } else {
                state.flag_scale = normalize(zoom_level, min_zoom, middle_point, t_max, t_middle);
            }
        }

        window.addEventListener( 'resize', onWindowResize, false );

        function onWindowResize(){
            let tab_containers = $('.tab-menu-cont')
            let active_tab_container = null;
            for (let i = 0; i < tab_containers.length; ++i)
            {
                if (tab_containers[i].getAttribute('aria-hidden') === "false"){
                    active_tab_container = $(tab_containers[i]);
                    break;
                }
            }

            if (active_tab_container != null) {
                let width = active_tab_container.width();
                let height = active_tab_container.height();
                let ratio = width / height;

                updateCanvasLocation();
            }

        }

        function setTravelLine(lat, lon)
        {
            let newLineDashed = createTravelLine(lat, lon, true);
            let newLineSolid = createTravelLine(lat, lon, false);

            if (lineDashed !== null) {
                group.remove(lineDashed);
            }

            if (lineSolid !== null) {
                group.remove(lineSolid);
            }

            group.add(newLineDashed);
            group.add(newLineSolid);
            lineDashed = newLineDashed;
            lineSolid = newLineSolid;
        }


        // MENU
        var gui = new dat.GUI({ autoPlace: false, width: 147 });
        gui.domElement.id = 'gui';
        gui.add(options, "rotation_speed", -0.5, 0.5, 0.01).name("Rotation").onChange(
            function() {
                clearTimeout(timeout);
                state.rotation_speed = options.rotation_speed;
            }
        );
        gui.close();

        earth_location.appendChild(gui.domElement);
        //----------------

        $('#skip-to-content').after($('<h4 class="ta3D left" style="margin-left: 6px; font-size: 16px">3.0D</h4>'));

        GM_addStyle('.z-text {  transform: rotateX(23deg) rotateY(-31deg) rotateZ(-5deg); }');
        var ztxt = new Ztextify(".ta3D", {
            depth: "18px",
            layers: 3,
            fade: true,
            direction: "both"
        });

        updateFlagScaling();
    }
})();