FetLife Follower Growth Tracker

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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 });

})();