diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..112c386
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+upload/
+upload_chunks/
diff --git a/config.php b/config.php
index e71c7bc..2af42b6 100644
--- a/config.php
+++ b/config.php
@@ -1,4 +1,72 @@
1024 ** 4,
+ 'GB' => 1024 ** 3,
+ 'MB' => 1024 ** 2,
+ 'KB' => 1024,
+ 'B' => 1
+ ];
+
+ // Проверяем каждую единицу измерения (начиная с самых больших)
+ foreach ($units as $unit => $multiplier) {
+ if (substr($size, -strlen($unit)) === $unit) {
+ $numberPart = trim(substr($size, 0, -strlen($unit)));
+ error_log("Found unit: $unit, number part: '$numberPart'");
+
+ if (is_numeric($numberPart)) {
+ $result = intval(floatval($numberPart) * $multiplier);
+ error_log("Calculated result: $result bytes");
+ return $result;
+ }
+ }
+ }
+
+ // Если не найдено совпадений с единицами измерения, пробуем преобразовать как число
+ if (is_numeric($size)) {
+ $result = intval($size);
+ error_log("Fallback to numeric: $result");
+ return $result;
+ }
+
+ error_log("Could not parse size: $size, returning 0");
+ return 0;
+}
+
+// Получить размер файла в байтах
+function getMaxFileSize()
+{
+ $size = parseFileSize(MAX_FILE_SIZE);
+
+ // Отладочная информация
+ error_log("MAX_FILE_SIZE constant: " . MAX_FILE_SIZE);
+ error_log("Parsed max file size: " . $size);
+
+ return $size;
+}
+
+// Получить размер чанка в байтах
+function getChunkSize()
+{
+ $size = parseFileSize(CHUNK_SIZE);
+
+ // Отладочная информация
+ error_log("CHUNK_SIZE constant: " . CHUNK_SIZE);
+ error_log("Parsed chunk size: " . $size);
+
+ return $size;
+}
+
+// Функция для генерации CSRF токена
+function generateCSRFToken()
+{
+ if (!isset($_SESSION['csrf_token'])) {
+ $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
+ }
+ return $_SESSION['csrf_token'];
+}
+
+// Функция для проверки CSRF токена
+function verifyCSRFToken($token)
+{
+ return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
+}
+
+// Функция для валидации имени файла
+function validateFileName($filename)
+{
+ // Удаляем опасные символы и пути
+ $filename = basename($filename);
+
+ // Проверяем на недопустимые символы
+ if (preg_match('/[<>:"|?*\x00-\x1f]/', $filename)) {
+ return false;
+ }
+
+ // Проверяем длину
+ if (strlen($filename) > 255) {
+ return false;
+ }
+
+ // Проверяем расширение
+ $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
+ if (!in_array($extension, ALLOWED_EXTENSIONS)) {
+ return false;
+ }
+
+ return true;
+}
+
+// Функция для получения безопасного имени файла
+function getSafeFileName($filename)
+{
+ $filename = basename($filename);
+ $filename = preg_replace('/[<>:"|?*\x00-\x1f]/', '_', $filename);
+ return substr($filename, 0, 255);
+}
+
+// Функция для форматирования размера файла
+function formatFileSize($size)
+{
+ $units = ['B', 'KB', 'MB', 'GB'];
+ $unitIndex = 0;
+ while ($size >= 1024 && $unitIndex < count($units) - 1) {
+ $size /= 1024;
+ $unitIndex++;
+ }
+ return round($size, 2) . ' ' . $units[$unitIndex];
+}
+
+// Функция для отладки - показать все константы
+function debugConstants()
+{
+ error_log("=== DEBUG CONSTANTS ===");
+ error_log("MAX_FILE_SIZE: " . (defined('MAX_FILE_SIZE') ? MAX_FILE_SIZE : 'NOT DEFINED'));
+ error_log("CHUNK_SIZE: " . (defined('CHUNK_SIZE') ? CHUNK_SIZE : 'NOT DEFINED'));
+ error_log("UPLOAD_DIR: " . (defined('UPLOAD_DIR') ? UPLOAD_DIR : 'NOT DEFINED'));
+ error_log("CHUNK_DIR: " . (defined('CHUNK_DIR') ? CHUNK_DIR : 'NOT DEFINED'));
+ error_log("======================");
+}
diff --git a/index.php b/index.php
index 719f9c9..4305143 100644
--- a/index.php
+++ b/index.php
@@ -2,86 +2,571 @@
session_start();
require_once __DIR__ . '/config.php';
+require_once __DIR__ . '/functions.php';
-if (!file_exists(UPLOAD_DIR)) mkdir(UPLOAD_DIR);
-if (!file_exists(CHUNK_DIR)) mkdir(CHUNK_DIR);
+if (!file_exists(UPLOAD_DIR)) mkdir(UPLOAD_DIR, 0755, true);
+if (!file_exists(CHUNK_DIR)) mkdir(CHUNK_DIR, 0755, true);
-if (!isset($_SESSION['logged_in']) || ($_GET['action'] ?? '') === 'logout') {
- if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['password'] ?? '') === PASSWORD) {
- $_SESSION['logged_in'] = true;
- header("Location: index.php");
+// Обработка запросов файлов напрямую из корня
+$requestUri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
+$requestedFile = ltrim($requestUri, '/');
+
+// Если запрашивается файл (не административный интерфейс)
+if (
+ !empty($requestedFile) &&
+ $requestedFile !== 'index.php' &&
+ $requestedFile !== 'upload_chunk.php' &&
+ $requestedFile !== 'merge_chunks.php' &&
+ !isset($_GET['action']) &&
+ !isset($_GET['delete']) &&
+ !isset($_POST['password'])
+) {
+
+ $filename = basename($requestedFile);
+ $filepath = UPLOAD_DIR . $filename;
+
+ if (file_exists($filepath)) {
+ // Определение MIME типа
+ $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
+ $mimeTypes = [
+ // Изображения
+ 'jpg' => 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'png' => 'image/png',
+ 'gif' => 'image/gif',
+ 'webp' => 'image/webp',
+ 'svg' => 'image/svg+xml',
+ 'bmp' => 'image/bmp',
+ 'tiff' => 'image/tiff',
+ 'tif' => 'image/tiff',
+ 'ico' => 'image/x-icon',
+ 'heic' => 'image/heic',
+ 'heif' => 'image/heif',
+ 'avif' => 'image/avif',
+
+ // Документы
+ 'pdf' => 'application/pdf',
+ 'txt' => 'text/plain',
+ 'rtf' => 'application/rtf',
+ 'doc' => 'application/msword',
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'odt' => 'application/vnd.oasis.opendocument.text',
+ 'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
+ 'odp' => 'application/vnd.oasis.opendocument.presentation',
+ 'ppt' => 'application/vnd.ms-powerpoint',
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'epub' => 'application/epub+zip',
+ 'mobi' => 'application/x-mobipocket-ebook',
+
+ // Архивы
+ 'zip' => 'application/zip',
+ 'rar' => 'application/vnd.rar',
+ '7z' => 'application/x-7z-compressed',
+ 'tar' => 'application/x-tar',
+ 'gz' => 'application/gzip',
+ 'bz2' => 'application/x-bzip2',
+ 'xz' => 'application/x-xz',
+ 'iso' => 'application/x-iso9660-image',
+
+ // Видео
+ 'mp4' => 'video/mp4',
+ 'avi' => 'video/x-msvideo',
+ 'mkv' => 'video/x-matroska',
+ 'mov' => 'video/quicktime',
+ 'webm' => 'video/webm',
+ 'flv' => 'video/x-flv',
+ 'wmv' => 'video/x-ms-wmv',
+ 'mpg' => 'video/mpeg',
+ 'mpeg' => 'video/mpeg',
+ 'm4v' => 'video/x-m4v',
+ '3gp' => 'video/3gpp',
+ 'ogv' => 'video/ogg',
+
+ // Аудио
+ 'mp3' => 'audio/mpeg',
+ 'wav' => 'audio/wav',
+ 'flac' => 'audio/flac',
+ 'ogg' => 'audio/ogg',
+ 'aac' => 'audio/aac',
+ 'm4a' => 'audio/mp4',
+ 'wma' => 'audio/x-ms-wma',
+ 'opus' => 'audio/opus',
+ 'aiff' => 'audio/aiff',
+
+ // Данные
+ 'json' => 'application/json',
+ 'xml' => 'application/xml',
+ 'csv' => 'text/csv',
+ 'xls' => 'application/vnd.ms-excel',
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'yaml' => 'text/yaml',
+ 'yml' => 'text/yaml',
+ 'toml' => 'text/toml',
+
+ // Код
+ 'html' => 'text/html',
+ 'htm' => 'text/html',
+ 'css' => 'text/css',
+ 'js' => 'text/javascript',
+ 'php' => 'text/x-php',
+ 'py' => 'text/x-python',
+ 'java' => 'text/x-java-source',
+ 'cpp' => 'text/x-c++src',
+ 'c' => 'text/x-csrc',
+ 'h' => 'text/x-chdr',
+ 'cs' => 'text/x-csharp',
+ 'rb' => 'text/x-ruby',
+ 'go' => 'text/x-go',
+ 'rs' => 'text/x-rust',
+ 'sql' => 'text/x-sql',
+ 'md' => 'text/markdown',
+ 'log' => 'text/plain',
+ 'sh' => 'text/x-shellscript',
+ 'bat' => 'text/x-msdos-batch',
+
+ // Веб и CMS
+ 'wpress' => 'application/octet-stream',
+ 'backup' => 'application/octet-stream',
+ 'bak' => 'application/octet-stream',
+ 'db' => 'application/x-sqlite3',
+ 'sqlite' => 'application/x-sqlite3',
+ 'sqlite3' => 'application/x-sqlite3',
+
+ // Шрифты
+ 'ttf' => 'font/ttf',
+ 'otf' => 'font/otf',
+ 'woff' => 'font/woff',
+ 'woff2' => 'font/woff2',
+ 'eot' => 'application/vnd.ms-fontobject',
+
+ // Дизайн
+ 'psd' => 'image/vnd.adobe.photoshop',
+ 'ai' => 'application/postscript',
+ 'eps' => 'application/postscript',
+ 'indd' => 'application/x-indesign',
+
+ // CAD
+ 'dwg' => 'image/vnd.dwg',
+ 'dxf' => 'image/vnd.dxf',
+ 'obj' => 'model/obj',
+ 'stl' => 'model/stl',
+
+ // Мобильные приложения и исполняемые файлы
+ 'apk' => 'application/vnd.android.package-archive',
+ 'ipa' => 'application/octet-stream',
+ 'exe' => 'application/x-msdownload',
+ 'msi' => 'application/x-msi',
+ 'deb' => 'application/vnd.debian.binary-package',
+ 'rpm' => 'application/x-rpm',
+ 'dmg' => 'application/x-apple-diskimage',
+ 'pkg' => 'application/x-newton-compatible-pkg',
+
+ // Прочие
+ 'torrent' => 'application/x-bittorrent',
+ 'ics' => 'text/calendar',
+ 'vcf' => 'text/vcard',
+ 'gpx' => 'application/gpx+xml',
+ 'kml' => 'application/vnd.google-earth.kml+xml',
+ 'kmz' => 'application/vnd.google-earth.kmz'
+ ];
+
+ $mimeType = $mimeTypes[$extension] ?? 'application/octet-stream';
+
+ header('Content-Type: ' . $mimeType);
+ header('Content-Length: ' . filesize($filepath));
+ header('Content-Disposition: inline; filename="' . $filename . '"');
+ header('Cache-Control: public, max-age=31536000'); // Кеш на год
+
+ readfile($filepath);
+ exit;
+ } else {
+ http_response_code(404);
+ echo '404 - Файл не найден';
exit;
}
-
- echo '
';
- exit;
}
-// Удаление
-if (isset($_GET['delete'])) {
- $f = basename($_GET['delete']);
- @unlink(UPLOAD_DIR . $f);
- header("Location: index.php");
- exit;
-}
+// Проверка авторизации
+if (!isset($_SESSION['logged_in']) || ($_GET['action'] ?? '') === 'logout') {
+ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $password = $_POST['password'] ?? '';
+ $csrf_token = $_POST['csrf_token'] ?? '';
-$files = array_diff(scandir(UPLOAD_DIR), ['.', '..']);
-?>
-
-Файловое хранилище
-
-Файловое хранилище
-Выйти
-
-
-
-
- -
- = htmlspecialchars($file) ?>
- [Скачать]
- [Удалить]
-
-
-
-
-
-
+ echo '
+
+
+
+
+ Вход - Файловое хранилище
+
+
+
+
+
+ ';
+ exit;
+}
+
+// Удаление файла
+if (isset($_GET['delete']) && isset($_GET['csrf_token'])) {
+ if (verifyCSRFToken($_GET['csrf_token'])) {
+ $filename = basename($_GET['delete']);
+ $filepath = UPLOAD_DIR . $filename;
+
+ if (file_exists($filepath)) {
+ @unlink($filepath);
+ $message = 'Файл удален успешно';
+ } else {
+ $error = 'Файл не найден';
+ }
+ } else {
+ $error = 'Ошибка безопасности';
+ }
+
+ header("Location: index.php" . (isset($message) ? "?msg=" . urlencode($message) : (isset($error) ? "?err=" . urlencode($error) : "")));
+ exit;
+}
+
+// Показ сообщений
+$message = $_GET['msg'] ?? null;
+$error = $_GET['err'] ?? null;
+
+$files = array_diff(scandir(UPLOAD_DIR), ['.', '..']);
+$csrf_token = generateCSRFToken();
+
+// Функция для форматирования размера файла уже в functions.php
+?>
+
+
+
+
+
+
+ Файловое хранилище
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Нажмите для выбора файла или перетащите файл сюда
+
+
+
+
+
+
+
+
+
+
Файлы не загружены
+
+
+
+
+ -
+
+
= htmlspecialchars($file) ?>
+
= formatFileSize($filesize) ?>
+
+
+
+
+
+
+
+
+
+
+
diff --git a/merge_chunks.php b/merge_chunks.php
index d57f5e4..2803ec4 100644
--- a/merge_chunks.php
+++ b/merge_chunks.php
@@ -1,21 +1,188 @@
getMaxFileSize()) {
+ error_log("ERROR: File too large");
+ // Очистка чанков
+ for ($i = 0; $i < $total; $i++) {
+ @unlink("$tmpDir/$i.part");
+ }
+ @rmdir($tmpDir);
+ http_response_code(413); // Payload Too Large
+ echo "File too large";
+ exit;
+ }
+
+ // Проверка прав на запись в директорию загрузок
+ if (!is_writable(UPLOAD_DIR)) {
+ error_log("ERROR: Upload directory is not writable: " . UPLOAD_DIR);
+ http_response_code(500);
+ echo "Upload directory is not writable";
+ exit;
+ }
+
+ // Объединение чанков
+ error_log("Starting file merge");
+ $out = fopen($finalPath, 'wb');
+ if (!$out) {
+ error_log("ERROR: Cannot create final file: $finalPath");
+ http_response_code(500);
+ echo "Cannot create final file";
+ exit;
+ }
+
+ for ($i = 0; $i < $total; $i++) {
+ $partPath = "$tmpDir/$i.part";
+ error_log("Merging chunk $i from $partPath");
+
+ $in = fopen($partPath, 'rb');
+ if (!$in) {
+ error_log("ERROR: Cannot open chunk $i");
+ fclose($out);
+ @unlink($finalPath);
+ http_response_code(500);
+ echo "Cannot open chunk $i";
+ exit;
+ }
+
+ $copied = stream_copy_to_stream($in, $out);
+ fclose($in);
+
+ error_log("Copied $copied bytes from chunk $i");
+
+ // Удаление чанка после успешного копирования
+ if (!unlink($partPath)) {
+ error_log("WARNING: Cannot delete chunk $i");
+ }
+ }
+
+ fclose($out);
+
+ // Удаление директории чанков
+ if (!rmdir($tmpDir)) {
+ error_log("WARNING: Cannot delete chunk directory: $tmpDir");
+ }
+
+ $finalSize = filesize($finalPath);
+ error_log("Final file size: $finalSize bytes");
+ error_log("SUCCESS: File merged successfully");
+
+ http_response_code(200);
+ echo "OK";
+
+ error_log("=== MERGE CHUNKS END ===");
+} catch (Exception $e) {
+ error_log("EXCEPTION in merge_chunks.php: " . $e->getMessage());
+ error_log("Stack trace: " . $e->getTraceAsString());
+ http_response_code(500);
+ echo "Server error: " . $e->getMessage();
+} catch (Error $e) {
+ error_log("FATAL ERROR in merge_chunks.php: " . $e->getMessage());
+ error_log("Stack trace: " . $e->getTraceAsString());
+ http_response_code(500);
+ echo "Fatal error: " . $e->getMessage();
}
-fclose($out);
-rmdir($tmpDir);
-http_response_code(200);
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..98bbd56
--- /dev/null
+++ b/style.css
@@ -0,0 +1,432 @@
+/* Сброс и базовые стили */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+:root {
+ /* Цветовая палитра */
+ --primary: #2563eb;
+ --primary-hover: #1d4ed8;
+ --success: #059669;
+ --success-light: #d1fae5;
+ --error: #dc2626;
+ --error-light: #fee2e2;
+ --warning: #d97706;
+ --warning-light: #fef3c7;
+
+ /* Нейтральные цвета */
+ --gray-50: #f9fafb;
+ --gray-100: #f3f4f6;
+ --gray-200: #e5e7eb;
+ --gray-300: #d1d5db;
+ --gray-400: #9ca3af;
+ --gray-500: #6b7280;
+ --gray-600: #4b5563;
+ --gray-700: #374151;
+ --gray-800: #1f2937;
+ --gray-900: #111827;
+
+ /* Размеры и отступы */
+ --border-radius: 8px;
+ --border-radius-lg: 12px;
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+ --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
+
+ /* Шрифты */
+ --font-system: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+ --font-mono: 'SF Mono', Monaco, Inconsolata, 'Roboto Mono', Consolas, 'Courier New', monospace;
+}
+
+body {
+ font-family: var(--font-system);
+ font-size: 15px;
+ line-height: 1.6;
+ color: var(--gray-700);
+ background: var(--gray-50);
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 2rem 1rem;
+}
+
+/* Контейнер */
+.container {
+ width: 100%;
+ max-width: 720px;
+ background: white;
+ border-radius: var(--border-radius-lg);
+ box-shadow: var(--shadow);
+ overflow: hidden;
+}
+
+/* Заголовок */
+.header {
+ background: linear-gradient(135deg, var(--primary) 0%, var(--primary-hover) 100%);
+ color: white;
+ padding: 2rem;
+ text-align: center;
+}
+
+.header h1 {
+ font-size: 1.5rem;
+ font-weight: 600;
+ margin-bottom: 0.5rem;
+}
+
+.header .logout {
+ color: rgba(255, 255, 255, 0.8);
+ text-decoration: none;
+ font-size: 0.9rem;
+ transition: color 0.2s ease;
+}
+
+.header .logout:hover {
+ color: white;
+}
+
+/* Основной контент */
+.content {
+ padding: 2rem;
+}
+
+/* Форма входа */
+.login-form {
+ max-width: 400px;
+ margin: 4rem auto;
+ background: white;
+ padding: 2.5rem;
+ border-radius: var(--border-radius-lg);
+ box-shadow: var(--shadow-lg);
+}
+
+.login-form h3 {
+ font-size: 1.5rem;
+ font-weight: 600;
+ text-align: center;
+ margin-bottom: 2rem;
+ color: var(--gray-800);
+}
+
+/* Уведомления */
+.notification {
+ padding: 1rem 1.25rem;
+ margin-bottom: 1.5rem;
+ border-radius: var(--border-radius);
+ border: 1px solid;
+ font-size: 0.9rem;
+ font-weight: 500;
+ display: none;
+ animation: slideIn 0.3s ease-out;
+}
+
+.notification.success {
+ background-color: var(--success-light);
+ border-color: var(--success);
+ color: #065f46;
+}
+
+.notification.error {
+ background-color: var(--error-light);
+ border-color: var(--error);
+ color: #991b1b;
+}
+
+.notification.info {
+ background-color: #dbeafe;
+ border-color: var(--primary);
+ color: #1e40af;
+}
+
+@keyframes slideIn {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* Поля ввода */
+input[type="password"],
+input[type="file"] {
+ width: 100%;
+ padding: 0.75rem 1rem;
+ border: 2px solid var(--gray-200);
+ border-radius: var(--border-radius);
+ font-size: 0.95rem;
+ transition: all 0.2s ease;
+ background: white;
+}
+
+input[type="password"]:focus,
+input[type="file"]:focus {
+ outline: none;
+ border-color: var(--primary);
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
+}
+
+input[type="password"] {
+ margin-bottom: 1.5rem;
+}
+
+/* Кнопки */
+button,
+.btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0.75rem 1.5rem;
+ font-size: 0.9rem;
+ font-weight: 500;
+ border-radius: var(--border-radius);
+ border: none;
+ cursor: pointer;
+ text-decoration: none;
+ transition: all 0.2s ease;
+ gap: 0.5rem;
+}
+
+.btn-primary {
+ background: var(--primary);
+ color: white;
+}
+
+.btn-primary:hover {
+ background: var(--primary-hover);
+ transform: translateY(-1px);
+ box-shadow: var(--shadow);
+}
+
+.btn-secondary {
+ background: var(--gray-100);
+ color: var(--gray-700);
+ border: 1px solid var(--gray-200);
+}
+
+.btn-secondary:hover {
+ background: var(--gray-200);
+ border-color: var(--gray-300);
+}
+
+.btn-small {
+ padding: 0.5rem 0.75rem;
+ font-size: 0.8rem;
+}
+
+.btn-danger {
+ background: var(--error);
+ color: white;
+}
+
+.btn-danger:hover {
+ background: #b91c1c;
+}
+
+/* Область загрузки */
+.upload-area {
+ margin-bottom: 2rem;
+}
+
+.upload-zone {
+ border: 2px dashed var(--gray-300);
+ border-radius: var(--border-radius);
+ padding: 2rem;
+ text-align: center;
+ transition: all 0.3s ease;
+ cursor: pointer;
+ position: relative;
+ overflow: hidden;
+}
+
+.upload-zone:hover {
+ border-color: var(--primary);
+ background: rgba(37, 99, 235, 0.02);
+}
+
+.upload-zone.dragover {
+ border-color: var(--primary);
+ background: rgba(37, 99, 235, 0.05);
+ transform: scale(1.02);
+}
+
+.file-input-hidden {
+ position: absolute;
+ opacity: 0;
+ width: 100%;
+ height: 100%;
+ cursor: pointer;
+}
+
+.upload-text {
+ color: var(--gray-500);
+ font-size: 0.9rem;
+}
+
+.upload-text strong {
+ color: var(--primary);
+}
+
+/* Прогресс загрузки */
+.progress-container {
+ margin-top: 1rem;
+ display: none;
+}
+
+.progress-bar {
+ width: 100%;
+ height: 8px;
+ background: var(--gray-200);
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+.progress-fill {
+ height: 100%;
+ background: linear-gradient(90deg, var(--primary), var(--primary-hover));
+ border-radius: 4px;
+ transition: width 0.3s ease;
+ width: 0%;
+}
+
+.progress-text {
+ font-size: 0.8rem;
+ color: var(--gray-600);
+ text-align: center;
+ margin-top: 0.5rem;
+}
+
+/* Список файлов */
+.files-list {
+ list-style: none;
+}
+
+.file-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 1rem;
+ border: 1px solid var(--gray-200);
+ border-radius: var(--border-radius);
+ margin-bottom: 0.75rem;
+ background: white;
+ transition: all 0.2s ease;
+}
+
+.file-item:hover {
+ border-color: var(--gray-300);
+ box-shadow: var(--shadow-sm);
+}
+
+.file-info {
+ flex: 1;
+ min-width: 0;
+}
+
+.file-name {
+ font-weight: 500;
+ color: var(--gray-800);
+ margin-bottom: 0.25rem;
+ word-break: break-all;
+}
+
+.file-size {
+ font-size: 0.8rem;
+ color: var(--gray-500);
+ font-family: var(--font-mono);
+}
+
+.file-actions {
+ display: flex;
+ gap: 0.5rem;
+ flex-shrink: 0;
+ margin-left: 1rem;
+}
+
+.file-actions .btn {
+ padding: 0.4rem 0.8rem;
+ font-size: 0.8rem;
+}
+
+/* Пустое состояние */
+.empty-state {
+ text-align: center;
+ padding: 3rem 2rem;
+ color: var(--gray-500);
+}
+
+.empty-state svg {
+ width: 48px;
+ height: 48px;
+ margin-bottom: 1rem;
+ opacity: 0.5;
+}
+
+/* Адаптивность */
+@media (max-width: 640px) {
+ body {
+ padding: 1rem 0.5rem;
+ }
+
+ .container {
+ border-radius: 0;
+ box-shadow: none;
+ min-height: 100vh;
+ }
+
+ .header,
+ .content {
+ padding: 1.5rem;
+ }
+
+ .file-item {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 1rem;
+ }
+
+ .file-actions {
+ margin-left: 0;
+ align-self: stretch;
+ }
+
+ .file-actions .btn {
+ flex: 1;
+ }
+
+ .upload-zone {
+ padding: 1.5rem 1rem;
+ }
+}
+
+/* Темные акценты для интерактивных элементов */
+input[type="file"]::-webkit-file-upload-button {
+ background: var(--primary);
+ color: white;
+ border: none;
+ padding: 0.5rem 1rem;
+ border-radius: calc(var(--border-radius) - 2px);
+ font-size: 0.8rem;
+ font-weight: 500;
+ cursor: pointer;
+ margin-right: 1rem;
+}
+
+input[type="file"]::-webkit-file-upload-button:hover {
+ background: var(--primary-hover);
+}
+
+/* Анимации */
+.fade-in {
+ animation: fadeIn 0.5s ease-out;
+}
+
+@keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
diff --git a/upload_chunk.php b/upload_chunk.php
index b59154d..e3c1216 100644
--- a/upload_chunk.php
+++ b/upload_chunk.php
@@ -1,8 +1,118 @@
getMessage());
+ error_log("Stack trace: " . $e->getTraceAsString());
+ http_response_code(500);
+ echo "Server error: " . $e->getMessage();
+} catch (Error $e) {
+ error_log("FATAL ERROR in upload_chunk.php: " . $e->getMessage());
+ error_log("Stack trace: " . $e->getTraceAsString());
+ http_response_code(500);
+ echo "Fatal error: " . $e->getMessage();
+}