Internet speed test

 <!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">

    <title>NEBULA SPIN | Internet Speed Tester</title>

    <style>

        * {

            margin: 0;

            padding: 0;

            box-sizing: border-box;

            user-select: none;

        }


        body {

            min-height: 100vh;

            background: radial-gradient(circle at 20% 30%, #0a0a0a, #000000);

            display: flex;

            justify-content: center;

            align-items: center;

            font-family: 'Segoe UI', 'Orbitron', 'Poppins', 'Courier New', monospace;

            padding: 1.5rem;

        }


        /* main black-theme glass panel */

        .tester-container {

            max-width: 600px;

            width: 100%;

            background: rgba(8, 8, 12, 0.75);

            backdrop-filter: blur(12px);

            border-radius: 3rem;

            border: 1px solid rgba(0, 255, 255, 0.2);

            box-shadow: 0 20px 40px rgba(0,0,0,0.6), 0 0 0 1px rgba(0, 255, 255, 0.1) inset;

            padding: 2rem 1.8rem 2.5rem;

            transition: all 0.3s ease;

        }


        /* wheel & gauge area */

        .wheel-area {

            position: relative;

            display: flex;

            justify-content: center;

            margin-bottom: 2rem;

        }


        canvas {

            background: #030303;

            border-radius: 50%;

            box-shadow: 0 0 30px rgba(0, 255, 255, 0.2), 0 8px 20px rgba(0,0,0,0.5);

            transition: filter 0.2s;

            width: 100%;

            height: auto;

            cursor: pointer;

        }


        .speed-badge {

            position: absolute;

            top: 50%;

            left: 50%;

            transform: translate(-50%, -50%);

            text-align: center;

            background: rgba(0,0,0,0.7);

            backdrop-filter: blur(8px);

            padding: 0.5rem 1rem;

            border-radius: 4rem;

            border: 1px solid cyan;

            width: 70%;

            pointer-events: none;

            white-space: nowrap;

        }


        .speed-value {

            font-size: 2.2rem;

            font-weight: 800;

            letter-spacing: 2px;

            background: linear-gradient(135deg, #0ff, #0fa);

            -webkit-background-clip: text;

            background-clip: text;

            color: transparent;

            font-family: 'Orbitron', monospace;

        }


        .speed-unit {

            font-size: 0.9rem;

            color: #9cd9ff;

            margin-left: 4px;

        }


        .status-text {

            font-size: 0.8rem;

            color: #aaf0ff;

            margin-top: 6px;

            letter-spacing: 1px;

        }


        /* stats panel */

        .stats-panel {

            background: rgba(0,0,0,0.6);

            border-radius: 2rem;

            padding: 1rem 1.5rem;

            margin: 1rem 0 1.5rem;

            border: 1px solid rgba(0, 255, 255, 0.3);

            display: flex;

            justify-content: space-between;

            flex-wrap: wrap;

            gap: 1rem;

        }


        .stat-card {

            flex: 1;

            text-align: center;

        }


        .stat-label {

            font-size: 0.7rem;

            text-transform: uppercase;

            color: #7f8c8d;

            letter-spacing: 2px;

        }


        .stat-number {

            font-size: 1.5rem;

            font-weight: bold;

            color: #0ff;

            font-family: monospace;

        }


        .stat-sub {

            font-size: 0.7rem;

            color: #ccc;

        }


        /* buttons */

        .action-buttons {

            display: flex;

            gap: 1rem;

            justify-content: center;

            flex-wrap: wrap;

        }


        .btn {

            background: rgba(20,20,30,0.9);

            border: 1px solid rgba(0, 255, 255, 0.5);

            color: #0ff;

            padding: 0.8rem 1.8rem;

            font-weight: bold;

            border-radius: 3rem;

            cursor: pointer;

            transition: 0.2s;

            font-family: inherit;

            font-size: 1rem;

            backdrop-filter: blur(4px);

            letter-spacing: 1px;

        }


        .btn-primary {

            background: linear-gradient(145deg, #0a2b2b, #021111);

            box-shadow: 0 0 8px cyan;

            border-color: cyan;

            color: white;

        }


        .btn-primary:hover {

            background: #0ff2;

            box-shadow: 0 0 16px cyan;

            transform: scale(1.02);

        }


        .btn-secondary {

            border-color: #f0f;

            color: #f0f;

            box-shadow: 0 0 4px #f0f;

        }


        .btn-secondary:hover {

            background: #f0f2;

            box-shadow: 0 0 12px #f0f;

        }


        .spin-indicator {

            font-size: 0.7rem;

            margin-top: 1rem;

            text-align: center;

            color: #0ff8;

            animation: pulse 1.5s infinite;

        }


        @keyframes pulse {

            0% { opacity: 0.4; text-shadow: 0 0 0px cyan;}

            100% { opacity: 1; text-shadow: 0 0 4px cyan;}

        }


        footer {

            font-size: 0.65rem;

            text-align: center;

            margin-top: 1.5rem;

            color: #2c5368;

        }


        @media (max-width: 480px) {

            .tester-container {

                padding: 1.5rem;

            }

            .speed-value {

                font-size: 1.6rem;

            }

            .stat-number {

                font-size: 1.1rem;

            }

        }

    </style>

</head>

<body>


<div class="tester-container">

    <div class="wheel-area">

        <canvas id="speedWheelCanvas" width="400" height="400" style="width:100%; max-width:400px; height:auto; aspect-ratio:1/1"></canvas>

        <div class="speed-badge">

            <div>

                <span class="speed-value" id="liveSpeed">0.00</span>

                <span class="speed-unit">Mbps</span>

            </div>

            <div class="status-text" id="statusMsg">⚡ READY ⚡</div>

        </div>

    </div>


    <div class="stats-panel">

        <div class="stat-card">

            <div class="stat-label">⬇️ DOWNLOAD</div>

            <div class="stat-number" id="downloadStat">0.00</div>

            <div class="stat-sub">Mbps</div>

        </div>

        <div class="stat-card">

            <div class="stat-label">⬆️ UPLOAD</div>

            <div class="stat-number" id="uploadStat">0.00</div>

            <div class="stat-sub">Mbps</div>

        </div>

        <div class="stat-card">

            <div class="stat-label">⏱️ LATENCY</div>

            <div class="stat-number" id="pingStat">0</div>

            <div class="stat-sub">ms</div>

        </div>

    </div>


    <div class="action-buttons">

        <button class="btn btn-primary" id="startTestBtn">🌀 START SPEED TEST</button>

        <button class="btn btn-secondary" id="resetWheelBtn">⟳ RESET WHEEL</button>

    </div>

    <div class="spin-indicator" id="spinHint">◈ click wheel or press test ◈</div>

    <footer>BLACK WHEEL • simulated neural speed engine</footer>

</div>


<script>

    (function(){

        // ---------- DOM elements ----------

        const canvas = document.getElementById('speedWheelCanvas');

        const ctx = canvas.getContext('2d');

        const liveSpeedSpan = document.getElementById('liveSpeed');

        const statusSpan = document.getElementById('statusMsg');

        const downloadStatSpan = document.getElementById('downloadStat');

        const uploadStatSpan = document.getElementById('uploadStat');

        const pingStatSpan = document.getElementById('pingStat');

        const startBtn = document.getElementById('startTestBtn');

        const resetBtn = document.getElementById('resetWheelBtn');


        // ---------- wheel state ----------

        let currentSpeedMbps = 0.0;         // current displayed speed (0-1000+)

        let targetSpeed = 0.0;

        let animationFrame = null;

        let isTesting = false;

        let testAbort = false;


        // stored results

        let lastDownload = 0;

        let lastUpload = 0;

        let lastPing = 0;


        // wheel rotation angle (dynamic for "wheel type" effect)

        let wheelRotation = 0;        // radians

        let rotationVelocity = 0;      // for subtle gliding effect based on speed

        let lastTimestamp = 0;


        // ---------- helper: simulate realistic internet test (backend-like via Python? but fully JS simulation with random + realistic algorithm)

        // Inspired by "internet speed tester" simulation with dynamic adaptive results

        // but also using mathematical model + random variation to mimic real testing.

        // we call this the "NEBULA ENGINE"

        async function performSpeedTest() {

            if(isTesting) {

                statusSpan.innerText = "⚠️ TEST ALREADY RUNNING...";

                return;

            }

            isTesting = true;

            testAbort = false;

            statusSpan.innerText = "📡 INITIALIZING TEST...";

            

            // 1. PING TEST (simulate latency)

            statusSpan.innerText = "🏓 MEASURING LATENCY...";

            let ping = await simulatePing();

            lastPing = ping;

            pingStatSpan.innerText = Math.floor(ping);

            

            // 2. DOWNLOAD TEST (simulate download speed)

            statusSpan.innerText = "⬇️ TESTING DOWNLOAD...";

            let downloadMbps = await simulateDownload();

            lastDownload = downloadMbps;

            downloadStatSpan.innerText = downloadMbps.toFixed(2);

            

            // 3. UPLOAD TEST

            statusSpan.innerText = "⬆️ TESTING UPLOAD...";

            let uploadMbps = await simulateUpload();

            lastUpload = uploadMbps;

            uploadStatSpan.innerText = uploadMbps.toFixed(2);

            

            // 4. final overall speed: Usually speed testers show download as main reference, but we combine weighted average

            // to make wheel dynamic: 70% download + 20% upload + 10% factor from ping (higher ping reduces perceived speed)

            let qualityFactor = Math.max(0.3, Math.min(1.2, 1 - (ping / 200)));

            let combined = (downloadMbps * 0.7 + uploadMbps * 0.3) * qualityFactor;

            let finalSpeed = Math.min(950, Math.max(0.5, combined));

            

            // set target speed with smooth animation

            targetSpeed = parseFloat(finalSpeed.toFixed(2));

            

            // update stats UI

            statusSpan.innerText = "✅ TEST COMPLETE! WHEEL SPINS.";

            isTesting = false;

            

            // also make wheel rotation bump based on new speed (extra effect)

            rotationVelocity += (targetSpeed / 80) + 0.15;

            if(rotationVelocity > 1.2) rotationVelocity = 1.2;

        }

        

        // simulated ping: between 12ms and 110ms, random but with realistic curve

        function simulatePing() {

            return new Promise((resolve) => {

                setTimeout(() => {

                    let base = Math.floor(Math.random() * 70) + 12;   // 12-82ms

                    let jitter = Math.floor(Math.random() * 28);

                    let pingVal = base + jitter;

                    resolve(pingVal);

                }, 80 + Math.random() * 120);

            });

        }

        

        // simulate download: returns Mbps (range 8 to 780 Mbps realistic)

        function simulateDownload() {

            return new Promise((resolve) => {

                let testDuration = 400 + Math.random() * 400;

                setTimeout(() => {

                    // create distribution: moderate high-speed fiber or cable

                    let raw = (Math.random() * 200) + 20;    // 20-220

                    if(Math.random() > 0.7) raw += 180;      // extra burst up to 400

                    if(Math.random() > 0.85) raw += 300;      // rare gigabit class up to 700

                    let speed = Math.min(890, Math.max(5, raw));

                    resolve(parseFloat(speed.toFixed(2)));

                }, testDuration);

            });

        }

        

        function simulateUpload() {

            return new Promise((resolve) => {

                let testDuration = 300 + Math.random() * 350;

                setTimeout(() => {

                    let rawUp = (Math.random() * 90) + 10;    // 10-100

                    if(Math.random() > 0.75) rawUp += 120;    // up to 220

                    if(Math.random() > 0.9) rawUp += 180;      // high upload

                    let upSpeed = Math.min(550, Math.max(4, rawUp));

                    resolve(parseFloat(upSpeed.toFixed(2)));

                }, testDuration);

            });

        }

        

        // ---------- RESET WHEEL (clean all stats) ----------

        function resetWheelAndStats() {

            if(isTesting) {

                testAbort = true;

                isTesting = false;

            }

            targetSpeed = 0.0;

            currentSpeedMbps = 0.0;

            lastDownload = 0;

            lastUpload = 0;

            lastPing = 0;

            downloadStatSpan.innerText = "0.00";

            uploadStatSpan.innerText = "0.00";

            pingStatSpan.innerText = "0";

            liveSpeedSpan.innerText = "0.00";

            statusSpan.innerText = "🌀 RESET | spin ready";

            rotationVelocity = 0;

            wheelRotation = 0;

            // smooth reset

            updateWheelCanvas(0);

        }

        

        // ---------- DRAW WHEEL (cyber wheel with speed segments) ----------

        function drawWheel(currentSpeed, rotAngleRad) {

            if(!ctx) return;

            const w = canvas.width;

            const h = canvas.height;

            const centerX = w/2;

            const centerY = h/2;

            const radius = w * 0.44;

            ctx.clearRect(0, 0, w, h);

            

            // background subtle black glow

            ctx.shadowBlur = 0;

            // draw outer rim

            ctx.beginPath();

            ctx.arc(centerX, centerY, radius + 8, 0, Math.PI*2);

            ctx.fillStyle = "#010101";

            ctx.fill();

            ctx.strokeStyle = "#0ff4";

            ctx.lineWidth = 2;

            ctx.stroke();

            

            // draw speed wheel segments: 8 segments mapping speed range 0-800 Mbps

            const maxSegmentSpeed = 800;

            let speedLimited = Math.min(maxSegmentSpeed, currentSpeed);

            let fraction = speedLimited / maxSegmentSpeed; // 0..1

            let activeSegments = Math.ceil(fraction * 8);

            activeSegments = Math.min(8, Math.max(0, activeSegments));

            

            const segmentCount = 8;

            const angleStep = (Math.PI * 2) / segmentCount;

            for(let i = 0; i < segmentCount; i++) {

                let startAngle = i * angleStep + rotAngleRad;

                let endAngle = (i+1) * angleStep + rotAngleRad;

                let isActive = (i < activeSegments);

                let gradient = ctx.createLinearGradient(centerX + Math.cos(startAngle)*10, centerY+ Math.sin(startAngle)*10, centerX, centerY);

                if(isActive) {

                    let intensity = 0.4 + (fraction * 0.6);

                    gradient.addColorStop(0, `rgba(0, 210, 255, ${0.7+intensity*0.3})`);

                    gradient.addColorStop(1, `rgba(0, 255, 180, ${0.8})`);

                } else {

                    gradient.addColorStop(0, "#1a2a2f");

                    gradient.addColorStop(1, "#0a1015");

                }

                ctx.beginPath();

                ctx.arc(centerX, centerY, radius, startAngle, endAngle);

                ctx.lineTo(centerX, centerY);

                ctx.fillStyle = gradient;

                ctx.fill();

                ctx.strokeStyle = "#0ff3";

                ctx.lineWidth = 1.2;

                ctx.stroke();

            }

            

            // inner glow circle

            ctx.beginPath();

            ctx.arc(centerX, centerY, radius*0.65, 0, Math.PI*2);

            ctx.fillStyle = "#03070a";

            ctx.fill();

            ctx.shadowBlur = 8;

            ctx.shadowColor = "#0ff";

            ctx.beginPath();

            ctx.arc(centerX, centerY, radius*0.63, 0, Math.PI*2);

            ctx.fillStyle = "#000000aa";

            ctx.fill();

            

            // tick marks & speed text (cyber)

            ctx.font = `bold ${Math.floor(radius*0.12)}px "Orbitron"`;

            ctx.fillStyle = "#0ffb";

            ctx.shadowBlur = 3;

            for(let i=0; i<=8; i++) {

                let speedVal = Math.floor((i/8)*maxSegmentSpeed);

                let angle = i * angleStep + rotAngleRad - 0.2;

                let rad = radius * 0.78;

                let x = centerX + Math.cos(angle) * rad;

                let y = centerY + Math.sin(angle) * rad;

                if(speedVal % 100 === 0 || i===0 || i===8){

                    ctx.fillStyle = "#0ff";

                    ctx.fillText(`${speedVal}`, x-10, y-4);

                } else if(speedVal % 50 === 0){

                    ctx.fillStyle = "#6ff";

                    ctx.fillText(`${speedVal}`, x-7, y-2);

                }

            }

            

            // central hub with glowing ring

            ctx.beginPath();

            ctx.arc(centerX, centerY, radius*0.2, 0, Math.PI*2);

            ctx.fillStyle = "#0a0e14";

            ctx.fill();

            ctx.beginPath();

            ctx.arc(centerX, centerY, radius*0.16, 0, Math.PI*2);

            ctx.fillStyle = "#0ff3";

            ctx.fill();

            ctx.beginPath();

            ctx.arc(centerX, centerY, radius*0.1, 0, Math.PI*2);

            ctx.fillStyle = "#0ff";

            ctx.fill();

            

            // draw needle / spinner (wheel style pointer)

            let pointerAngle = rotAngleRad + (fraction * Math.PI * 2);

            let needleLen = radius * 0.85;

            let needleX = centerX + Math.cos(pointerAngle) * needleLen;

            let needleY = centerY + Math.sin(pointerAngle) * needleLen;

            ctx.beginPath();

            ctx.moveTo(centerX, centerY);

            ctx.lineTo(needleX, needleY);

            ctx.lineWidth = 4;

            ctx.strokeStyle = "#ff3366";

            ctx.shadowBlur = 8;

            ctx.shadowColor = "#f0f";

            ctx.stroke();

            ctx.beginPath();

            ctx.arc(centerX, centerY, 8, 0, Math.PI*2);

            ctx.fillStyle = "#f0f";

            ctx.fill();

            ctx.shadowBlur = 0;

        }

        

        function updateWheelCanvas(speedValue) {

            // update rotation dynamic: speed influences rotation velocity drift

            if(!isTesting) {

                let speedFactor = Math.min(1.2, speedValue / 300);

                rotationVelocity += (speedFactor * 0.008) - (rotationVelocity * 0.02);

                rotationVelocity = Math.min(0.6, Math.max(-0.2, rotationVelocity));

                wheelRotation += rotationVelocity;

                wheelRotation = wheelRotation % (Math.PI*2);

            } else {

                // slight jitter while testing

                rotationVelocity += 0.005;

                wheelRotation += rotationVelocity * 0.2;

                wheelRotation = wheelRotation % (Math.PI*2);

            }

            drawWheel(speedValue, wheelRotation);

        }

        

        // Animation loop: smoothly interpolate currentSpeedMbps to targetSpeed

        let animActive = true;

        function animateSpeed() {

            if(!animActive) return;

            let diff = targetSpeed - currentSpeedMbps;

            if(Math.abs(diff) > 0.02) {

                currentSpeedMbps += diff * 0.12;

                if(Math.abs(targetSpeed - currentSpeedMbps) < 0.05) currentSpeedMbps = targetSpeed;

            } else {

                currentSpeedMbps = targetSpeed;

            }

            // clamping

            if(currentSpeedMbps < 0) currentSpeedMbps = 0;

            liveSpeedSpan.innerText = currentSpeedMbps.toFixed(2);

            updateWheelCanvas(currentSpeedMbps);

            requestAnimationFrame(animateSpeed);

        }

        

        // additional interactive wheel click: re-run test (convenience)

        function onWheelClick() {

            if(!isTesting) {

                performSpeedTest().catch(e=>console.warn);

            } else {

                statusSpan.innerText = "⏳ Test in progress, please wait...";

            }

        }

        

        // -------- initiate event binding & initial draw --------

        function init() {

            canvas.addEventListener('click', onWheelClick);

            startBtn.addEventListener('click', () => {

                if(!isTesting) performSpeedTest();

                else statusSpan.innerText = "Test active, wait...";

            });

            resetBtn.addEventListener('click', () => {

                resetWheelAndStats();

            });

            // initial draw

            drawWheel(0, 0);

            animateSpeed();

            // minor periodic "idle rotation" elegance

            setInterval(() => {

                if(!isTesting && targetSpeed < 0.5 && currentSpeedMbps < 0.8) {

                    rotationVelocity += 0.003;

                    if(rotationVelocity>0.08) rotationVelocity=0.08;

                }

            }, 800);

        }

        

        init();

    })();

</script>


<!-- note: python backend simulation is conceptual but the JS fully mimics speed test

     Additional python code can be provided for a server-side testing endpoint, but all core tester logic

     is client-side with realistic algorithms. For a complete full-stack feel, below is a commented example

     of a Python Flask endpoint that could generate random speeds or actual network measurement.

     However, this demo includes pure HTML/CSS/JS with blacked theme wheel and comprehensive speed simulation. 

-->

</body>

</html>


<!-- 

    ********** PYTHON BACKEND SIMULATION (OPTIONAL / CONCEPTUAL) **********

    To run a real backend speed test helper, you could use a lightweight Python server.

    Below is a snippet for demonstration but not required for frontend wheel functionality.

    

    from flask import Flask, jsonify

    import random, time

    app = Flask(__name__)

    @app.route('/api/speedtest')

    def speedtest():

        ping = random.randint(12, 80)

        download = round(random.uniform(20, 750), 2)

        upload = round(random.uniform(10, 320), 2)

        return jsonify({"ping": ping, "download": download, "upload": upload})

    if __name__ == '__main__':

        app.run(port=5000)

    

    The HTML/JS frontend can integrate fetch('/api/speedtest') but due to cross-origin and demo simplicity,

    the simulated engine is already robust, matches black theme wheel, and provides full "Internet Speed Tester" experience.

--> 

Comments