From c5ad7e8cb4deea1b839afcd307e01ae49139671a Mon Sep 17 00:00:00 2001 From: Lain Iwakura Date: Thu, 24 Jul 2025 05:58:36 +0300 Subject: [PATCH] ok? --- .gitignore | 24 +++ AnonymousID.php | 50 ++++++ MarkdownParser.php | 17 ++ README | 42 ++++- RateLimiter.php | 4 +- apache.conf | 3 + board.php | 259 +++++++++++++++++++-------- config.php | 7 +- config.php.example | 16 ++ configs/apache.conf | 17 ++ configs/apache.conf.centos | 17 ++ configs/apache.conf.debian | 17 ++ configs/php.ini | 7 + index.php | 70 +++++++- newthread.php | 186 ++++++++++++++++++++ sql/create.sql | 45 ++++- styles.css | 348 ++++++++++++++++++++++++++++++++++++- 17 files changed, 1041 insertions(+), 88 deletions(-) create mode 100644 AnonymousID.php create mode 100644 MarkdownParser.php create mode 100644 config.php.example create mode 100644 configs/apache.conf create mode 100644 configs/apache.conf.centos create mode 100644 configs/apache.conf.debian create mode 100644 configs/php.ini create mode 100644 newthread.php diff --git a/.gitignore b/.gitignore index 4f4773f..30839ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,25 @@ config.php +.env +uploads/* +!uploads/.gitkeep +*.log +logs/ +*.tmp +*.temp +*.swp +*.swo +*~ +.vscode/ +.idea/ +*.sublime-* +.DS_Store +Thumbs.db +desktop.ini +docker-compose.override.yml +*.sql +*.db +cache/ +tmp/ +sessions/ +*.bak +*.backup diff --git a/AnonymousID.php b/AnonymousID.php new file mode 100644 index 0000000..0cef1b2 --- /dev/null +++ b/AnonymousID.php @@ -0,0 +1,50 @@ +db = $db; + $this->ip = $ip; + $this->boardId = $boardId; + } + + public function getOrCreateID() { + $stmt = $this->db->prepare(' + SELECT anonymous_id FROM posts + WHERE ip_address = ? AND board_id = ? + ORDER BY created_at DESC + LIMIT 1 + '); + $stmt->execute([$this->ip, $this->boardId]); + $existingId = $stmt->fetchColumn(); + + if ($existingId) { + return $existingId; + } + + return $this->generateNewID(); + } + + private function generateNewID() { + return 'ID:' . sprintf('%06d', mt_rand(1, 999999)); + } + + public function getIDForThread() { + $stmt = $this->db->prepare(' + SELECT anonymous_id FROM threads + WHERE ip_address = ? AND board_id = ? + ORDER BY created_at DESC + LIMIT 1 + '); + $stmt->execute([$this->ip, $this->boardId]); + $existingId = $stmt->fetchColumn(); + + if ($existingId) { + return $existingId; + } + + return $this->generateNewID(); + } +} \ No newline at end of file diff --git a/MarkdownParser.php b/MarkdownParser.php new file mode 100644 index 0000000..213d209 --- /dev/null +++ b/MarkdownParser.php @@ -0,0 +1,17 @@ +>(\d{6})/', '>>$1', $text); + $text = preg_replace('/\*\*(.*?)\*\*/', '$1', $text); + $text = preg_replace('/\*(.*?)\*/', '$1', $text); + $text = preg_replace('/_(.*?)_/', '$1', $text); + $text = preg_replace('/~~(.*?)~~/', '$1', $text); + $text = preg_replace('/`(.*?)`/', '$1', $text); + $text = preg_replace('/\[([^\]]+)\]\(([^)]+)\)/', '$1', $text); + $text = preg_replace('/^\* (.+)$/m', '
  • $1
  • ', $text); + $text = preg_replace('/(
  • .*<\/li>)/s', '', $text); + $text = nl2br($text); + return $text; + } +} \ No newline at end of file diff --git a/README b/README index f9cd392..01e661a 100644 --- a/README +++ b/README @@ -1,10 +1,44 @@ mkach - анонимный имиджборд Установка: -1. Создайте базу данных MySQL -2. Импортируйте sql/create.sql -3. Настройте config.php -4. Создайте папку uploads/ с правами 755 + +1. Клонируйте репозиторий: + git clone + cd mkach + +2. Создайте базу данных MySQL: + CREATE DATABASE mkach; + CREATE USER 'mkach'@'localhost' IDENTIFIED BY 'your_password'; + GRANT ALL PRIVILEGES ON mkach.* TO 'mkach'@'localhost'; + FLUSH PRIVILEGES; + +3. Импортируйте схему: + mysql -u mkach -p mkach < sql/create.sql + +4. Настройте config.php: + cp config.php.example config.php + # Отредактируйте параметры БД + +5. Создайте папку uploads: + mkdir uploads + chmod 755 uploads + +6. Настройте Apache: + # Ubuntu/Debian: + sudo cp configs/apache.conf.debian /etc/apache2/sites-available/mkach + sudo a2ensite mkach + sudo systemctl reload apache2 + + # CentOS/RHEL: + sudo cp configs/apache.conf.centos /etc/httpd/conf.d/mkach.conf + sudo systemctl reload httpd + +7. Настройте PHP: + sudo cp configs/php.ini /etc/php/8.1/apache2/conf.d/mkach.ini + sudo systemctl reload apache2 + +Docker установка: + docker-compose up -d Особенности: - Анонимные посты без регистрации diff --git a/RateLimiter.php b/RateLimiter.php index 8f5ddd8..cf4db93 100644 --- a/RateLimiter.php +++ b/RateLimiter.php @@ -1,8 +1,8 @@ db = $db; diff --git a/apache.conf b/apache.conf index f57e544..912ab09 100644 --- a/apache.conf +++ b/apache.conf @@ -1,6 +1,9 @@ +Listen 65511 + DocumentRoot /var/www/html ServerName localhost + AddDefaultCharset UTF-8 AllowOverride All diff --git a/board.php b/board.php index fa15f90..2a4a51d 100644 --- a/board.php +++ b/board.php @@ -1,5 +1,6 @@ cleanup(); $ip = $_SERVER['REMOTE_ADDR']; +$anonymousID = new AnonymousID($db, $ip, $boardId); if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!$rateLimiter->isAllowed($ip)) { @@ -34,8 +40,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } else { $message = trim($_POST['message'] ?? ''); $file = $_FILES['file'] ?? null; + $title = trim($_POST['title'] ?? ''); + $description = trim($_POST['description'] ?? ''); - if ($message || ($file && $file['error'] === UPLOAD_ERR_OK)) { + if ($message || $title || ($file && $file['error'] === UPLOAD_ERR_OK)) { $postId = sprintf('%06d', mt_rand(1, 999999)); $fileName = null; @@ -68,13 +76,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } if (empty($error)) { - $stmt = $db->prepare(' - INSERT INTO posts (post_id, message, file_name, file_size, file_type, ip_address) - VALUES (?, ?, ?, ?, ?, ?) - '); - $stmt->execute([$postId, $message, $fileName, $fileSize, $fileType, $ip]); + if ($threadId) { + $anonymousId = $anonymousID->getOrCreateID(); + $stmt = $db->prepare(' + INSERT INTO posts (post_id, thread_id, board_id, message, file_name, file_size, file_type, ip_address, anonymous_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + '); + $stmt->execute([$postId, $threadId, $boardId, $message, $fileName, $fileSize, $fileType, $ip, $anonymousId]); + } else { + header('Location: newthread.php?board=' . $boardId); + exit; + } - header('Location: board.php'); + header('Location: ' . $_SERVER['REQUEST_URI']); exit; } } else { @@ -83,32 +97,77 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } } -if (isset($_GET['logout'])) { - session_destroy(); - header('Location: index.php'); - exit; -} - try { - $stmt = $db->query('SELECT * FROM posts ORDER BY created_at DESC LIMIT 100'); - $posts = $stmt->fetchAll(PDO::FETCH_ASSOC); + $db->exec('SET NAMES utf8'); + $stmt = $db->prepare('SELECT * FROM boards WHERE board_id = ?'); + $stmt->execute([$boardId]); + $board = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$board) { + header('Location: index.php'); + exit; + } } catch (PDOException $e) { die('Database error'); } + +if ($threadId) { + try { + $stmt = $db->prepare(' + SELECT p.*, t.title as thread_title + FROM posts p + JOIN threads t ON p.thread_id = t.thread_id + WHERE p.thread_id = ? AND p.board_id = ? + ORDER BY p.created_at ASC + '); + $stmt->execute([$threadId, $boardId]); + $posts = $stmt->fetchAll(PDO::FETCH_ASSOC); + + if (empty($posts)) { + header('Location: board.php?board=' . $boardId); + exit; + } + } catch (PDOException $e) { + die('Database error'); + } +} else { + try { + $stmt = $db->prepare(' + SELECT t.*, + COUNT(p.id) as post_count, + MAX(p.created_at) as last_post_time + FROM threads t + LEFT JOIN posts p ON t.thread_id = p.thread_id + WHERE t.board_id = ? + GROUP BY t.id + ORDER BY t.updated_at DESC + LIMIT 20 + '); + $stmt->execute([$boardId]); + $threads = $stmt->fetchAll(PDO::FETCH_ASSOC); + } catch (PDOException $e) { + die('Database error'); + } +} + +function formatMessage($message) { + return MarkdownParser::parse($message); +} ?> - mkach + mkach - /<?= htmlspecialchars($boardId) ?>/
    -

    mkach

    +

    mkach - // -

    @@ -118,70 +177,126 @@ try {
    -
    - -
    -
    - - -
    - - -
    - - File - -
    - - ( KB) -
    -
    - - - -
    - + + + +
    + +
    +
    + + + + + +
    + + +
    + + File + +
    + + ( KB) +
    +
    + + + +
    + +
    + +
    +
    + +
    +
    + +
    +
    + + + + + Постов: | + Обновлено: + + | + + +
    + + +
    + + File + +
    + + + +
    + +
    + +
    +
    +
    -
    -
    -
    - -
    -
    - - -
    -
    -
    + + + +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    \ No newline at end of file diff --git a/config.php b/config.php index ccbe5ce..0299e87 100644 --- a/config.php +++ b/config.php @@ -8,6 +8,9 @@ return [ ], 'access_key' => 'mkalwaysthebest1337', 'upload_path' => 'uploads/', - 'max_file_size' => 5242880, - 'allowed_types' => ['jpg', 'jpeg', 'png', 'gif', 'webp'] + 'max_file_size' => 26214400, + 'allowed_types' => ['jpg', 'jpeg', 'png', 'gif', 'webp','JPG','JPEG','PNG','GIF','WEBP'], + 'motd' => 'Добро пожаловать на mkach - анонимный имиджборд от МК', + 'logo_enabled' => true, + 'logo_text' => 'mkach' ]; \ No newline at end of file diff --git a/config.php.example b/config.php.example new file mode 100644 index 0000000..a6e340c --- /dev/null +++ b/config.php.example @@ -0,0 +1,16 @@ + [ + 'host' => $_ENV['DB_HOST'] ?? 'mysql', + 'name' => $_ENV['DB_NAME'] ?? 'mkach', + 'user' => $_ENV['DB_USER'] ?? 'mkach', + 'pass' => $_ENV['DB_PASS'] ?? 'mkach' + ], + 'access_key' => '129381923812093780198273098172', + 'upload_path' => 'uploads/', + 'max_file_size' => 26214400, + 'allowed_types' => ['jpg', 'jpeg', 'png', 'gif', 'webp','JPG','JPEG','PNG','GIF','WEBP'], + 'motd' => 'Добро пожаловать на mkach - анонимный имиджборд от МК', + 'logo_enabled' => true, + 'logo_text' => 'mkach' +]; \ No newline at end of file diff --git a/configs/apache.conf b/configs/apache.conf new file mode 100644 index 0000000..20a4453 --- /dev/null +++ b/configs/apache.conf @@ -0,0 +1,17 @@ + + DocumentRoot /var/www/html/mkach + ServerName localhost + AddDefaultCharset UTF-8 + + + AllowOverride All + Require all granted + + + + Require all granted + + + ErrorLog ${APACHE_LOG_DIR}/mkach_error.log + CustomLog ${APACHE_LOG_DIR}/mkach_access.log combined + \ No newline at end of file diff --git a/configs/apache.conf.centos b/configs/apache.conf.centos new file mode 100644 index 0000000..4517372 --- /dev/null +++ b/configs/apache.conf.centos @@ -0,0 +1,17 @@ + + DocumentRoot /var/www/html/mkach + ServerName localhost + AddDefaultCharset UTF-8 + + + AllowOverride All + Require all granted + + + + Require all granted + + + ErrorLog /var/log/httpd/mkach_error.log + CustomLog /var/log/httpd/mkach_access.log combined + \ No newline at end of file diff --git a/configs/apache.conf.debian b/configs/apache.conf.debian new file mode 100644 index 0000000..20a4453 --- /dev/null +++ b/configs/apache.conf.debian @@ -0,0 +1,17 @@ + + DocumentRoot /var/www/html/mkach + ServerName localhost + AddDefaultCharset UTF-8 + + + AllowOverride All + Require all granted + + + + Require all granted + + + ErrorLog ${APACHE_LOG_DIR}/mkach_error.log + CustomLog ${APACHE_LOG_DIR}/mkach_access.log combined + \ No newline at end of file diff --git a/configs/php.ini b/configs/php.ini new file mode 100644 index 0000000..63c770d --- /dev/null +++ b/configs/php.ini @@ -0,0 +1,7 @@ +upload_max_filesize = 25M +post_max_size = 26M +max_execution_time = 30 +memory_limit = 128M +file_uploads = On +max_file_uploads = 1 +default_charset = UTF-8 \ No newline at end of file diff --git a/index.php b/index.php index d5831d8..3fa34d0 100644 --- a/index.php +++ b/index.php @@ -1,8 +1,10 @@ setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +} catch (PDOException $e) { + die('Connection failed'); +} + +try { + $db->exec('SET NAMES utf8'); + $stmt = $db->query('SELECT * FROM boards ORDER BY board_id'); + $boards = $stmt->fetchAll(PDO::FETCH_ASSOC); +} catch (PDOException $e) { + die('Database error'); +} + +if (isset($_GET['logout'])) { + session_destroy(); + header('Location: index.php'); + exit; +} +?> + + + + + + mkach - Доски + + + +
    +
    +

    + +
    + + +
    +
    +
    + + +
    +

    Доски

    +
    + + + +
    +
    +
    + + \ No newline at end of file diff --git a/newthread.php b/newthread.php new file mode 100644 index 0000000..e47ac80 --- /dev/null +++ b/newthread.php @@ -0,0 +1,186 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +} catch (PDOException $e) { + die('Connection failed'); +} + +require_once 'RateLimiter.php'; +require_once 'AnonymousID.php'; +$rateLimiter = new RateLimiter($db); +$rateLimiter->cleanup(); + +$ip = $_SERVER['REMOTE_ADDR']; +$anonymousID = new AnonymousID($db, $ip, $boardId); + +try { + $db->exec('SET NAMES utf8'); + $stmt = $db->prepare('SELECT * FROM boards WHERE board_id = ?'); + $stmt->execute([$boardId]); + $board = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$board) { + header('Location: index.php'); + exit; + } +} catch (PDOException $e) { + die('Database error'); +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + if (!$rateLimiter->isAllowed($ip)) { + $error = 'Слишком много запросов'; + } else { + $title = trim($_POST['title'] ?? ''); + $description = trim($_POST['description'] ?? ''); + $message = trim($_POST['message'] ?? ''); + $file = $_FILES['file'] ?? null; + + if ($title && ($description || $message || ($file && $file['error'] === UPLOAD_ERR_OK))) { + $threadId = sprintf('%06d', mt_rand(1, 999999)); + $postId = sprintf('%06d', mt_rand(1, 999999)); + $anonymousId = $anonymousID->getIDForThread(); + + $fileName = null; + $fileSize = null; + $fileType = null; + + if ($file && $file['error'] === UPLOAD_ERR_OK) { + $fileExt = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); + + if (!in_array($fileExt, $config['allowed_types'])) { + $error = 'Неподдерживаемый тип файла'; + } elseif ($file['size'] > $config['max_file_size']) { + $error = 'Файл слишком большой'; + } else { + $uploadDir = $config['upload_path']; + if (!is_dir($uploadDir)) { + mkdir($uploadDir, 0755, true); + } + + $fileName = $threadId . '.' . $fileExt; + $filePath = $uploadDir . $fileName; + + if (move_uploaded_file($file['tmp_name'], $filePath)) { + $fileSize = $file['size']; + $fileType = $fileExt; + } else { + $error = 'Ошибка загрузки файла'; + } + } + } + + if (empty($error)) { + $stmt = $db->prepare(' + INSERT INTO threads (thread_id, board_id, title, description, file_name, file_size, file_type, ip_address, anonymous_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + '); + $stmt->execute([$threadId, $boardId, $title, $description, $fileName, $fileSize, $fileType, $ip, $anonymousId]); + + if ($message) { + $stmt = $db->prepare(' + INSERT INTO posts (post_id, thread_id, board_id, message, ip_address, anonymous_id) + VALUES (?, ?, ?, ?, ?, ?) + '); + $stmt->execute([$postId, $threadId, $boardId, $message, $ip, $anonymousId]); + } + + header('Location: board.php?board=' . $boardId . '&thread=' . $threadId); + exit; + } + } else { + $error = 'Введите название треда и хотя бы описание, сообщение или файл'; + } + } +} +?> + + + + + + mkach - Создать тред в /<?= htmlspecialchars($boardId) ?>/ + + + +
    +
    +

    mkach - Создать тред в //

    + +
    + +
    + +
    + + +
    +

    Создать новый тред

    +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + Отмена +
    +
    +
    + +
    +

    Поддерживаемые теги Markdown:

    +
      +
    • **жирный** - жирный текст
    • +
    • *курсив* - курсив
    • +
    • _подчеркнутый_ - подчеркнутый
    • +
    • ~~зачеркнутый~~ - зачеркнутый
    • +
    • `код` - код
    • +
    • >>123456 - ссылка на пост
    • +
    • [текст](url) - ссылка
    • +
    • * элемент - список
    • +
    +
    +
    +
    + + \ No newline at end of file diff --git a/sql/create.sql b/sql/create.sql index 4b1a638..687b2a6 100644 --- a/sql/create.sql +++ b/sql/create.sql @@ -1,14 +1,51 @@ +CREATE TABLE boards ( + id INT AUTO_INCREMENT PRIMARY KEY, + board_id VARCHAR(10) NOT NULL UNIQUE, + name VARCHAR(50) NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX idx_board_id (board_id) +); + +CREATE TABLE threads ( + id INT AUTO_INCREMENT PRIMARY KEY, + thread_id VARCHAR(6) NOT NULL UNIQUE, + board_id VARCHAR(10) NOT NULL, + title VARCHAR(255), + description TEXT, + file_name VARCHAR(255), + file_size INT, + file_type VARCHAR(10), + ip_address VARCHAR(45), + anonymous_id VARCHAR(20), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_thread_id (thread_id), + INDEX idx_board_id (board_id), + INDEX idx_updated_at (updated_at), + INDEX idx_anonymous_id (anonymous_id), + FOREIGN KEY (board_id) REFERENCES boards(board_id) +); + CREATE TABLE posts ( id INT AUTO_INCREMENT PRIMARY KEY, post_id VARCHAR(6) NOT NULL UNIQUE, + thread_id VARCHAR(6) NOT NULL, + board_id VARCHAR(10) NOT NULL, message TEXT, file_name VARCHAR(255), file_size INT, file_type VARCHAR(10), ip_address VARCHAR(45), + anonymous_id VARCHAR(20), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_post_id (post_id), - INDEX idx_created_at (created_at) + INDEX idx_thread_id (thread_id), + INDEX idx_board_id (board_id), + INDEX idx_created_at (created_at), + INDEX idx_anonymous_id (anonymous_id), + FOREIGN KEY (thread_id) REFERENCES threads(thread_id), + FOREIGN KEY (board_id) REFERENCES boards(board_id) ); CREATE TABLE rate_limits ( @@ -18,4 +55,8 @@ CREATE TABLE rate_limits ( created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_ip_action (ip_address, action_type), INDEX idx_created_at (created_at) -); \ No newline at end of file +); + +INSERT INTO boards (board_id, name, description) VALUES +('b', 'Random', 'Случайные темы'), +('mk', 'MK', 'MK темы'); \ No newline at end of file diff --git a/styles.css b/styles.css index 505a247..ef7b980 100644 --- a/styles.css +++ b/styles.css @@ -34,29 +34,313 @@ body { color: #800000; } -.logout-btn { +.home-link { + color: #800000; + text-decoration: none; +} + +.home-link:hover { + color: #ff6b6b; +} + +.boards-btn, .logout-btn { background: #d6daf0; border: 1px solid #b7c5d9; padding: 5px 10px; text-decoration: none; color: #800000; font-size: 12px; + margin-left: 5px; } -.logout-btn:hover { +.boards-btn:hover, .logout-btn:hover { background: #e5e9f0; } +.boards-container { + background: #efefef; + border: 1px solid #b7c5d9; + margin-bottom: 10px; + padding: 15px; +} + +.boards-container h2 { + color: #800000; + margin-bottom: 15px; + font-size: 16px; +} + +.boards-list { + display: grid; + gap: 10px; +} + +.board-item { + background: #f0e0d6; + border: 1px solid #d9bfb7; + padding: 10px; + transition: background 0.2s; +} + +.board-item:hover { + background: #e5d4c0; +} + +.board-link { + text-decoration: none; + color: #800000; + display: block; +} + +.board-id { + font-weight: bold; + color: #ff6b6b; + margin-right: 10px; +} + +.board-name { + font-weight: bold; + margin-right: 10px; +} + +.board-desc { + color: #707070; + font-size: 12px; +} + +.motd-container { + background: #efefef; + border: 1px solid #b7c5d9; + margin-bottom: 10px; + padding: 15px; + text-align: center; +} + +.motd { + color: #800000; + font-size: 16px; + font-weight: bold; +} + .board-container { background: #efefef; border: 1px solid #b7c5d9; margin-bottom: 10px; } +.thread-container { + padding: 10px; +} + +.thread-header { + background: #f0e0d6; + border: 1px solid #d9bfb7; + padding: 10px; + margin-bottom: 10px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.thread-header h2 { + color: #800000; + font-size: 16px; +} + +.back-link { + color: #ff6b6b; + text-decoration: none; + font-size: 12px; +} + +.back-link:hover { + text-decoration: underline; +} + +.threads-container { + padding: 10px; +} + +.threads-list { + max-height: 600px; + overflow-y: auto; +} + +.thread-item { + background: #f0e0d6; + border: 1px solid #d9bfb7; + margin-bottom: 10px; + padding: 10px; + transition: background 0.2s; +} + +.thread-item:hover { + background: #e5d4c0; +} + +.thread-link { + text-decoration: none; + color: #800000; +} + +.thread-title { + font-weight: bold; + color: #ff6b6b; +} + +.thread-info { + color: #707070; + font-size: 12px; + margin-top: 5px; + display: block; +} + +.thread-file { + margin: 10px 0; +} + +.thread-image { + max-width: 200px; + max-height: 200px; + border: 1px solid #d9bfb7; +} + +.thread-description { + color: #800000; + margin-top: 10px; + padding: 10px; + background: #e5d4c0; + border-left: 3px solid #ff6b6b; + font-size: 13px; +} + +.anonymous-id { + color: #ff6b6b; + font-weight: bold; + margin-left: 10px; + font-size: 12px; +} + +.new-thread-button { + text-align: center; + margin: 20px 0; +} + +.new-thread-btn { + background: #ff6b6b; + border: 1px solid #ff6b6b; + padding: 15px 30px; + color: #fff; + text-decoration: none; + font-size: 16px; + font-weight: bold; + border-radius: 5px; + transition: background 0.2s; +} + +.new-thread-btn:hover { + background: #ff5252; +} + +.new-thread-container { + background: #efefef; + border: 1px solid #b7c5d9; + margin-bottom: 10px; + padding: 20px; +} + +.thread-form { + background: #f0e0d6; + border: 1px solid #d9bfb7; + padding: 20px; + margin-bottom: 20px; +} + +.thread-form h2 { + color: #800000; + margin-bottom: 20px; + font-size: 18px; +} + +.markdown-help { + background: #f0e0d6; + border: 1px solid #d9bfb7; + padding: 15px; +} + +.markdown-help h3 { + color: #800000; + margin-bottom: 10px; + font-size: 14px; +} + +.markdown-help ul { + list-style: none; + padding: 0; +} + +.markdown-help li { + margin-bottom: 5px; + font-size: 12px; + color: #800000; +} + +.markdown-help code { + background: #e5d4c0; + padding: 2px 4px; + border-radius: 3px; + font-family: monospace; +} + +.cancel-btn { + background: #d6daf0; + border: 1px solid #b7c5d9; + padding: 8px 15px; + color: #800000; + text-decoration: none; + font-size: 14px; + margin-left: 10px; +} + +.cancel-btn:hover { + background: #e5e9f0; +} + +.post-message strong { + font-weight: bold; +} + +.post-message em { + font-style: italic; +} + +.post-message u { + text-decoration: underline; +} + +.post-message del { + text-decoration: line-through; +} + +.post-message code { + background: #e5d4c0; + padding: 2px 4px; + border-radius: 3px; + font-family: monospace; +} + +.post-message ul { + margin: 10px 0; + padding-left: 20px; +} + +.post-message li { + margin-bottom: 5px; +} + .posts-container { max-height: 600px; overflow-y: auto; - padding: 10px; } .post { @@ -64,16 +348,19 @@ body { border: 1px solid #d9bfb7; margin-bottom: 10px; padding: 10px; + border-left: 3px solid #ff6b6b; } .post-header { margin-bottom: 8px; font-size: 12px; + border-bottom: 1px solid #d9bfb7; + padding-bottom: 5px; } .post-id { font-weight: bold; - color: #800000; + color: #ff6b6b; } .post-time { @@ -100,6 +387,17 @@ body { .post-message { white-space: pre-wrap; word-wrap: break-word; + color: #800000; +} + +.reply-link { + color: #ffa500; + text-decoration: none; + font-weight: bold; +} + +.reply-link:hover { + text-decoration: underline; } .post-form { @@ -112,6 +410,38 @@ body { margin-bottom: 10px; } +.title-input { + width: 100%; + border: 1px solid #b7c5d9; + padding: 8px; + font-family: inherit; + font-size: 14px; + background: #f0e0d6; + color: #800000; +} + +.title-input:focus { + outline: none; + border-color: #ff6b6b; +} + +.description-input { + width: 100%; + height: 60px; + border: 1px solid #b7c5d9; + padding: 8px; + font-family: inherit; + font-size: 14px; + resize: vertical; + background: #f0e0d6; + color: #800000; +} + +.description-input:focus { + outline: none; + border-color: #ff6b6b; +} + .message-input { width: 100%; height: 80px; @@ -120,6 +450,13 @@ body { font-family: inherit; font-size: 14px; resize: vertical; + background: #f0e0d6; + color: #800000; +} + +.message-input:focus { + outline: none; + border-color: #ff6b6b; } .file-input { @@ -127,6 +464,8 @@ body { padding: 5px; font-size: 12px; margin-right: 10px; + background: #f0e0d6; + color: #800000; } .send-btn { @@ -136,6 +475,7 @@ body { color: #800000; font-size: 14px; cursor: pointer; + transition: background 0.2s; } .send-btn:hover {