<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

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

    <title>Joinable Membership Scanner</title>

    <style>

        * {

            margin: 0;

            padding: 0;

            box-sizing: border-box;

        }


        body {

            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;

            background: #f5f5f5;

            padding: 20px;

            min-height: 100vh;

        }


        .container {

            max-width: 600px;

            margin: 0 auto;

        }


        h1 {

            font-size: 32px;

            margin-bottom: 30px;

            color: #333;

        }


        .upload-section {

            background: white;

            padding: 25px;

            border-radius: 12px;

            box-shadow: 0 2px 8px rgba(0,0,0,0.1);

            margin-bottom: 20px;

        }


        .upload-section h2 {

            font-size: 24px;

            margin-bottom: 15px;

            color: #333;

        }


        input[type="file"] {

            margin-bottom: 15px;

            width: 100%;

        }


        button {

            background: #007AFF;

            color: white;

            border: none;

            padding: 15px 30px;

            font-size: 18px;

            border-radius: 8px;

            cursor: pointer;

            width: 100%;

            margin-bottom: 10px;

            font-weight: 600;

        }


        button:active {

            background: #0051D5;

        }


        button:disabled {

            background: #ccc;

            cursor: not-allowed;

        }


        .status {

            padding: 15px;

            border-radius: 8px;

            margin-bottom: 15px;

            font-size: 16px;

        }


        .status.success {

            background: #d4edda;

            color: #155724;

        }


        .status.error {

            background: #f8d7da;

            color: #721c24;

        }


        .tip {

            background: #e3f2fd;

            padding: 15px;

            border-radius: 8px;

            color: #1565c0;

            font-size: 14px;

            line-height: 1.5;

        }


        /* Scanner Styles */

        #scanner-container {

            display: none;

            position: fixed;

            top: 0;

            left: 0;

            width: 100%;

            height: 100%;

            background: black;

            z-index: 1000;

        }


        #scanner-container.active {

            display: block;

        }


        #video {

            width: 100%;

            height: 100%;

            object-fit: cover;

        }


        .scanner-overlay {

            position: absolute;

            top: 0;

            left: 0;

            width: 100%;

            height: 100%;

            pointer-events: none;

        }


        .scanner-box {

            position: absolute;

            top: 50%;

            left: 50%;

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

            width: 80%;

            max-width: 400px;

            height: 200px;

            border: 3px solid #00FF00;

            border-radius: 12px;

            box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);

        }


        .scanner-corners {

            position: absolute;

            top: 50%;

            left: 50%;

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

            width: 80%;

            max-width: 400px;

            height: 200px;

        }


        .corner {

            position: absolute;

            width: 30px;

            height: 30px;

            border: 4px solid #00FF00;

        }


        .corner.top-left {

            top: -2px;

            left: -2px;

            border-right: none;

            border-bottom: none;

        }


        .corner.top-right {

            top: -2px;

            right: -2px;

            border-left: none;

            border-bottom: none;

        }


        .corner.bottom-left {

            bottom: -2px;

            left: -2px;

            border-right: none;

            border-top: none;

        }


        .corner.bottom-right {

            bottom: -2px;

            right: -2px;

            border-left: none;

            border-top: none;

        }


        .scanner-instruction {

            position: absolute;

            top: 30%;

            left: 50%;

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

            color: white;

            font-size: 18px;

            text-align: center;

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

            padding: 15px 25px;

            border-radius: 8px;

            pointer-events: none;

        }


        #stop-scan-btn {

            position: absolute;

            bottom: 40px;

            left: 50%;

            transform: translateX(-50%);

            background: #FF3B30;

            color: white;

            border: none;

            padding: 15px 40px;

            font-size: 18px;

            border-radius: 8px;

            cursor: pointer;

            font-weight: 600;

            z-index: 10;

            pointer-events: all;

        }


        /* Result Overlay */

        .result-overlay {

            display: none;

            position: fixed;

            top: 0;

            left: 0;

            width: 100%;

            height: 100%;

            z-index: 2000;

            align-items: center;

            justify-content: center;

            animation: fadeOut 2s forwards;

        }


        .result-overlay.active {

            display: flex;

        }


        .result-overlay.valid {

            background: #4CAF50;

        }


        .result-overlay.invalid {

            background: #F44336;

        }


        .result-content {

            text-align: center;

            color: white;

        }


        .result-icon {

            font-size: 120px;

            margin-bottom: 20px;

            animation: scaleIn 0.3s ease-out;

        }


        .result-text {

            font-size: 32px;

            font-weight: bold;

            animation: scaleIn 0.3s ease-out 0.1s backwards;

        }


        @keyframes fadeOut {

            0%, 70% {

                opacity: 1;

            }

            100% {

                opacity: 0;

            }

        }


        @keyframes scaleIn {

            0% {

                transform: scale(0);

            }

            100% {

                transform: scale(1);

            }

        }


        canvas {

            display: none;

        }


        .hidden {

            display: none;

        }

    </style>

</head>

<body>

    <div class="container">

        <h1>Joinable Membership Scanner</h1>


        <div class="upload-section">

            <h2>Upload members CSV</h2>

            <input type="file" id="csv-file" accept=".csv">

            <button id="load-csv-btn">Load CSV</button>

            <div id="status"></div>

        </div>


        <button id="start-scan-btn" disabled>Start Fullscreen Camera Scanner</button>


        <div class="tip">

            <strong>Tip:</strong> For handheld USB/Bluetooth scanners, click the input box then scan — the scanner types the code and press Enter to check membership validity.

        </div>

    </div>


    <!-- Scanner Container -->

    <div id="scanner-container">

        <video id="video" playsinline></video>

        <canvas id="canvas"></canvas>

        <div class="scanner-overlay">

            <div class="scanner-instruction">Position barcode within the box</div>

            <div class="scanner-box"></div>

            <div class="scanner-corners">

                <div class="corner top-left"></div>

                <div class="corner top-right"></div>

                <div class="corner bottom-left"></div>

                <div class="corner bottom-right"></div>

            </div>

        </div>

        <button id="stop-scan-btn">Stop Scanning</button>

    </div>


    <!-- Result Overlay -->

    <div id="result-overlay" class="result-overlay">

        <div class="result-content">

            <div class="result-icon" id="result-icon"></div>

            <div class="result-text" id="result-text"></div>

        </div>

    </div>


    <script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.2/papaparse.min.js"></script>

    <script src="https://unpkg.com/@zxing/library@0.19.1/umd/index.min.js"></script>


    <script>

        let membersData = [];

        let codeReader = null;

        let isScanning = false;

        let lastScannedCode = '';

        let lastScanTime = 0;


        const csvFile = document.getElementById('csv-file');

        const loadCsvBtn = document.getElementById('load-csv-btn');

        const startScanBtn = document.getElementById('start-scan-btn');

        const stopScanBtn = document.getElementById('stop-scan-btn');

        const statusDiv = document.getElementById('status');

        const scannerContainer = document.getElementById('scanner-container');

        const video = document.getElementById('video');

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

        const resultOverlay = document.getElementById('result-overlay');

        const resultIcon = document.getElementById('result-icon');

        const resultText = document.getElementById('result-text');


        // Load CSV

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

            const file = csvFile.files[0];

            if (!file) {

                showStatus('Please select a CSV file', 'error');

                return;

            }


            Papa.parse(file, {

                header: true,

                skipEmptyLines: true,

                complete: (results) => {

                    membersData = results.data.map(row => ({

                        membershipId: String(row['Membership ID'] || '').trim(),

                        status: String(row['Status'] || '').trim()

                    }));


                    if (membersData.length === 0) {

                        showStatus('No valid data found in CSV', 'error');

                        startScanBtn.disabled = true;

                    } else {

                        showStatus(`Loaded ${membersData.length} members successfully`, 'success');

                        startScanBtn.disabled = false;

                    }

                },

                error: (error) => {

                    showStatus('Error reading CSV: ' + error.message, 'error');

                    startScanBtn.disabled = true;

                }

            });

        });


        // Start Scanner

        startScanBtn.addEventListener('click', async () => {

            try {

                scannerContainer.classList.add('active');

                await startScanning();

            } catch (error) {

                showStatus('Error accessing camera: ' + error.message, 'error');

                scannerContainer.classList.remove('active');

            }

        });


        // Stop Scanner

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

            stopScanning();

            scannerContainer.classList.remove('active');

        });


        async function startScanning() {

            isScanning = true;

            

            const constraints = {

                video: {

                    facingMode: 'environment',

                    width: { ideal: 1920 },

                    height: { ideal: 1080 }

                }

            };


            try {

                const stream = await navigator.mediaDevices.getUserMedia(constraints);

                video.srcObject = stream;

                video.setAttribute('playsinline', true);

                await video.play();


                codeReader = new ZXing.BrowserMultiFormatReader();

                

                requestAnimationFrame(scan);

            } catch (error) {

                throw error;

            }

        }


        function scan() {

            if (!isScanning) return;


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

            canvas.width = video.videoWidth;

            canvas.height = video.videoHeight;


            if (canvas.width > 0 && canvas.height > 0) {

                ctx.drawImage(video, 0, 0);

                

                // Image enhancement for better barcode reading from phone screens

                const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

                enhanceImage(imageData);

                ctx.putImageData(imageData, 0, 0);


                try {

                    const result = codeReader.decodeFromCanvas(canvas);

                    if (result) {

                        handleScan(result.text);

                    }

                } catch (error) {

                    // No barcode found, continue scanning

                }

            }


            requestAnimationFrame(scan);

        }


        function enhanceImage(imageData) {

            const data = imageData.data;

            

            // Increase contrast and reduce blur

            for (let i = 0; i < data.length; i += 4) {

                // Convert to grayscale

                const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];

                

                // Increase contrast

                const contrast = 1.5;

                const factor = (259 * (contrast + 255)) / (255 * (259 - contrast));

                let adjusted = factor * (gray - 128) + 128;

                

                // Apply threshold for sharper blacks and whites

                adjusted = adjusted < 128 ? Math.max(0, adjusted - 30) : Math.min(255, adjusted + 30);

                

                data[i] = data[i + 1] = data[i + 2] = adjusted;

            }

        }


        function handleScan(code) {

            const now = Date.now();

            

            // Prevent duplicate scans within 3 seconds

            if (code === lastScannedCode && now - lastScanTime < 3000) {

                return;

            }


            lastScannedCode = code;

            lastScanTime = now;


            // Clean the scanned code

            const cleanCode = code.trim();


            // Find member by membership ID

            const member = membersData.find(m => m.membershipId === cleanCode);


            if (member) {

                const isActive = member.status === 'Active';

                showResult(isActive);

            } else {

                // Member not found - treat as inactive

                showResult(false);

            }

        }


        function showResult(isValid) {

            resultOverlay.classList.remove('valid', 'invalid');

            resultOverlay.classList.add(isValid ? 'valid' : 'invalid');

            resultIcon.textContent = isValid ? '✓' : '✗';

            resultText.textContent = isValid ? 'ACTIVE' : 'INACTIVE';

            resultOverlay.classList.add('active');


            setTimeout(() => {

                resultOverlay.classList.remove('active');

                // Continue scanning automatically

            }, 2000);

        }


        function stopScanning() {

            isScanning = false;

            

            if (video.srcObject) {

                const tracks = video.srcObject.getTracks();

                tracks.forEach(track => track.stop());

                video.srcObject = null;

            }


            if (codeReader) {

                codeReader = null;

            }

        }


        function showStatus(message, type) {

            statusDiv.textContent = message;

            statusDiv.className = `status ${type}`;

            statusDiv.style.display = 'block';

        }

    </script>

</body>

</html>