Jump to content

MediaWiki:Common.js: Difference between revisions

The comprehensive free global encyclopedia of CEOs, corporate leadership, and business excellence
Updated to true 3D interactive rotating tag cloud - mouse-controlled sphere rotation, depth-based scaling, interactive movement
Fixed 3D tag cloud - now auto-rotates continuously, uses Canvas for visibility, clickable tags, proper depth sorting
Line 44: Line 44:


/**
/**
  * 3D Interactive Rotating Tag Cloud for Main Page
  * Simple 3D Rotating Tag Cloud for Main Page
  * True 3D sphere with mouse interaction
  * Auto-rotates continuously, very visible
  */
  */
(function() {
(function() {
Line 53: Line 53:
     }
     }


    // Wait for DOM to be ready
     $(document).ready(function() {
     $(document).ready(function() {
         var placeholder = document.getElementById('ceo-tagcloud-placeholder');
         var placeholder = document.getElementById('ceo-tagcloud-placeholder');
         if (!placeholder) return;
         if (!placeholder) return;


         // Popular topics with importance weights
         // Topics
         var topics = [
         var topics = [
             { text: 'Satya Nadella', url: '/wiki/Satya_Nadella', weight: 9 },
             { text: 'Satya Nadella', url: '/wiki/Satya_Nadella' },
             { text: 'Tim Cook', url: '/wiki/Tim_Cook', weight: 8 },
             { text: 'Tim Cook', url: '/wiki/Tim_Cook' },
             { text: 'Elon Musk', url: '/wiki/Elon_Musk', weight: 10 },
             { text: 'Elon Musk', url: '/wiki/Elon_Musk' },
             { text: 'Mark Zuckerberg', url: '/wiki/Mark_Zuckerberg', weight: 7 },
             { text: 'Mark Zuckerberg', url: '/wiki/Mark_Zuckerberg' },
             { text: 'Sundar Pichai', url: '/wiki/Sundar_Pichai', weight: 7 },
             { text: 'Sundar Pichai', url: '/wiki/Sundar_Pichai' },
             { text: 'Andy Jassy', url: '/wiki/Andy_Jassy', weight: 6 },
             { text: 'Andy Jassy', url: '/wiki/Andy_Jassy' },
             { text: 'Jensen Huang', url: '/wiki/Jensen_Huang', weight: 8 },
             { text: 'Jensen Huang', url: '/wiki/Jensen_Huang' },
             { text: 'Lisa Su', url: '/wiki/Lisa_Su', weight: 6 },
             { text: 'Lisa Su', url: '/wiki/Lisa_Su' },
             { text: 'Mary Barra', url: '/wiki/Mary_Barra', weight: 5 },
             { text: 'Mary Barra', url: '/wiki/Mary_Barra' },
             { text: 'Jamie Dimon', url: '/wiki/Jamie_Dimon', weight: 5 },
             { text: 'Jamie Dimon', url: '/wiki/Jamie_Dimon' },
             { text: 'Microsoft', url: '/wiki/Microsoft', weight: 5 },
             { text: 'Microsoft', url: '/wiki/Microsoft' },
             { text: 'Apple', url: '/wiki/Apple_Inc.', weight: 5 },
             { text: 'Apple', url: '/wiki/Apple_Inc.' },
             { text: 'Amazon', url: '/wiki/Amazon', weight: 5 },
             { text: 'Amazon', url: '/wiki/Amazon' },
             { text: 'Google', url: '/wiki/Google', weight: 5 },
             { text: 'Google', url: '/wiki/Google' },
             { text: 'Tesla', url: '/wiki/Tesla', weight: 5 },
             { text: 'Tesla', url: '/wiki/Tesla' },
             { text: 'Leadership', url: '/wiki/Category:Chief_executive_officers', weight: 4 },
             { text: 'Leadership', url: '/wiki/Category:Chief_executive_officers' },
             { text: 'Innovation', url: '/wiki/Category:Business_strategies', weight: 4 },
             { text: 'Innovation', url: '/wiki/Category:Business_strategies' },
             { text: 'Technology', url: '/wiki/Category:Companies', weight: 4 },
             { text: 'Technology', url: '/wiki/Category:Companies' },
             { text: 'Strategy', url: '/wiki/Category:Business_strategies', weight: 4 },
             { text: 'Strategy', url: '/wiki/Category:Business_strategies' },
             { text: 'Cloud Computing', url: '/wiki/Category:Industry_analysis', weight: 4 }
             { text: 'Cloud Computing', url: '/wiki/Category:Industry_analysis' }
         ];
         ];


         // Create container
         // Create container
         var container = document.createElement('div');
         var container = $('<div>').css({
        container.id = 'tagcloud-3d-container';
            'background': 'linear-gradient(135deg, #0a1929 0%, #1a2942 50%, #0d2744 100%)',
        container.style.cssText = 'background: linear-gradient(135deg, #0a1929 0%, #1a2942 50%, #0d2744 100%); padding: 40px 20px; border-radius: 12px; box-shadow: 0 15px 40px rgba(0,0,0,0.4), inset 0 2px 10px rgba(255,255,255,0.05); min-height: 400px; display: flex; align-items: center; justify-content: center; position: relative; overflow: hidden; perspective: 1000px;';
            'padding': '40px 20px',
 
            'border-radius': '12px',
        // Add animated background pattern
            'box-shadow': '0 15px 40px rgba(0,0,0,0.4)',
        var bgPattern = document.createElement('div');
            'min-height': '400px',
        bgPattern.style.cssText = 'position: absolute; top: 0; left: 0; right: 0; bottom: 0; opacity: 0.1; background-image: radial-gradient(circle at 20% 50%, rgba(255,255,255,0.1) 0%, transparent 50%), radial-gradient(circle at 80% 80%, rgba(255,255,255,0.1) 0%, transparent 50%);';
            'position': 'relative',
         container.appendChild(bgPattern);
            'overflow': 'hidden'
         });


         // Title
         // Title
         var title = document.createElement('div');
         var title = $('<div>').text('TRENDING TOPICS').css({
        title.style.cssText = 'position: absolute; top: 20px; left: 50%; transform: translateX(-50%); color: white; font-size: 1.4em; font-weight: 700; text-shadow: 0 4px 10px rgba(0,0,0,0.5), 0 0 20px rgba(255,255,255,0.1); z-index: 10; letter-spacing: 1px;';
            'color': 'white',
         title.textContent = 'TRENDING TOPICS';
            'font-size': '1.4em',
         container.appendChild(title);
            'font-weight': '700',
            'text-align': 'center',
            'margin-bottom': '30px',
            'text-shadow': '0 4px 10px rgba(0,0,0,0.5)'
         });
         container.append(title);


         // 3D Cloud container
         // Canvas for 3D cloud
         var cloud = document.createElement('div');
         var canvas = $('<canvas>').attr({
        cloud.id = 'tagcloud-3d';
            'id': 'tagcloud3d-canvas',
         cloud.style.cssText = 'position: relative; width: 100%; max-width: 700px; height: 350px; transform-style: preserve-3d;';
            'width': '800',
            'height': '400'
         }).css({
            'display': 'block',
            'margin': '0 auto',
            'max-width': '100%'
        });
        container.append(canvas);
        placeholder.appendChild(container[0]);


         // Create tags with 3D positioning
         // 3D Tag Cloud Implementation
         var radius = 180;
         var ctx = canvas[0].getContext('2d');
         var tags = [];
         var tags = [];
        var radius = 150;
        var fallLength = 500;
         var angleX = 0;
         var angleX = 0;
         var angleY = 0;
         var angleY = 0;
         var speed = 0.3;
         var speedX = 0.01; // Auto-rotation speed
         var mouseX = 0;
         var speedY = 0.015;
        var mouseY = 0;


         topics.forEach(function(topic, index) {
        // Initialize tags in 3D space
            var link = document.createElement('a');
         topics.forEach(function(topic, i) {
            link.href = topic.url;
             var phi = Math.acos(-1 + (2 * i) / topics.length);
            link.textContent = topic.text;
 
            var fontSize = 14 + (topic.weight * 2);
            link.style.cssText = 'position: absolute; left: 50%; top: 50%; color: white; text-decoration: none; font-size: ' + fontSize + 'px; font-weight: 600; padding: 8px 18px; background: rgba(255,255,255,0.12); border-radius: 25px; backdrop-filter: blur(10px); transition: all 0.3s ease; white-space: nowrap; border: 1px solid rgba(255,255,255,0.2); box-shadow: 0 4px 15px rgba(0,0,0,0.3);';
 
            // Calculate initial 3D position on sphere
             var phi = Math.acos(-1 + (2 * index) / topics.length);
             var theta = Math.sqrt(topics.length * Math.PI) * phi;
             var theta = Math.sqrt(topics.length * Math.PI) * phi;


             link.phi = phi;
             tags.push({
            link.theta = theta;
                text: topic.text,
                url: topic.url,
                x: radius * Math.sin(phi) * Math.cos(theta),
                y: radius * Math.cos(phi),
                z: radius * Math.sin(phi) * Math.sin(theta),
                originalX: radius * Math.sin(phi) * Math.cos(theta),
                originalY: radius * Math.cos(phi),
                originalZ: radius * Math.sin(phi) * Math.sin(theta)
            });
        });


            // Hover effects
        // Rotation functions
            link.onmouseenter = function() {
        function rotateX(x, y, z, angle) {
                this.style.transform = this.style.transform.replace(/scale\([^)]+\)/, '') + ' scale(1.25)';
            var cos = Math.cos(angle);
                this.style.background = 'rgba(255,255,255,0.25)';
            var sin = Math.sin(angle);
                 this.style.boxShadow = '0 8px 25px rgba(0,0,0,0.5), 0 0 30px rgba(255,255,255,0.2)';
            return {
                 this.style.zIndex = '1000';
                 x: x,
                y: y * cos - z * sin,
                 z: y * sin + z * cos
             };
             };
            link.onmouseleave = function() {
        }
                this.style.transform = this.style.transform.replace(/scale\([^)]+\)/, '') + ' scale(1)';
 
                this.style.background = 'rgba(255,255,255,0.12)';
        function rotateY(x, y, z, angle) {
                 this.style.boxShadow = '0 4px 15px rgba(0,0,0,0.3)';
            var cos = Math.cos(angle);
                 this.style.zIndex = '';
            var sin = Math.sin(angle);
            return {
                 x: x * cos + z * sin,
                y: y,
                 z: -x * sin + z * cos
             };
             };
        }


            cloud.appendChild(link);
        // Animation loop
             tags.push(link);
        function animate() {
        });
            // Clear canvas
             ctx.clearRect(0, 0, canvas[0].width, canvas[0].height);


        container.appendChild(cloud);
            // Auto-rotate
        placeholder.appendChild(container);
            angleX += speedX;
            angleY += speedY;


        // Mouse tracking for interactive rotation
            // Update and draw tags
        container.addEventListener('mousemove', function(e) {
            tags.forEach(function(tag) {
            var rect = container.getBoundingClientRect();
                // Apply rotations
            mouseX = (e.clientX - rect.left - rect.width / 2) / (rect.width / 2);
                var rotated = rotateX(tag.originalX, tag.originalY, tag.originalZ, angleX);
            mouseY = (e.clientY - rect.top - rect.height / 2) / (rect.height / 2);
                rotated = rotateY(rotated.x, rotated.y, rotated.z, angleY);
        });


        container.addEventListener('mouseleave', function() {
                tag.x = rotated.x;
            mouseX = 0;
                tag.y = rotated.y;
            mouseY = 0;
                tag.z = rotated.z;
        });
            });


        // Animation loop
            // Sort by z-depth (back to front)
        function animate() {
            tags.sort(function(a, b) {
            // Update rotation based on mouse position or auto-rotate
                return a.z - b.z;
            angleY += (mouseX * 2 - angleY) * 0.05 + speed * 0.01;
             });
             angleX += (mouseY * 2 - angleX) * 0.05;


             // Update each tag position
             // Draw tags
             tags.forEach(function(tag) {
             tags.forEach(function(tag) {
                 var phi = tag.phi;
                 var scale = fallLength / (fallLength + tag.z);
                 var theta = tag.theta + angleY;
                 var x = tag.x * scale + canvas[0].width / 2;
 
                 var y = tag.y * scale + canvas[0].height / 2;
                // 3D to 2D projection
                 var alpha = (tag.z + radius) / (2 * radius);
                var x = radius * Math.sin(phi) * Math.cos(theta);
                 alpha = Math.max(0.3, Math.min(1, alpha));
                 var y = radius * Math.cos(phi) + Math.sin(angleX) * 30;
                 var z = radius * Math.sin(phi) * Math.sin(theta);
 
                // Calculate scale based on z-depth
                var scale = (z + radius) / (2 * radius);
                 scale = Math.max(0.5, Math.min(1.2, scale));


                 // Calculate opacity based on z-depth
                 // Font size based on depth
                 var opacity = (z + radius) / (2 * radius);
                 var fontSize = Math.floor(14 + 10 * scale);
                opacity = Math.max(0.4, Math.min(1, opacity));


                 // Apply 3D transform
                 ctx.save();
                 var transformStyle = 'translate(-50%, -50%) translate3d(' + x + 'px, ' + y + 'px, ' + z + 'px) scale(' + scale + ')';
                 ctx.font = fontSize + 'px sans-serif, "Helvetica Neue", Helvetica, Arial';
                 tag.style.transform = transformStyle;
                ctx.fillStyle = 'rgba(255, 255, 255, ' + alpha + ')';
                 tag.style.opacity = opacity;
                ctx.textAlign = 'center';
                 tag.style.zIndex = Math.floor(z + radius);
                 ctx.textBaseline = 'middle';
                ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
                 ctx.shadowBlur = 5;
                 ctx.fillText(tag.text, x, y);
                ctx.restore();
             });
             });


Line 194: Line 214:
         }
         }


        // Start animation
         animate();
         animate();


         // Add instruction text
         // Make tags clickable
         var instruction = document.createElement('div');
         canvas.on('click', function(e) {
        instruction.style.cssText = 'position: absolute; bottom: 15px; left: 50%; transform: translateX(-50%); color: rgba(255,255,255,0.6); font-size: 0.9em; text-shadow: 0 2px 5px rgba(0,0,0,0.5); z-index: 10;';
            var rect = canvas[0].getBoundingClientRect();
        instruction.textContent = 'Move your mouse to rotate';
            var x = e.clientX - rect.left;
         container.appendChild(instruction);
            var y = e.clientY - rect.top;
 
            // Check if click is near any tag
            for (var i = tags.length - 1; i >= 0; i--) {
                var tag = tags[i];
                var scale = fallLength / (fallLength + tag.z);
                var tagX = tag.x * scale + canvas[0].width / 2;
                var tagY = tag.y * scale + canvas[0].height / 2;
 
                ctx.font = Math.floor(14 + 10 * scale) + 'px sans-serif';
                var metrics = ctx.measureText(tag.text);
                var width = metrics.width;
                var height = Math.floor(14 + 10 * scale);
 
                if (x >= tagX - width/2 && x <= tagX + width/2 &&
                    y >= tagY - height/2 && y <= tagY + height/2) {
                    window.location.href = tag.url;
                    break;
                }
            }
        });
 
        // Change cursor on hover
        canvas.on('mousemove', function(e) {
            var rect = canvas[0].getBoundingClientRect();
            var x = e.clientX - rect.left;
            var y = e.clientY - rect.top;
            var overTag = false;
 
            for (var i = tags.length - 1; i >= 0; i--) {
                var tag = tags[i];
                var scale = fallLength / (fallLength + tag.z);
                var tagX = tag.x * scale + canvas[0].width / 2;
                var tagY = tag.y * scale + canvas[0].height / 2;
 
                ctx.font = Math.floor(14 + 10 * scale) + 'px sans-serif';
                var metrics = ctx.measureText(tag.text);
                var width = metrics.width;
                var height = Math.floor(14 + 10 * scale);
 
                if (x >= tagX - width/2 && x <= tagX + width/2 &&
                    y >= tagY - height/2 && y <= tagY + height/2) {
                    overTag = true;
                    break;
                }
            }
 
            canvas.css('cursor', overTag ? 'pointer' : 'default');
         });
     });
     });
})();
})();

Revision as of 09:45, 20 October 2025

/**
 * Add statistics banner to account creation page
 */
(function() {
    // Only run on Special:CreateAccount page
    if (mw.config.get('wgCanonicalSpecialPageName') !== 'CreateAccount') {
        return;
    }

    // Get site statistics via API
    var api = new mw.Api();

    api.get({
        action: 'query',
        meta: 'siteinfo',
        siprop: 'statistics',
        format: 'json'
    }).done(function(data) {
        var stats = data.query.statistics;

        // Create statistics banner
        var banner = $('<div>').addClass('createaccount-statistics').html(
            '<div class="createaccount-statistics-title">CEO.wiki is made by people like you.</div>' +
            '<div class="createaccount-statistics-grid">' +
                '<div class="createaccount-stat-item">' +
                    '<div class="createaccount-stat-number">' + stats.edits.toLocaleString() + '</div>' +
                    '<div class="createaccount-stat-label">Edits</div>' +
                '</div>' +
                '<div class="createaccount-stat-item">' +
                    '<div class="createaccount-stat-number">' + stats.pages.toLocaleString() + '</div>' +
                    '<div class="createaccount-stat-label">Pages</div>' +
                '</div>' +
                '<div class="createaccount-stat-item">' +
                    '<div class="createaccount-stat-number">' + stats.activeusers.toLocaleString() + '</div>' +
                    '<div class="createaccount-stat-label">Recent Contributors</div>' +
                '</div>' +
            '</div>'
        );

        // Insert banner before the form
        $('#userloginForm').before(banner);
    });
})();

/**
 * Simple 3D Rotating Tag Cloud for Main Page
 * Auto-rotates continuously, very visible
 */
(function() {
    // Only run on Main Page
    if (mw.config.get('wgPageName') !== 'Main_Page') {
        return;
    }

    $(document).ready(function() {
        var placeholder = document.getElementById('ceo-tagcloud-placeholder');
        if (!placeholder) return;

        // Topics
        var topics = [
            { text: 'Satya Nadella', url: '/wiki/Satya_Nadella' },
            { text: 'Tim Cook', url: '/wiki/Tim_Cook' },
            { text: 'Elon Musk', url: '/wiki/Elon_Musk' },
            { text: 'Mark Zuckerberg', url: '/wiki/Mark_Zuckerberg' },
            { text: 'Sundar Pichai', url: '/wiki/Sundar_Pichai' },
            { text: 'Andy Jassy', url: '/wiki/Andy_Jassy' },
            { text: 'Jensen Huang', url: '/wiki/Jensen_Huang' },
            { text: 'Lisa Su', url: '/wiki/Lisa_Su' },
            { text: 'Mary Barra', url: '/wiki/Mary_Barra' },
            { text: 'Jamie Dimon', url: '/wiki/Jamie_Dimon' },
            { text: 'Microsoft', url: '/wiki/Microsoft' },
            { text: 'Apple', url: '/wiki/Apple_Inc.' },
            { text: 'Amazon', url: '/wiki/Amazon' },
            { text: 'Google', url: '/wiki/Google' },
            { text: 'Tesla', url: '/wiki/Tesla' },
            { text: 'Leadership', url: '/wiki/Category:Chief_executive_officers' },
            { text: 'Innovation', url: '/wiki/Category:Business_strategies' },
            { text: 'Technology', url: '/wiki/Category:Companies' },
            { text: 'Strategy', url: '/wiki/Category:Business_strategies' },
            { text: 'Cloud Computing', url: '/wiki/Category:Industry_analysis' }
        ];

        // Create container
        var container = $('<div>').css({
            'background': 'linear-gradient(135deg, #0a1929 0%, #1a2942 50%, #0d2744 100%)',
            'padding': '40px 20px',
            'border-radius': '12px',
            'box-shadow': '0 15px 40px rgba(0,0,0,0.4)',
            'min-height': '400px',
            'position': 'relative',
            'overflow': 'hidden'
        });

        // Title
        var title = $('<div>').text('TRENDING TOPICS').css({
            'color': 'white',
            'font-size': '1.4em',
            'font-weight': '700',
            'text-align': 'center',
            'margin-bottom': '30px',
            'text-shadow': '0 4px 10px rgba(0,0,0,0.5)'
        });
        container.append(title);

        // Canvas for 3D cloud
        var canvas = $('<canvas>').attr({
            'id': 'tagcloud3d-canvas',
            'width': '800',
            'height': '400'
        }).css({
            'display': 'block',
            'margin': '0 auto',
            'max-width': '100%'
        });
        container.append(canvas);
        placeholder.appendChild(container[0]);

        // 3D Tag Cloud Implementation
        var ctx = canvas[0].getContext('2d');
        var tags = [];
        var radius = 150;
        var fallLength = 500;
        var angleX = 0;
        var angleY = 0;
        var speedX = 0.01; // Auto-rotation speed
        var speedY = 0.015;

        // Initialize tags in 3D space
        topics.forEach(function(topic, i) {
            var phi = Math.acos(-1 + (2 * i) / topics.length);
            var theta = Math.sqrt(topics.length * Math.PI) * phi;

            tags.push({
                text: topic.text,
                url: topic.url,
                x: radius * Math.sin(phi) * Math.cos(theta),
                y: radius * Math.cos(phi),
                z: radius * Math.sin(phi) * Math.sin(theta),
                originalX: radius * Math.sin(phi) * Math.cos(theta),
                originalY: radius * Math.cos(phi),
                originalZ: radius * Math.sin(phi) * Math.sin(theta)
            });
        });

        // Rotation functions
        function rotateX(x, y, z, angle) {
            var cos = Math.cos(angle);
            var sin = Math.sin(angle);
            return {
                x: x,
                y: y * cos - z * sin,
                z: y * sin + z * cos
            };
        }

        function rotateY(x, y, z, angle) {
            var cos = Math.cos(angle);
            var sin = Math.sin(angle);
            return {
                x: x * cos + z * sin,
                y: y,
                z: -x * sin + z * cos
            };
        }

        // Animation loop
        function animate() {
            // Clear canvas
            ctx.clearRect(0, 0, canvas[0].width, canvas[0].height);

            // Auto-rotate
            angleX += speedX;
            angleY += speedY;

            // Update and draw tags
            tags.forEach(function(tag) {
                // Apply rotations
                var rotated = rotateX(tag.originalX, tag.originalY, tag.originalZ, angleX);
                rotated = rotateY(rotated.x, rotated.y, rotated.z, angleY);

                tag.x = rotated.x;
                tag.y = rotated.y;
                tag.z = rotated.z;
            });

            // Sort by z-depth (back to front)
            tags.sort(function(a, b) {
                return a.z - b.z;
            });

            // Draw tags
            tags.forEach(function(tag) {
                var scale = fallLength / (fallLength + tag.z);
                var x = tag.x * scale + canvas[0].width / 2;
                var y = tag.y * scale + canvas[0].height / 2;
                var alpha = (tag.z + radius) / (2 * radius);
                alpha = Math.max(0.3, Math.min(1, alpha));

                // Font size based on depth
                var fontSize = Math.floor(14 + 10 * scale);

                ctx.save();
                ctx.font = fontSize + 'px sans-serif, "Helvetica Neue", Helvetica, Arial';
                ctx.fillStyle = 'rgba(255, 255, 255, ' + alpha + ')';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
                ctx.shadowBlur = 5;
                ctx.fillText(tag.text, x, y);
                ctx.restore();
            });

            requestAnimationFrame(animate);
        }

        // Start animation
        animate();

        // Make tags clickable
        canvas.on('click', function(e) {
            var rect = canvas[0].getBoundingClientRect();
            var x = e.clientX - rect.left;
            var y = e.clientY - rect.top;

            // Check if click is near any tag
            for (var i = tags.length - 1; i >= 0; i--) {
                var tag = tags[i];
                var scale = fallLength / (fallLength + tag.z);
                var tagX = tag.x * scale + canvas[0].width / 2;
                var tagY = tag.y * scale + canvas[0].height / 2;

                ctx.font = Math.floor(14 + 10 * scale) + 'px sans-serif';
                var metrics = ctx.measureText(tag.text);
                var width = metrics.width;
                var height = Math.floor(14 + 10 * scale);

                if (x >= tagX - width/2 && x <= tagX + width/2 &&
                    y >= tagY - height/2 && y <= tagY + height/2) {
                    window.location.href = tag.url;
                    break;
                }
            }
        });

        // Change cursor on hover
        canvas.on('mousemove', function(e) {
            var rect = canvas[0].getBoundingClientRect();
            var x = e.clientX - rect.left;
            var y = e.clientY - rect.top;
            var overTag = false;

            for (var i = tags.length - 1; i >= 0; i--) {
                var tag = tags[i];
                var scale = fallLength / (fallLength + tag.z);
                var tagX = tag.x * scale + canvas[0].width / 2;
                var tagY = tag.y * scale + canvas[0].height / 2;

                ctx.font = Math.floor(14 + 10 * scale) + 'px sans-serif';
                var metrics = ctx.measureText(tag.text);
                var width = metrics.width;
                var height = Math.floor(14 + 10 * scale);

                if (x >= tagX - width/2 && x <= tagX + width/2 &&
                    y >= tagY - height/2 && y <= tagY + height/2) {
                    overTag = true;
                    break;
                }
            }

            canvas.css('cursor', overTag ? 'pointer' : 'default');
        });
    });
})();