<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
    <title>IMTMA | Upload Event Images</title>
    <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
    <style>
        :root {
            --bg: #07141d;
            --bg-2: #0c2130;
            --panel: #0e2231;
            --panel-soft: #173246;
            --panel-soft-2: #1b3a50;
            --line: rgba(146, 202, 240, 0.16);
            --line-strong: rgba(146, 202, 240, 0.28);
            --text: #eef8ff;
            --muted: #a8c0d1;
            --accent: #69c4ff;
            --accent-strong: #2896e7;
            --accent-soft: rgba(105, 196, 255, 0.12);
            --danger: #ff6f6f;
            --shadow: 0 26px 70px rgba(0, 0, 0, 0.38);

        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;

        body {
            min-height: 100vh;
            font-family: "Manrope", sans-serif;
            color: var(--text);
            background:
                radial-gradient(circle at top right, rgba(75, 145, 190, 0.15), transparent 18%),
                radial-gradient(circle at top left, rgba(33, 86, 120, 0.22), transparent 24%),
                linear-gradient(180deg, var(--bg-2) 0%, var(--bg) 100%);
            padding: 16px;

        .page {
            max-width: 1180px;
            margin: 0 auto;

        .topbar {
            display: flex;
            justify-content: space-between;
            align-items: center;
            gap: 16px;
            padding: 14px 18px;
            border-radius: 22px;
            background: linear-gradient(180deg, rgba(10, 24, 35, 0.98) 0%, rgba(11, 28, 40, 0.95) 100%);
            border: 1px solid var(--line);
            box-shadow: var(--shadow);

        .brand {
            display: inline-flex;
            align-items: center;
            gap: 12px;
            font-size: 18px;
            font-weight: 800;

        .brand img {
            width: 52px;
            height: 52px;
            border-radius: 12px;
            object-fit: contain;
            background: #fff;
            padding: 4px;
            border: 1px solid rgba(255, 255, 255, 0.12);

        .logout-btn {
            min-height: 44px;
            border: 1px solid var(--line-strong);
            border-radius: 999px;
            padding: 0 18px;
            background: rgba(255, 111, 111, 0.12);
            color: var(--danger);
            font: inherit;
            font-weight: 700;
            cursor: pointer;
            transition: 0.2s ease;

        .logout-btn:hover {
            background: rgba(255, 111, 111, 0.22);
            border-color: var(--danger);

        .user-chip {
            padding: 10px 16px;
            border-radius: 999px;
            background: var(--accent-soft);
            border: 1px solid rgba(105, 196, 255, 0.22);
            font-weight: 600;
            color: var(--accent);
            font-size: 13px;

        .counter-btn {
            min-width: 98px;
            min-height: 44px;
            border: none;
            border-radius: 999px;
            padding: 0 16px;
            background: linear-gradient(135deg, var(--accent) 0%, #98ddff 100%);
            color: #082035;
            font: inherit;
            font-weight: 800;
            cursor: pointer;
            box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18);

        .upload-dropdown {
            position: fixed;
            top: 74px;
            right: 18px;
            width: min(360px, calc(100vw - 28px));
            max-height: 430px;
            overflow: auto;
            display: none;
            padding: 12px;
            border-radius: 18px;
            background: linear-gradient(180deg, rgba(11, 28, 39, 0.99) 0%, rgba(9, 23, 34, 0.98) 100%);
            border: 1px solid var(--line-strong);
            box-shadow: var(--shadow);
            z-index: 999;

        .upload-dropdown.open {
            display: block;

        .dropdown-title {
            color: var(--muted);
            text-transform: uppercase;
            letter-spacing: 0.08em;
            font-size: 12px;
            font-weight: 800;
            margin-bottom: 10px;

        .shell {
            margin-top: 16px;
            border-radius: 30px;
            overflow: hidden;
            background: linear-gradient(180deg, rgba(10, 24, 35, 0.92) 0%, rgba(8, 19, 29, 0.96) 100%);
            border: 1px solid var(--line);
            box-shadow: var(--shadow);

        .header-image,
        .footer-image {
            width: 100%;
            display: block;
            background: linear-gradient(180deg, #f4f6f8 0%, #ebeff3 100%);
            padding: 14px 18px;

        .header-image img,
        .footer-image img {
            width: 100%;
            display: block;
            height: auto;
            object-fit: contain;
            border-radius: 12px;
            background: #fff;
            box-shadow: inset 0 0 0 1px rgba(12, 28, 40, 0.05);

        .header-image img {
            max-height: 150px;

        .footer-image img {
            max-height: 120px;

        .content {
            padding: 26px;
            background:
                radial-gradient(circle at top center, rgba(64, 145, 200, 0.08), transparent 32%),
                linear-gradient(180deg, rgba(10, 24, 35, 0.9) 0%, rgba(8, 20, 30, 0.96) 100%);

        .content-head {
            display: flex;
            justify-content: space-between;
            align-items: center;
            gap: 16px;
            margin-bottom: 22px;

        .content-head h1 {
            font-size: clamp(1.6rem, 3vw, 2.3rem);
            margin-bottom: 10px;
            letter-spacing: -0.03em;

        .content-head p {
            color: var(--muted);
            line-height: 1.6;
            max-width: 700px;

        .badge {
            padding: 10px 14px;
            border-radius: 999px;
            background: var(--accent-soft);
            border: 1px solid rgba(105, 196, 255, 0.22);
            color: #99ddff;
            font-size: 13px;
            font-weight: 700;
            white-space: nowrap;

        .actions {
            display: grid;
            grid-template-columns: 1fr 1fr auto;
            gap: 12px;
            margin-bottom: 20px;

        .action-btn,
        .upload-btn {
            min-height: 54px;
            border: none;
            border-radius: 18px;
            font: inherit;
            font-weight: 800;
            cursor: pointer;

        .action-btn {
            background: linear-gradient(135deg, rgba(105, 196, 255, 0.14) 0%, rgba(40, 150, 231, 0.22) 100%);
            color: var(--text);
            border: 1px solid rgba(105, 196, 255, 0.18);
            box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);

        .upload-btn {
            padding: 0 24px;
            background: linear-gradient(135deg, #8bddff 0%, var(--accent-strong) 100%);
            color: #041a2c;
            box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.24);

        .dropzone {
            border-radius: 26px;
            border: 1px dashed rgba(105, 196, 255, 0.34);
            background:
                radial-gradient(circle at top center, rgba(105, 196, 255, 0.16), transparent 36%),
                linear-gradient(180deg, rgba(255, 255, 255, 0.03) 0%, rgba(105, 196, 255, 0.05) 100%);
            text-align: center;
            padding: 42px 24px;
            transition: 0.2s ease;
            min-height: 340px;
            display: flex;
            flex-direction: column;
            justify-content: center;

        .dropzone.dragging {
            transform: translateY(-1px);
            border-color: #93ddff;
            box-shadow: 0 0 0 1px rgba(147, 221, 255, 0.14) inset;

        .drop-icon {
            width: 76px;
            height: 76px;
            margin: 0 auto 16px;
            display: grid;
            place-items: center;
            border-radius: 24px;
            background: linear-gradient(135deg, rgba(87, 187, 255, 0.18) 0%, rgba(13, 42, 63, 0.94) 100%);
            border: 1px solid rgba(105, 196, 255, 0.2);
            color: #8dd9ff;
            font-size: 28px;
            font-weight: 800;
            box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);

        .dropzone h2 {
            font-size: clamp(1.3rem, 2.4vw, 1.9rem);
            margin-bottom: 10px;

        .dropzone p {
            color: var(--muted);
            line-height: 1.7;
            max-width: 760px;
            margin: 0 auto;

        .hint-row {
            display: flex;
            justify-content: center;
            flex-wrap: wrap;
            gap: 10px;
            margin-top: 18px;

        .hint {
            padding: 10px 14px;
            border-radius: 999px;
            background: rgba(255, 255, 255, 0.05);
            border: 1px solid rgba(255, 255, 255, 0.08);
            color: var(--muted);
            font-size: 13px;
            font-weight: 700;

        .workspace {
            display: grid;
            grid-template-columns: minmax(0, 1.18fr) minmax(320px, 0.82fr);
            gap: 18px;
            margin-top: 18px;
            align-items: start;

        .main-pane {
            display: grid;
            gap: 16px;

        .side-pane {
            display: grid;
            gap: 16px;
            align-content: start;

        .panel {
            border-radius: 24px;
            background: linear-gradient(180deg, var(--panel-soft) 0%, var(--panel-soft-2) 100%);
            border: 1px solid var(--line);
            padding: 18px;
            box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);

        .panel-head {
            display: flex;
            justify-content: space-between;
            align-items: center;
            gap: 12px;
            margin-bottom: 14px;

        .panel-head h3 {
            color: var(--muted);
            font-size: 13px;
            text-transform: uppercase;
            letter-spacing: 0.08em;

        .panel-head span {
            color: var(--muted);
            font-size: 13px;

        .list {
            display: grid;
            gap: 10px;
            max-height: 240px;
            overflow: auto;

        .item {
            border-radius: 18px;
            border: 1px solid rgba(255, 255, 255, 0.07);
            background: rgba(255, 255, 255, 0.04);
            padding: 12px 14px;

        .item-name {
            word-break: break-all;
            line-height: 1.45;

        .item-meta {
            display: flex;
            justify-content: space-between;
            gap: 12px;
            margin-top: 6px;
            color: var(--muted);
            font-size: 12px;

        .progress-bar {
            margin-top: 8px;
            height: 6px;
            border-radius: 999px;
            overflow: hidden;
            background: rgba(255, 255, 255, 0.08);

        .progress-fill {
            width: 0;
            height: 100%;
            background: linear-gradient(90deg, var(--accent) 0%, #94e2ff 100%);
            transition: width 0.2s ease;

        .response-box {
            padding: 18px;
            border-radius: 24px;
            border: 1px solid var(--line);
            background: linear-gradient(180deg, rgba(24, 50, 69, 0.58) 0%, rgba(18, 38, 54, 0.44) 100%);

        .response-title {
            margin-bottom: 10px;
            color: var(--muted);
            font-size: 13px;
            text-transform: uppercase;
            letter-spacing: 0.08em;
            font-weight: 800;

        #response {
            white-space: pre-wrap;
            word-break: break-word;
            font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
            font-size: 13px;

        .empty {
            color: var(--muted);
            line-height: 1.6;
            font-size: 14px;

        .footer {
            margin-top: 14px;
            text-align: center;
            color: var(--muted);
            font-size: 12px;

        .toast-wrap {
            position: fixed;
            right: 18px;
            bottom: 18px;
            display: flex;
            flex-direction: column;
            gap: 10px;
            z-index: 1000;

        .toast {
            min-width: 260px;
            max-width: min(360px, calc(100vw - 28px));
            padding: 12px 14px;
            border-radius: 14px;
            background: linear-gradient(135deg, #ff8282 0%, #d94a4a 100%);
            color: #fff;
            box-shadow: var(--shadow);

        @media (max-width: 920px) {
            .content-head,
            .actions,
            .workspace {
                display: grid;
                grid-template-columns: 1fr;

            .upload-btn {
                width: 100%;

        @media (max-width: 640px) {
            body {
                padding: 12px;

            .topbar,
            .shell {
                border-radius: 22px;

            .content {
                padding: 18px;

            .brand img {
                width: 44px;
                height: 44px;

            .header-image,
            .footer-image {
                padding: 10px 12px;

            .header-image img {
                max-height: 110px;

            .footer-image img {
                max-height: 90px;

            .dropzone {
                padding: 28px 16px;
                min-height: auto;
    </style>
</head>
<body>
<div class="page">
    <div class="topbar">
        <div class="brand">
            {% if logo_image %}
                {% if logo_image.startswith('http') or logo_image.startswith('data:') %}
                    <img src="{{ logo_image }}" alt="IMTMA logo">
                {% else %}
                    <img src="{{ url_for('static', filename=logo_image) }}" alt="IMTMA logo">
                {% endif %}
            {% endif %}
            <span>IMTMA Upload</span>
        </div>

        <div style="display: flex; align-items: center; gap: 12px;">
            <span class="user-chip">Signed in as {{ session.get('username', 'photographer') }}</span>
            <button type="button" class="logout-btn" id="logoutBtn">Logout</button>
            <form method="post" action="/logout" id="logoutForm" style="display: none;"></form>
            <button type="button" class="counter-btn" onclick="toggleUploadDropdown()">
                <span id="uploadCounter">0 / 0</span>
            </button>
        </div>
    </div>

    <div id="uploadDropdown" class="upload-dropdown">
        <div class="dropdown-title">Upload progress</div>
        <div id="uploadList">
            <div class="empty">No uploads started yet.</div>
        </div>
    </div>

    <section class="shell">
        {% if header_image %}
            <div class="header-image">
                {% if header_image.startswith('http') or header_image.startswith('data:') %}
                    <img src="{{ header_image }}" alt="Event header banner">
                {% else %}
                    <img src="{{ url_for('static', filename=header_image) }}" alt="Event header banner">
                {% endif %}
            </div>
        {% endif %}

        <div class="content">
            <div class="content-head">
                <div>
                    <h1>Upload Event Images</h1>
                    <p>Select image files or full folders, watch live upload progress, and keep duplicate warnings non-blocking while the rest of the queue continues.</p>
                </div>
                <div class="badge">Files and folders supported</div>
            </div>

            <form id="uploadForm">
                <div class="actions">
                    <button type="button" class="action-btn" onclick="selectFiles()">Upload Files</button>
                    <button type="button" class="action-btn" onclick="selectFolder()">Upload Folder</button>
                    <button type="submit" class="upload-btn">Start Upload</button>
                </div>

                <div class="workspace">
                    <div class="main-pane">
                        <div class="dropzone" id="dropZone">
                            <div class="drop-icon">UP</div>
                            <h2>Drop files or folders here</h2>
                            <p>You can choose files, choose a complete folder, or drag a folder into this area. Uploads continue even if some files already exist.</p>
                            <div class="hint-row">
                                <div class="hint">Folder upload fixed</div>
                                <div class="hint">1 / 100 live counter</div>
                                <div class="hint">Duplicate popup without stop</div>
                            </div>
                        </div>

                        <div class="response-box">
                            <div class="response-title">Server response</div>
                            <p id="response">Waiting for upload...</p>
                        </div>
                    </div>

                    <div class="side-pane">
                        <div class="panel">
                            <div class="panel-head">
                                <h3>Selected Files</h3>
                                <span id="selectedCount">0 items</span>
                            </div>
                            <div id="selectedList" class="list">
                                <div class="empty">No files selected yet.</div>
                            </div>
                        </div>

                        <div class="panel">
                            <div class="panel-head">
                                <h3>Upload Queue</h3>
                                <span>Visible while uploading</span>
                            </div>
                            <div id="progressArea" class="list">
                                <div class="empty">Upload progress will appear here.</div>
                            </div>
                        </div>
                    </div>
                </div>

                <input type="file" id="fileInput" name="images" multiple accept="image/*" style="display:none;">
                <input type="file" id="folderInput" webkitdirectory directory multiple style="display:none;">
            </form>
        </div>

        {% if footer_image %}
            <div class="footer-image">
                {% if footer_image.startswith('http') or footer_image.startswith('data:') %}
                    <img src="{{ footer_image }}" alt="Event footer banner">
                {% else %}
                    <img src="{{ url_for('static', filename=footer_image) }}" alt="Event footer banner">
                {% endif %}
            </div>
        {% endif %}
    </section>

    <div class="footer">&copy; 2026 IMTMA Event AI | All Rights Reserved</div>
</div>

<div id="toastWrap" class="toast-wrap"></div>

<script>
    const uploadForm = document.getElementById("uploadForm");
    const fileInput = document.getElementById("fileInput");
    const folderInput = document.getElementById("folderInput");
    const dropZone = document.getElementById("dropZone");
    const selectedList = document.getElementById("selectedList");
    const selectedCount = document.getElementById("selectedCount");
    const progressArea = document.getElementById("progressArea");
    const responseBox = document.getElementById("response");
    const uploadCounter = document.getElementById("uploadCounter");
    const uploadDropdown = document.getElementById("uploadDropdown");
    const uploadList = document.getElementById("uploadList");
    const toastWrap = document.getElementById("toastWrap");

    let allFiles = [];
    let uploadedCount = 0;
    let progressReady = false;

    function escapeHtml(value) {
        return String(value).replace(/[&<>"]/g, function(match) {
            if (match === "&") return "&amp;";
            if (match === "<") return "&lt;";
            if (match === ">") return "&gt;";
            if (match === '"') return "&quot;";
            return match;

    function getRelativePath(file) {
        return file.webkitRelativePath || file.relativePath || file.name;

    function buildFileKey(file) {
        return `${getRelativePath(file)}:${file.size}:${file.lastModified}`;

    function updateCounter() {
        uploadCounter.innerText = `${uploadedCount} / ${allFiles.length}`;

    function toggleUploadDropdown() {
        uploadDropdown.classList.toggle("open");

    function resetInput(input) {
        input.value = "";

    function withRelativePath(file, relativePath) {
        if (!relativePath) {
            return file;

        try {
            Object.defineProperty(file, "relativePath", {
                value: relativePath,
                configurable: true
            file.relativePath = relativePath;

        return file;

    function addFiles(files) {
        for (let i = 0; i < files.length; i += 1) {
            const file = files[i];
            if (!file.type || !file.type.startsWith("image/")) {
                continue;

            const key = buildFileKey(file);
            const exists = allFiles.some((existing) => buildFileKey(existing) === key);
            if (!exists) {
                allFiles.push(file);

        renderSelectedFiles();
        updateCounter();

    function renderSelectedFiles() {
        selectedCount.innerText = `${allFiles.length} items`;

        if (!allFiles.length) {
            selectedList.innerHTML = '<div class="empty">No files selected yet.</div>';
            return;

        selectedList.innerHTML = "";

        allFiles.forEach((file) => {
            const item = document.createElement("div");
            item.className = "item";
            item.innerHTML = `
                <div class="item-name">${escapeHtml(getRelativePath(file))}</div>
                <div class="item-meta">
                    <span>${escapeHtml(file.type || "image file")}</span>
                    <span>${(file.size / 1024).toFixed(1)} KB</span>
                </div>
            `;
            selectedList.appendChild(item);

    function showToast(message) {
        const toast = document.createElement("div");
        toast.className = "toast";
        toast.innerText = message;
        toastWrap.appendChild(toast);
        setTimeout(() => toast.remove(), 3000);

    function ensureRows(file, index) {
        let row = document.getElementById(`progress-row-${index}`);
        if (row) {
            return;

        if (!progressReady) {
            progressArea.innerHTML = "";
            uploadList.innerHTML = "";
            progressReady = true;

        row = document.createElement("div");
        row.className = "item";
        row.id = `progress-row-${index}`;
        row.innerHTML = `
            <div class="item-name">${escapeHtml(getRelativePath(file))}</div>
            <div class="item-meta">
                <span id="status-${index}">Waiting...</span>
                <span id="percent-${index}">0%</span>
            </div>
            <div class="progress-bar">
                <div id="bar-${index}" class="progress-fill"></div>
            </div>
        `;
        progressArea.appendChild(row);

        const dropdownRow = document.createElement("div");
        dropdownRow.className = "item";
        dropdownRow.id = `dropdown-row-${index}`;
        dropdownRow.innerHTML = `
            <div class="item-name">${escapeHtml(getRelativePath(file))}</div>
            <div class="item-meta">
                <span id="drop-status-${index}">Waiting...</span>
                <span id="drop-percent-${index}">0%</span>
            </div>
            <div class="progress-bar">
                <div id="drop-bar-${index}" class="progress-fill"></div>
            </div>
        `;
        uploadList.appendChild(dropdownRow);

    function setStatus(index, label, percent) {
        [
            `status-${index}`,
            `drop-status-${index}`
        ].forEach((id) => {
            const node = document.getElementById(id);
            if (node) {
                node.innerText = label;

        [
            `percent-${index}`,
            `drop-percent-${index}`
        ].forEach((id) => {
            const node = document.getElementById(id);
            if (node) {
                node.innerText = `${percent}%`;

        [
            `bar-${index}`,
            `drop-bar-${index}`
        ].forEach((id) => {
            const node = document.getElementById(id);
            if (node) {
                node.style.width = `${percent}%`;

    function markFinished(index, label, percent) {
        uploadedCount += 1;
        updateCounter();
        setStatus(index, label, percent);

    async function checkFileExists(file) {
        const response = await fetch("/api/check-file?name=" + encodeURIComponent(file.name));
        const data = await response.json();

        if (!response.ok) {
            throw new Error(data.error || "Failed to check file");

        return Boolean(data.exists);

    function uploadSingleFile(file, index) {
        return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            const formData = new FormData();
            formData.append("images", file);

            xhr.open("POST", "/api/upload-event-images");

            xhr.upload.onprogress = function(event) {
                if (event.lengthComputable) {
                    const percent = Math.floor((event.loaded / event.total) * 100);
                    setStatus(index, `${percent}% uploading`, percent);

            xhr.onload = function() {
                let payload = {};

                try {
                    payload = JSON.parse(xhr.responseText || "{}");
                    payload = { error: "Invalid server response" };

                if (xhr.status >= 200 && xhr.status < 300) {
                    resolve(payload);
                    return;

                reject(new Error(payload.error || "Upload failed"));

            xhr.onerror = function() {
                reject(new Error("Network error"));

            xhr.send(formData);

    async function handleFile(file, index, results) {
        ensureRows(file, index);
        setStatus(index, "Checking duplicate", 0);

        try {
            const exists = await checkFileExists(file);
            if (exists) {
                showToast(file.name + " already exists");
                markFinished(index, "Skipped duplicate", 100);
                results.push({
                    file: getRelativePath(file),
                    status: "duplicate"
                return;

            const payload = await uploadSingleFile(file, index);
            markFinished(index, "Uploaded", 100);
            results.push({
                file: getRelativePath(file),
                status: "uploaded",
                response: payload
            markFinished(index, "Failed", 0);
            results.push({
                file: getRelativePath(file),
                status: "failed",
                error: error.message

    function selectFiles() {
        fileInput.click();

    async function readDirectoryHandle(handle, prefix) {
        const files = [];

        for await (const entry of handle.values()) {
            const nextPrefix = prefix ? `${prefix}/${entry.name}` : entry.name;

            if (entry.kind === "file") {
                const file = await entry.getFile();
                files.push(withRelativePath(file, nextPrefix));
                continue;

            if (entry.kind === "directory") {
                const nested = await readDirectoryHandle(entry, nextPrefix);
                files.push(...nested);

        return files;

    async function selectFolder() {
        if (window.showDirectoryPicker) {
            try {
                const dirHandle = await window.showDirectoryPicker();
                const files = await readDirectoryHandle(dirHandle, dirHandle.name);
                addFiles(files);
                return;
                if (error && error.name === "AbortError") {
                    return;

        folderInput.click();

    async function readDroppedEntry(entry, prefix) {
        if (entry.isFile) {
            const file = await new Promise((resolve, reject) => entry.file(resolve, reject));
            const nextPath = prefix ? `${prefix}/${file.name}` : file.name;
            return [withRelativePath(file, nextPath)];

        if (!entry.isDirectory) {
            return [];

        const reader = entry.createReader();
        const entries = await new Promise((resolve, reject) => {
            const chunks = [];

            function readBatch() {
                reader.readEntries((batch) => {
                    if (!batch.length) {
                        resolve(chunks);
                        return;

                    chunks.push(...batch);
                    readBatch();

            readBatch();

        const nextPrefix = prefix ? `${prefix}/${entry.name}` : entry.name;
        const nested = await Promise.all(entries.map((child) => readDroppedEntry(child, nextPrefix)));
        return nested.flat();

    async function collectDroppedFiles(items, fallbackFiles) {
        if (!items || !items.length) {
            return Array.from(fallbackFiles || []);

        const collected = [];
        for (const item of items) {
            if (!item.webkitGetAsEntry) {
                continue;

            const entry = item.webkitGetAsEntry();
            if (!entry) {
                continue;

            const files = await readDroppedEntry(entry, "");
            collected.push(...files);

        return collected.length ? collected : Array.from(fallbackFiles || []);

    fileInput.addEventListener("change", function(event) {
        addFiles(event.target.files);
        resetInput(fileInput);

    folderInput.addEventListener("change", function(event) {
        addFiles(event.target.files);
        resetInput(folderInput);

    dropZone.addEventListener("click", function() {
        selectFiles();

    dropZone.addEventListener("dragenter", function(event) {
        event.preventDefault();
        dropZone.classList.add("dragging");

    dropZone.addEventListener("dragover", function(event) {
        event.preventDefault();
        dropZone.classList.add("dragging");

    dropZone.addEventListener("dragleave", function(event) {
        event.preventDefault();
        if (!dropZone.contains(event.relatedTarget)) {
            dropZone.classList.remove("dragging");

    dropZone.addEventListener("drop", async function(event) {
        event.preventDefault();
        dropZone.classList.remove("dragging");
        const files = await collectDroppedFiles(event.dataTransfer.items, event.dataTransfer.files);
        addFiles(files);

    uploadForm.addEventListener("submit", async function(event) {
        event.preventDefault();

        if (!allFiles.length) {
            responseBox.innerText = JSON.stringify({ error: "Please select at least one file." }, null, 2);
            return;

        uploadedCount = 0;
        progressReady = false;
        progressArea.innerHTML = '<div class="empty">Preparing uploads...</div>';
        uploadList.innerHTML = '<div class="empty">Preparing uploads...</div>';
        updateCounter();
        responseBox.innerText = "Uploading...";

        const results = [];
        const tasks = allFiles.map((file, index) => handleFile(file, index, results));
        await Promise.all(tasks);
        responseBox.innerText = JSON.stringify({ results: results }, null, 2);

    renderSelectedFiles();
    updateCounter();
</script>
</body>
</html>
  
