FetLife Follower Growth Tracker

Track your follower growth over time on FetLife with modern Chart.js visualization

// ==UserScript==
// @name         FetLife Follower Growth Tracker
// @namespace    http://violentmonkey.com/
// @version      1.0
// @description  Track your follower growth over time on FetLife with modern Chart.js visualization
// @match        https://fetlife.com/*
// @exclude      https://fetlife.com/home*
// @exclude      https://fetlife.com/feed*
// @exclude      https://fetlife.com/explore*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    console.log('🚀 FetLife Follower Tracker v2.0 with Chart.js starting...');

    // Storage key for follower data
    const STORAGE_KEY = 'fetlifeFollowerData';

    // Get current follower count from the page
    function getCurrentFollowerCount() {
        // Method 1: Try to find follower count in the page data
        try {
            // Look for the followers link in the DOM
            const followerLink = document.querySelector('a[href*="/followers"]');
            if (followerLink) {
                const text = followerLink.textContent.trim();
                // Look for numbers in the link text, handle comma-separated numbers
                const match = text.match(/([\d,]+)/);
                if (match) {
                    return parseInt(match[1].replace(/,/g, ''), 10);
                }
            }
        } catch (e) {
            console.log('Method 1 failed:', e);
        }

        // Method 2: Look for any element containing "followers" text with numbers
        const elements = document.querySelectorAll('*');
        for (const el of elements) {
            const text = el.textContent.toLowerCase();
            if (text.includes('followers') && !text.includes('following')) {
                const match = el.textContent.match(/([\d,]+)/);
                if (match) {
                    return parseInt(match[1].replace(/,/g, ''), 10);
                }
            }
        }

        // Method 3: Look in all links that might contain follower info
        const allLinks = document.querySelectorAll('a');
        for (const link of allLinks) {
            if (link.href.includes('/followers')) {
                const text = link.textContent.trim();
                const match = text.match(/([\d,]+)/);
                if (match) {
                    return parseInt(match[1].replace(/,/g, ''), 10);
                }
            }
        }

        return null;
    }

    // Get stored follower data
    function getStoredData() {
        const data = localStorage.getItem(STORAGE_KEY);
        return data ? JSON.parse(data) : [];
    }

    // Save follower data point with automatic tracking
    function saveDataPoint(count) {
        const data = getStoredData();
        const now = new Date();
        const today = now.toISOString().split('T')[0]; // YYYY-MM-DD format
        const timeKey = now.getHours() + ':' + String(now.getMinutes()).padStart(2, '0'); // HH:MM format

        // Check if we already have data for this exact hour today
        const existingIndex = data.findIndex(point =>
            point.date === today &&
            point.time === timeKey
        );

        if (existingIndex >= 0) {
            // Update this hour's count if different
            if (data[existingIndex].count !== count) {
                data[existingIndex].count = count;
                data[existingIndex].timestamp = now.toISOString();
                console.log(`📊 Updated follower count for ${today} ${timeKey}: ${count}`);
            }
        } else {
            // Add new data point
            const newPoint = {
                date: today,
                time: timeKey,
                count: count,
                timestamp: now.toISOString()
            };
            data.push(newPoint);
            console.log(`📈 New follower count recorded for ${today} ${timeKey}: ${count}`);
        }

        localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
        return data;
    }

    // Check if we're on our own profile (for auto-tracking)
    function isOnOwnProfile() {
        // Check if this is our own profile by looking at the current user data
        try {
            if (window.FL && window.FL.user && window.FL.user.nickname) {
                const currentUser = window.FL.user.nickname;
                const pathname = window.location.pathname;
                // Check if pathname matches our username
                return pathname === `/${currentUser}` || pathname.startsWith(`/${currentUser}/`);
            }
        } catch (e) {
            console.log('Could not determine if on own profile:', e);
        }
        return false;
    }

    // Auto-track follower count when on own profile
    function autoTrackFollowers() {
        if (!isOnOwnProfile()) {
            console.log('Not on own profile, skipping auto-track');
            return;
        }

        // Wait a bit for page to load completely
        setTimeout(() => {
            const count = getCurrentFollowerCount();
            if (count !== null) {
                const data = saveDataPoint(count);
                const latestEntry = data[data.length - 1];
                console.log(`🎯 Auto-tracked followers: ${count} at ${latestEntry.time}`);

                // Update button text to show latest count
                updateChartButtonText(count);
            } else {
                console.log('Could not auto-track: follower count not found');
            }
        }, 2000);
    }

    // Calculate check frequency and growth per check statistics
    function calculateCheckFrequencyStats(data) {
        if (data.length < 2) {
            return {
                averageTimeBetweenChecks: 0,
                totalChecks: data.length,
                growthPerCheck: 0,
                checkingDays: 0,
                averageChecksPerDay: 0
            };
        }

        // Calculate time differences between checks
        const timeDiffs = [];
        for (let i = 1; i < data.length; i++) {
            const prevTime = new Date(data[i - 1].timestamp);
            const currentTime = new Date(data[i].timestamp);
            const diffHours = (currentTime - prevTime) / (1000 * 60 * 60);
            timeDiffs.push(diffHours);
        }

        const averageTimeBetweenChecks = timeDiffs.reduce((sum, diff) => sum + diff, 0) / timeDiffs.length;

        // Calculate growth per check
        const totalGrowth = data[data.length - 1].count - data[0].count;
        const growthPerCheck = totalGrowth / (data.length - 1);

        // Calculate tracking period and frequency
        const firstDate = new Date(data[0].timestamp);
        const lastDate = new Date(data[data.length - 1].timestamp);
        const totalDays = Math.max(1, Math.ceil((lastDate - firstDate) / (1000 * 60 * 60 * 24)));
        const averageChecksPerDay = data.length / totalDays;

        return {
            averageTimeBetweenChecks,
            totalChecks: data.length,
            growthPerCheck,
            checkingDays: totalDays,
            averageChecksPerDay
        };
    }
    function calculateDailyGrowth(data) {
        if (data.length < 2) {
            return {
                dailyAverage: 0,
                totalDays: 0,
                bestDay: { growth: 0, date: 'N/A' },
                hasEnoughData: false
            };
        }

        // Group data by date to calculate daily changes
        const dailyGrowth = {};
        const dailyCounts = {};

        // Group data points by date and get the last (highest) count for each day
        data.forEach(point => {
            const date = point.date;
            if (!dailyCounts[date]) {
                dailyCounts[date] = [];
            }
            dailyCounts[date].push(point);
        });

        // Calculate growth for each day (comparing end of day to end of previous day)
        const dates = Object.keys(dailyCounts).sort();
        let bestDayGrowth = 0;
        let bestDayDate = 'N/A';
        const validDailyGrowths = [];

        for (let i = 1; i < dates.length; i++) {
            const prevDate = dates[i - 1];
            const currentDate = dates[i];

            // Get the last (latest) count of each day
            const prevDayLastCount = Math.max(...dailyCounts[prevDate].map(p => p.count));
            const currentDayLastCount = Math.max(...dailyCounts[currentDate].map(p => p.count));

            const dayGrowth = currentDayLastCount - prevDayLastCount;
            dailyGrowth[currentDate] = dayGrowth;
            validDailyGrowths.push(dayGrowth);

            if (dayGrowth > bestDayGrowth) {
                bestDayGrowth = dayGrowth;
                bestDayDate = new Date(currentDate).toLocaleDateString();
            }
        }

        // Calculate average daily growth from actual day-to-day changes
        let dailyAverage = 0;
        if (validDailyGrowths.length > 0) {
            const totalDailyGrowth = validDailyGrowths.reduce((sum, growth) => sum + growth, 0);
            dailyAverage = totalDailyGrowth / validDailyGrowths.length;
        } else if (dates.length >= 2) {
            // Fallback: if we have data spanning multiple days but not enough daily comparisons
            const firstDate = new Date(data[0].timestamp);
            const lastDate = new Date(data[data.length - 1].timestamp);
            const actualDays = Math.max(1, Math.ceil((lastDate - firstDate) / (1000 * 60 * 60 * 24)));

            // Only calculate if we have at least 2 full days of data
            if (actualDays >= 2) {
                const totalGrowth = data[data.length - 1].count - data[0].count;
                dailyAverage = totalGrowth / actualDays;
            }
        }

        return {
            dailyAverage,
            totalDays: Math.max(dates.length - 1, 1),
            bestDay: {
                growth: bestDayGrowth,
                date: bestDayDate
            },
            hasEnoughData: validDailyGrowths.length > 0 || dates.length >= 2
        };
    }

    // Get milestone emoji based on follower count
    function getMilestoneEmoji(milestone) {
        if (milestone >= 100000) return "👑";
        if (milestone >= 50000) return "🌟";
        if (milestone >= 25000) return "🚀";
        if (milestone >= 10000) return "💎";
        if (milestone >= 7500) return "🔥";
        if (milestone >= 5000) return "⭐";
        if (milestone >= 3000) return "🎯";
        if (milestone >= 2000) return "🏆";
        if (milestone >= 1500) return "🎉";
        if (milestone >= 1000) return "🥳";
        if (milestone >= 500) return "🎊";
        return "🌸";
    }

    // Create D3.js chart with zoom functionality
    function createD3Chart(container, data) {
        // Clear container
        d3.select(container).selectAll("*").remove();

        // Set dimensions and margins
        const margin = { top: 20, right: 30, bottom: 60, left: 80 };
        const width = container.clientWidth - margin.left - margin.right;
        const height = container.clientHeight - margin.top - margin.bottom;

        // Create SVG
        const svg = d3.select(container)
            .append("svg")
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom);

        const g = svg.append("g")
            .attr("transform", `translate(${margin.left},${margin.top})`);

        // Create clip path for zooming
        svg.append("defs").append("clipPath")
            .attr("id", "clip")
            .append("rect")
            .attr("width", width)
            .attr("height", height);

        // Create scales
        let xScale, yScale, originalXScale, originalYScale;

        if (data.length === 0) {
            // Empty state - show default axes
            const now = new Date();
            const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);

            xScale = d3.scaleTime()
                .domain([oneHourAgo, now])
                .range([0, width]);

            yScale = d3.scaleLinear()
                .domain([0, 1000])
                .range([height, 0]);

            originalXScale = xScale.copy();
            originalYScale = yScale.copy();
        } else {
            // With data
            const parseTime = d3.timeParse("%Y-%m-%dT%H:%M:%S.%LZ");
            const processedData = data.map(d => ({
                date: new Date(d.timestamp),
                count: d.count
            }));

            const counts = processedData.map(d => d.count);
            const minCount = d3.min(counts);
            const maxCount = d3.max(counts);
            const range = maxCount - minCount;
            const padding = Math.max(range * 0.1, 10);

            xScale = d3.scaleTime()
                .domain(d3.extent(processedData, d => d.date))
                .range([0, width]);

            yScale = d3.scaleLinear()
                .domain([Math.max(0, minCount - padding), maxCount + padding])
                .range([height, 0]);

            // Store original scales for reset
            originalXScale = xScale.copy();
            originalYScale = yScale.copy();
        }

        // Create zoom behavior
        const zoom = d3.zoom()
            .scaleExtent([1, 50])
            .extent([[0, 0], [width, height]])
            .on("zoom", zoomed);

        // Apply zoom to SVG
        svg.call(zoom);

        // Create containers for zoomable content
        const gridContainer = g.append("g").attr("class", "grid-container");
        const dataContainer = g.append("g").attr("class", "data-container").attr("clip-path", "url(#clip)");
        const axisContainer = g.append("g").attr("class", "axis-container");

        function updateChart() {
            // Clear previous content
            gridContainer.selectAll("*").remove();
            dataContainer.selectAll("*").remove();
            axisContainer.selectAll("*").remove();

            // Create grid lines
            // X-axis grid
            gridContainer.selectAll(".grid-x")
                .data(xScale.ticks())
                .enter()
                .append("line")
                .attr("class", "grid-x")
                .attr("x1", d => xScale(d))
                .attr("x2", d => xScale(d))
                .attr("y1", 0)
                .attr("y2", height)
                .attr("stroke", "rgba(255, 255, 255, 0.1)")
                .attr("stroke-width", 1);

            // Y-axis grid
            gridContainer.selectAll(".grid-y")
                .data(yScale.ticks())
                .enter()
                .append("line")
                .attr("class", "grid-y")
                .attr("x1", 0)
                .attr("x2", width)
                .attr("y1", d => yScale(d))
                .attr("y2", d => yScale(d))
                .attr("stroke", "rgba(255, 255, 255, 0.1)")
                .attr("stroke-width", 1);

            // Create axes
            const xAxis = d3.axisBottom(xScale)
                .tickFormat(d3.timeFormat("%m/%d %H:%M"));

            const yAxis = d3.axisLeft(yScale)
                .tickFormat(d => d.toLocaleString());

            // Add X axis
            axisContainer.append("g")
                .attr("class", "x-axis")
                .attr("transform", `translate(0,${height})`)
                .call(xAxis)
                .selectAll("text")
                .style("fill", "#999")
                .style("font-family", "monospace")
                .style("font-size", "11px");

            // Add Y axis
            axisContainer.append("g")
                .attr("class", "y-axis")
                .call(yAxis)
                .selectAll("text")
                .style("fill", "#999")
                .style("font-family", "monospace")
                .style("font-size", "11px");

            // Style axis lines
            axisContainer.selectAll(".domain")
                .style("stroke", "#404040");

            axisContainer.selectAll(".tick line")
                .style("stroke", "#404040");

            if (data.length === 0) {
                // Add empty state message
                dataContainer.append("text")
                    .attr("x", width / 2)
                    .attr("y", height / 2 - 10)
                    .attr("text-anchor", "middle")
                    .style("fill", "#666")
                    .style("font-size", "16px")
                    .text("📊");

                dataContainer.append("text")
                    .attr("x", width / 2)
                    .attr("y", height / 2 + 10)
                    .attr("text-anchor", "middle")
                    .style("fill", "#666")
                    .style("font-size", "14px")
                    .text("Start tracking to see your growth!");
            } else {
                // Process data for line
                const processedData = data.map(d => ({
                    date: new Date(d.timestamp),
                    count: d.count
                }));

                // Create line generator
                const line = d3.line()
                    .x(d => xScale(d.date))
                    .y(d => yScale(d.count))
                    .curve(d3.curveCardinal);

                // Add area under the line
                const area = d3.area()
                    .x(d => xScale(d.date))
                    .y0(height)
                    .y1(d => yScale(d.count))
                    .curve(d3.curveCardinal);

                // Add the area
                dataContainer.append("path")
                    .datum(processedData)
                    .attr("fill", "rgba(229, 62, 62, 0.1)")
                    .attr("d", area);

                // Add the line
                dataContainer.append("path")
                    .datum(processedData)
                    .attr("fill", "none")
                    .attr("stroke", "#e53e3e")
                    .attr("stroke-width", 3)
                    .attr("d", line);

                // Add milestone markers
                const milestones = [500, 1000, 1500, 2000, 2500, 3000, 4000, 5000, 7500, 10000, 15000, 20000, 25000, 50000, 100000];
                const achievedMilestones = [];

                // Debug: log the data we're working with
                console.log('Processing milestones for data:', processedData.map(d => d.count));

                // Find achieved milestones with correct logic
                milestones.forEach(milestone => {
                    // Find the first data point that crosses this milestone
                    let milestonePoint = null;
                    let wasCrossed = false;

                    // Look for the crossing point
                    for (let i = 0; i < processedData.length; i++) {
                        const currentCount = processedData[i].count;
                        const prevCount = i > 0 ? processedData[i - 1].count : 0;

                        // Check if this point crosses the milestone
                        if (currentCount >= milestone && prevCount < milestone) {
                            milestonePoint = processedData[i];
                            wasCrossed = true;
                            console.log(`Milestone ${milestone} crossed at point:`, milestonePoint.count);
                            break;
                        }
                    }

                    if (wasCrossed && milestonePoint) {
                        // Double-check: make sure this milestone hasn't been added already
                        const alreadyExists = achievedMilestones.some(existing => existing.milestone === milestone);
                        if (!alreadyExists) {
                            achievedMilestones.push({
                                milestone: milestone,
                                point: milestonePoint,
                                emoji: getMilestoneEmoji(milestone)
                            });
                            console.log(`Added milestone: ${milestone} at count ${milestonePoint.count}`);
                        }
                    }
                });

                console.log('Final achieved milestones:', achievedMilestones.map(m => m.milestone));

                // Check if milestones should be shown
                const showMilestones = container.parentElement.querySelector('.milestone-toggle')?.checked !== false;

                // Add milestone markers (only if enabled)
                if (showMilestones) {
                    console.log('Drawing milestones:', achievedMilestones);

                    achievedMilestones.forEach((ms, index) => {
                        const x = xScale(ms.point.date);
                        const y = yScale(ms.point.count);

                        console.log(`Drawing milestone ${ms.milestone} at position (${x}, ${y})`);

                        // Add subtle milestone line (vertical dashed line)
                        dataContainer.append("line")
                            .attr("class", "milestone-line")
                            .attr("data-milestone", ms.milestone)
                            .attr("x1", x)
                            .attr("x2", x)
                            .attr("y1", 0)
                            .attr("y2", height)
                            .attr("stroke", "#ff6b6b")
                            .attr("stroke-width", 1)
                            .attr("stroke-dasharray", "3,3")
                            .attr("opacity", 0.4);

                        // Add milestone marker (subtle special dot)
                        dataContainer.append("circle")
                            .attr("class", "milestone-marker")
                            .attr("data-milestone", ms.milestone)
                            .attr("data-index", index)
                            .attr("cx", x)
                            .attr("cy", y)
                            .attr("r", 8)
                            .attr("fill", "#e53e3e")
                            .attr("stroke", "#ff6b6b")
                            .attr("stroke-width", 2)
                            .style("cursor", "pointer")
                            .style("filter", "drop-shadow(0px 0px 3px rgba(229, 62, 62, 0.6))");

                        // Add milestone emoji (smaller and more subtle)
                        dataContainer.append("text")
                            .attr("class", "milestone-emoji")
                            .attr("data-milestone", ms.milestone)
                            .attr("x", x)
                            .attr("y", y + 3)
                            .attr("text-anchor", "middle")
                            .attr("font-size", "10px")
                            .text(ms.emoji)
                            .style("pointer-events", "none");
                    });

                    // Add milestone tooltips
                    dataContainer.selectAll(".milestone-marker")
                        .on("mouseover", function(event, d) {
                            // Get milestone data from the element's data attribute
                            const milestoneValue = parseInt(this.getAttribute('data-milestone'));
                            const milestoneIndex = parseInt(this.getAttribute('data-index'));
                            const milestone = achievedMilestones[milestoneIndex];

                            console.log('Hovering milestone:', milestoneValue, milestone);

                            if (milestone && milestone.milestone === milestoneValue) {
                                const containerRect = container.getBoundingClientRect();
                                const mouseX = event.clientX - containerRect.left;
                                const mouseY = event.clientY - containerRect.top;

                                // Remove existing milestone tooltip
                                d3.select(container).selectAll(".milestone-tooltip").remove();

                                const milestoneTooltip = d3.select(container)
                                    .append("div")
                                    .attr("class", "milestone-tooltip")
                                    .style("position", "absolute")
                                    .style("padding", "8px 12px")
                                    .style("background", "linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%)")
                                    .style("color", "#ff6b6b")
                                    .style("border", "1px solid #e53e3e")
                                    .style("border-radius", "8px")
                                    .style("font-size", "12px")
                                    .style("font-weight", "600")
                                    .style("font-family", "monospace")
                                    .style("pointer-events", "none")
                                    .style("opacity", 0)
                                    .style("z-index", "1001")
                                    .style("box-shadow", "0 4px 12px rgba(229, 62, 62, 0.3)")
                                    .html(`🎯 <strong>Milestone Reached!</strong><br/>${milestone.emoji} ${milestone.milestone.toLocaleString()} followers<br/><small style="color: #999;">${milestone.point.date.toLocaleDateString()} ${milestone.point.date.toLocaleTimeString()}</small>`);

                                milestoneTooltip.transition()
                                    .duration(200)
                                    .style("opacity", 1);

                                milestoneTooltip
                                    .style("left", (mouseX + 15) + "px")
                                    .style("top", (mouseY - 15) + "px");
                            }
                        })
                        .on("mouseout", function() {
                            d3.select(container).selectAll(".milestone-tooltip")
                                .transition()
                                .duration(300)
                                .style("opacity", 0)
                                .remove();
                        });
                }

                // Add dots
                dataContainer.selectAll(".dot")
                    .data(processedData)
                    .enter().append("circle")
                    .attr("class", "dot")
                    .attr("cx", d => xScale(d.date))
                    .attr("cy", d => yScale(d.count))
                    .attr("r", 6)
                    .attr("fill", "#ff6b6b")
                    .attr("stroke", "#e53e3e")
                    .attr("stroke-width", 2)
                    .style("cursor", "pointer");

                // Remove existing tooltip to avoid duplicates
                d3.select(container).selectAll(".tooltip").remove();

                // Add tooltips
                const tooltip = d3.select(container)
                    .append("div")
                    .attr("class", "tooltip")
                    .style("position", "absolute")
                    .style("padding", "8px 12px")
                    .style("background", "rgba(26, 26, 26, 0.95)")
                    .style("color", "#ff6b6b")
                    .style("border", "1px solid #404040")
                    .style("border-radius", "8px")
                    .style("font-size", "12px")
                    .style("font-family", "monospace")
                    .style("pointer-events", "none")
                    .style("opacity", 0)
                    .style("z-index", "1000");

                dataContainer.selectAll(".dot")
                    .on("mouseover", function(event, d) {
                        // Get the container's position for proper tooltip positioning
                        const containerRect = container.getBoundingClientRect();
                        const mouseX = event.clientX - containerRect.left;
                        const mouseY = event.clientY - containerRect.top;

                        tooltip.transition()
                            .duration(200)
                            .style("opacity", 1);
                        tooltip.html(`${d.date.toLocaleDateString()} ${d.date.toLocaleTimeString()}<br/>${d.count.toLocaleString()} followers`)
                            .style("left", (mouseX + 10) + "px")
                            .style("top", (mouseY - 10) + "px");
                    })
                    .on("mouseout", function(d) {
                        tooltip.transition()
                            .duration(500)
                            .style("opacity", 0);
                    })
                    .on("mousemove", function(event, d) {
                        // Update tooltip position as mouse moves
                        const containerRect = container.getBoundingClientRect();
                        const mouseX = event.clientX - containerRect.left;
                        const mouseY = event.clientY - containerRect.top;

                        tooltip
                            .style("left", (mouseX + 10) + "px")
                            .style("top", (mouseY - 10) + "px");
                    });
            }
        }

        function zoomed(event) {
            const transform = event.transform;

            // Update scales with zoom transform
            const newXScale = transform.rescaleX(originalXScale);
            const newYScale = transform.rescaleY(originalYScale);

            xScale = newXScale;
            yScale = newYScale;

            // Update chart
            updateChart();
        }

        // Add axis labels
        g.append("text")
            .attr("transform", "rotate(-90)")
            .attr("y", 0 - margin.left)
            .attr("x", 0 - (height / 2))
            .attr("dy", "1em")
            .style("text-anchor", "middle")
            .style("fill", "#999")
            .style("font-size", "12px")
            .text("Followers");

        g.append("text")
            .attr("transform", `translate(${width / 2}, ${height + margin.bottom - 10})`)
            .style("text-anchor", "middle")
            .style("fill", "#999")
            .style("font-size", "12px")
            .text("Time");

        // Add zoom controls
        const zoomControls = d3.select(container)
            .append("div")
            .style("position", "absolute")
            .style("top", "10px")
            .style("right", "10px")
            .style("display", "flex")
            .style("gap", "4px");

        // Zoom in button
        zoomControls.append("button")
            .style("padding", "4px 8px")
            .style("background", "rgba(26, 26, 26, 0.9)")
            .style("color", "#e53e3e")
            .style("border", "1px solid #404040")
            .style("border-radius", "4px")
            .style("cursor", "pointer")
            .style("font-size", "12px")
            .text("🔍+")
            .on("click", function() {
                svg.transition().duration(300).call(zoom.scaleBy, 1.5);
            });

        // Zoom out button
        zoomControls.append("button")
            .style("padding", "4px 8px")
            .style("background", "rgba(26, 26, 26, 0.9)")
            .style("color", "#e53e3e")
            .style("border", "1px solid #404040")
            .style("border-radius", "4px")
            .style("cursor", "pointer")
            .style("font-size", "12px")
            .text("🔍-")
            .on("click", function() {
                svg.transition().duration(300).call(zoom.scaleBy, 0.67);
            });

        // Reset zoom button
        zoomControls.append("button")
            .style("padding", "4px 8px")
            .style("background", "rgba(26, 26, 26, 0.9)")
            .style("color", "#e53e3e")
            .style("border", "1px solid #404040")
            .style("border-radius", "4px")
            .style("cursor", "pointer")
            .style("font-size", "12px")
            .text("↺")
            .on("click", function() {
                svg.transition().duration(500).call(zoom.transform, d3.zoomIdentity);
            });

        // Initial chart render
        updateChart();

        // Add zoom instructions
        if (data.length > 0) {
            const instructions = d3.select(container)
                .append("div")
                .style("position", "absolute")
                .style("bottom", "10px")
                .style("left", "10px")
                .style("background", "rgba(26, 26, 26, 0.8)")
                .style("color", "#999")
                .style("padding", "4px 8px")
                .style("border-radius", "4px")
                .style("font-size", "10px")
                .style("font-family", "monospace")
                .text("🖱️ Scroll to zoom • Drag to pan • Click buttons to control");
        }
    }
    function showChart(data) {
        // Create modal overlay
        const overlay = document.createElement('div');
        overlay.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.85);
            z-index: 1000000;
            display: flex;
            justify-content: center;
            align-items: center;
            backdrop-filter: blur(10px);
        `;

        // Create modal content with dark theme
        const modal = document.createElement('div');
        modal.style.cssText = `
            background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
            padding: 24px;
            border-radius: 16px;
            max-width: 1100px;
            width: 95%;
            max-height: 90vh;
            position: relative;
            border: 1px solid #404040;
            box-shadow: 0 20px 60px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.1);
            color: #e53e3e;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            overflow-y: auto;
        `;

        // Close button with FetLife styling
        const closeBtn = document.createElement('button');
        closeBtn.textContent = '×';
        closeBtn.style.cssText = `
            position: absolute;
            top: 16px;
            right: 20px;
            background: rgba(229, 62, 62, 0.15);
            border: 1px solid rgba(229, 62, 62, 0.3);
            border-radius: 8px;
            padding: 8px 12px;
            font-size: 20px;
            cursor: pointer;
            color: #e53e3e;
            transition: all 0.2s ease;
            backdrop-filter: blur(5px);
        `;
        closeBtn.onmouseover = () => {
            closeBtn.style.background = 'rgba(229, 62, 62, 0.25)';
            closeBtn.style.color = '#ff6b6b';
        };
        closeBtn.onmouseout = () => {
            closeBtn.style.background = 'rgba(229, 62, 62, 0.15)';
            closeBtn.style.color = '#e53e3e';
        };
        closeBtn.onclick = () => document.body.removeChild(overlay);

        // Chart title
        const title = document.createElement('h2');
        title.textContent = '📈 Follower Growth Analytics';
        title.style.cssText = `
            margin: 0 0 16px 0;
            color: #e53e3e;
            text-align: center;
            font-size: 24px;
            font-weight: 600;
            text-shadow: 0 2px 4px rgba(0,0,0,0.5);
            letter-spacing: 0.5px;
        `;

        // Time range controls
        const timeRangeContainer = document.createElement('div');
        timeRangeContainer.style.cssText = `
            display: flex;
            justify-content: center;
            gap: 8px;
            margin-bottom: 20px;
            flex-wrap: wrap;
        `;

        const timeRanges = [
            { label: '4H', hours: 4, active: false },
            { label: '8H', hours: 8, active: false },
            { label: '12H', hours: 12, active: false },
            { label: '24H', hours: 24, active: false },
            { label: '2D', hours: 24 * 2, active: false },
            { label: '3D', hours: 24 * 3, active: false },
            { label: '5D', hours: 24 * 5, active: false },
            { label: '7D', hours: 24 * 7, active: false },
            { label: '14D', hours: 24 * 14, active: false },
            { label: '30D', hours: 24 * 30, active: false },
            { label: 'ALL', hours: null, active: true }
        ];

        let currentTimeRange = 'ALL';
        let currentData = data;

        timeRanges.forEach(range => {
            const btn = document.createElement('button');
            btn.textContent = range.label;
            btn.style.cssText = `
                padding: 8px 16px;
                border: 1px solid #404040;
                border-radius: 6px;
                background: ${range.active ? 'linear-gradient(135deg, #e53e3e 0%, #ff6b6b 100%)' : 'linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%)'};
                color: ${range.active ? '#fff' : '#e53e3e'};
                cursor: pointer;
                font-weight: 600;
                font-size: 12px;
                font-family: inherit;
                transition: all 0.2s ease;
                min-width: 50px;
            `;

            btn.onmouseover = () => {
                if (currentTimeRange !== range.label) {
                    btn.style.background = 'linear-gradient(135deg, #2d2d2d 0%, #3a3a3a 100%)';
                    btn.style.color = '#ff6b6b';
                }
            };

            btn.onmouseout = () => {
                if (currentTimeRange !== range.label) {
                    btn.style.background = 'linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%)';
                    btn.style.color = '#e53e3e';
                }
            };

            btn.onclick = () => {
                // Update active state
                currentTimeRange = range.label;

                // Filter data based on time range
                if (range.hours) {
                    const cutoffTime = new Date(Date.now() - range.hours * 60 * 60 * 1000);
                    currentData = data.filter(point => new Date(point.timestamp) >= cutoffTime);
                } else {
                    currentData = data; // Show all data
                }

                // Update all buttons
                timeRangeContainer.querySelectorAll('button').forEach(b => {
                    if (b === btn) {
                        b.style.background = 'linear-gradient(135deg, #e53e3e 0%, #ff6b6b 100%)';
                        b.style.color = '#fff';
                    } else {
                        b.style.background = 'linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%)';
                        b.style.color = '#e53e3e';
                    }
                });

                // Recreate chart with filtered data
                const chartDiv = document.getElementById('followerChart');
                if (chartDiv && window.d3) {
                    createD3Chart(chartDiv, currentData);
                }

                // Update stats with filtered data
                updateStatsForTimeRange(currentData);
            };

            timeRangeContainer.appendChild(btn);
        });

        // Chart container
        const chartContainer = document.createElement('div');
        chartContainer.style.cssText = `
            width: 100%;
            height: 500px;
            position: relative;
            border: 1px solid #404040;
            background: linear-gradient(135deg, #0f0f0f 0%, #1a1a1a 100%);
            margin-bottom: 20px;
            border-radius: 12px;
            padding: 20px;
            box-sizing: border-box;
        `;

        if (data.length === 0) {
            // Create D3.js chart with empty state
            const chartDiv = document.createElement('div');
            chartDiv.id = 'followerChart';
            chartDiv.style.cssText = 'width: 100%; height: 100%; position: relative;';
            chartContainer.appendChild(chartDiv);

            // Load D3.js and create empty chart
            const script = document.createElement('script');
            script.src = 'https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js';
            script.onload = () => {
                createD3Chart(chartDiv, []);
            };
            document.head.appendChild(script);
        } else {
            // Create D3.js chart with data
            const chartDiv = document.createElement('div');
            chartDiv.id = 'followerChart';
            chartDiv.style.cssText = 'width: 100%; height: 100%; position: relative;';
            chartContainer.appendChild(chartDiv);

            // Load D3.js and create chart
            const script = document.createElement('script');
            script.src = 'https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js';
            script.onload = () => {
                createD3Chart(chartDiv, data);
            };
            document.head.appendChild(script);
        }

        // Calculate daily growth statistics
        const dailyStats = calculateDailyGrowth(currentData);

        // Calculate check frequency statistics
        const checkStats = calculateCheckFrequencyStats(currentData);

        // Function to update stats for time range
        function updateStatsForTimeRange(filteredData) {
            const newDailyStats = calculateDailyGrowth(filteredData);
            const newCheckStats = calculateCheckFrequencyStats(filteredData);
            const newCurrentCount = filteredData.length > 0 ? filteredData[filteredData.length - 1].count : 0;

            // Calculate intelligent recent change for filtered data
            let newRecentChange = 0;
            let newChangeLabel = 'Recent Change';
            let newShowRecentChange = false;

            if (filteredData.length >= 2) {
                const now = new Date();
                const latest = filteredData[filteredData.length - 1];

                // For different time ranges, use different comparison logic
                let comparisonPoint = null;

                if (currentTimeRange === '4H') {
                    // For 4H view, compare to 2h ago
                    const twoHoursAgo = new Date(now.getTime() - 2 * 60 * 60 * 1000);
                    for (let i = filteredData.length - 2; i >= 0; i--) {
                        const pointDate = new Date(filteredData[i].timestamp);
                        if (pointDate <= twoHoursAgo) {
                            comparisonPoint = filteredData[i];
                            newChangeLabel = 'Last 2h';
                            break;
                        }
                    }
                } else if (currentTimeRange === '8H') {
                    // For 8H view, compare to 4h ago
                    const fourHoursAgo = new Date(now.getTime() - 4 * 60 * 60 * 1000);
                    for (let i = filteredData.length - 2; i >= 0; i--) {
                        const pointDate = new Date(filteredData[i].timestamp);
                        if (pointDate <= fourHoursAgo) {
                            comparisonPoint = filteredData[i];
                            newChangeLabel = 'Last 4h';
                            break;
                        }
                    }
                } else if (currentTimeRange === '12H') {
                    // For 12H view, compare to 6h ago
                    const sixHoursAgo = new Date(now.getTime() - 6 * 60 * 60 * 1000);
                    for (let i = filteredData.length - 2; i >= 0; i--) {
                        const pointDate = new Date(filteredData[i].timestamp);
                        if (pointDate <= sixHoursAgo) {
                            comparisonPoint = filteredData[i];
                            newChangeLabel = 'Last 6h';
                            break;
                        }
                    }
                } else if (currentTimeRange === '24H') {
                    // For 24H view, compare to 12h ago or earlier
                    const twelveHoursAgo = new Date(now.getTime() - 12 * 60 * 60 * 1000);
                    for (let i = filteredData.length - 2; i >= 0; i--) {
                        const pointDate = new Date(filteredData[i].timestamp);
                        if (pointDate <= twelveHoursAgo) {
                            comparisonPoint = filteredData[i];
                            newChangeLabel = 'Last 12h';
                            break;
                        }
                    }
                } else if (currentTimeRange === '2D') {
                    // For 2D view, compare to 1 day ago
                    const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
                    for (let i = filteredData.length - 2; i >= 0; i--) {
                        const pointDate = new Date(filteredData[i].timestamp);
                        if (pointDate <= oneDayAgo) {
                            comparisonPoint = filteredData[i];
                            newChangeLabel = 'Last 24h';
                            break;
                        }
                    }
                } else if (currentTimeRange === '3D') {
                    // For 3D view, compare to 1.5 days ago
                    const oneAndHalfDaysAgo = new Date(now.getTime() - 1.5 * 24 * 60 * 60 * 1000);
                    for (let i = filteredData.length - 2; i >= 0; i--) {
                        const pointDate = new Date(filteredData[i].timestamp);
                        if (pointDate <= oneAndHalfDaysAgo) {
                            comparisonPoint = filteredData[i];
                            newChangeLabel = 'Last 1.5d';
                            break;
                        }
                    }
                } else if (currentTimeRange === '5D') {
                    // For 5D view, compare to 2 days ago
                    const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000);
                    for (let i = filteredData.length - 2; i >= 0; i--) {
                        const pointDate = new Date(filteredData[i].timestamp);
                        if (pointDate <= twoDaysAgo) {
                            comparisonPoint = filteredData[i];
                            newChangeLabel = 'Last 2d';
                            break;
                        }
                    }
                } else if (currentTimeRange === '14D') {
                    // For 14D view, compare to 7 days ago
                    const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
                    for (let i = filteredData.length - 2; i >= 0; i--) {
                        const pointDate = new Date(filteredData[i].timestamp);
                        if (pointDate <= sevenDaysAgo) {
                            comparisonPoint = filteredData[i];
                            newChangeLabel = 'Last 7d';
                            break;
                        }
                    }
                } else if (currentTimeRange === '7D') {
                    // For 7D view, compare to 1 day ago
                    const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
                    for (let i = filteredData.length - 2; i >= 0; i--) {
                        const pointDate = new Date(filteredData[i].timestamp);
                        if (pointDate <= oneDayAgo) {
                            comparisonPoint = filteredData[i];
                            newChangeLabel = 'Last 24h';
                            break;
                        }
                    }
                } else if (currentTimeRange === '30D') {
                    // For 30D view, compare to 7 days ago
                    const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
                    for (let i = filteredData.length - 2; i >= 0; i--) {
                        const pointDate = new Date(filteredData[i].timestamp);
                        if (pointDate <= sevenDaysAgo) {
                            comparisonPoint = filteredData[i];
                            newChangeLabel = 'Last 7d';
                            break;
                        }
                    }
                } else {
                    // For ALL view, use the original logic
                    const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
                    for (let i = filteredData.length - 2; i >= 0; i--) {
                        const pointDate = new Date(filteredData[i].timestamp);
                        if (pointDate <= yesterday) {
                            comparisonPoint = filteredData[i];
                            newChangeLabel = 'Last 24h';
                            break;
                        }
                    }
                }

                if (comparisonPoint) {
                    newRecentChange = newCurrentCount - comparisonPoint.count;
                    newShowRecentChange = true;
                }
            }

            // Calculate total growth for filtered data (smart logic)
            let newTotalGrowth = 0;
            let newShowTotalGrowth = false;
            let newFirstDate = '';

            if (filteredData.length >= 3) { // Need at least 3 points to show meaningful growth
                // Only show period growth if it's been more than a few hours of tracking
                const timeSinceStart = new Date().getTime() - new Date(filteredData[0].timestamp).getTime();
                const hoursSinceStart = timeSinceStart / (1000 * 60 * 60);

                if (hoursSinceStart >= 6) { // Only after 6+ hours of tracking
                    newTotalGrowth = newCurrentCount - filteredData[0].count;
                    newFirstDate = new Date(filteredData[0].timestamp).toLocaleDateString();

                    // For ALL view, only show if growth is reasonable (not the initial jump)
                    if (currentTimeRange === 'ALL') {
                        // Only show total growth if we have multiple data points over time
                        // and the growth seems realistic (not just the initial data point)
                        if (filteredData.length >= 5 && Math.abs(newTotalGrowth) <= 500) {
                            newShowTotalGrowth = true;
                        }
                    } else {
                        // For time-filtered views, always show if we have data
                        newShowTotalGrowth = true;
                    }
                }
            }

            // Check if realistic metrics should be shown
            const showRealisticMetrics = container.parentElement.querySelector('.realistic-toggle')?.checked !== false;

            // Update the stats display
            stats.innerHTML = `
                <div style="min-width: 140px;">
                    <strong style="color: #ff6b6b;">Current Followers</strong><br>
                    <span style="font-size: 28px; color: #e53e3e; font-weight: 600;">${newCurrentCount.toLocaleString()}</span>
                </div>

                ${newShowRecentChange ? `
                    <div style="min-width: 140px;">
                        <strong style="color: #ff6b6b;">${newChangeLabel}</strong><br>
                        <span style="font-size: 28px; color: ${newRecentChange >= 0 ? '#4ade80' : '#ef4444'}; font-weight: 600;">
                            ${newRecentChange >= 0 ? '+' : ''}${newRecentChange}
                        </span>
                    </div>
                ` : `
                    <div style="min-width: 140px;">
                        <strong style="color: #ff6b6b;">Recent Change</strong><br>
                        <span style="font-size: 20px; color: #666; font-weight: 600;">
                            ${currentTimeRange === 'ALL' ? 'Just started tracking' : 'No data in range'}
                        </span>
                        <div style="font-size: 11px; color: #999; margin-top: 2px;">Check back later</div>
                    </div>
                `}

                <div style="min-width: 140px;">
                    <strong style="color: #ff6b6b;">Growth per Check</strong><br>
                    ${newCheckStats.totalChecks >= 2 ? `
                        <span style="font-size: 24px; color: ${newCheckStats.growthPerCheck >= 0 ? '#4ade80' : '#ef4444'}; font-weight: 600;">
                            ${newCheckStats.growthPerCheck >= 0 ? '+' : ''}${newCheckStats.growthPerCheck.toFixed(1)}
                        </span>
                        <div style="font-size: 11px; color: #999; margin-top: 2px;">Per profile visit</div>
                    ` : `
                        <span style="font-size: 20px; color: #666; font-weight: 600;">
                            Need more checks
                        </span>
                        <div style="font-size: 11px; color: #999; margin-top: 2px;">Visit more often</div>
                    `}
                </div>

                ${showRealisticMetrics ? `
                    <div style="min-width: 140px;">
                        <strong style="color: #ff6b6b;">Daily Average</strong><br>
                        ${newDailyStats.hasEnoughData ? `
                            <span style="font-size: 24px; color: ${newDailyStats.dailyAverage >= 0 ? '#4ade80' : '#ef4444'}; font-weight: 600;">
                                ${newDailyStats.dailyAverage >= 0 ? '+' : ''}${newDailyStats.dailyAverage.toFixed(1)}/day
                            </span>
                            <div style="font-size: 11px; color: #999; margin-top: 2px;">Over ${newDailyStats.totalDays} days</div>
                        ` : `
                            <span style="font-size: 20px; color: #666; font-weight: 600;">
                                Calculating...
                            </span>
                            <div style="font-size: 11px; color: #999; margin-top: 2px;">Need more time</div>
                        `}
                    </div>
                ` : `
                    <div style="min-width: 140px;">
                        <strong style="color: #ff6b6b;">Check Frequency</strong><br>
                        ${newCheckStats.totalChecks >= 2 ? `
                            <span style="font-size: 18px; color: #e53e3e; font-weight: 600;">
                                ${newCheckStats.averageTimeBetweenChecks < 24 ?
                                    `${newCheckStats.averageTimeBetweenChecks.toFixed(1)}h` :
                                    `${(newCheckStats.averageTimeBetweenChecks / 24).toFixed(1)}d`}
                            </span>
                            <div style="font-size: 11px; color: #999; margin-top: 2px;">Between visits</div>
                        ` : `
                            <span style="font-size: 20px; color: #666; font-weight: 600;">
                                Just started
                            </span>
                            <div style="font-size: 11px; color: #999; margin-top: 2px;">Keep tracking</div>
                        `}
                    </div>
                `}

                <div style="min-width: 140px;">
                    <strong style="color: #ff6b6b;">Total Checks</strong><br>
                    <span style="font-size: 24px; color: #e53e3e; font-weight: 600;">${newCheckStats.totalChecks}</span>
                    <div style="font-size: 11px; color: #999; margin-top: 2px;">Profile visits</div>
                </div>

                ${newShowTotalGrowth ? `
                    <div style="min-width: 140px;">
                        <strong style="color: #ff6b6b;">Period Growth</strong><br>
                        <span style="font-size: 20px; color: ${newTotalGrowth >= 0 ? '#4ade80' : '#ef4444'}; font-weight: 600;">
                            ${newTotalGrowth >= 0 ? '+' : ''}${newTotalGrowth}
                        </span>
                        <div style="font-size: 11px; color: #999; margin-top: 2px;">In ${currentTimeRange === 'ALL' ? 'tracking period' : currentTimeRange}</div>
                    </div>
                ` : `
                    <div style="min-width: 140px;">
                        <strong style="color: #ff6b6b;">Tracking Since</strong><br>
                        <span style="font-size: 16px; color: #e53e3e; font-weight: 600;">
                            ${data.length > 0 ? new Date(data[0].timestamp).toLocaleDateString() : 'Today'}
                        </span>
                        <div style="font-size: 11px; color: #999; margin-top: 2px;">${filteredData.length} data points</div>
                    </div>
                `}
            `;
        }

        // Stats section with enhanced metrics
        const stats = document.createElement('div');
        stats.style.cssText = `
            display: flex;
            justify-content: space-around;
            text-align: center;
            margin-bottom: 20px;
            background: rgba(15, 15, 15, 0.6);
            padding: 16px;
            border-radius: 12px;
            border: 1px solid #2d2d2d;
            flex-wrap: wrap;
            gap: 16px;
        `;

        const currentCount = data.length > 0 ? data[data.length - 1].count : 0;

        // Calculate intelligent recent change
        let recentChange = 0;
        let changeLabel = 'Recent Change';
        let showRecentChange = false;

        if (data.length >= 2) {
            const now = new Date();
            const latest = data[data.length - 1];

            // Look for a meaningful comparison point (not the very first data point)
            let comparisonPoint = null;

            // Try to find data from 24 hours ago
            const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
            for (let i = data.length - 2; i >= 0; i--) {
                const pointDate = new Date(data[i].timestamp);
                if (pointDate <= yesterday) {
                    comparisonPoint = data[i];
                    changeLabel = 'Last 24h';
                    break;
                }
            }

            // If no 24h data, try 12 hours
            if (!comparisonPoint) {
                const twelveHoursAgo = new Date(now.getTime() - 12 * 60 * 60 * 1000);
                for (let i = data.length - 2; i >= 0; i--) {
                    const pointDate = new Date(data[i].timestamp);
                    if (pointDate <= twelveHoursAgo) {
                        comparisonPoint = data[i];
                        changeLabel = 'Last 12h';
                        break;
                    }
                }
            }

            // If no 12h data, try 6 hours
            if (!comparisonPoint) {
                const sixHoursAgo = new Date(now.getTime() - 6 * 60 * 60 * 1000);
                for (let i = data.length - 2; i >= 0; i--) {
                    const pointDate = new Date(data[i].timestamp);
                    if (pointDate <= sixHoursAgo) {
                        comparisonPoint = data[i];
                        changeLabel = 'Last 6h';
                        break;
                    }
                }
            }

            // If no meaningful time period, use previous data point but only if it's not the first
            if (!comparisonPoint && data.length >= 3) {
                comparisonPoint = data[data.length - 2];
                const timeDiff = new Date(latest.timestamp).getTime() - new Date(comparisonPoint.timestamp).getTime();
                const hoursDiff = Math.round(timeDiff / (1000 * 60 * 60));
                if (hoursDiff >= 1) {
                    changeLabel = hoursDiff === 1 ? 'Last Hour' : `Last ${hoursDiff}h`;
                    showRecentChange = true;
                }
            } else if (comparisonPoint) {
                showRecentChange = true;
            }

            if (comparisonPoint) {
                recentChange = currentCount - comparisonPoint.count;
            }
        }

        // Calculate intelligent total growth (only show if meaningful)
        let totalGrowth = 0;
        let showTotalGrowth = false;
        let firstDate = '';

        if (data.length >= 3) { // Only show total growth if we have multiple data points
            totalGrowth = currentCount - data[0].count;
            firstDate = new Date(data[0].timestamp).toLocaleDateString();

            // Only show total growth if it's been more than a few hours
            const timeSinceStart = new Date().getTime() - new Date(data[0].timestamp).getTime();
            const hoursSinceStart = timeSinceStart / (1000 * 60 * 60);
            showTotalGrowth = hoursSinceStart >= 6; // Show only after 6+ hours of tracking
        }

        stats.innerHTML = `
            <div style="min-width: 140px;">
                <strong style="color: #ff6b6b;">Current Followers</strong><br>
                <span style="font-size: 28px; color: #e53e3e; font-weight: 600;">${currentCount.toLocaleString()}</span>
            </div>

            ${showRecentChange ? `
                <div style="min-width: 140px;">
                    <strong style="color: #ff6b6b;">${changeLabel}</strong><br>
                    <span style="font-size: 28px; color: ${recentChange >= 0 ? '#4ade80' : '#ef4444'}; font-weight: 600;">
                        ${recentChange >= 0 ? '+' : ''}${recentChange}
                    </span>
                </div>
            ` : `
                <div style="min-width: 140px;">
                    <strong style="color: #ff6b6b;">Recent Change</strong><br>
                    <span style="font-size: 20px; color: #666; font-weight: 600;">
                        Just started tracking
                    </span>
                    <div style="font-size: 11px; color: #999; margin-top: 2px;">Check back later</div>
                </div>
            `}

            <div style="min-width: 140px;">
                <strong style="color: #ff6b6b;">Daily Average</strong><br>
                ${dailyStats.hasEnoughData ? `
                    <span style="font-size: 24px; color: ${dailyStats.dailyAverage >= 0 ? '#4ade80' : '#ef4444'}; font-weight: 600;">
                        ${dailyStats.dailyAverage >= 0 ? '+' : ''}${dailyStats.dailyAverage.toFixed(1)}/day
                    </span>
                    <div style="font-size: 11px; color: #999; margin-top: 2px;">Over ${dailyStats.totalDays} days</div>
                ` : `
                    <span style="font-size: 20px; color: #666; font-weight: 600;">
                        Calculating...
                    </span>
                    <div style="font-size: 11px; color: #999; margin-top: 2px;">Need more time</div>
                `}
            </div>

            <div style="min-width: 140px;">
                <strong style="color: #ff6b6b;">Best Day</strong><br>
                ${dailyStats.bestDay.growth > 0 ? `
                    <span style="font-size: 20px; color: #4ade80; font-weight: 600;">
                        +${dailyStats.bestDay.growth}
                    </span>
                    <div style="font-size: 11px; color: #999; margin-top: 2px;">${dailyStats.bestDay.date}</div>
                ` : `
                    <span style="font-size: 20px; color: #666; font-weight: 600;">
                        No data yet
                    </span>
                    <div style="font-size: 11px; color: #999; margin-top: 2px;">Keep tracking</div>
                `}
            </div>

            ${showTotalGrowth ? `
                <div style="min-width: 140px;">
                    <strong style="color: #ff6b6b;">Total Growth</strong><br>
                    <span style="font-size: 20px; color: ${totalGrowth >= 0 ? '#4ade80' : '#ef4444'}; font-weight: 600;">
                        ${totalGrowth >= 0 ? '+' : ''}${totalGrowth}
                    </span>
                    <div style="font-size: 11px; color: #999; margin-top: 2px;">Since ${firstDate}</div>
                </div>
            ` : `
                <div style="min-width: 140px;">
                    <strong style="color: #ff6b6b;">Tracking Since</strong><br>
                    <span style="font-size: 16px; color: #e53e3e; font-weight: 600;">
                        ${data.length > 0 ? new Date(data[0].timestamp).toLocaleDateString() : 'Today'}
                    </span>
                    <div style="font-size: 11px; color: #999; margin-top: 2px;">${data.length} data points</div>
                </div>
            `}
        `;

        // Buttons with dark theme
        const buttonContainer = document.createElement('div');
        buttonContainer.style.cssText = 'display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;';

        // Export button
        const exportBtn = document.createElement('button');
        exportBtn.textContent = '📁 Export Data';
        exportBtn.style.cssText = `
            background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
            color: #e53e3e;
            border: 1px solid #404040;
            padding: 12px 18px;
            border-radius: 8px;
            cursor: pointer;
            font-weight: 600;
            transition: all 0.2s ease;
            font-family: inherit;
        `;
        exportBtn.onmouseover = () => {
            exportBtn.style.background = 'linear-gradient(135deg, #2d2d2d 0%, #3a3a3a 100%)';
            exportBtn.style.color = '#ff6b6b';
        };
        exportBtn.onmouseout = () => {
            exportBtn.style.background = 'linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%)';
            exportBtn.style.color = '#e53e3e';
        };
        exportBtn.onclick = () => {
            const dataStr = JSON.stringify(data, null, 2);
            const blob = new Blob([dataStr], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = 'fetlife-follower-data.json';
            a.click();
            URL.revokeObjectURL(url);
        };

        // Clear data button
        const clearBtn = document.createElement('button');
        clearBtn.textContent = '🗑️ Clear Data';
        clearBtn.style.cssText = `
            background: linear-gradient(135deg, #2d1a1a 0%, #3d2d2d 100%);
            color: #ef4444;
            border: 1px solid #404040;
            padding: 12px 18px;
            border-radius: 8px;
            cursor: pointer;
            font-weight: 600;
            transition: all 0.2s ease;
            font-family: inherit;
        `;
        clearBtn.onmouseover = () => {
            clearBtn.style.background = 'linear-gradient(135deg, #3d2d2d 0%, #4a3a3a 100%)';
            clearBtn.style.color = '#ff6b6b';
        };
        clearBtn.onmouseout = () => {
            clearBtn.style.background = 'linear-gradient(135deg, #2d1a1a 0%, #3d2d2d 100%)';
            clearBtn.style.color = '#ef4444';
        };
        clearBtn.onclick = () => {
            if (confirm('Are you sure you want to clear all follower data?')) {
                localStorage.removeItem(STORAGE_KEY);
                document.body.removeChild(overlay);
                console.log('📊 Follower data cleared');
            }
        };

        buttonContainer.appendChild(exportBtn);
        buttonContainer.appendChild(clearBtn);

        // Add controls container
        const controlsContainer = document.createElement('div');
        controlsContainer.style.cssText = `
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 20px;
            margin-top: 15px;
            flex-wrap: wrap;
        `;

        // Milestone toggle
        const milestoneToggleContainer = document.createElement('div');
        milestoneToggleContainer.style.cssText = `
            display: flex;
            align-items: center;
            gap: 8px;
        `;

        const milestoneCheckbox = document.createElement('input');
        milestoneCheckbox.type = 'checkbox';
        milestoneCheckbox.className = 'milestone-toggle';
        milestoneCheckbox.checked = true;
        milestoneCheckbox.style.cssText = `
            width: 16px;
            height: 16px;
            accent-color: #e53e3e;
            cursor: pointer;
        `;

        const milestoneLabel = document.createElement('label');
        milestoneLabel.textContent = 'Show Milestones';
        milestoneLabel.style.cssText = `
            color: #999;
            font-size: 12px;
            cursor: pointer;
            user-select: none;
        `;

        milestoneLabel.onclick = () => {
            milestoneCheckbox.checked = !milestoneCheckbox.checked;
            milestoneCheckbox.dispatchEvent(new Event('change'));
        };

        milestoneCheckbox.onchange = () => {
            const chartDiv = document.getElementById('followerChart');
            if (chartDiv && window.d3) {
                createD3Chart(chartDiv, currentData);
            }
        };

        milestoneToggleContainer.appendChild(milestoneCheckbox);
        milestoneToggleContainer.appendChild(milestoneLabel);

        // Realistic metrics toggle
        const realisticToggleContainer = document.createElement('div');
        realisticToggleContainer.style.cssText = `
            display: flex;
            align-items: center;
            gap: 8px;
        `;

        const realisticCheckbox = document.createElement('input');
        realisticCheckbox.type = 'checkbox';
        realisticCheckbox.className = 'realistic-toggle';
        realisticCheckbox.checked = false; // Default to showing realistic metrics
        realisticCheckbox.style.cssText = `
            width: 16px;
            height: 16px;
            accent-color: #e53e3e;
            cursor: pointer;
        `;

        const realisticLabel = document.createElement('label');
        realisticLabel.textContent = 'Show Unrealistic Metrics';
        realisticLabel.style.cssText = `
            color: #999;
            font-size: 12px;
            cursor: pointer;
            user-select: none;
        `;

        // Info button
        const infoButton = document.createElement('span');
        infoButton.textContent = 'ℹ️';
        infoButton.style.cssText = `
            cursor: help;
            font-size: 14px;
            margin-left: 4px;
            opacity: 0.7;
            transition: opacity 0.2s ease;
        `;

        infoButton.onmouseover = () => {
            infoButton.style.opacity = '1';

            // Remove existing tooltip
            document.querySelectorAll('.info-tooltip').forEach(t => t.remove());

            // Create info tooltip
            const tooltip = document.createElement('div');
            tooltip.className = 'info-tooltip';
            tooltip.style.cssText = `
                position: absolute;
                bottom: 100%;
                left: 50%;
                transform: translateX(-50%);
                background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
                color: #ff6b6b;
                border: 1px solid #e53e3e;
                border-radius: 8px;
                padding: 12px;
                font-size: 11px;
                font-family: monospace;
                white-space: nowrap;
                z-index: 1002;
                box-shadow: 0 4px 12px rgba(229, 62, 62, 0.3);
                max-width: 300px;
                white-space: normal;
                line-height: 1.4;
            `;
            tooltip.innerHTML = `
                <strong>ℹ️ About Unrealistic Metrics:</strong><br>
                This script only tracks when YOU visit your profile.<br>
                "Daily Average" assumes constant growth, which isn't realistic.<br>
                Turn this ON only if you check your profile very frequently.
            `;

            realisticToggleContainer.style.position = 'relative';
            realisticToggleContainer.appendChild(tooltip);
        };

        infoButton.onmouseout = () => {
            infoButton.style.opacity = '0.7';
            document.querySelectorAll('.info-tooltip').forEach(t => t.remove());
        };

        realisticLabel.onclick = () => {
            realisticCheckbox.checked = !realisticCheckbox.checked;
            realisticCheckbox.dispatchEvent(new Event('change'));
        };

        realisticCheckbox.onchange = () => {
            updateStatsForTimeRange(currentData);
        };

        realisticToggleContainer.appendChild(realisticCheckbox);
        realisticToggleContainer.appendChild(realisticLabel);
        realisticToggleContainer.appendChild(infoButton);

        controlsContainer.appendChild(milestoneToggleContainer);
        controlsContainer.appendChild(realisticToggleContainer);

        // Assemble modal
        modal.appendChild(closeBtn);
        modal.appendChild(title);
        modal.appendChild(timeRangeContainer);
        modal.appendChild(chartContainer);
        modal.appendChild(stats);
        modal.appendChild(buttonContainer);
        modal.appendChild(controlsContainer);
        overlay.appendChild(modal);
        document.body.appendChild(overlay);

        // Initialize with ALL data
        updateStatsForTimeRange(currentData);
    }

    // Check if we're on a user profile page
    function isOnProfilePage() {
        // Check if URL matches a profile pattern
        const path = window.location.pathname;
        // Exclude common non-profile pages
        const excludePatterns = [
            '/home', '/feed', '/explore', '/login', '/help', '/conversations',
            '/inbox', '/events', '/groups', '/fetishes', '/kinksters', '/places',
            '/pictures', '/videos', '/posts', '/audio', '/guidelines'
        ];

        // Check if path starts with any excluded pattern
        for (const pattern of excludePatterns) {
            if (path.startsWith(pattern)) {
                return false;
            }
        }

        // If path is just a username (like /username123), it's likely a profile
        const pathParts = path.split('/').filter(part => part.length > 0);
        if (pathParts.length === 1 && pathParts[0].length > 0) {
            return true;
        }

        // Also check if the page contains profile-specific elements
        return document.querySelector('[data-component="UserProfile"]') !== null;
    }

    // Update chart button text to show current follower count
    function updateChartButtonText(count) {
        const button = document.getElementById('chart-viewer-btn');
        if (button && count) {
            button.textContent = `📊 View Chart (${count.toLocaleString()})`;
        }
    }

    // Add view chart button (remove tracking button since we auto-track now)
    function addChartButton() {
        // Check if button already exists
        if (document.getElementById('chart-viewer-btn')) return;

        const btn = document.createElement('button');
        btn.id = 'chart-viewer-btn';
        btn.textContent = '📊 View Growth Chart';
        btn.style.cssText = `
            position: fixed !important;
            top: 50%;
            right: 20px !important;
            transform: translateY(-50%);
            z-index: 999999 !important;
            background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%) !important;
            color: #e53e3e !important;
            border: 1px solid #404040 !important;
            padding: 12px 18px !important;
            border-radius: 12px !important;
            font-weight: 600 !important;
            font-size: 13px !important;
            cursor: pointer !important;
            box-shadow: 0 8px 32px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.1) !important;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important;
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
            backdrop-filter: blur(10px) !important;
            text-shadow: 0 1px 2px rgba(0,0,0,0.5) !important;
            letter-spacing: 0.3px !important;
            user-select: none !important;
            min-width: 200px !important;
        `;

        btn.onclick = () => {
            const data = getStoredData();
            showChart(data);
        };

        // Add hover effect
        btn.onmouseover = () => {
            btn.style.background = 'linear-gradient(135deg, #2d2d2d 0%, #3a3a3a 100%) !important';
            btn.style.color = '#ff6b6b !important';
            btn.style.transform = 'translateY(-50%) translateY(-2px) scale(1.02)';
            btn.style.boxShadow = '0 12px 40px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.15) !important';
            btn.style.borderColor = '#555555 !important';
        };
        btn.onmouseout = () => {
            btn.style.background = 'linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%) !important';
            btn.style.color = '#e53e3e !important';
            btn.style.transform = 'translateY(-50%) translateY(0) scale(1)';
            btn.style.boxShadow = '0 8px 32px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.1) !important';
            btn.style.borderColor = '#404040 !important';
        };

        document.body.appendChild(btn);
        console.log('📊 Chart button added to page');
    }

    // Initialize the script
    function init() {
        console.log('🚀 FetLife Follower Tracker initializing...');
        console.log('Current URL:', window.location.href);
        console.log('Is on profile page:', isOnProfilePage());
        console.log('Is on own profile:', isOnOwnProfile());

        if (isOnProfilePage()) {
            console.log('✅ On profile page - adding chart button');
            // Only add chart button, remove manual tracking button
            addChartButton();

            // Auto-track followers when on own profile
            if (isOnOwnProfile()) {
                console.log('✅ On own profile - starting auto-track');
                autoTrackFollowers();
            } else {
                console.log('ℹ️ On someone else\'s profile - no auto-tracking');
            }
        } else {
            console.log('❌ Not on profile page - no button added');
        }
    }

    // Wait for page to load
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    // Also run on page navigation (for single page app behavior)
    let lastUrl = location.href;
    new MutationObserver(() => {
        const url = location.href;
        if (url !== lastUrl) {
            lastUrl = url;
            setTimeout(init, 1000); // Delay to allow page to load
        }
    }).observe(document, { subtree: true, childList: true });

})();