Files
VH_posting_system/index.php
T
zuevav 5d62158b1b Improve VK photo-upload error message and token instructions
- Replace the misleading 'community token cannot upload photos' fallback warning with the actual VK error and a concrete checklist (photos right + token bound to the same group)
- Extend UI hint with troubleshooting note for the 'photos as links' symptom

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 15:53:14 +03:00

740 lines
42 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* VH Posting System - Главная страница
* Управление фотографиями Flickr и публикация в соцсети
*/
session_start();
// Load configuration
$configFile = __DIR__ . '/config.php';
if (!file_exists($configFile)) {
die('Файл конфигурации не найден. Скопируйте config.example.php в config.php и настройте его.');
}
$config = require $configFile;
// Autoload classes
spl_autoload_register(function ($class) {
$file = __DIR__ . '/classes/' . $class . '.php';
if (file_exists($file)) {
require_once $file;
}
});
// Initialize Auth
$auth = new Auth();
// Handle setup if no users exist
if (!$auth->hasUsers()) {
header('Location: setup.php');
exit;
}
// Check authentication
if (!$auth->isAuthenticated()) {
header('Location: login.php');
exit;
}
// Get current user
$currentUser = $auth->getCurrentUser();
// Handle logout
if (isset($_GET['logout'])) {
$auth->logout();
header('Location: login.php');
exit;
}
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VH Posting System</title>
<link rel="icon" type="image/png" href="image.png">
<link rel="stylesheet" href="css/style.css?v=<?= filemtime(__DIR__ . '/css/style.css') ?>">
<script>
// Apply saved theme immediately to prevent flash
(function() {
const theme = localStorage.getItem('theme') || 'light';
if (theme === 'dark') {
document.documentElement.setAttribute('data-theme', 'dark');
}
})();
</script>
</head>
<body>
<div class="app-container">
<!-- Header -->
<header class="app-header">
<h1>VH Posting System</h1>
<div class="user-menu">
<button class="theme-toggle" id="theme-toggle" title="Переключить тему"></button>
<span class="username"><?= htmlspecialchars($currentUser) ?></span>
<a href="?logout=1" class="btn btn-small btn-secondary">Выход</a>
</div>
</header>
<!-- Main Navigation -->
<nav class="main-nav">
<button class="nav-btn" data-tab="gallery">Галерея</button>
<button class="nav-btn active" data-tab="posting">Публикация</button>
<button class="nav-btn" data-tab="converter">Конвертер</button>
<button class="nav-btn" data-tab="widget">Виджет</button>
<button class="nav-btn" data-tab="settings">Настройки</button>
</nav>
<!-- Tab: Flickr Gallery -->
<section id="tab-gallery" class="tab-content">
<div class="panel">
<!-- OAuth Status Banner -->
<div id="oauth-banner" class="oauth-banner hidden">
<div class="oauth-banner-content">
<span class="oauth-banner-icon">🔐</span>
<span class="oauth-banner-text">
<strong>Оригиналы недоступны</strong> — требуется авторизация Flickr
</span>
<a href="flickr_auth.php" class="btn btn-small btn-primary">Авторизовать</a>
</div>
</div>
<!-- Albums View (default) -->
<div id="albums-view" class="gallery-view">
<div class="gallery-header">
<h2>Альбомы Flickr</h2>
<div class="gallery-toolbar">
<button id="btn-load-albums" class="btn btn-secondary">
<span class="btn-icon-text">↻</span> Обновить
</button>
<div class="toolbar-search">
<input type="text" id="search-albums" placeholder="Поиск альбомов...">
</div>
</div>
</div>
<p class="drag-hint" id="drag-hint">💡 Перетащите альбомы для изменения порядка</p>
<div id="albums-grid" class="albums-grid">
<p class="placeholder">Загрузка альбомов...</p>
</div>
</div>
<!-- Photos View (when inside album) -->
<div id="photos-view" class="gallery-view hidden">
<div class="gallery-header">
<div class="breadcrumb">
<button id="btn-back-to-albums" class="btn btn-text">
← Назад к альбомам
</button>
<span class="breadcrumb-separator">/</span>
<span id="current-album-title" class="breadcrumb-current">Альбом</span>
</div>
<div class="gallery-toolbar">
<div class="toolbar-search">
<input type="text" id="search-photos" placeholder="Поиск фото...">
</div>
<span id="photos-count" class="photos-count"></span>
<button id="btn-download-album" class="btn btn-small" onclick="downloadAllPhotos()" title="Скачать все фото альбома">
↓ Скачать альбом
</button>
</div>
</div>
<!-- Photo Grid -->
<div id="photo-gallery" class="photo-gallery">
<p class="placeholder">Загрузка фотографий...</p>
</div>
<!-- Pagination -->
<div class="pagination">
<button id="btn-prev-page" class="btn btn-small" disabled>←</button>
<span id="page-info">1</span>
<button id="btn-next-page" class="btn btn-small" disabled>→</button>
</div>
</div>
</div>
<!-- Floating Action Bar (appears when photos selected) -->
<div id="selection-bar" class="floating-action-bar hidden">
<div class="action-bar-left">
<span id="selected-count" class="selection-count">0</span>
<span class="selection-label">выбрано</span>
</div>
<div class="action-bar-center">
<button id="btn-select-all" class="action-btn" title="Выбрать все">
<span class="action-icon">☑</span>
<span class="action-text">Все</span>
</button>
<button id="btn-deselect-all" class="action-btn" title="Снять выбор">
<span class="action-icon">☐</span>
<span class="action-text">Сброс</span>
</button>
</div>
<div class="action-bar-right">
<button id="btn-download-selected" class="action-btn" title="Скачать оригиналы">
<span class="action-icon">↓</span>
<span class="action-text">Скачать</span>
</button>
<button id="btn-convert-selected" class="action-btn action-secondary" title="Конвертировать в BBCode/HTML">
<span class="action-icon">{ }</span>
<span class="action-text">Код</span>
</button>
<button id="btn-telegram-selected" class="action-btn action-primary" title="Опубликовать в соцсети">
<span class="action-icon">↗</span>
<span class="action-text">Опубликовать</span>
</button>
</div>
</div>
</section>
<!-- Tab: Multi-Platform Posting -->
<section id="tab-posting" class="tab-content active">
<div class="panel">
<h2>Публикация в социальные сети</h2>
<p class="help-text">Выберите платформы и опубликуйте одним нажатием</p>
<!-- Photos Section -->
<div class="form-group">
<label>Фото и видео: <span id="photo-counter" class="photo-counter">0/9</span></label>
<div class="photo-source-buttons">
<button type="button" class="btn btn-secondary" id="btn-select-from-flickr">
<span class="btn-icon-text">🖼</span> Выбрать с Flickr
</button>
<input type="file" id="file-upload" multiple accept="image/*,video/*" style="display: none;">
<button type="button" class="btn btn-secondary" id="btn-upload-files">
<span class="btn-icon-text">📤</span> Загрузить
</button>
</div>
<div id="post-photos-preview" class="photos-preview combined-preview">
<p class="placeholder">Нажмите кнопку выше чтобы добавить фото</p>
</div>
</div>
<!-- Post Text -->
<div class="form-group">
<label for="post-text">Текст публикации:</label>
<div class="text-editor">
<div class="editor-toolbar">
<button type="button" class="toolbar-btn" data-format="bold" title="Жирный"><b>B</b></button>
<button type="button" class="toolbar-btn" data-format="italic" title="Курсив"><i>I</i></button>
<button type="button" class="toolbar-btn" data-format="underline" title="Подчёркнутый"><u>U</u></button>
<button type="button" class="toolbar-btn" data-format="strike" title="Зачёркнутый"><s>S</s></button>
<span class="toolbar-separator"></span>
<button type="button" class="toolbar-btn" data-format="link" title="Ссылка">🔗</button>
<button type="button" class="toolbar-btn" data-format="code" title="Код">&lt;/&gt;</button>
</div>
<textarea id="post-text" rows="4" placeholder="Введите текст для публикации..."></textarea>
</div>
</div>
<!-- Tags -->
<div class="form-group">
<label>Теги:</label>
<div class="tags-container" id="post-tags-container">
<div class="tags-list" id="post-tags-list"></div>
<div class="tags-input-wrapper">
<input type="text" id="post-tags-input" class="tags-input" placeholder="Добавить тег...">
<div class="tags-suggestions" id="post-tags-suggestions"></div>
</div>
</div>
<div class="tags-presets">
<span class="tags-presets-label">Быстрые теги:</span>
<div class="presets-list" id="post-presets-list"></div>
<button type="button" class="preset-add-btn" id="post-preset-add" title="Добавить пресет">+</button>
<button type="button" class="preset-manage-btn" id="post-preset-manage" title="Управление пресетами">⚙</button>
</div>
</div>
<!-- Platform Selection -->
<div class="form-group">
<label>Выберите платформы:</label>
<div class="platforms-grid">
<!-- Telegram -->
<div class="platform-card">
<div class="platform-header">
<label class="platform-checkbox">
<input type="checkbox" id="chk-telegram" checked>
<span class="checkmark"></span>
</label>
<div class="platform-info">
<span class="platform-name">Telegram</span>
<span id="tg-status-mini" class="status-mini">Не подключён</span>
</div>
</div>
<select id="tg-channel" class="platform-target">
<option value="">Выберите канал...</option>
</select>
</div>
<!-- VK -->
<div class="platform-card">
<div class="platform-header">
<label class="platform-checkbox">
<input type="checkbox" id="chk-vk" checked>
<span class="checkmark"></span>
</label>
<div class="platform-info">
<span class="platform-name">ВКонтакте</span>
<span id="vk-status-mini" class="status-mini">Не подключён</span>
</div>
</div>
<select id="vk-group" class="platform-target">
<option value="">Выберите группу...</option>
</select>
</div>
<!-- Instagram (info only) -->
<div class="platform-card platform-disabled">
<div class="platform-header">
<label class="platform-checkbox">
<input type="checkbox" id="chk-instagram" disabled>
<span class="checkmark"></span>
</label>
<div class="platform-info">
<span class="platform-name">Instagram</span>
<span class="status-mini">Требует настройки</span>
</div>
</div>
<p class="platform-note">Требуется Facebook Business API</p>
</div>
</div>
</div>
<!-- Post Options -->
<div class="post-options-grid">
<div class="form-group">
<label for="post-parse-mode">Формат:</label>
<select id="post-parse-mode">
<option value="HTML">HTML</option>
<option value="Markdown">Markdown</option>
<option value="">Текст</option>
</select>
</div>
<div class="form-group">
<label class="checkbox-label compact">
<input type="checkbox" id="chk-cross-promo" checked>
<span>Кросс-промо</span>
</label>
</div>
<div class="form-group schedule-toggle">
<label class="checkbox-label compact">
<input type="checkbox" id="chk-schedule">
<span>Отложить</span>
</label>
</div>
</div>
<!-- Schedule Options (hidden by default) -->
<div id="schedule-options" class="schedule-options hidden">
<label class="schedule-label">📅 Когда опубликовать:</label>
<div class="schedule-presets">
<button type="button" class="preset-btn" data-preset="1h">Через 1 час</button>
<button type="button" class="preset-btn" data-preset="3h">Через 3 часа</button>
<button type="button" class="preset-btn" data-preset="tomorrow-10">Завтра 10:00</button>
<button type="button" class="preset-btn" data-preset="tomorrow-18">Завтра 18:00</button>
</div>
<div class="schedule-custom">
<div class="schedule-date-row">
<div class="schedule-field">
<label for="schedule-date">Дата:</label>
<input type="date" id="schedule-date" class="schedule-input">
</div>
<div class="schedule-field">
<label for="schedule-time">Время:</label>
<input type="time" id="schedule-time" class="schedule-input">
</div>
</div>
<input type="hidden" id="scheduled-datetime">
</div>
</div>
<!-- Action Buttons -->
<div class="post-actions">
<button id="btn-send-post" class="btn btn-primary btn-large">
🚀 Опубликовать
</button>
</div>
<div id="post-result" class="result-message"></div>
<!-- Scheduled Posts List -->
<div class="scheduled-section">
<div class="scheduled-header">
<h3>📅 Отложенные публикации</h3>
<span id="scheduled-count" class="badge">0</span>
</div>
<div id="scheduled-posts-list" class="scheduled-posts-list">
<p class="placeholder">Нет запланированных постов</p>
</div>
</div>
<!-- Published Posts Archive -->
<div class="archive-section">
<div class="archive-header">
<h3>✓ Архив публикаций</h3>
<button type="button" class="btn btn-small btn-secondary" id="btn-refresh-archive">↻</button>
</div>
<div id="published-posts-list" class="published-posts-list">
<p class="placeholder">Загрузка...</p>
</div>
</div>
</div>
</section>
<!-- Tab: Link Converter -->
<section id="tab-converter" class="tab-content">
<div class="panel">
<h2>Конвертер ссылок Flickr</h2>
<p class="help-text">Преобразование ссылок в различные форматы для форумов и соцсетей</p>
<div class="converter-grid">
<!-- Left Column: Input -->
<div class="converter-input-section">
<div class="form-group">
<label for="input-urls">Ссылки Flickr:</label>
<textarea id="input-urls" rows="5" placeholder="https://www.flickr.com/photos/username/12345678901/
https://flic.kr/p/ABC123
https://live.staticflickr.com/65535/12345678901_abcdef1234_b.jpg"></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label for="image-size">Размер:</label>
<select id="image-size">
<option value="Large" selected>Большой (1024px)</option>
<option value="Large1600">1600px</option>
<option value="Large2048">2048px</option>
<option value="Original">Оригинал</option>
<option value="Medium640">640px</option>
</select>
</div>
<div class="form-group">
<label for="output-format">Формат:</label>
<select id="output-format">
<optgroup label="Кукольные форумы">
<option value="bjdclub">BJDClub.ru</option>
<option value="babiki">Babiki.ru</option>
<option value="babiki_simple">Babiki (простой)</option>
<option value="doll_forum">Универсальный</option>
</optgroup>
<optgroup label="BBCode">
<option value="bbcode">BBCode</option>
<option value="bbcode_linked">BBCode + ссылка</option>
</optgroup>
<optgroup label="HTML / Markdown">
<option value="html">HTML</option>
<option value="markdown">Markdown</option>
<option value="url_only">Только URL</option>
</optgroup>
</select>
</div>
</div>
<button id="btn-convert" class="btn btn-primary btn-block">Конвертировать</button>
</div>
<!-- Right Column: Text & Tags -->
<div class="converter-text-section">
<div class="form-group">
<label for="converter-title">Заголовок:</label>
<input type="text" id="converter-title" placeholder="Название поста...">
</div>
<div class="form-group">
<label for="converter-text">Текст к фотографиям:</label>
<div class="text-editor">
<div class="editor-toolbar">
<button type="button" class="toolbar-btn" data-format="bold" data-target="converter-text" title="Жирный"><b>B</b></button>
<button type="button" class="toolbar-btn" data-format="italic" data-target="converter-text" title="Курсив"><i>I</i></button>
<button type="button" class="toolbar-btn" data-format="underline" data-target="converter-text" title="Подчёркнутый"><u>U</u></button>
<span class="toolbar-separator"></span>
<button type="button" class="toolbar-btn" data-format="link" data-target="converter-text" title="Ссылка">🔗</button>
</div>
<textarea id="converter-text" rows="3" placeholder="Описание, комментарии к фото..."></textarea>
</div>
</div>
<div class="form-group">
<label>Теги:</label>
<div class="tags-container" id="converter-tags-container">
<div class="tags-list" id="converter-tags-list"></div>
<div class="tags-input-wrapper">
<input type="text" id="converter-tags-input" class="tags-input" placeholder="Добавить тег...">
<div class="tags-suggestions" id="converter-tags-suggestions"></div>
</div>
</div>
<div class="tags-presets">
<span class="tags-presets-label">Быстрые:</span>
<div class="presets-list" id="converter-presets-list"></div>
<button type="button" class="preset-add-btn" id="converter-preset-add" title="Добавить пресет">+</button>
<button type="button" class="preset-manage-btn" id="converter-preset-manage" title="Управление пресетами">⚙</button>
</div>
</div>
</div>
</div>
<!-- Output Section -->
<div class="converter-output-section">
<div class="form-group">
<label for="output-result">Результат:</label>
<textarea id="output-result" rows="6" readonly placeholder="Результат появится здесь после конвертации..."></textarea>
<div class="output-actions">
<button id="btn-copy" class="btn btn-secondary">📋 Скопировать</button>
<button id="btn-copy-with-tags" class="btn btn-secondary">📋 С тегами</button>
<span id="copy-status" class="copy-status"></span>
</div>
</div>
</div>
</div>
</section>
<!-- Tab: Widget Settings -->
<section id="tab-widget" class="tab-content">
<div class="panel">
<h2>Виджет для WordPress</h2>
<p class="help-text">Настройте мозаику фотографий для отображения на вашем сайте</p>
<!-- Widget Status -->
<div class="settings-section">
<h3>Статус виджета</h3>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" id="widget-enabled" checked>
<span>Виджет включён</span>
</label>
</div>
<div class="form-group">
<label>API URL для WordPress:</label>
<input type="text" id="widget-api-url" readonly>
<button type="button" class="btn btn-small btn-secondary" onclick="copyWidgetUrl()">Копировать</button>
</div>
</div>
<!-- Album Selection -->
<div class="settings-section">
<h3>Выбор альбомов</h3>
<p class="help-text">Выберите альбомы для отображения в виджете. Если ничего не выбрано — показываются последние фото.</p>
<div class="form-group">
<button type="button" id="btn-load-widget-albums" class="btn btn-secondary">Загрузить альбомы</button>
</div>
<div id="widget-albums-list" class="widget-albums-grid">
<p class="placeholder">Нажмите «Загрузить альбомы» для выбора</p>
</div>
</div>
<!-- Widget Options -->
<div class="settings-section">
<h3>Параметры</h3>
<div class="form-row">
<div class="form-group">
<label for="widget-max-photos">Максимум фото:</label>
<input type="number" id="widget-max-photos" value="30" min="5" max="100">
</div>
<div class="form-group">
<label for="widget-cache-time">Кэш (секунд):</label>
<input type="number" id="widget-cache-time" value="3600" min="60" max="86400">
<span class="hint">3600 = 1 час</span>
</div>
</div>
</div>
<button id="btn-save-widget-settings" class="btn btn-primary btn-large">Сохранить настройки виджета</button>
<span id="widget-save-status" class="save-status"></span>
<!-- WordPress Installation -->
<div class="settings-section" style="margin-top: 32px;">
<h3>Установка на WordPress</h3>
<div class="code-block">
<p>1. Скачайте плагин:</p>
<a href="vh-flickr-mosaic.zip" class="btn btn-secondary" id="download-plugin-btn">Скачать плагин</a>
</div>
<div class="code-block">
<p>2. Установите плагин в WordPress (Плагины → Добавить новый → Загрузить)</p>
</div>
<div class="code-block">
<p>3. В настройках плагина укажите API URL:</p>
<code id="widget-api-url-code"></code>
</div>
</div>
</div>
</section>
<!-- Tab: Settings -->
<section id="tab-settings" class="tab-content">
<div class="panel">
<h2>Настройки</h2>
<!-- Flickr Settings -->
<div class="settings-section">
<h3>Flickr API</h3>
<div class="form-group">
<label>API ключ:</label>
<input type="text" value="<?= !empty($config['flickr']['api_key']) ? '••••••••' . substr($config['flickr']['api_key'], -4) : '' ?>" readonly>
<span class="status <?= !empty($config['flickr']['api_key']) ? 'connected' : 'disconnected' ?>">
<?= !empty($config['flickr']['api_key']) ? 'Настроен' : 'Не настроен' ?>
</span>
</div>
<div class="form-group">
<label>ID пользователя:</label>
<input type="text" value="<?= htmlspecialchars($config['flickr_user_id'] ?? '') ?>" readonly>
</div>
<div class="form-group">
<label>OAuth авторизация:</label>
<span id="flickr-oauth-status" class="status">Проверка...</span>
<a href="flickr_auth.php" id="flickr-oauth-btn" class="btn btn-small btn-primary" style="margin-left: 10px;">Авторизовать</a>
<span class="hint">Требуется для загрузки оригиналов</span>
</div>
</div>
<!-- Telegram Settings -->
<div class="settings-section">
<h3>Telegram</h3>
<div class="form-group">
<label>Статус бота:</label>
<span id="tg-bot-status" class="status">Проверка...</span>
</div>
<div class="form-group">
<label for="tg-channels-list">Каналы (по одному на строку):</label>
<textarea id="tg-channels-list" rows="3" placeholder="@channel_username
-1001234567890"><?php
$channels = $config['telegram']['channels'] ?? [];
foreach ($channels as $ch) {
echo htmlspecialchars($ch['id'] ?? $ch) . "\n";
}
?></textarea>
</div>
</div>
<!-- VK Settings -->
<div class="settings-section">
<h3>ВКонтакте</h3>
<div class="form-group">
<label>Статус:</label>
<span id="vk-status" class="status">Проверка...</span>
</div>
<div class="form-group">
<label for="vk-token-input">Access Token:</label>
<input type="password" id="vk-token-input" placeholder="Вставьте токен сюда..." value="<?= !empty($config['vk']['access_token']) ? $config['vk']['access_token'] : '' ?>">
<button id="btn-save-vk-token" class="btn btn-primary btn-small" style="margin-left: 10px;">Сохранить</button>
<button id="btn-toggle-vk-token" class="btn btn-secondary btn-small" style="margin-left: 5px;">👁</button>
</div>
<div class="form-group">
<span id="vk-token-save-status" class="save-status"></span>
</div>
<div class="form-group">
<details class="vk-help" open>
<summary>Как получить токен сообщества (рекомендуется)?</summary>
<ol style="margin: 10px 0; padding-left: 20px; font-size: 0.9em;">
<li>Откройте свою группу в VK → <strong>Управление</strong></li>
<li>В меню справа выберите <strong>«Работа с API»</strong> → вкладка <strong>«Ключи доступа»</strong> → <strong>«Создать ключ»</strong></li>
<li>Включите права: <strong>Управление сообществом</strong>, <strong>Сообщения сообщества</strong>, <strong>Фотографии</strong>, <strong>Стена</strong> (Wall)</li>
<li>Скопируйте созданный ключ и вставьте его в поле выше → «Сохранить»</li>
</ol>
<p style="font-size: 0.85em; color: var(--text-secondary); margin-top: 10px;">
<strong>Важно:</strong> ключ сообщества бессрочный и не блокируется VK. Он постит и грузит фото только в свою группу — для нескольких групп создайте отдельный ключ в каждой.
</p>
<p style="font-size: 0.85em; color: var(--text-secondary); margin-top: 8px;">
<strong>Если фото уходят как ссылки</strong> (предупреждение в архиве) — значит у ключа нет права <strong>Фотографии</strong>, или ключ создан в другой группе. Удалите ключ и создайте заново в той же группе, в которую постите, отметив права: Управление, Стена, Фотографии, Сообщения сообщества.
</p>
<p style="font-size: 0.8em; color: var(--text-secondary); margin-top: 8px;">
Старый способ через <a href="https://vkhost.github.io/" target="_blank">vkhost.github.io</a> / VK Admin сейчас не работает — VK заблокировал это приложение (ошибка <code>[8] Application is blocked</code>).
</p>
</details>
</div>
</div>
<!-- Cross-Promo Settings -->
<div class="settings-section">
<h3>Кросс-промо каналов</h3>
<p class="help-text">Ссылки на ваши каналы для автоматического добавления в посты</p>
<div class="form-group">
<label for="cross-promo-telegram">Ссылка на Telegram канал:</label>
<input type="text" id="cross-promo-telegram" placeholder="https://t.me/your_channel">
<span class="hint">Будет добавлена в посты VK</span>
</div>
<div class="form-group">
<label for="cross-promo-vk">Ссылка на ВКонтакте:</label>
<input type="text" id="cross-promo-vk" placeholder="https://vk.com/your_group">
<span class="hint">Будет добавлена в посты Telegram</span>
</div>
<div class="form-group">
<label for="cross-promo-text-tg">Текст для Telegram:</label>
<input type="text" id="cross-promo-text-tg" placeholder="Мой канал ВКонтакте" value="Мой канал ВКонтакте">
</div>
<div class="form-group">
<label for="cross-promo-text-vk">Текст для ВКонтакте:</label>
<input type="text" id="cross-promo-text-vk" placeholder="Мой канал в Telegram" value="Мой канал в Telegram">
</div>
<button id="btn-save-cross-promo" class="btn btn-primary">Сохранить настройки кросс-промо</button>
<span id="cross-promo-save-status" class="save-status"></span>
</div>
<!-- Theme -->
<div class="settings-section">
<h3>Оформление</h3>
<div class="form-group">
<label>Тема интерфейса:</label>
<button id="btn-toggle-theme" class="btn btn-secondary">Переключить тему</button>
</div>
</div>
<!-- Password Change -->
<div class="settings-section">
<h3>Смена пароля</h3>
<div class="form-group">
<label for="current-password">Текущий пароль:</label>
<input type="password" id="current-password">
</div>
<div class="form-group">
<label for="new-password">Новый пароль:</label>
<input type="password" id="new-password">
<span class="hint">Минимум 8 символов</span>
</div>
<div class="form-group">
<label for="confirm-password">Подтвердите пароль:</label>
<input type="password" id="confirm-password">
</div>
<button id="btn-change-password" class="btn btn-primary">Сменить пароль</button>
</div>
</div>
</section>
</div>
<!-- Preset Management Modal -->
<div id="preset-modal" class="modal-overlay" style="display: none;">
<div class="modal-content preset-modal">
<div class="modal-header">
<h3 id="preset-modal-title">Управление пресетами</h3>
<button type="button" class="modal-close" id="preset-modal-close">&times;</button>
</div>
<div class="modal-body">
<!-- Add/Edit Form -->
<div id="preset-form-section" style="display: none;">
<div class="form-group">
<label for="preset-name">Название пресета:</label>
<input type="text" id="preset-name" class="form-control" placeholder="Например: BJD">
</div>
<div class="form-group">
<label for="preset-tags">Теги (через запятую):</label>
<input type="text" id="preset-tags" class="form-control" placeholder="bjd, doll, куклы">
</div>
<div class="preset-form-actions">
<button type="button" id="preset-save" class="btn btn-primary">Сохранить</button>
<button type="button" id="preset-cancel" class="btn btn-secondary">Отмена</button>
</div>
</div>
<!-- Presets List -->
<div id="preset-list-section">
<div class="preset-manager-list" id="preset-manager-list"></div>
<button type="button" id="preset-add-new" class="btn btn-primary btn-block">+ Добавить пресет</button>
</div>
</div>
</div>
</div>
<script src="js/app.js?v=<?= filemtime(__DIR__ . '/js/app.js') ?>"></script>
</body>
</html>