267 lines
12 KiB
HTML
267 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Post - LocalBooru</title>
|
|
<style>
|
|
:root {
|
|
--bg-main: #1e1e1e;
|
|
--bg-header: #252525;
|
|
--bg-panel: #2a2a2a;
|
|
--bg-input: #333;
|
|
--text-main: #eee;
|
|
--text-muted: #aaa;
|
|
--border-color: #444;
|
|
--link-color: #4da8da;
|
|
|
|
--color-g: #4caf50;
|
|
--color-s: #ff9800;
|
|
--color-q: #ffeb3b;
|
|
--color-e: #f44336;
|
|
}
|
|
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
body {
|
|
background-color: var(--bg-main);
|
|
color: var(--text-main);
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
font-size: 14px;
|
|
}
|
|
|
|
header {
|
|
display: flex; align-items: center; background-color: var(--bg-header);
|
|
padding: 10px 20px; border-bottom: 1px solid var(--border-color); gap: 15px;
|
|
}
|
|
.logo { font-weight: bold; font-size: 18px; color: white; text-decoration: none; }
|
|
|
|
/* Search & Autocomplete */
|
|
.search-wrapper { flex-grow: 1; max-width: 400px; position: relative; }
|
|
.search-wrapper input {
|
|
width: 100%; background-color: var(--bg-input); border: 1px solid var(--border-color);
|
|
color: white; padding: 6px 10px; border-radius: 3px; outline: none;
|
|
}
|
|
.search-wrapper input:focus { border-color: var(--text-muted); }
|
|
.autocomplete-list {
|
|
position: absolute; top: 100%; left: 0; right: 0; background-color: var(--bg-panel);
|
|
border: 1px solid var(--border-color); border-radius: 3px; margin-top: 4px;
|
|
list-style: none; z-index: 1000; max-height: 250px; overflow-y: auto;
|
|
box-shadow: 0 4px 10px rgba(0,0,0,0.5); display: none;
|
|
}
|
|
.autocomplete-item { padding: 8px 10px; cursor: pointer; display: flex; justify-content: space-between; border-bottom: 1px solid #333; }
|
|
.autocomplete-item:last-child { border-bottom: none; }
|
|
.autocomplete-item:hover { background-color: var(--bg-input); }
|
|
.autocomplete-count { color: var(--text-muted); font-size: 12px; }
|
|
|
|
main {
|
|
padding: 20px;
|
|
max-width: 1600px;
|
|
margin: 0 auto;
|
|
display: grid;
|
|
grid-template-columns: 300px 1fr;
|
|
gap: 20px;
|
|
align-items: start;
|
|
}
|
|
|
|
.sidebar {
|
|
background-color: var(--bg-panel); border: 1px solid var(--border-color);
|
|
border-radius: 4px; padding: 15px; display: flex; flex-direction: column; gap: 20px;
|
|
}
|
|
|
|
.section-title { font-size: 16px; font-weight: bold; border-bottom: 1px solid var(--border-color); padding-bottom: 8px; margin-bottom: 10px; }
|
|
.stat-row { margin-bottom: 6px; line-height: 1.4; word-break: break-all;}
|
|
.stat-label { font-weight: bold; }
|
|
|
|
.stat-val-g { color: var(--color-g); }
|
|
.stat-val-s { color: var(--color-s); }
|
|
.stat-val-q { color: var(--color-q); }
|
|
.stat-val-e { color: var(--color-e); }
|
|
|
|
.tags-list { display: flex; flex-direction: column; gap: 6px; }
|
|
.tags-list a { color: var(--link-color); text-decoration: none; word-break: break-word; }
|
|
.tags-list a:hover { text-decoration: underline; }
|
|
|
|
.media-container {
|
|
background-color: var(--bg-panel); border: 1px solid var(--border-color);
|
|
border-radius: 4px; padding: 20px; display: flex; justify-content: center;
|
|
align-items: center; min-height: 400px;
|
|
}
|
|
|
|
.media-container img, .media-container video { max-width: 100%; max-height: 85vh; object-fit: contain; border-radius: 2px; }
|
|
|
|
.loader { grid-column: 1 / -1; text-align: center; padding: 50px; color: var(--text-muted); }
|
|
|
|
@media (max-width: 900px) {
|
|
main { grid-template-columns: 1fr; }
|
|
.media-container { order: -1; }
|
|
header { flex-wrap: wrap; }
|
|
.search-wrapper { min-width: 100%; order: 3; margin-top: 10px; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<header>
|
|
<a href="index.html" class="logo">LocalBooru</a>
|
|
<div class="search-wrapper">
|
|
<input type="text" id="searchInput" placeholder="Search tags...">
|
|
<ul id="autocompleteList" class="autocomplete-list"></ul>
|
|
</div>
|
|
</header>
|
|
|
|
<main id="mainContent">
|
|
<div class="loader" id="loader">Загрузка поста...</div>
|
|
</main>
|
|
|
|
<script>
|
|
// Инициализация поиска с автодополнением
|
|
const searchInput = document.getElementById('searchInput');
|
|
const list = document.getElementById('autocompleteList');
|
|
let debounceTimer;
|
|
|
|
searchInput.addEventListener('input', () => {
|
|
clearTimeout(debounceTimer);
|
|
const words = searchInput.value.split(' ');
|
|
const currentWord = words[words.length - 1];
|
|
|
|
if (currentWord.length < 2) { list.style.display = 'none'; return; }
|
|
|
|
debounceTimer = setTimeout(async () => {
|
|
const req = { type: "autocomplete", q: currentWord };
|
|
|
|
// РЕАЛЬНОЕ API: const data = await fetch('/api', ...);
|
|
const data = await mockAutocompleteAPI(req);
|
|
|
|
if (data.length === 0) { list.style.display = 'none'; return; }
|
|
|
|
list.innerHTML = data.map(item => `
|
|
<li class="autocomplete-item" data-tag="${item.tag}">
|
|
<span>${item.tag}</span><span class="autocomplete-count">${item.count}</span>
|
|
</li>
|
|
`).join('');
|
|
list.style.display = 'block';
|
|
|
|
list.querySelectorAll('.autocomplete-item').forEach(li => {
|
|
li.addEventListener('click', () => {
|
|
words[words.length - 1] = li.getAttribute('data-tag');
|
|
searchInput.value = words.join(' ') + ' ';
|
|
list.style.display = 'none';
|
|
searchInput.focus();
|
|
});
|
|
});
|
|
}, 200);
|
|
});
|
|
|
|
// Скрываем список при клике вне него
|
|
document.addEventListener('click', (e) => {
|
|
if (e.target !== searchInput && e.target !== list) list.style.display = 'none';
|
|
});
|
|
|
|
// Переход на главную при нажатии Enter
|
|
searchInput.addEventListener('keypress', (e) => {
|
|
if(e.key === 'Enter') window.location.href = `index.html?query=${searchInput.value.trim()}`;
|
|
});
|
|
|
|
|
|
// Загрузка поста
|
|
async function fetchPost() {
|
|
let postId = new URLSearchParams(window.location.search).get('id');
|
|
if (!postId) postId = window.location.pathname.split('/').pop();
|
|
|
|
if (!postId || postId.includes('.html')) {
|
|
document.getElementById('mainContent').innerHTML = `<div class="loader">Ошибка: ID поста не найден</div>`;
|
|
return;
|
|
}
|
|
|
|
const requestBody = { type: "post", post_id: postId };
|
|
|
|
try {
|
|
// РЕАЛЬНОЕ API: fetch('/api', ...);
|
|
const data = await generateMockPostData(requestBody);
|
|
renderPost(data);
|
|
} catch (error) {
|
|
document.getElementById('mainContent').innerHTML = `<div class="loader">Ошибка загрузки</div>`;
|
|
}
|
|
}
|
|
|
|
function renderPost(data) {
|
|
const main = document.getElementById('mainContent');
|
|
|
|
let mediaHtml = '';
|
|
if (data.media_type === 'video') {
|
|
mediaHtml = `<video controls autoplay loop src="${data.media_url}"></video>`;
|
|
} else {
|
|
mediaHtml = `<img src="${data.media_url}" alt="Post Media">`;
|
|
}
|
|
|
|
const tagsHtml = data.tags.map(tag => `<a href="index.html?query=${tag}">${tag}</a>`).join('');
|
|
|
|
main.innerHTML = `
|
|
<aside class="sidebar">
|
|
<div class="statistics">
|
|
<div class="section-title">Statistics</div>
|
|
<div class="stat-row"><span class="stat-label">File:</span> ${data.file_name}</div>
|
|
<div class="stat-row"><span class="stat-label">Date:</span> ${data.date}</div>
|
|
<div class="stat-row"><span class="stat-label">Rating:</span> <span class="stat-val-${data.rating[0].toLowerCase()}">${data.rating}</span></div>
|
|
<div class="stat-row"><span class="stat-label">Score:</span> ${data.score}</div>
|
|
<div class="stat-row"><span class="stat-label">Size:</span> ${data.size}</div>
|
|
<div class="stat-row"><span class="stat-label">Source:</span> <a href="${data.source}" style="color: var(--link-color); text-decoration: none;">Link</a></div>
|
|
</div>
|
|
|
|
<div class="tags">
|
|
<div class="section-title">Tags</div>
|
|
<div class="tags-list">
|
|
${tagsHtml}
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<div class="media-container">
|
|
${mediaHtml}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// --- ВРЕМЕННЫЕ ЗАГЛУШКИ ДЛЯ API ---
|
|
function mockAutocompleteAPI(req) {
|
|
return new Promise(res => {
|
|
const db = [
|
|
{tag: "long_hair", count: 8184}, {tag: "long_sleeves", count: 2828},
|
|
{tag: "looking_at_viewer", count: 8062}, {tag: "1girl", count: 11061},
|
|
{tag: "blue_hair", count: 3279}, {tag: "blue_eyes", count: 3279}
|
|
];
|
|
res(db.filter(t => t.tag.includes(req.q.toLowerCase())));
|
|
});
|
|
}
|
|
|
|
function generateMockPostData(req) {
|
|
return new Promise(resolve => {
|
|
setTimeout(() => {
|
|
resolve({
|
|
post_id: req.post_id,
|
|
file_name: `${req.post_id}.jpg`,
|
|
date: "2026-05-24",
|
|
rating: "general",
|
|
score: 0,
|
|
size: "3916x3062",
|
|
source: "#",
|
|
media_type: "image",
|
|
media_url: "data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22800%22%20height%3D%22600%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22%23444%22%2F%3E%3Ctext%20x%3D%2250%25%22%20y%3D%2250%25%22%20fill%3D%22%23fff%22%20font-size%3D%2224%22%20text-anchor%3D%22middle%22%3E%D0%9F%D0%BB%D0%B5%D0%B9%D1%81%D1%85%D0%BE%D0%BB%D0%B4%D0%B5%D1%80%3C%2Ftext%3E%3C%2Fsvg%3E",
|
|
tags: [
|
|
"1boy", "absurdres", "arknights", "blue_hair", "blue_neckerchief",
|
|
"commentary", "english_commentary", "gloves", "hat", "highres",
|
|
"holding", "holding_umbrella", "infection_monitor_(arknights)",
|
|
"looking_at_viewer", "male_focus", "mizuki_(arknights)", "neckerchief",
|
|
"pink_eyes", "pppmepl", "rain", "smile", "solo", "teruterubouzu",
|
|
"trap", "umbrella", "upper_body"
|
|
]
|
|
});
|
|
}, 300);
|
|
});
|
|
}
|
|
|
|
fetchPost();
|
|
</script>
|
|
</body>
|
|
</html> |