.
гостевые
Сообщений 1 страница 7 из 7
Поделиться22026-05-05 15:53:59
[hideprofile]
[html]<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>Гостевая книга | ON:AIR — K-pop шоу</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: system-ui, 'Segoe UI', 'Poppins', 'Noto Sans KR', sans-serif;
}
body {
background: linear-gradient(145deg, #faf0f5 0%, #f7e9f0 100%);
min-height: 50vh;
padding: 2rem 1.5rem;
}
.container {
max-width: 1000px;
margin: 0 auto;
}
/* Хедер с атмосферой шоу */
.hero {
text-align: center;
margin-bottom: 2.5rem;
}
.hero h1 {
font-size: 3.2rem;
background: linear-gradient(135deg, #FF3B6F, #8B5CF6, #2DD4BF);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
letter-spacing: -0.5px;
display: inline-block;
text-shadow: 0 2px 5px rgba(0,0,0,0.05);
}
.hero p {
color: #4a4a5a;
font-size: 1.1rem;
margin-top: 0.5rem;
font-weight: 500;
}
.badge-live {
background: #ff2e63;
color: white;
font-size: 0.7rem;
padding: 0.2rem 0.8rem;
border-radius: 40px;
display: inline-block;
margin-top: 0.8rem;
font-weight: 600;
letter-spacing: 1px;
}
/* ГЛАВНАЯ СЕТКА: 2 КОЛОНКИ (мальчики/девочки) */
.two-columns {
display: flex;
flex-wrap: wrap;
gap: 2rem;
margin-bottom: 3rem;
}
.column {
flex: 1;
min-width: 280px;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(2px);
border-radius: 2rem;
box-shadow: 0 20px 35px -12px rgba(0,0,0,0.15);
overflow: hidden;
transition: transform 0.2s;
border: 1px solid rgba(255,255,240,0.8);
}
.column:hover {
transform: translateY(-5px);
}
.column-header {
padding: 1.2rem 1.5rem;
background: white;
border-bottom: 3px solid;
display: flex;
align-items: center;
gap: 0.6rem;
}
.boys .column-header {
border-bottom-color: #3b82f6;
background: #f0f9ff;
}
.girls .column-header {
border-bottom-color: #ec489a;
background: #fff0f7;
}
.column-header h2 {
font-size: 1.8rem;
font-weight: 700;
}
.boys h2 { color: #2563eb; }
.girls h2 { color: #db2777; }
.entry-list {
padding: 1rem 1.2rem;
max-height: 500px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 0.9rem;
background: rgba(255,248,245,0.5);
}
/* Стиль карточки гостя: ateez - (ссылка) choi san */
.guest-card {
background: white;
border-radius: 1.2rem;
padding: 0.9rem 1.2rem;
box-shadow: 0 4px 10px rgba(0,0,0,0.02);
transition: all 0.2s;
border: 1px solid #ffe7f0;
word-break: break-word;
}
.guest-card:hover {
border-color: #ffb7c5;
background: #fffbfd;
}
.guest-line {
font-size: 1rem;
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 0.3rem 0.5rem;
}
.group-name {
font-weight: 800;
color: #1e293b;
background: #f1f5f9;
padding: 0.2rem 0.6rem;
border-radius: 40px;
font-size: 0.8rem;
}
.guest-link {
color: #6366f1;
text-decoration: none;
font-weight: 500;
border-bottom: 1px dashed #cbd5e1;
}
.guest-link:hover {
color: #ec489a;
border-bottom-color: #f472b6;
}
.guest-name {
font-weight: 700;
background: linear-gradient(145deg, #2d2f36, #1f1f2a);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
font-size: 1rem;
}
.empty-message {
text-align: center;
color: #aaa8b8;
font-style: italic;
padding: 2rem;
font-size: 0.9rem;
}
/* ФОРМА ДОБАВЛЕНИЯ (универсальный блок) */
.form-section {
background: white;
border-radius: 2rem;
padding: 1.8rem;
margin-top: 1rem;
box-shadow: 0 20px 35px -12px rgba(0,0,0,0.1);
border: 1px solid #ffe2ef;
}
.form-title {
font-size: 1.5rem;
font-weight: 700;
margin-bottom: 1.2rem;
display: flex;
align-items: center;
gap: 0.6rem;
}
.form-title span {
background: #fdeef4;
padding: 0.2rem 0.8rem;
border-radius: 50px;
font-size: 0.8rem;
}
.form-grid {
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin-bottom: 1.5rem;
}
.input-group {
flex: 1;
min-width: 170px;
}
.input-group label {
display: block;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: #6b6b7e;
margin-bottom: 0.3rem;
}
.input-group input, .input-group select {
width: 100%;
padding: 0.8rem 1rem;
border-radius: 1.2rem;
border: 1.5px solid #edd3df;
background: #fefafc;
font-size: 0.9rem;
transition: 0.2s;
}
.input-group input:focus, .input-group select:focus {
outline: none;
border-color: #f472b6;
box-shadow: 0 0 0 3px rgba(236,72,153,0.1);
}
.gender-selector {
display: flex;
gap: 1rem;
align-items: center;
background: #faf3f7;
padding: 0.4rem 1rem;
border-radius: 3rem;
}
.gender-selector label {
display: flex;
align-items: center;
gap: 0.4rem;
font-weight: 500;
color: #4a4a5e;
}
.add-button {
background: linear-gradient(95deg, #FF3B6F, #C850C0);
border: none;
padding: 0.8rem 2rem;
border-radius: 3rem;
color: white;
font-weight: bold;
font-size: 1rem;
cursor: pointer;
transition: 0.2s;
box-shadow: 0 5px 12px rgba(255,59,111,0.3);
}
.add-button:hover {
transform: scale(1.02);
background: linear-gradient(95deg, #f82b61, #b53cad);
box-shadow: 0 8px 18px rgba(255,59,111,0.4);
}
.sample-note {
font-size: 0.75rem;
color: #857e8c;
margin-top: 0.8rem;
text-align: center;
background: #fcf4f8;
padding: 0.5rem;
border-radius: 2rem;
}
hr {
margin: 1rem 0;
border-color: #ffdae9;
}
footer {
text-align: center;
margin-top: 2rem;
font-size: 0.75rem;
color: #a48f9b;
}
@media (max-width: 780px) {
body {
padding: 1rem;
}
.hero h1 {
font-size: 2.3rem;
}
.column-header h2 {
font-size: 1.4rem;
}
}
/* Скролл для списков красивый */
.entry-list::-webkit-scrollbar {
width: 5px;
}
.entry-list::-webkit-scrollbar-track {
background: #ffecf0;
border-radius: 10px;
}
.entry-list::-webkit-scrollbar-thumb {
background: #f4a0bd;
border-radius: 10px;
}
</style>
</head>
<body>
<div class="container">
<div class="hero">
<h1>🎤 ON:AIR <span style="font-size: 2rem;">🎶</span></h1>
<p>музыкальное шоу | корейская волна · гостиная фанатов</p>
<div class="badge-live">✨ LIVE EDITION ✨</div>
</div>
<!-- Два блока: мальчики и девочки -->
<div class="two-columns">
<!-- Блок МАЛЬЧИКИ (BOYS) -->
<div class="column boys">
<div class="column-header">
<span>🧑🎤</span>
<h2>МАЛЬЧИКИ</h2>
<span style="font-size: 0.75rem; background:#eef2ff; padding:0.2rem 0.7rem; border-radius: 30px;">보이그룹</span>
</div>
<div id="boysList" class="entry-list">
<!-- Динамические карточки будут тут -->
<div class="empty-message">✨ пока нет отзывов, добавь своего любимого айдола ✨</div>
</div>
</div>
<!-- Блок ДЕВОЧКИ (GIRLS) -->
<div class="column girls">
<div class="column-header">
<span>👩🎤</span>
<h2>ДЕВОЧКИ</h2>
<span style="font-size: 0.75rem; background:#ffe4f0; padding:0.2rem 0.7rem; border-radius: 30px;">걸그룹</span>
</div>
<div id="girlsList" class="entry-list">
<div class="empty-message">🌸 добавь первую запись для девочек 🌸</div>
</div>
</div>
</div>
<!-- Форма добавления гостя (имя, группа, ссылка, выбор пола) -->
<div class="form-section">
<div class="form-title">
📝 Добавить в гостевую книгу
<span>ON:AIR</span>
</div>
<div class="form-grid">
<div class="input-group">
<label>🌟 Имя артиста / участника</label>
<input type="text" id="artistName" placeholder="например: Choi San, Hanni, Karina ..." autocomplete="off">
</div>
<div class="input-group">
<label>🎤 Группа / сольный проект</label>
<input type="text" id="groupName" placeholder="например: ATEEZ, NewJeans, IVE ..." autocomplete="off">
</div>
<div class="input-group">
<label>🔗 Ссылка (YouTube, Twitter, фан-арт...)</label>
<input type="url" id="linkUrl" placeholder="https://..." autocomplete="off">
</div>
<div class="input-group">
<label>⚧️ Категория</label>
<div class="gender-selector">
<label>
<input type="radio" name="gender" value="boy" checked> 🧑 мальчики
</label>
<label>
<input type="radio" name="gender" value="girl"> 👩 девочки
</label>
</div>
</div>
</div>
<div style="display: flex; justify-content: center; margin-top: 0.5rem;">
<button class="add-button" id="addEntryBtn">➕ Добавить запись →</button>
</div>
<div class="sample-note">
💡 Пример готового стиля: <strong>ATEEZ</strong> - <a href="#" style="text-decoration:none;">(ссылка)</a> <strong>Choi San</strong><br>
Заполни поля и запись появится в выбранном блоке.
</div>
</div>
<footer>
🌟 Гостевая книга шоу ON:AIR — K-pop фан-сообщество. Каждая запись в стиле «группа - (ссылка) имя артиста» 🌟
</footer>
</div>
<script>
// ----- Хранилище данных -----
// Структура: { id, name, group, link, gender } gender: 'boy' / 'girl'
let entries = [];
// Загрузка из localStorage, если есть
function loadFromStorage() {
const saved = localStorage.getItem('onair_guestbook');
if (saved) {
try {
entries = JSON.parse(saved);
} catch(e) { console.warn(e); entries = []; }
}
if (!entries || entries.length === 0) {
// Небольшие демо-записи для красоты, чтобы блоки не были пустыми (но можно и без них, но пусть будет мило)
entries = [
{ id: Date.now() + 1, name: "Choi San", group: "ATEEZ", link: "https://youtu.be/dQw4w9WgXcQ?feature=shared", gender: "boy" },
{ id: Date.now() + 2, name: "Hongjoong", group: "ATEEZ", link: "https://youtube.com", gender: "boy" },
{ id: Date.now() + 3, name: "Hanni", group: "NewJeans", link: "https://youtu.be/example", gender: "girl" },
{ id: Date.now() + 4, name: "Karina", group: "aespa", link: "https://youtu.be/example2", gender: "girl" },
{ id: Date.now() + 5, name: "Yeonjun", group: "TXT", link: "https://youtu.be/example3", gender: "boy" },
{ id: Date.now() + 6, name: "Winter", group: "aespa", link: "https://youtu.be/example4", gender: "girl" }
];
saveToStorage();
}
}
function saveToStorage() {
localStorage.setItem('onair_guestbook', JSON.stringify(entries));
}
// Функция рендеринга двух блоков (мальчики / девочки)
function renderEntries() {
const boysContainer = document.getElementById('boysList');
const girlsContainer = document.getElementById('girlsList');
// Фильтруем записи
const boys = entries.filter(entry => entry.gender === 'boy');
const girls = entries.filter(entry => entry.gender === 'girl');
// Рендер мальчиков
if (boys.length === 0) {
boysContainer.innerHTML = '<div class="empty-message">🎤 пока нет записей для мальчиков, добавь своего любимчика!</div>';
} else {
boysContainer.innerHTML = boys.map(entry => createGuestCardHTML(entry)).join('');
}
// Рендер девочек
if (girls.length === 0) {
girlsContainer.innerHTML = '<div class="empty-message">💖 пока нет записей для девочек, поделись любовью!</div>';
} else {
girlsContainer.innerHTML = girls.map(entry => createGuestCardHTML(entry)).join('');
}
}
// Создание HTML карточки в стиле: ateez - (ссылка) choi san
// По заданию "ateez - (ссылка) choi san"
function createGuestCardHTML(entry) {
// Экранирование спецсимволов для защиты XSS
const safeGroup = escapeHtml(entry.group) || "группа";
const safeName = escapeHtml(entry.name) || "имя";
let safeLink = entry.link ? entry.link.trim() : "#";
// если ссылка невалидная или пустая, то ставим заглушку, но сохраняем возможность клика
if (safeLink === "" || (!safeLink.startsWith("http://") && !safeLink.startsWith("https://"))) {
// если ссылка не начинается с протокола, но не пустая, делаем https префикс? но лучше просто заглушка
if(safeLink !== "#" && safeLink !== "") {
safeLink = "https://" + safeLink;
} else {
safeLink = "#";
}
}
// ссылка с атрибутом target blank для удобства
// создаем элемент согласно стилю: группа - (ссылка) имя
// Шаблон: "<span class=\"group-name\">ATEEZ</span> - <a href='ссылка' class='guest-link' target='_blank'>(ссылка)</a> <span class='guest-name'>Choi San</span>"
return `
<div class="guest-card" data-id="${entry.id}">
<div class="guest-line">
<span class="group-name">${safeGroup}</span>
<span> - </span>
<a href="${safeLink}" class="guest-link" target="_blank" rel="noopener noreferrer">(ссылка)</a>
<span class="guest-name">${safeName}</span>
</div>
</div>
`;
}
// вспомогательная функция экранирования
function escapeHtml(str) {
if (!str) return '';
return str.replace(/[&<>]/g, function(m) {
if (m === '&') return '&';
if (m === '<') return '<';
if (m === '>') return '>';
return m;
}).replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, function(c) {
return c;
});
}
// Добавление новой записи
function addEntry() {
// получаем поля
const nameInput = document.getElementById('artistName');
const groupInput = document.getElementById('groupName');
const linkInput = document.getElementById('linkUrl');
const genderRadio = document.querySelector('input[name="gender"]:checked');
let name = nameInput.value.trim();
let group = groupInput.value.trim();
let link = linkInput.value.trim();
const gender = genderRadio ? genderRadio.value : 'boy';
// Валидация: имя и группа обязательны
if (name === "") {
alert("❄️ Пожалуйста, введите Имя артиста / участника!");
return;
}
if (group === "") {
alert("🎶 Укажите группу или сольный проект!");
return;
}
// ссылка необязательна, но если ввели — проверяем протокол, но не строго
let finalLink = link;
if (finalLink !== "" && !finalLink.startsWith("http://") && !finalLink.startsWith("https://")) {
// если пользователь ввел что-то типа "youtube.com/..." попробуем добавить https
finalLink = "https://" + finalLink;
}
// Если ссылка пустая, то оставляем пустую строку, но карточка обработает как #, но лучше чтобы ссылка была с заглушкой -
// но по дизайну ссылка отображается как текст "(ссылка)" – она будет вести на введённый адрес или пустышку.
// Однако в createGuestCardHTML если safeLink пустой, то ставим "#".
if(finalLink === "") finalLink = ""; // тогда внутри функции станет '#', ссылка нерабочая но элемент есть.
// создаем новый объект
const newEntry = {
id: Date.now() + Math.random() * 10000,
name: name,
group: group,
link: finalLink,
gender: gender,
};
entries.push(newEntry);
saveToStorage();
renderEntries();
// очищаем форму, кроме радиокнопки (оставляем выбранный пол как был, удобно)
nameInput.value = "";
groupInput.value = "";
linkInput.value = "";
// можно фокус на имя для удобства
nameInput.focus();
// небольшой фидбек (опционально)
const toastMsg = document.createElement('div');
toastMsg.innerText = `✨ ${name} добавлен в ${gender === 'boy' ? 'мальчики' : 'девочки'} ✨`;
toastMsg.style.position = 'fixed';
toastMsg.style.bottom = '20px';
toastMsg.style.left = '50%';
toastMsg.style.transform = 'translateX(-50%)';
toastMsg.style.backgroundColor = '#1e1a2f';
toastMsg.style.color = '#fff0f5';
toastMsg.style.padding = '0.5rem 1.2rem';
toastMsg.style.borderRadius = '3rem';
toastMsg.style.fontSize = '0.8rem';
toastMsg.style.zIndex = '999';
toastMsg.style.backdropFilter = 'blur(4px)';
toastMsg.style.fontWeight = '500';
document.body.appendChild(toastMsg);
setTimeout(() => toastMsg.remove(), 2000);
}
function initDemoIfEmpty() {
if (entries.length === 0) {
entries = [
{ id: Date.now() + 10, name: "Choi San", group: "ATEEZ", link: "https://youtu.be/ATEEZ_SAN_fancam", gender: "boy" },
{ id: Date.now() + 11, name: "Jeon Soyeon", group: "(G)I-DLE", link: "https://youtu.be/soyeon_queen", gender: "girl" },
{ id: Date.now() + 12, name: "Felix", group: "Stray Kids", link: "https://youtu.be/felix_skz", gender: "boy" },
{ id: Date.now() + 13, name: "Wonyoung", group: "IVE", link: "https://youtu.be/wonyoung_star", gender: "girl" },
{ id: Date.now() + 14, name: "Beomgyu", group: "TXT", link: "https://youtu.be/beomgyu_melody", gender: "boy" }
];
saveToStorage();
}
}
// Обработчик кнопки добавить
document.getElementById('addEntryBtn').addEventListener('click', addEntry);
// Также добавим поддержку Enter в любом поле для удобства (нажатие Enter в любом инпуте добавляет запись)
const inputs = ['artistName', 'groupName', 'linkUrl'];
inputs.forEach(id => {
const el = document.getElementById(id);
if(el) {
el.addEventListener('keypress', function(e) {
if(e.key === 'Enter') {
e.preventDefault();
addEntry();
}
});
}
});
// запуск
loadFromStorage();
initDemoIfEmpty(); // повторная страховка
renderEntries();
</script>
</body>
</html>[/html]
Поделиться32026-05-05 15:55:35
[html]<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ON:AIR • Гостевая книга</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: system-ui, 'Segoe UI', 'Noto Sans KR', sans-serif;
}
body {
background: linear-gradient(145deg, #0b0820, #03000c);
min-height: 100vh;
padding: 2rem 1.5rem;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
/* хедер */
.hero {
text-align: center;
margin-bottom: 2.5rem;
}
.hero h1 {
font-size: 2.8rem;
background: linear-gradient(135deg, #FFB8E7, #AA7AFF, #6EE7FF);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
}
.hero p {
color: #a89ed0;
margin-top: 0.3rem;
}
/* две колонки */
.two-columns {
display: flex;
flex-wrap: wrap;
gap: 2rem;
}
.column {
flex: 1;
min-width: 260px;
background: rgba(25, 20, 50, 0.6);
backdrop-filter: blur(10px);
border-radius: 1.8rem;
border: 1px solid rgba(255, 150, 200, 0.3);
overflow: hidden;
}
.column-header {
padding: 1rem 1.5rem;
border-bottom: 2px solid;
display: flex;
justify-content: space-between;
align-items: center;
}
.boys .column-header { border-bottom-color: #4c6ef5; }
.girls .column-header { border-bottom-color: #f065aa; }
.column-header h2 {
font-size: 1.8rem;
font-weight: 700;
}
.boys h2 { color: #88aaff; }
.girls h2 { color: #ff9ace; }
.counter {
background: rgba(0,0,0,0.5);
padding: 0.2rem 0.7rem;
border-radius: 40px;
font-size: 0.75rem;
color: #ddd9ff;
}
.entry-list {
padding: 1.2rem;
max-height: 500px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 0.8rem;
}
.guest-card {
background: rgba(35, 28, 65, 0.6);
border-radius: 1.2rem;
padding: 0.8rem 1rem;
border: 1px solid rgba(255, 200, 230, 0.2);
transition: 0.2s;
}
.guest-card:hover {
background: rgba(55, 45, 90, 0.8);
border-color: #ff9fcd;
}
.guest-line {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 0.3rem 0.6rem;
font-size: 0.95rem;
}
.group-badge {
font-weight: 800;
background: #1e173b;
padding: 0.2rem 0.7rem;
border-radius: 30px;
font-size: 0.75rem;
color: #ffd6ed;
border-left: 2px solid #ff79b0;
}
.guest-link {
color: #b3aaff;
text-decoration: none;
font-size: 0.8rem;
background: rgba(100, 80, 200, 0.3);
padding: 0.1rem 0.55rem;
border-radius: 30px;
}
.guest-link:hover {
background: #c084fc;
color: #0a041f;
}
.guest-name {
font-weight: 700;
background: linear-gradient(145deg, #FFD5F0, #D5B8FF);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
}
.empty-message {
text-align: center;
color: #a094c2;
padding: 2rem;
font-size: 0.85rem;
font-style: italic;
}
footer {
text-align: center;
margin-top: 2rem;
font-size: 0.7rem;
color: #6f62a0;
}
@media (max-width: 700px) {
.hero h1 { font-size: 2rem; }
.column-header h2 { font-size: 1.4rem; }
}
</style>
</head>
<body>
<div class="container">
<div class="hero">
<h1>🎤 ON:AIR • 게스트북</h1>
<p>музыкальное шоу | гостевая книга фанатов</p>
</div>
<div class="two-columns">
<!-- Мальчики -->
<div class="column boys">
<div class="column-header">
<h2>🧑🎤 МАЛЬЧИКИ</h2>
<div class="counter" id="boysCounter">0</div>
</div>
<div id="boysList" class="entry-list"></div>
</div>
<!-- Девочки -->
<div class="column girls">
<div class="column-header">
<h2>👩🎤 ДЕВОЧКИ</h2>
<div class="counter" id="girlsCounter">0</div>
</div>
<div id="girlsList" class="entry-list"></div>
</div>
</div>
<footer>✨ каждая запись в стиле: группа — (ссылка) имя ✨</footer>
</div>
<script>
// готовые записи в нужном формате
const entries = [
// мальчики
{ group: "ATEEZ", name: "Choi San", link: "https://youtu.be/ATEEZ_SAN", gender: "boy" },
{ group: "ATEEZ", name: "Hongjoong", link: "https://youtu.be/hongjoong", gender: "boy" },
{ group: "Stray Kids", name: "Felix", link: "https://youtu.be/felix_skz", gender: "boy" },
{ group: "TXT", name: "Yeonjun", link: "https://youtu.be/yeonjun", gender: "boy" },
{ group: "BTS", name: "Jungkook", link: "https://youtu.be/jungkook", gender: "boy" },
{ group: "ENHYPEN", name: "Ni-ki", link: "https://youtu.be/niki", gender: "boy" },
// девочки
{ group: "aespa", name: "Karina", link: "https://youtu.be/karina", gender: "girl" },
{ group: "aespa", name: "Winter", link: "https://youtu.be/winter", gender: "girl" },
{ group: "NewJeans", name: "Hanni", link: "https://youtu.be/hanni", gender: "girl" },
{ group: "IVE", name: "Wonyoung", link: "https://youtu.be/wonyoung", gender: "girl" },
{ group: "(G)I-DLE", name: "Soyeon", link: "https://youtu.be/soyeon", gender: "girl" },
{ group: "LE SSERAFIM", name: "Chaewon", link: "https://youtu.be/chaewon", gender: "girl" }
];
function render() {
const boys = entries.filter(e => e.gender === 'boy');
const girls = entries.filter(e => e.gender === 'girl');
document.getElementById('boysCounter').innerText = boys.length;
document.getElementById('girlsCounter').innerText = girls.length;
const boysContainer = document.getElementById('boysList');
const girlsContainer = document.getElementById('girlsList');
boysContainer.innerHTML = boys.map(entry => `
<div class="guest-card">
<div class="guest-line">
<span class="group-badge">${escapeHtml(entry.group)}</span>
<span>–</span>
<a href="${entry.link}" class="guest-link" target="_blank" rel="noopener">(ссылка)</a>
<span class="guest-name">${escapeHtml(entry.name)}</span>
</div>
</div>
`).join('');
girlsContainer.innerHTML = girls.map(entry => `
<div class="guest-card">
<div class="guest-line">
<span class="group-badge">${escapeHtml(entry.group)}</span>
<span>–</span>
<a href="${entry.link}" class="guest-link" target="_blank" rel="noopener">(ссылка)</a>
<span class="guest-name">${escapeHtml(entry.name)}</span>
</div>
</div>
`).join('');
if (boys.length === 0) boysContainer.innerHTML = '<div class="empty-message">✨ пока нет записей ✨</div>';
if (girls.length === 0) girlsContainer.innerHTML = '<div class="empty-message">🌸 пока нет записей 🌸</div>';
}
function escapeHtml(str) {
if (!str) return '';
return str.replace(/[&<>]/g, function(m) {
if (m === '&') return '&';
if (m === '<') return '<';
if (m === '>') return '>';
return m;
});
}
render();
</script>
</body>
</html>[/html]
Поделиться42026-05-05 15:58:52
[html]<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ON:AIR · айдолы</title>
<style>
*{margin:0;padding:0;box-sizing:border-box;}
body{background:#faf7f2;font-family:system-ui,sans-serif;padding:24px 16px;}
.wrap{max-width:1000px;margin:0 auto;}
h1{text-align:center;font-size:2rem;background:linear-gradient(135deg,#5e7e8e,#c2826b);-webkit-background-clip:text;background-clip:text;color:transparent;}
.sub{text-align:center;color:#a08d77;font-size:0.75rem;margin-bottom:28px;}
.grid{display:flex;gap:20px;flex-wrap:wrap;}
.card{flex:1;min-width:260px;background:#fff;border-radius:28px;box-shadow:0 4px 12px rgba(0,0,0,0.03);overflow:hidden;}
.head{padding:14px 18px;display:flex;justify-content:space-between;border-bottom:1px solid #f0e4d8;}
.head h2{font-size:1.4rem;font-weight:550;}
.boys .head h2{color:#4f7c8f;}
.girls .head h2{color:#cc867b;}
.cnt{background:#ede3d9;padding:2px 10px;border-radius:30px;font-size:0.7rem;}
.msgs{padding:14px;max-height:460px;overflow-y:auto;display:flex;flex-direction:column;gap:10px;}
.idol{background:#fefbf8;border-radius:20px;padding:12px 16px;border-left:4px solid;}
.boys .idol{border-left-color:#6f9eb3;}
.girls .idol{border-left-color:#e0a196;}
.name{font-weight:600;font-size:0.9rem;color:#3d352c;}
.empty{text-align:center;color:#cbbaa8;padding:30px;}
footer{text-align:center;margin-top:24px;font-size:0.65rem;color:#c2ae9a;}
</style>
</head>
<body>
<div class="wrap">
<h1>🎧 ON:AIR</h1>
<div class="sub">гостевая книга · айдолы</div>
<div class="grid">
<div class="card boys">
<div class="head"><h2>🌟 МАЛЬЧИКИ</h2><span class="cnt" id="bc">0</span></div>
<div class="msgs" id="bl"></div>
</div>
<div class="card girls">
<div class="head"><h2>🌸 ДЕВОЧКИ</h2><span class="cnt" id="gc">0</span></div>
<div class="msgs" id="gl"></div>
</div>
</div>
<footer>✧ только имена айдолов ✧</footer>
</div>
<script>
const data = {
boys: [
"ateez - choi san",
"ateez - kim hongjoong",
"stray kids - bang chan",
"stray kids - lee know",
"txt - choi yeonjun",
"txt - kang taehyun",
"enhypen - jungwon",
"enhypen - ni-ki",
"bts - jungkook",
"bts - jimin",
"nct 127 - taeyong",
"nct dream - jeno",
"seventeen - vernon",
"seventeen - mingyu"
],
girls: [
"aespa - karina",
"aespa - winter",
"blackpink - jennie",
"blackpink - rosé",
"ive - jang wonyoung",
"ive - an yujin",
"newjeans - haerin",
"newjeans - minji",
"gidle - soyeon",
"gidle - miyeon",
"le sserafim - sakura",
"le sserafim - kim chaewon",
"twice - nayeon",
"twice - sana"
]
};
const esc = s => s.replace(/[&<>]/g, m => m=='&'?'&':m=='<'?'<':'>');
const render = () => {
const bl = document.getElementById('bl'), bc = document.getElementById('bc');
bc.innerText = data.boys.length;
bl.innerHTML = data.boys.map(n => `<div class="idol"><div class="name">✨ ${esc(n)}</div></div>`).join('');
const gl = document.getElementById('gl'), gc = document.getElementById('gc');
gc.innerText = data.girls.length;
gl.innerHTML = data.girls.map(n => `<div class="idol"><div class="name">🌸 ${esc(n)}</div></div>`).join('');
};
render();
</script>
</body>
</html>[/html]
Поделиться52026-05-05 15:59:18
[hideprofile][html]<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ON:AIR · айдолы</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #faf7f2;
font-family: system-ui, 'Segoe UI', 'Pretendard', sans-serif;
padding: 30px 20px;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
/* основная обёртка: картинка слева + блоки справа */
.main-layout {
max-width: 500px;
width: 100%;
margin: 0 auto;
display: flex;
gap: 32px;
align-items: flex-start;
flex-wrap: wrap;
}
/* Левая колонка с микрофоном */
.mic-side {
flex: 0 0 140px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
padding-top: 40px;
}
.mic-icon {
font-size: 90px;
filter: drop-shadow(0 8px 14px rgba(0,0,0,0.08));
transition: transform 0.2s ease;
}
.mic-icon:hover {
transform: scale(1.02);
}
.mic-caption {
text-align: center;
margin-top: 12px;
font-size: 0.7rem;
color: #b8a68c;
letter-spacing: 0.5px;
font-weight: 500;
}
/* Правая колонка (контент) */
.content-right {
flex: 1;
min-width: 260px;
}
/* хедер шоу */
.show-header {
text-align: center;
margin-bottom: 28px;
}
.show-header h1 {
font-size: 2.2rem;
background: linear-gradient(135deg, #5e7e8e, #c2826b);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
letter-spacing: -0.3px;
}
.show-header .sub {
color: #a08d77;
font-size: 0.75rem;
margin-top: 4px;
}
/* карточки блоков */
.guest-block {
background: #ffffffec;
border-radius: 32px;
box-shadow: 0 8px 22px rgba(0, 0, 0, 0.03), 0 1px 2px rgba(0, 0, 0, 0.02);
overflow: hidden;
margin-bottom: 28px;
backdrop-filter: blur(2px);
transition: 0.2s;
}
.guest-block:hover {
transform: translateY(-2px);
}
.block-header {
padding: 14px 20px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #f0e2d6;
}
.block-header h2 {
font-size: 1.5rem;
font-weight: 560;
letter-spacing: -0.2px;
}
.boys .block-header h2 {
color: #4f7c8f;
}
.girls .block-header h2 {
color: #cc867b;
}
.count-badge {
background: #ede3d9;
padding: 4px 14px;
border-radius: 40px;
font-size: 0.7rem;
font-weight: 600;
color: #6e5c4b;
}
/* список айдолов */
.idols-list {
padding: 16px 18px;
max-height: 360px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 10px;
}
.idol-card {
background: #fefbf8;
border-radius: 20px;
padding: 12px 16px;
border-left: 5px solid;
transition: 0.05s linear;
}
.boys .idol-card {
border-left-color: #6f9eb3;
}
.girls .idol-card {
border-left-color: #e0a196;
}
.idol-name {
font-weight: 600;
font-size: 0.92rem;
color: #3d352c;
display: flex;
align-items: center;
gap: 8px;
}
.idol-name .emoji-boy {
font-size: 0.9rem;
}
.idol-name .emoji-girl {
font-size: 0.9rem;
}
.empty-message {
text-align: center;
color: #cbbaa8;
padding: 28px 16px;
font-style: italic;
font-size: 0.85rem;
}
footer {
text-align: center;
margin-top: 20px;
font-size: 0.65rem;
color: #c2ae9a;
border-top: 1px solid #efe1d4;
padding-top: 16px;
}
/* скролл */
.idols-list::-webkit-scrollbar {
width: 4px;
}
.idols-list::-webkit-scrollbar-track {
background: #ede3d9;
border-radius: 10px;
}
.idols-list::-webkit-scrollbar-thumb {
background: #cfbaa6;
border-radius: 10px;
}
@media (max-width: 680px) {
.main-layout {
flex-direction: column;
align-items: center;
}
.mic-side {
flex: 0 0 auto;
flex-direction: row;
gap: 12px;
padding-top: 0;
width: 100%;
justify-content: center;
}
.mic-icon {
font-size: 60px;
}
.mic-caption {
margin-top: 0;
}
}
</style>
</head>
<body>
<div class="main-layout">
<!-- левая часть: картинка микрофона -->
<div class="mic-side">
<div class="mic-icon">🎤</div>
<div class="mic-caption">ON AIR · 스튜디오</div>
</div>
<!-- правая часть: контент -->
<div class="content-right">
<div class="show-header">
<h1>🎧 ON:AIR</h1>
<div class="sub">музыкальное шоу · гостевая книга айдолов</div>
</div>
<!-- БЛОК МАЛЬЧИКИ -->
<div class="guest-block boys">
<div class="block-header">
<h2>🌟 МАЛЬЧИКИ</h2>
<span class="count-badge" id="boysCount">0</span>
</div>
<div class="idols-list" id="boysList">
<div class="empty-message">✨ загрузка...</div>
</div>
</div>
<!-- БЛОК ДЕВОЧКИ (под мальчиками) -->
<div class="guest-block girls">
<div class="block-header">
<h2>🌸 ДЕВОЧКИ</h2>
<span class="count-badge" id="girlsCount">0</span>
</div>
<div class="idols-list" id="girlsList">
<div class="empty-message">🌸 загрузка...</div>
</div>
</div>
<footer>✧ только имена айдолов · on:air guestbook ✧</footer>
</div>
</div>
<script>
// список айдолов в формате "группа - имя"
const idolsData = {
boys: [
"ateez - choi san",
"ateez - kim hongjoong",
"stray kids - bang chan",
"stray kids - lee know",
"txt - choi yeonjun",
"txt - kang taehyun",
"enhypen - jungwon",
"enhypen - ni-ki",
"bts - jungkook",
"bts - jimin",
"nct 127 - taeyong",
"nct dream - jeno",
"seventeen - vernon",
"seventeen - mingyu",
"the boyz - juyeon",
"monsta x - shownu"
],
girls: [
"aespa - karina",
"aespa - winter",
"blackpink - jennie",
"blackpink - rosé",
"ive - jang wonyoung",
"ive - an yujin",
"newjeans - haerin",
"newjeans - minji",
"gidle - soyeon",
"gidle - miyeon",
"le sserafim - sakura",
"le sserafim - kim chaewon",
"twice - nayeon",
"twice - sana",
"red velvet - irene",
"itzy - ryujin"
]
};
// защита от XSS
function escapeHtml(str) {
if (!str) return '';
return str.replace(/[&<>]/g, function(m) {
if (m === '&') return '&';
if (m === '<') return '<';
if (m === '>') return '>';
return m;
});
}
function renderBlocks() {
// мальчики
const boysContainer = document.getElementById('boysList');
const boysCountSpan = document.getElementById('boysCount');
boysCountSpan.innerText = idolsData.boys.length;
if (idolsData.boys.length === 0) {
boysContainer.innerHTML = '<div class="empty-message">💬 пока нет имён</div>';
} else {
boysContainer.innerHTML = idolsData.boys.map(name => `
<div class="idol-card">
<div class="idol-name">
<span class="emoji-boy">✨</span> ${escapeHtml(name)}
</div>
</div>
`).join('');
}
// девочки
const girlsContainer = document.getElementById('girlsList');
const girlsCountSpan = document.getElementById('girlsCount');
girlsCountSpan.innerText = idolsData.girls.length;
if (idolsData.girls.length === 0) {
girlsContainer.innerHTML = '<div class="empty-message">🌸 пока нет имён</div>';
} else {
girlsContainer.innerHTML = idolsData.girls.map(name => `
<div class="idol-card">
<div class="idol-name">
<span class="emoji-girl">🌸</span> ${escapeHtml(name)}
</div>
</div>
`).join('');
}
}
renderBlocks();
</script>
</body>
</html>[/html]
Поделиться62026-05-05 16:02:15
[html]<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>ON:AIR | Корейское музыкальное шоу – гостевая книга айдолов</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #fefaf5;
font-family: 'Inter', 'Pretendard', system-ui, -apple-system, 'Segoe UI', 'Noto Sans KR', sans-serif;
min-height: 100vh;
padding: 2rem 2rem 1.5rem;
position: relative;
}
/* фоновые полупрозрачные элементы – визуальный шарм корейского шоу */
body::before {
content: "♪";
font-size: 220px;
position: fixed;
bottom: -30px;
left: -50px;
opacity: 0.03;
font-family: monospace;
pointer-events: none;
transform: rotate(-10deg);
}
body::after {
content: "🎵";
font-size: 180px;
position: fixed;
top: 10%;
right: -40px;
opacity: 0.04;
pointer-events: none;
transform: rotate(15deg);
}
.main-container {
max-width: 1300px;
margin: 0 auto;
position: relative;
z-index: 2;
}
/* хедер с атмосферой live-шоу */
.showcase-header {
text-align: center;
margin-bottom: 3rem;
position: relative;
}
.glow-text {
font-size: 3.3rem;
font-weight: 800;
letter-spacing: -0.02em;
background: linear-gradient(125deg, #F15F5F, #FFB347, #6C8C9E);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
display: inline-block;
filter: drop-shadow(0 2px 5px rgba(0,0,0,0.05));
}
.tagline {
background: rgba(255, 248, 235, 0.8);
backdrop-filter: blur(4px);
display: inline-block;
padding: 0.4rem 1.6rem;
border-radius: 60px;
font-size: 0.8rem;
font-weight: 500;
color: #b4815c;
margin-top: 10px;
letter-spacing: 0.3px;
gap: 8px;
align-items: center;
justify-content: center;
}
.tagline span {
display: inline-flex;
align-items: center;
gap: 5px;
}
/* нестандартная сетка: мальчики – вверху слева, девочки – снизу справа */
.dynamic-grid {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 2rem;
margin: 2rem 0 1rem;
position: relative;
}
/* левая зона (здесь находится блок мальчиков и он выше) */
.left-area {
flex: 1.2;
min-width: 280px;
display: flex;
flex-direction: column;
gap: 1.5rem;
}
/* правая зона (содержит “визуальный твист” + блок девочек снизу) */
.right-area {
flex: 0.9;
min-width: 280px;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 1.5rem;
}
/* карточка-блок */
.idol-card-block {
background: rgba(255, 255, 255, 0.92);
backdrop-filter: blur(2px);
border-radius: 2rem;
overflow: hidden;
box-shadow: 0 20px 32px -12px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.02);
transition: transform 0.2s ease, box-shadow 0.2s;
border: 1px solid #f7ede2;
}
.idol-card-block:hover {
transform: translateY(-4px);
box-shadow: 0 26px 36px -14px rgba(0, 0, 0, 0.12);
}
/* заголовок блока */
.block-title {
padding: 1rem 1.8rem;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #ffede1;
flex-wrap: wrap;
}
.title-deco {
display: flex;
align-items: center;
gap: 10px;
}
.title-deco h2 {
font-size: 1.7rem;
font-weight: 640;
letter-spacing: -0.3px;
}
.boys .title-deco h2 { color: #46788e; }
.girls .title-deco h2 { color: #cb7f70; }
.count-pill {
background: #f1e8e0;
padding: 0.25rem 0.9rem;
border-radius: 60px;
font-size: 0.75rem;
font-weight: 600;
color: #6d5c4b;
}
/* список айдолов */
.idol-list {
padding: 1.2rem 1.5rem 1.8rem;
max-height: 440px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 12px;
}
.idol-item {
background: #ffffff;
border-radius: 1.5rem;
padding: 0.9rem 1.1rem;
display: flex;
align-items: center;
gap: 12px;
transition: all 0.15s;
border: 1px solid #ffefe2;
box-shadow: 0 1px 3px rgba(0,0,0,0.02);
}
.boys .idol-item {
border-left: 4px solid #7fa5b9;
}
.girls .idol-item {
border-left: 4px solid #e7b7ab;
}
.idol-avatar {
font-size: 1.7rem;
background: #fcf5ed;
width: 44px;
height: 44px;
border-radius: 30px;
display: flex;
align-items: center;
justify-content: center;
}
.idol-info {
flex: 1;
}
.idol-name-text {
font-weight: 600;
font-size: 0.95rem;
color: #2d2a24;
letter-spacing: -0.2px;
}
.group-tag {
font-size: 0.65rem;
color: #b2947c;
margin-top: 2px;
font-weight: 500;
}
/* визуальные корейские элементы (микрофон, свет, эквалайзер) */
.visual-elements {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
background: rgba(255, 247, 240, 0.6);
border-radius: 2rem;
padding: 1rem 1.3rem;
margin-bottom: 0.5rem;
gap: 12px;
backdrop-filter: blur(4px);
}
.eq-bars {
display: flex;
align-items: flex-end;
gap: 4px;
height: 32px;
}
.bar {
width: 5px;
background: #dcaea0;
border-radius: 4px;
transition: height 0.2s ease;
animation: pulse 1.4s infinite alternate;
}
.bar:nth-child(1) { height: 12px; animation-delay: 0s; }
.bar:nth-child(2) { height: 24px; animation-delay: 0.2s; background: #c2836b; }
.bar:nth-child(3) { height: 18px; animation-delay: 0.4s; }
.bar:nth-child(4) { height: 28px; animation-delay: 0.1s; background: #c2836b; }
.bar:nth-child(5) { height: 10px; animation-delay: 0.3s; }
@keyframes pulse {
0% { opacity: 0.5; transform: scaleY(0.8); }
100% { opacity: 1; transform: scaleY(1.1); }
}
.live-badge {
background: #ffcfb0;
border-radius: 60px;
padding: 5px 14px;
font-size: 0.75rem;
font-weight: 700;
color: #b4512c;
display: flex;
align-items: center;
gap: 6px;
}
.vinyl-icon {
font-size: 1.5rem;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
}
/* правый декоративный стафф */
.kpop-quote {
background: #fffdf9;
border-radius: 1.5rem;
padding: 1rem 1.4rem;
text-align: center;
border: 1px solid #faebdd;
font-size: 0.8rem;
color: #b68b6e;
font-style: italic;
backdrop-filter: blur(4px);
}
footer {
margin-top: 3rem;
text-align: center;
font-size: 0.7rem;
color: #d1bfae;
border-top: 1px solid #f6e9de;
padding-top: 1.8rem;
display: flex;
justify-content: center;
gap: 20px;
}
/* скролл */
.idol-list::-webkit-scrollbar {
width: 5px;
}
.idol-list::-webkit-scrollbar-track {
background: #f3ebe2;
border-radius: 10px;
}
.idol-list::-webkit-scrollbar-thumb {
background: #decbb8;
border-radius: 10px;
}
@media (max-width: 780px) {
body { padding: 1rem; }
.dynamic-grid { flex-direction: column; }
.left-area, .right-area { width: 100%; }
.glow-text { font-size: 2.4rem; }
}
</style>
</head>
<body>
<div class="main-container">
<!-- верхняя часть с тематикой ON:AIR -->
<div class="showcase-header">
<div class="glow-text">🎙️ ON:AIR</div>
<div class="tagline">
<span>🎧 KOREAN MUSIC SHOW</span> | <span>📡 LIVE STAGE</span>
</div>
</div>
<!-- динамическая сетка: мальчики вверху слева / девочки снизу справа -->
<div class="dynamic-grid">
<!-- ЛЕВАЯ ЗОНА (содержит блок мальчиков наверху) -->
<div class="left-area">
<!-- Блок МАЛЬЧИКИ — размещается в верхней части левой колонки -->
<div class="idol-card-block boys">
<div class="block-title">
<div class="title-deco">
<span style="font-size:1.7rem;">🌟</span>
<h2>МАЛЬЧИКИ</h2>
</div>
<div class="count-pill" id="boysCount">0</div>
</div>
<div class="idol-list" id="boysList">
<!-- динамический рендер -->
<div class="idol-item" style="justify-content:center; background:transparent;">✨ загрузка...</div>
</div>
</div>
<!-- дополнительный визуальный элемент под мальчиками (эффект студии) -->
<div class="visual-elements">
<div class="eq-bars">
<div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div>
</div>
<div class="live-badge">
<span>🔴</span> ON AIR
</div>
<div class="vinyl-icon">🎚️🎛️</div>
</div>
</div>
<!-- ПРАВАЯ ЗОНА: сначала свободное пространство, потом блок девочек снизу -->
<div class="right-area">
<!-- Декоративный цитатник / корейский визуал -->
<div class="kpop-quote">
<span>🎤 “음악은 우리의 언어”</span><br>
<span style="font-size:0.7rem;">✧ музыка — наш общий язык ✧</span>
</div>
<!-- Блок ДЕВОЧКИ — снизу справа -->
<div class="idol-card-block girls">
<div class="block-title">
<div class="title-deco">
<span style="font-size:1.7rem;">🌸</span>
<h2>ДЕВОЧКИ</h2>
</div>
<div class="count-pill" id="girlsCount">0</div>
</div>
<div class="idol-list" id="girlsList">
<div class="idol-item" style="justify-content:center; background:transparent;">🌸 загрузка...</div>
</div>
</div>
<!-- ещё один музыкальный штрих -->
<div style="display: flex; justify-content: flex-end; margin-top: 8px;">
<span style="background:#fff5ed; border-radius: 50px; padding:4px 12px; font-size:0.7rem; color:#b98866;">🎶 K-STAGE</span>
</div>
</div>
</div>
<footer>
<span>🎧 ON:AIR guestbook</span>
<span>✧ только айдолы ✧</span>
<span>🎤 music show archive</span>
</footer>
</div>
<script>
// СПИСОК АЙДОЛОВ (формат группа - имя) только имена, без пожеланий
const idolsLibrary = {
boys: [
"ateez - choi san",
"ateez - kim hongjoong",
"stray kids - bang chan",
"stray kids - lee know",
"txt - choi yeonjun",
"txt - kang taehyun",
"enhypen - jungwon",
"enhypen - ni-ki",
"bts - jungkook",
"bts - jimin",
"nct 127 - taeyong",
"nct dream - jeno",
"seventeen - vernon",
"seventeen - mingyu",
"the boyz - juyeon",
"monsta x - shownu",
"exo - baekhyun",
"shinee - taemin"
],
girls: [
"aespa - karina",
"aespa - winter",
"blackpink - jennie",
"blackpink - rosé",
"ive - jang wonyoung",
"ive - an yujin",
"newjeans - haerin",
"newjeans - minji",
"gidle - soyeon",
"gidle - miyeon",
"le sserafim - sakura",
"le sserafim - kim chaewon",
"twice - nayeon",
"twice - sana",
"red velvet - irene",
"itzy - ryujin",
"nmixx - lily",
"fromis_9 - saerom"
]
};
// вспомогательная функция экранирования
function escapeHtml(str) {
if (!str) return '';
return str.replace(/[&<>]/g, function(m) {
if (m === '&') return '&';
if (m === '<') return '<';
if (m === '>') return '>';
return m;
});
}
// парсим строку "группа - имя" для возможного разделения (для отображения группы в подзаголовке)
function formatIdolItem(fullName) {
const parts = fullName.split(' - ');
if (parts.length === 2) {
return { group: parts[0].trim(), name: parts[1].trim() };
}
return { group: '', name: fullName };
}
// рендер блока мальчиков
function renderBoys() {
const container = document.getElementById('boysList');
const countSpan = document.getElementById('boysCount');
const boysArray = idolsLibrary.boys;
countSpan.innerText = boysArray.length;
if (!boysArray.length) {
container.innerHTML = '<div class="idol-item" style="justify-content:center;">✨ пока нет айдолов</div>';
return;
}
let html = '';
for (let idol of boysArray) {
const { group, name } = formatIdolItem(idol);
const displayName = group ? `${group} - ${name}` : name;
html += `
<div class="idol-item">
<div class="idol-avatar">🎤</div>
<div class="idol-info">
<div class="idol-name-text">${escapeHtml(displayName)}</div>
${group ? `<div class="group-tag">🎧 ${escapeHtml(group)}</div>` : ''}
</div>
<span style="font-size:0.8rem; opacity:0.6;">🎵</span>
</div>
`;
}
container.innerHTML = html;
}
// рендер блока девочек
function renderGirls() {
const container = document.getElementById('girlsList');
const countSpan = document.getElementById('girlsCount');
const girlsArray = idolsLibrary.girls;
countSpan.innerText = girlsArray.length;
if (!girlsArray.length) {
container.innerHTML = '<div class="idol-item" style="justify-content:center;">🌸 пока нет айдолов</div>';
return;
}
let html = '';
for (let idol of girlsArray) {
const { group, name } = formatIdolItem(idol);
const displayName = group ? `${group} - ${name}` : name;
html += `
<div class="idol-item">
<div class="idol-avatar">🎤</div>
<div class="idol-info">
<div class="idol-name-text">${escapeHtml(displayName)}</div>
${group ? `<div class="group-tag">🎧 ${escapeHtml(group)}</div>` : ''}
</div>
<span style="font-size:0.8rem; opacity:0.6;">💗</span>
</div>
`;
}
container.innerHTML = html;
}
// Дополнительный эффект: анимация для эквалайзера (уже через css)
renderBoys();
renderGirls();
// Маленькая анимация для визуала: микрофонная стойка мигает? добавим рандомные имена? готово
// также дополнительно добавляем иконки "винил" в живую
const liveBadge = document.querySelector('.live-badge');
if(liveBadge) {
setInterval(() => {
const dot = liveBadge.querySelector('span');
if(dot) {
dot.style.opacity = dot.style.opacity === '0.4' ? '1' : '0.4';
}
}, 700);
}
</script>
</body>
</html>[/html][hideprofile]
Поделиться72026-05-05 16:34:44
[hideprofile]
[html]<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>Гостевая книга | ON:AIR — K-pop шоу</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: system-ui, 'Segoe UI', 'Poppins', 'Noto Sans KR', sans-serif;
}
body {
background: linear-gradient(145deg, #faf0f5 0%, #f7e9f0 90%);
min-height: 50vh;
padding: 2rem 1.5rem;
}
.container {
max-width: 700px;
margin: 0 auto;
}
/* Хедер с атмосферой шоу */
.hero {
text-align: center;
margin-bottom: 20px;
}
.hero h1 {
font-size: 23px;
background: linear-gradient(135deg, #FF3B6F, #8B5CF6, #2DD4BF);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
letter-spacing: -0.5px;
display: inline-block;
text-shadow: 0 2px 5px rgba(0,0,0,0.05);
}
.hero p {
color: #4a4a5a;
font-size: 15px;
margin-top: 0.5rem;
font-weight: 100;
}
.badge-live {
background: #ff2e63;
color: white;
font-size: 10px;
padding: 0.2rem 0.8rem;
border-radius: 20px;
display: inline-block;
margin-top: 0.8rem;
font-weight: 300;
letter-spacing: 1px;
}
/* ГЛАВНАЯ СЕТКА: 2 КОЛОНКИ (мальчики/девочки) */
.two-columns {
display: flex;
flex-wrap: wrap;
gap: 2rem;
margin-bottom: 3rem;
}
.column {
flex: 1;
min-width: 180px;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(2px);
border-radius: 2rem;
box-shadow: 0 20px 35px -12px rgba(0,0,0,0.15);
overflow: hidden;
transition: transform 0.2s;
border: 1px solid rgba(255,255,240,0.8);
}
.column:hover {
transform: translateY(-5px);
}
.column-header {
padding: 1.2rem 1.5rem;
background: white;
border-bottom: 3px solid;
display: flex;
align-items: center;
gap: 0.6rem;
}
.boys .column-header {
border-bottom-color: #3b82f6;
background: #f0f9ff;
}
.girls .column-header {
border-bottom-color: #ec489a;
background: #fff0f7;
}
.column-header h2 {
font-size: 15px;
font-weight: 300;
}
.boys h2 { color: #2563eb; }
.girls h2 { color: #db2777; }
.entry-list {
padding: 1rem 1.2rem;
max-height: 150px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 0.9rem;
background: rgba(255,248,245,0.5);
}
/* Стиль карточки гостя: ateez - (ссылка) choi san */
.guest-card {
background: white;
border-radius: 1.2rem;
padding: 0.9rem 1.2rem;
box-shadow: 0 4px 10px rgba(0,0,0,0.02);
transition: all 0.2s;
border: 1px solid #ffe7f0;
word-break: break-word;
}
.guest-card:hover {
border-color: #ffb7c5;
background: #fffbfd;
}
.guest-line {
font-size: 15px;
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 0.3rem 0.5rem;
}
.group-name {
font-weight: 100;
color: #1e293b;
background: #f1f5f9;
padding: 0.2rem 0.6rem;
border-radius: 40px;
font-size: 15px;
}
.guest-link {
color: #6366f1;
text-decoration: none;
font-weight: 100;
border-bottom: 1px dashed #cbd5e1;
}
.guest-link:hover {
color: #ec489a;
border-bottom-color: #f472b6;
}
.guest-name {
font-weight: 100;
background: linear-gradient(145deg, #2d2f36, #1f1f2a);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
font-size: 15px;
}
.empty-message {
text-align: center;
color: #aaa8b8;
font-style: italic;
padding: 2rem;
font-size: 0.9rem;
}
/* ФОРМА ДОБАВЛЕНИЯ (универсальный блок) */
.form-section {
background: white;
border-radius: 2rem;
padding: 1.8rem;
margin-top: 1rem;
box-shadow: 0 20px 35px -12px rgba(0,0,0,0.1);
border: 1px solid #ffe2ef;
}
.form-title {
font-size: 1.5rem;
font-weight: 200;
margin-bottom: 1.2rem;
display: flex;
align-items: center;
gap: 0.6rem;
}
.form-title span {
background: #fdeef4;
padding: 0.2rem 0.8rem;
border-radius: 50px;
font-size: 0.8rem;
}
.form-grid {
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin-bottom: 1.5rem;
}
.input-group {
flex: 1;
min-width: 170px;
}
.input-group label {
display: block;
font-size: 15px;
font-weight: 100;
text-transform: uppercase;
letter-spacing: 0.5px;
color: #6b6b7e;
margin-bottom: 0.3rem;
}
.input-group input, .input-group select {
width: 100%;
padding: 0.8rem 1rem;
border-radius: 1.2rem;
border: 1.5px solid #edd3df;
background: #fefafc;
font-size: 15px;
transition: 0.2s;
}
.input-group input:focus, .input-group select:focus {
outline: none;
border-color: #f472b6;
box-shadow: 0 0 0 3px rgba(236,72,153,0.1);
}
.gender-selector {
display: flex;
gap: 1rem;
align-items: center;
background: #faf3f7;
padding: 0.4rem 1rem;
border-radius: 3rem;
}
.gender-selector label {
display: flex;
align-items: center;
gap: 0.4rem;
font-weight: 200;
color: #4a4a5e;
}
.add-button {
background: linear-gradient(95deg, #FF3B6F, #C850C0);
border: none;
padding: 0.8rem 2rem;
border-radius: 3rem;
color: white;
font-weight: bold;
font-size: 1rem;
cursor: pointer;
transition: 0.2s;
box-shadow: 0 5px 12px rgba(255,59,111,0.3);
}
.add-button:hover {
transform: scale(1.02);
background: linear-gradient(95deg, #f82b61, #b53cad);
box-shadow: 0 8px 18px rgba(255,59,111,0.4);
}
.sample-note {
font-size: 5px;
color: #857e8c;
margin-top: 0.8rem;
text-align: center;
background: #fcf4f8;
padding: 0.5rem;
border-radius: 2rem;
}
hr {
margin: 1rem 0;
border-color: #ffdae9;
}
footer {
text-align: center;
margin-top: 2rem;
font-size: 10px;
color: #a48f9b;
}
@media (max-width: 480px) {
body {
padding: 1rem;
}
.hero h1 {
font-size: 15px;
}
.column-header h2 {
font-size: 10px;
}
}
/* Скролл для списков красивый */
.entry-list::-webkit-scrollbar {
width: 5px;
}
.entry-list::-webkit-scrollbar-track {
background: #ffecf0;
border-radius: 10px;
}
.entry-list::-webkit-scrollbar-thumb {
background: #f4a0bd;
border-radius: 10px;
}
</style>
</head>
<body>
<div class="container">
<div class="hero">
<h1>🎤 ON:AIR <span style="font-size: 2rem;">🎶</span></h1>
<p>музыкальное шоу | корейская волна · гостиная фанатов</p>
<div class="badge-live">✨ LIVE EDITION ✨</div>
</div>
<!-- Два блока: мальчики и девочки -->
<div class="two-columns">
<!-- Блок МАЛЬЧИКИ (BOYS) -->
<div class="column boys">
<div class="column-header">
<span>🧑🎤</span>
<h2>МАЛЬЧИКИ</h2>
<span style="font-size: 0.75rem; background:#eef2ff; padding:0.2rem 0.7rem; border-radius: 30px;">보이그룹</span>
</div>
<div id="boysList" class="entry-list">
<!-- Динамические карточки будут тут -->
<div class="empty-message">✨ пока нет отзывов, добавь своего любимого айдола ✨</div>
</div>
</div>
<!-- Блок ДЕВОЧКИ (GIRLS) -->
<div class="column girls">
<div class="column-header">
<span>👩🎤</span>
<h2>ДЕВОЧКИ</h2>
<span style="font-size: 0.75rem; background:#ffe4f0; padding:0.2rem 0.7rem; border-radius: 30px;">걸그룹</span>
</div>
<div id="girlsList" class="entry-list">
<div class="empty-message">🌸 добавь первую запись для девочек 🌸</div>
</div>
</div>
</div>
<!-- Форма добавления гостя (имя, группа, ссылка, выбор пола) -->
<div class="form-section">
<div class="form-title">
📝 Добавить в гостевую книгу
<span>ON:AIR</span>
</div>
<div class="form-grid">
<div class="input-group">
<label>🌟 Имя артиста / участника</label>
<input type="text" id="artistName" placeholder="например: Choi San, Hanni, Karina ..." autocomplete="off">
</div>
<div class="input-group">
<label>🎤 Группа / сольный проект</label>
<input type="text" id="groupName" placeholder="например: ATEEZ, NewJeans, IVE ..." autocomplete="off">
</div>
<div class="input-group">
<label>🔗 Ссылка (YouTube, Twitter, фан-арт...)</label>
<input type="url" id="linkUrl" placeholder="https://..." autocomplete="off">
</div>
<div class="input-group">
<label>⚧ Категория</label>
<div class="gender-selector">
<label>
<input type="radio" name="gender" value="boy" checked> 🧑 мальчики
</label>
<label>
<input type="radio" name="gender" value="girl"> 👩 девочки
</label>
</div>
</div>
</div>
<div style="display: flex; justify-content: center; margin-top: 0.5rem;">
<button class="add-button" id="addEntryBtn">➕ Добавить запись →</button>
</div>
<div class="sample-note">
💡 Пример готового стиля: <strong>ATEEZ</strong> - <a href="#" style="text-decoration:none;">(ссылка)</a> <strong>Choi San</strong><br>
Заполни поля и запись появится в выбранном блоке.
</div>
</div>
<footer>
🌟 Гостевая книга шоу ON:AIR — K-pop фан-сообщество. Каждая запись в стиле «группа - (ссылка) имя артиста» 🌟
</footer>
</div>
<script>
// ----- Хранилище данных -----
// Структура: { id, name, group, link, gender } gender: 'boy' / 'girl'
let entries = [];
// Загрузка из localStorage, если есть
function loadFromStorage() {
const saved = localStorage.getItem('onair_guestbook');
if (saved) {
try {
entries = JSON.parse(saved);
} catch(e) { console.warn(e); entries = []; }
}
if (!entries || entries.length === 0) {
// Небольшие демо-записи для красоты, чтобы блоки не были пустыми (но можно и без них, но пусть будет мило)
entries = [
{ id: Date.now() + 1, name: "Choi San", group: "ATEEZ", link: "https://youtu.be/dQw4w9WgXcQ?feature=shared", gender: "boy" },
{ id: Date.now() + 2, name: "Hongjoong", group: "ATEEZ", link: "https://youtube.com", gender: "boy" },
{ id: Date.now() + 3, name: "Hanni", group: "NewJeans", link: "https://youtu.be/example", gender: "girl" },
{ id: Date.now() + 4, name: "Karina", group: "aespa", link: "https://youtu.be/example2", gender: "girl" },
{ id: Date.now() + 5, name: "Yeonjun", group: "TXT", link: "https://youtu.be/example3", gender: "boy" },
{ id: Date.now() + 6, name: "Winter", group: "aespa", link: "https://youtu.be/example4", gender: "girl" }
];
saveToStorage();
}
}
function saveToStorage() {
localStorage.setItem('onair_guestbook', JSON.stringify(entries));
}
// Функция рендеринга двух блоков (мальчики / девочки)
function renderEntries() {
const boysContainer = document.getElementById('boysList');
const girlsContainer = document.getElementById('girlsList');
// Фильтруем записи
const boys = entries.filter(entry => entry.gender === 'boy');
const girls = entries.filter(entry => entry.gender === 'girl');
// Рендер мальчиков
if (boys.length === 0) {
boysContainer.innerHTML = '<div class="empty-message">🎤 пока нет записей для мальчиков, добавь своего любимчика!</div>';
} else {
boysContainer.innerHTML = boys.map(entry => createGuestCardHTML(entry)).join('');
}
// Рендер девочек
if (girls.length === 0) {
girlsContainer.innerHTML = '<div class="empty-message">💖 пока нет записей для девочек, поделись любовью!</div>';
} else {
girlsContainer.innerHTML = girls.map(entry => createGuestCardHTML(entry)).join('');
}
}
// Создание HTML карточки в стиле: ateez - (ссылка) choi san
// По заданию "ateez - (ссылка) choi san"
function createGuestCardHTML(entry) {
// Экранирование спецсимволов для защиты XSS
const safeGroup = escapeHtml(entry.group) || "группа";
const safeName = escapeHtml(entry.name) || "имя";
let safeLink = entry.link ? entry.link.trim() : "#";
// если ссылка невалидная или пустая, то ставим заглушку, но сохраняем возможность клика
if (safeLink === "" || (!safeLink.startsWith("http://") && !safeLink.startsWith("https://"))) {
// если ссылка не начинается с протокола, но не пустая, делаем https префикс? но лучше просто заглушка
if(safeLink !== "#" && safeLink !== "") {
safeLink = "https://" + safeLink;
} else {
safeLink = "#";
}
}
// ссылка с атрибутом target blank для удобства
// создаем элемент согласно стилю: группа - (ссылка) имя
// Шаблон: "<span class=\"group-name\">ATEEZ</span> - <a href='ссылка' class='guest-link' target='_blank'>(ссылка)</a> <span class='guest-name'>Choi San</span>"
return `
<div class="guest-card" data-id="${entry.id}">
<div class="guest-line">
<span class="group-name">${safeGroup}</span>
<span> - </span>
<a href="${safeLink}" class="guest-link" target="_blank" rel="noopener noreferrer">(ссылка)</a>
<span class="guest-name">${safeName}</span>
</div>
</div>
`;
}
// вспомогательная функция экранирования
function escapeHtml(str) {
if (!str) return '';
return str.replace(/[&<>]/g, function(m) {
if (m === '&') return '&';
if (m === '<') return '<';
if (m === '>') return '>';
return m;
}).replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, function(c) {
return c;
});
}
// Добавление новой записи
function addEntry() {
// получаем поля
const nameInput = document.getElementById('artistName');
const groupInput = document.getElementById('groupName');
const linkInput = document.getElementById('linkUrl');
const genderRadio = document.querySelector('input[name="gender"]:checked');
let name = nameInput.value.trim();
let group = groupInput.value.trim();
let link = linkInput.value.trim();
const gender = genderRadio ? genderRadio.value : 'boy';
// Валидация: имя и группа обязательны
if (name === "") {
alert("❄ Пожалуйста, введите Имя артиста / участника!");
return;
}
if (group === "") {
alert("🎶 Укажите группу или сольный проект!");
return;
}
// ссылка необязательна, но если ввели — проверяем протокол, но не строго
let finalLink = link;
if (finalLink !== "" && !finalLink.startsWith("http://") && !finalLink.startsWith("https://")) {
// если пользователь ввел что-то типа "youtube.com/..." попробуем добавить https
finalLink = "https://" + finalLink;
}
// Если ссылка пустая, то оставляем пустую строку, но карточка обработает как #, но лучше чтобы ссылка была с заглушкой -
// но по дизайну ссылка отображается как текст "(ссылка)" – она будет вести на введённый адрес или пустышку.
// Однако в createGuestCardHTML если safeLink пустой, то ставим "#".
if(finalLink === "") finalLink = ""; // тогда внутри функции станет '#', ссылка нерабочая но элемент есть.
// создаем новый объект
const newEntry = {
id: Date.now() + Math.random() * 10000,
name: name,
group: group,
link: finalLink,
gender: gender,
};
entries.push(newEntry);
saveToStorage();
renderEntries();
// очищаем форму, кроме радиокнопки (оставляем выбранный пол как был, удобно)
nameInput.value = "";
groupInput.value = "";
linkInput.value = "";
// можно фокус на имя для удобства
nameInput.focus();
// небольшой фидбек (опционально)
const toastMsg = document.createElement('div');
toastMsg.innerText = `✨ ${name} добавлен в ${gender === 'boy' ? 'мальчики' : 'девочки'} ✨`;
toastMsg.style.position = 'fixed';
toastMsg.style.bottom = '20px';
toastMsg.style.left = '50%';
toastMsg.style.transform = 'translateX(-50%)';
toastMsg.style.backgroundColor = '#1e1a2f';
toastMsg.style.color = '#fff0f5';
toastMsg.style.padding = '0.5rem 1.2rem';
toastMsg.style.borderRadius = '3rem';
toastMsg.style.fontSize = '0.8rem';
toastMsg.style.zIndex = '999';
toastMsg.style.backdropFilter = 'blur(4px)';
toastMsg.style.fontWeight = '500';
document.body.appendChild(toastMsg);
setTimeout(() => toastMsg.remove(), 2000);
}
function initDemoIfEmpty() {
if (entries.length === 0) {
entries = [
{ id: Date.now() + 10, name: "Choi San", group: "ATEEZ", link: "https://youtu.be/ATEEZ_SAN_fancam", gender: "boy" },
{ id: Date.now() + 11, name: "Jeon Soyeon", group: "(G)I-DLE", link: "https://youtu.be/soyeon_queen", gender: "girl" },
{ id: Date.now() + 12, name: "Felix", group: "Stray Kids", link: "https://youtu.be/felix_skz", gender: "boy" },
{ id: Date.now() + 13, name: "Wonyoung", group: "IVE", link: "https://youtu.be/wonyoung_star", gender: "girl" },
{ id: Date.now() + 14, name: "Beomgyu", group: "TXT", link: "https://youtu.be/beomgyu_melody", gender: "boy" }
];
saveToStorage();
}
}
// Обработчик кнопки добавить
document.getElementById('addEntryBtn').addEventListener('click', addEntry);
// Также добавим поддержку Enter в любом поле для удобства (нажатие Enter в любом инпуте добавляет запись)
const inputs = ['artistName', 'groupName', 'linkUrl'];
inputs.forEach(id => {
const el = document.getElementById(id);
if(el) {
el.addEventListener('keypress', function(e) {
if(e.key === 'Enter') {
e.preventDefault();
addEntry();
}
});
}
});
// запуск
loadFromStorage();
initDemoIfEmpty(); // повторная страховка
renderEntries();
</script>
</body>
</html>[/html]
