Balabony®
Слухати →
Аудіоісторії та серіали

Всесвіт
неймовірних
історій

Дід Панас і його синій блокнот — серце платформи Balabony®. Слухайте українські аудіоісторії без реєстрації.

Дід Панас із синім блокнотом
ДІД ПАНАС
Серце платформи Balabony®
ОРИГІНАЛЬНИЙ АРТ
1Серія безкоштовно
4+Серій вийшло
AIСинтез голосу
1₴Соціальний доступ
5Платформ
Разом ми прослухали 24 831 годин українського контенту
Партнер
Рекомендовано від партнерів
Спеціальна пропозиція для слухачів Balabony

Мій кабінет

Ваша бібліотека та історія читання. Продовжуйте «Синій блокнот».

Статистика прослуховувань: 12 год
8 серій із 20 прослухано
Історія витрат
Подарувати підписку

Creator Studio

Тип: ПАРТНЕР
Фото профілю *обов'язково
Вивід коштів недоступний без верифікованого фото та договору ДІЯ
📤 Завантажити контент
💰 Wallet 74 ₴
До порогу виплати: 26 ₴ (мін. 100 ₴)
На перевірці (2)
Опубліковано (18)
Статистика нарахувань
Перша серія — безкоштовно

Відеоісторія дня

БЕЗКОШТОВНО
Синій блокнот та таємниця Балабонів
Дивитися відеоісторію · 12:34
Читайте та слухайте одночасно

Синхронізований рідер

Шрифт: Офлайн
СЕРІЯ 1 ІЗ 20

Синій блокнот та таємниця Балабонів

БЕЗКОШТОВНО
Радіовистава: вимк. Закладок: 0
Серії сезону (1 Сезон = 20 Серій)

Дід Панас повільно відкрив свій синій блокнот. Папір зашурхотів, наче листя під ногами в осінньому лісі. Тут панував спокій. Текст на цій сторінці ніколи не рухається сам — він чекає на ваші очі.

Баба Ганя принесла духмяний чай. «Ти знову в своїх думках?» — усміхнулася вона. Дід мовчав, бо знав: найкращі історії народжуються в тиші.

Але Панас не відповідав. Він дивився на зашифрований запис, який зробив ще тридцять років тому, коли над селом пролетіла таємнича вогняна куля.

«Ганю,» — нарешті промовив він тихо, — «здається, я знайшов відповідь. Все було у цьому блокноті весь цей час...»

Де слухати

Доступно на 5 платформах

Спецпропозиція
Ексклюзивна пропозиція для наших слухачів
Дізнайтесь більше від наших партнерів

Наша місія

Balabony — інклюзивний цифровізований літопис. Ми поєднуємо традиції сторітелінгу з AI-технологіями, щоб зробити українську культуру доступною для кожного, включаючи людей з порушеннями зору та слуху.

Гейміфікація

Досягнення та бейджі

Нагороди та бейджі

Перші кроки

Ти прослухав свою першу історію. Початок великої подорожі покладено!

Читець

Прочитано 10 творів. Слово стало твоєю суперсилою.

Слухач

5 годин аудіоісторій. Вуха — теж серце.

7 днів поспіль

Читай щодня 7 днів. Звичка — це характер.

Нічний мандрівник

Прочитано 3 історії після 22:00. Твоя уява не знає сну.

Легенда

Прослухано всі 20 серій сезону. Ти — частина легенди.
Рейтинг

ТОП-5 сезону

Формула: (Час прослуховування × 0.6) + (Оцінки × 0.3) + (Збереження × 0.1) · Автор №1 отримує 2000 ₴ щомісяця
1Таємниця Балабонів 🏆 +2000 ₴Рейтинг: 9.42
2Дід Панас та ШІРейтинг: 8.87
3Синій блокнотРейтинг: 8.31
4Ганина таємницяРейтинг: 7.94
5Вогняна куляРейтинг: 7.66
Підписки

Оберіть свій план доступу

1 Сезон = 20 Серій
FRANCHISE: Балабони — 100% платформи ORIGINAL: Партнери — 50/50
Серії 1–3 безкоштовно. Серії 4–20 — одна на 24 год або одразу з Premium.
🎧
«Спробуй» — Поштучний доступ
Одна серія або одна окрема історія без підписки. Доступ відкривається миттєво після оплати.
7
разова оплата
Також доступно в кредит:
-45% ВИГОДИ
Фанат Balabony
899 ₴/рік

Замість 1188 ₴

Економія при оплаті за рік · Всього 75 ₴/міс

💳 Доступно в кредит
  • Все, що у «Світі історій»
  • Офлайн-завантаження (в літаку чи метро)
  • Унікальний бейдж «Золотий Меценат»
  • Пріоритетна підтримка

Стань частиною історії

«Якщо вам просто подобається те, що ми робимо — ви можете підтримати розвиток українського аудіоконтенту будь-якою сумою. Кожна гривня йде на озвучення нових творів та підтримку інклюзивних ініціатив.»

Ваше ім'я в титрах Вплив на сюжет Закритий чат Всі переваги Premium
🎬 Краудфандинг: Продакшн «Балабони — Сезон 2»
Зібрано: 18 450 ₴ з мети 50 000 ₴

Реферальна програма

Запроси друга — отримайте обидва по 50 бонусних балів на рахунок. Ділитися цікавим — це теж мистецтво.

Синій блокнот (Серія 1)
Голос: Панас (ШІ-синтез) · Спробувати перші 2 серії безкоштовно
0:00 / 12:34
let surveyStep = 1; function nextStep() { document.getElementById('q' + surveyStep).classList.remove('active'); document.getElementById('dot' + surveyStep).classList.remove('active'); surveyStep++; if (surveyStep > 5) { closeSurvey(); return; } document.getElementById('q' + surveyStep).classList.add('active'); document.getElementById('dot' + surveyStep).classList.add('active'); } function showSocialOffer() { sessionStorage.setItem('socialEligible', '1'); } function closeSurvey() { document.getElementById('surveyOverlay').style.display = 'none'; localStorage.setItem('balabony_survey', 'done'); } window.addEventListener('load', () => { if (localStorage.getItem('balabony_survey')) document.getElementById('surveyOverlay').style.display = 'none'; }); function setLang(btn) { document.querySelectorAll('.lang-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); } let fontSize = 24, isSerif = true; function changeFont(d) { fontSize = Math.max(14, Math.min(26, fontSize + d)); document.getElementById('readerContent').style.fontSize = fontSize + 'px'; } function toggleSerif() { isSerif = !isSerif; document.getElementById('readerContent').classList.toggle('serif', isSerif); const btn = document.getElementById('serifBtn'); btn.classList.toggle('active', isSerif); btn.textContent = isSerif ? 'З зарубками (Книжковий)' : 'Без зарубок (Сучасний)'; } let zenMode = false; function toggleZen() { zenMode = !zenMode; document.body.classList.toggle('zen-mode', zenMode); document.getElementById('zenExitBtn').style.display = zenMode ? 'flex' : 'none'; document.getElementById('zenBtn').innerHTML = zenMode ? '' : ''; if (zenMode) document.getElementById('reader').scrollIntoView(); } let playing = false, progress = 0, raf = null; const totalSec = 754; const fpBtn = document.getElementById('fpBtn'); const fpFill = document.getElementById('fpFill'); const fpTime = document.getElementById('fpTime'); const fpTriangle = document.getElementById('fpTriangle'); const fpPause = document.getElementById('fpPause'); function fmt(s) { const m = Math.floor(s/60), sec = Math.floor(s%60); return m+':'+(sec<10?'0':'')+sec; } function setPlayerIcon() { fpTriangle.style.display = playing ? 'none' : 'block'; fpPause.style.display = playing ? 'flex' : 'none'; } function tick() { if (!playing) return; progress = Math.min(progress + 100/totalSec/10, 100); fpFill.style.width = progress + '%'; fpTime.textContent = fmt(progress/100*totalSec) + ' / 12:34'; syncReaderHighlight(progress/100*totalSec); if (progress < 100) raf = setTimeout(tick, 100); else { playing = false; setPlayerIcon(); } } fpBtn.addEventListener('click', () => { playing = !playing; setPlayerIcon(); if (playing) { tick(); // Cinema Mode — затемнення фону на 40% const overlay = document.getElementById('cinemaOverlay'); if (overlay) overlay.style.background = 'rgba(5,8,18,0.42)'; } else { clearTimeout(raf); // Вимикаємо Cinema Mode const overlay = document.getElementById('cinemaOverlay'); if (overlay) overlay.style.background = 'rgba(5,8,18,0)'; } }); function seekAudio(e) { const bar = document.getElementById('fpBarWrap'); const ratio = e.offsetX / bar.offsetWidth; progress = ratio * 100; fpFill.style.width = progress + '%'; fpTime.textContent = fmt(progress/100*totalSec) + ' / 12:34'; } const paraTimings = [ { id: 'p1', start: 0, end: 188 }, { id: 'p2', start: 188, end: 376 }, { id: 'p3', start: 376, end: 565 }, { id: 'p4', start: 565, end: 754 }, ]; function syncReaderHighlight(currentSec) { const active = paraTimings.find(p => currentSec >= p.start && currentSec < p.end); document.querySelectorAll('.reader-content p').forEach(p => p.classList.remove('active')); if (active) { const el = document.getElementById(active.id); if (el) { el.classList.add('active'); const rect = el.getBoundingClientRect(); const target = window.innerHeight / 3; if (rect.top > target || rect.bottom < 0) { window.scrollBy({ top: rect.top - target, behavior: 'smooth' }); } } } } const speeds = [1.0, 1.25, 1.5]; let speedIdx = 0; function cycleSpeed() { speedIdx = (speedIdx + 1) % speeds.length; document.getElementById('fpSpeed').textContent = speeds[speedIdx].toFixed(1) + '×'; } const sleepOptions = [null, 15, 30, 60]; let sleepIdx = 0; let sleepTimer = null; function cycleSleep() { if (sleepTimer) { clearTimeout(sleepTimer); sleepTimer = null; } sleepIdx = (sleepIdx + 1) % sleepOptions.length; const mins = sleepOptions[sleepIdx]; const btn = document.getElementById('sleepBtn'); if (mins === null) { btn.innerHTML = ' Сон'; } else { btn.textContent = mins + ' хв'; sleepTimer = setTimeout(() => { playing = false; setPlayerIcon(); clearTimeout(raf); sleepIdx = 0; btn.innerHTML = ' Сон'; }, mins * 60000); } } function copyRef() { const inp = document.querySelector('.referral-card input'); if (inp) { inp.select(); document.execCommand('copy'); } } const modalContent = { gift: { title: 'Подарувати підписку', text: 'Введіть email або Telegram-нік отримувача, оберіть тариф і ми надішлемо подарунковий код. Дарувати — це також мистецтво.' }, perks: { title: 'Переваги підписки', text: '' }, income: { title: 'Статистика нарахувань', text: 'Березень 2026: 3 унікальних слухачі × 149 ₴ = 447 ₴ (частка автора: 35%). Лютий 2026: 2 слухачі = 298 ₴. Загалом зароблено: 745 ₴. Наступна виплата: 1 квітня 2026.' }, dropoff: { title: 'Графік точок виходу аудиторії', text: 'Серія 1: 94% дослухали до кінця. Серія 2: 87%. Серія 3: 79% (точка виходу ~14:30 — рекомендуємо скоротити середину). Серія 4: 91%. Середнє утримання сезону: 88%.' }, history: { title: 'Історія витрат', text: 'Лютий 2026: Підписка Premium — 149 ₴. Січень 2026: Підписка Premium — 149 ₴. Усього витрачено: 298 ₴.' }, about: { title: 'Про нас', text: 'Balabony — інклюзивна цифрова платформа українського аудіоконтенту. Ми відроджуємо мистецтво живого сторітелінгу за допомогою AI-технологій.' }, contacts: { title: 'Контакти', text: 'Пишіть нам у Telegram: @balabony_bot або на email: hello@balabony.com.ua' }, help: { title: 'Допомога (FAQ)', text: 'Як підписатися? Перейдіть до розділу "Тарифи" і оберіть потрібний план. Де слухати? У Telegram-боті @balabony_bot або на сайті. Як скасувати? Напишіть боту /cancel.' } }; function openModal(key) { const d = modalContent[key]; document.getElementById('modalPkg').textContent = d.text; const box = document.getElementById('payModal').querySelector('.modal-box'); box.querySelector('h3').textContent = d.title; box.querySelectorAll('.pay-btn, .modal-cancel').forEach(el => { el.style.display = 'none'; }); const closeBtn = box.querySelector('.modal-cancel'); if (closeBtn) { closeBtn.style.display = 'block'; closeBtn.textContent = 'Закрити'; } document.getElementById('payModal').classList.add('open'); } const videoBox = document.getElementById('videoBox'); const mainVideo = document.getElementById('mainVideo'); const videoCover = document.getElementById('videoCover'); videoBox.addEventListener('click', () => { if (mainVideo.paused) { videoCover.classList.add('hidden'); mainVideo.setAttribute('preload','auto'); mainVideo.play().catch(() => {}); } else { mainVideo.pause(); videoCover.classList.remove('hidden'); } }); function togglePiP() { if (document.pictureInPictureElement) document.exitPictureInPicture(); else if (mainVideo.readyState >= 2) mainVideo.requestPictureInPicture().catch(() => {}); } function openPayment(name) { const box = document.getElementById('payModal').querySelector('.modal-box'); box.querySelector('h3').textContent = 'Оплата пакету'; document.getElementById('modalPkg').textContent = name; box.querySelectorAll('.pay-btn').forEach(el => { el.style.display = 'flex'; }); const cancelBtn = box.querySelector('.modal-cancel'); if (cancelBtn) { cancelBtn.style.display = 'block'; cancelBtn.textContent = 'Скасувати'; } document.getElementById('payModal').classList.add('open'); } function closePayment() { document.getElementById('payModal').classList.remove('open'); } document.getElementById('payModal').addEventListener('click', function(e) { if (e.target === this) closePayment(); }); function openDiiaFlow() { document.getElementById('diiaStep1').classList.add('show'); document.getElementById('diiaStep2').classList.remove('show'); document.getElementById('diiaModal').classList.add('open'); } function diiaNextStep() { document.getElementById('diiaStep1').classList.remove('show'); document.getElementById('diiaStep2').classList.add('show'); } function closeDiia() { document.getElementById('diiaModal').classList.remove('open'); } document.getElementById('diiaModal').addEventListener('click', function(e) { if (e.target === this) closeDiia(); }); function shareStory() { const data = { title: 'Balabony — Синій блокнот', text: 'Слухайте безкоштовно першу серію аудіосеріалу «Балабони»!', url: window.location.href }; if (navigator.share) { navigator.share(data).catch(() => {}); } else { navigator.clipboard.writeText(data.url + '\n' + data.text).then(() => { alert('Посилання скопійовано!'); }); } } document.addEventListener('mouseup', () => { const sel = window.getSelection(); if (sel && sel.toString().trim().length > 10) { const bar = document.getElementById('quoteBar'); document.getElementById('quoteText').textContent = sel.toString().trim(); bar.style.display = 'flex'; } }); function generateQuoteImage(bgType) { const text = document.getElementById('quoteText').textContent; const canvas = document.createElement('canvas'); canvas.width = 1080; canvas.height = 1920; const ctx = canvas.getContext('2d'); if (bgType === 'cover') { ctx.fillStyle = '#0f172a'; ctx.fillRect(0, 0, 1080, 1920); const vg = ctx.createRadialGradient(540, 960, 100, 540, 960, 800); vg.addColorStop(0, 'rgba(239,159,39,0.22)'); vg.addColorStop(1, 'transparent'); ctx.fillStyle = vg; ctx.fillRect(0, 0, 1080, 1920); } else { const grad = ctx.createLinearGradient(0, 0, 0, 1920); grad.addColorStop(0, '#0f172a'); grad.addColorStop(0.4, '#1e293b'); grad.addColorStop(1, '#0f172a'); ctx.fillStyle = grad; ctx.fillRect(0, 0, 1080, 1920); ctx.fillStyle = '#ef9f27'; ctx.fillRect(80, 200, 6, 1500); } ctx.fillStyle = '#ef9f27'; ctx.font = 'bold 64px Georgia, serif'; ctx.fillText('Balabony', 120, 180); ctx.fillStyle = 'rgba(239,159,39,0.18)'; ctx.font = 'bold 320px Georgia, serif'; ctx.fillText('«', 60, 680); ctx.fillStyle = '#f8fafc'; ctx.font = '62px Georgia, serif'; const words = text.split(' '); let line = '', y = 760, maxW = 900; const lines = []; for (const word of words) { const test = line + word + ' '; if (ctx.measureText(test).width > maxW && line) { lines.push(line.trim()); line = word + ' '; } else { line = test; } } if (line.trim()) lines.push(line.trim()); lines.forEach((l, i) => ctx.fillText((i === 0 ? '«' : '') + l + (i === lines.length-1 ? '»' : ''), 120, y + i * 80)); ctx.fillStyle = 'rgba(239,159,39,0.7)'; ctx.font = '36px Montserrat, sans-serif'; ctx.fillText('Сезон 1 | Серія 1 із 20', 120, 1700); ctx.strokeStyle = '#ef9f27'; ctx.lineWidth = 3; ctx.strokeRect(120, 1760, 120, 120); ctx.fillStyle = '#ef9f27'; ctx.font = 'bold 22px Montserrat, sans-serif'; ctx.fillText('QR', 168, 1830); ctx.font = '26px Montserrat, sans-serif'; ctx.fillStyle = '#f8fafc'; ctx.fillText('balabony.com', 260, 1820); const a = document.createElement('a'); a.download = 'balabony-story.png'; a.href = canvas.toDataURL('image/png'); a.click(); document.getElementById('quoteBar').style.display = 'none'; } function openPerksModal() { document.getElementById('perksModal').classList.add('open'); } function closePerks() { document.getElementById('perksModal').classList.remove('open'); } document.getElementById('perksModal').addEventListener('click', function(e) { if (e.target === this) closePerks(); }); const UNLOCK_INTERVAL = 24 * 60 * 60 * 1000; function checkEpisodeAccess(episodeId) { if (episodeId <= 3) return { access: true }; if (isPremium) return { access: true }; const lastUnlock = localStorage.getItem('last_episode_unlock_time'); const now = Date.now(); if (!lastUnlock || (now - parseInt(lastUnlock) >= UNLOCK_INTERVAL)) { localStorage.setItem('last_episode_unlock_time', now); return { access: true }; } const timeLeft = UNLOCK_INTERVAL - (now - parseInt(lastUnlock)); return { access: false, timeLeft }; } let readerFontSize = 24; function changeFontSizeV35(delta) { readerFontSize = Math.max(18, Math.min(40, readerFontSize + delta)); const el = document.getElementById('storyContent') || document.getElementById('readerContent'); if (el) { el.style.fontSize = readerFontSize + 'px'; el.style.lineHeight = '1.6'; } localStorage.setItem('balabony_font_size', readerFontSize); } (function() { const saved = parseInt(localStorage.getItem('balabony_font_size')); if (saved && saved >= 18 && saved <= 40) { readerFontSize = saved; const el = document.getElementById('storyContent') || document.getElementById('readerContent'); if (el) el.style.fontSize = saved + 'px'; } })(); function setAmberMode() { document.body.classList.remove('dark-mode'); document.body.classList.add('amber-mode'); const el = document.getElementById('storyContent') || document.getElementById('readerContent'); if (el) el.style.color = '#ffbf00'; } const EPISODES = 20; const FREE_EPISODES = [1, 2, 3]; let isPremium = false; function getUnlockState() { try { return JSON.parse(localStorage.getItem('balabony_unlocks') || '{}'); } catch(e) { return {}; } } function saveUnlockState(state) { localStorage.setItem('balabony_unlocks', JSON.stringify(state)); } function isEpisodeUnlocked(ep) { if (isPremium) return true; if (FREE_EPISODES.includes(ep)) return true; const state = getUnlockState(); if (state[ep] && Date.now() < state[ep]) return true; return false; } function getNextFreeUnlockTime() { const state = getUnlockState(); const now = Date.now(); for (const ep in state) { if (state[ep] > now) return state[ep]; } return null; } function unlockEpisode(ep) { const result = checkEpisodeAccess(ep); if (result.access) { const state = getUnlockState(); if (!FREE_EPISODES.includes(ep) && !state[ep]) { state[ep] = Date.now() + UNLOCK_INTERVAL; saveUnlockState(state); } openEpisode(ep); } else { const unlockAt = Date.now() + result.timeLeft; showCountdown(unlockAt); } } function openEpisode(ep) { document.getElementById('unlockTimer').style.display = 'none'; document.querySelectorAll('.ep-btn').forEach(b => b.classList.remove('ep-active')); const btn = document.getElementById('ep-' + ep); if (btn) btn.classList.add('ep-active'); const titles = {1:'Синій блокнот та таємниця Балабонів',2:'Баба Ганя та загадковий лист',3:'Дід Панас і вогняна куля'}; document.getElementById('readerContent').querySelector('p').innerHTML = `Серія ${ep} із ${EPISODES}

` + (titles[ep] || `Серія ${ep}: текст завантажується...`); const counter = document.querySelector('[style*="СЕРІЯ"]') || document.querySelector('.series-counter'); if (counter) counter.textContent = `СЕРІЯ ${ep} ІЗ 20`; } function showCountdown(unlockTime) { const timerEl = document.getElementById('unlockTimer'); const textEl = document.getElementById('timerText'); timerEl.style.display = 'block'; function tick() { const diff = unlockTime - Date.now(); if (diff <= 0) { textEl.textContent = 'Розблокуйте нову серію!'; clearInterval(iv); return; } const h = Math.floor(diff/3600000); const m = Math.floor((diff%3600000)/60000); const s = Math.floor((diff%60000)/1000); textEl.textContent = `Доступно через ${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}`; } tick(); const iv = setInterval(tick, 1000); } function buildEpisodeGrid() { const grid = document.getElementById('episodeGrid'); if (!grid) return; grid.innerHTML = ''; for (let ep = 1; ep <= EPISODES; ep++) { const unlocked = isEpisodeUnlocked(ep); const isFree = FREE_EPISODES.includes(ep); const btn = document.createElement('button'); btn.id = 'ep-' + ep; btn.className = 'ep-btn'; btn.style.cssText = ` padding:8px 10px; border-radius:8px; border:1.5px solid ${unlocked ? 'var(--accent-gold)' : 'var(--border)'}; background:${unlocked ? 'rgba(239,159,39,0.12)' : 'var(--white)'}; color:${unlocked ? 'var(--accent-gold)' : 'var(--muted)'}; font-size:12px; font-weight:700; cursor:pointer; font-family:'Montserrat',sans-serif; min-width:44px; min-height:44px; position:relative; `; btn.textContent = isFree ? ep + ' ★' : ep; btn.title = isFree ? 'Серія ' + ep + ' — безкоштовно' : unlocked ? 'Серія ' + ep + ' — розблоковано' : 'Серія ' + ep + ' — розблокувати'; btn.onclick = () => unlockEpisode(ep); if (ep === 1) { btn.classList.add('ep-active'); btn.style.background = 'var(--accent-gold)'; btn.style.color = '#fff'; } grid.appendChild(btn); } } window.addEventListener('load', function() { buildEpisodeGrid(); if (localStorage.getItem('balabony_admin') === '1') { document.getElementById('adminPanel').style.display = 'block'; startAdminDashboard(); } const lockTime = getNextFreeUnlockTime(); if (lockTime) showCountdown(lockTime); }); const _origOpenPayment = openPayment; function openPayment(name) { if (name && (name.includes('Premium') || name.includes('Світ') || name.includes('Фанат') || name.includes('Меценат'))) { isPremium = true; buildEpisodeGrid(); } _origOpenPayment(name); } function startAdminDashboard() { const genres = ['Пригоди','Детектив','Містика','Life Stories','Фантастика','Гумор','Фентезі','Історія','Філософія','Казки']; const genreData = [42,38,31,28,25,22,19,16,12,9]; function updateOnline() { const n = 12 + Math.floor(Math.random() * 8); document.getElementById('onlineSessions').textContent = n; document.getElementById('listeningCount').textContent = Math.floor(n * 0.6); document.getElementById('readingCount').textContent = Math.floor(n * 0.3); document.getElementById('pricingCount').textContent = Math.floor(n * 0.1); } updateOnline(); setInterval(updateOnline, 8000); const visits = 1240, demos = 874, pays = 142; document.getElementById('funnelVisit').textContent = visits.toLocaleString(); document.getElementById('funnelDemo').textContent = demos.toLocaleString() + ' (' + Math.round(demos/visits*100) + '%)'; document.getElementById('funnelPay').textContent = pays.toLocaleString() + ' (' + Math.round(pays/visits*100) + '%)'; document.getElementById('funnelConv').textContent = Math.round(pays/visits*100) + '%'; buildRetention20Chart(); const chart = document.getElementById('genreChart'); if (chart) { const max = Math.max(...genreData); genres.forEach((g, i) => { const col = document.createElement('div'); col.style.cssText = 'display:flex;flex-direction:column;align-items:center;gap:4px;flex:1;'; const bar = document.createElement('div'); const h = Math.round((genreData[i] / max) * 72); bar.style.cssText = `height:${h}px;width:100%;background:var(--accent-gold);border-radius:4px 4px 0 0;opacity:${0.4+genreData[i]/max*0.6};`; const label = document.createElement('div'); label.style.cssText = 'font-size:9px;color:var(--muted);text-align:center;line-height:1.2;'; label.textContent = g.split(' ')[0]; col.appendChild(bar); col.appendChild(label); chart.appendChild(col); }); } } function buildRetention20Chart() { const chart = document.getElementById('retention20Chart'); if (!chart) return; const retData = [100,94,87,79,74,70,67,64,61,58,55,52,50,48,46,44,42,40,38,36]; chart.innerHTML = ''; const max = 100; retData.forEach((v, i) => { const col = document.createElement('div'); col.style.cssText = 'display:flex;flex-direction:column;align-items:center;gap:2px;flex:1;'; const bar = document.createElement('div'); const h = Math.round((v / max) * 96); const color = v > 70 ? '#10b981' : v > 50 ? '#ef9f27' : '#e63946'; bar.style.cssText = `height:${h}px;width:100%;background:${color};border-radius:3px 3px 0 0;`; bar.title = `Серія ${i+1}: ${v}% дослухали`; const label = document.createElement('div'); label.style.cssText = 'font-size:8px;color:var(--muted);text-align:center;'; label.textContent = i + 1; col.appendChild(bar); col.appendChild(label); chart.appendChild(col); }); } let brandClicks = 0; document.querySelector('.brand').addEventListener('click', function(e) { e.preventDefault(); brandClicks++; if (brandClicks >= 5) { brandClicks = 0; localStorage.setItem('balabony_admin', '1'); document.getElementById('adminPanel').style.display = 'block'; startAdminDashboard(); } }); function setStoryCover(imgUrl) { const header = document.getElementById('storyHeader'); if (!header) return; if (imgUrl) { header.style.backgroundImage = 'url(' + imgUrl + ')'; header.style.backgroundSize = 'cover'; header.style.backgroundPosition = 'center'; } window.addEventListener('scroll', function() { const rect = header.getBoundingClientRect(); const opacity = Math.max(0, Math.min(1, rect.bottom / header.offsetHeight)); header.style.opacity = opacity; }, { passive: true }); } let currentFontSize = 24; function changeFontSize(delta) { currentFontSize += delta; const el = document.getElementById('storyContent') || document.getElementById('readerContent'); if (el) el.style.fontSize = currentFontSize + 'px'; } window.addEventListener('load', function() { setStoryCover(null); }); const eyeModes = ['day', 'night', 'amber']; let eyeModeIdx = 0; const eyeLabels = ['☀️ День', '🌙 Ніч', '🟡 Бурштин']; function cycleEyeMode() { document.body.classList.remove('dark-mode', 'amber-mode'); eyeModeIdx = (eyeModeIdx + 1) % eyeModes.length; if (eyeModes[eyeModeIdx] === 'night') document.body.classList.add('dark-mode'); if (eyeModes[eyeModeIdx] === 'amber') document.body.classList.add('amber-mode'); const btn = document.getElementById('eyeBtn'); if (btn) btn.title = 'Режим: ' + eyeLabels[eyeModeIdx]; } let soundscapeOn = false; let soundCtx = null; let soundNodes = []; function toggleSoundscape() { soundscapeOn = !soundscapeOn; const label = document.getElementById('soundscapeLabel'); const btn = document.getElementById('soundscapeBtn'); if (soundscapeOn) { try { soundCtx = soundCtx || new (window.AudioContext || window.webkitAudioContext)(); const bufferSize = soundCtx.sampleRate * 2; const buffer = soundCtx.createBuffer(1, bufferSize, soundCtx.sampleRate); const data = buffer.getChannelData(0); for (let i = 0; i < bufferSize; i++) data[i] = (Math.random() * 2 - 1) * 0.04; const src = soundCtx.createBufferSource(); src.buffer = buffer; src.loop = true; const filter = soundCtx.createBiquadFilter(); filter.type = 'lowpass'; filter.frequency.value = 400; const gain = soundCtx.createGain(); gain.gain.value = 0.3; src.connect(filter).connect ? src.connect(filter) : null; filter.connect(gain); gain.connect(soundCtx.destination); src.start(); soundNodes = [src, filter, gain]; } catch(e) {} if (label) label.innerHTML = ' Радіовистава: увімк.'; if (btn) { btn.textContent = 'Вимкнути фон'; btn.style.borderColor = 'var(--accent-gold)'; btn.style.color = 'var(--accent-gold)'; } } else { soundNodes.forEach(n => { try { n.stop ? n.stop() : n.disconnect(); } catch(e) {} }); soundNodes = []; if (label) label.innerHTML = ' Радіовистава: вимк.'; if (btn) { btn.textContent = 'Увімкнути фон'; btn.style.borderColor = ''; btn.style.color = ''; } } } let bookmarks = []; function saveAudioBookmark() { const secs = Math.floor(progress / 100 * totalSec); const h = Math.floor(secs/3600), m = Math.floor((secs%3600)/60), s = secs%60; const label = `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}`; bookmarks.push({ time: secs, label }); const listEl = document.getElementById('bookmarkList'); if (listEl) listEl.textContent = 'Закладок: ' + bookmarks.length; localStorage.setItem('balabony_bookmarks', JSON.stringify(bookmarks)); const hint = document.createElement('div'); hint.style.cssText = 'position:fixed;bottom:110px;right:20px;background:var(--accent-gold);color:#fff;padding:10px 16px;border-radius:10px;font-size:13px;font-weight:700;z-index:400;'; hint.textContent = '📍 Закладку збережено: ' + label; document.body.appendChild(hint); setTimeout(() => hint.remove(), 2500); } window.addEventListener('load', function() { try { const saved = JSON.parse(localStorage.getItem('balabony_bookmarks') || '[]'); bookmarks = saved; const listEl = document.getElementById('bookmarkList'); if (listEl && saved.length) listEl.textContent = 'Закладок: ' + saved.length; } catch(e) {} }); (function() { let h = 24831; setInterval(() => { h += Math.floor(Math.random() * 3); const el = document.getElementById('totalHours'); if (el) el.textContent = h.toLocaleString('uk-UA'); }, 7000); })(); const CONTENT_TYPES = { FRANCHISE: { label: 'FRANCHISE', revenueShare: 1.0, royalty: 0 }, ORIGINAL: { label: 'ORIGINAL', revenueShare: 0.5, royalty: 0.5 } }; const proRataTracker = { totalTime: 0, originalTime: 0, start: function(type) { this._startedAt = Date.now(); this._currentType = type || 'FRANCHISE'; }, stop: function() { if (!this._startedAt) return; const elapsed = (Date.now() - this._startedAt) / 1000; this.totalTime += elapsed; if (this._currentType === 'ORIGINAL') this.originalTime += elapsed; this._startedAt = null; }, calcRoyalty: function(netRevenue) { if (this.totalTime === 0) return 0; const originalRatio = this.originalTime / this.totalTime; return netRevenue * originalRatio * CONTENT_TYPES.ORIGINAL.royalty; }, report: function() { const pct = this.totalTime > 0 ? Math.round(this.originalTime / this.totalTime * 100) : 0; return { totalMin: Math.round(this.totalTime / 60), originalMin: Math.round(this.originalTime / 60), originalPct: pct, royalty99: this.calcRoyalty(99).toFixed(2), royalty1800: this.calcRoyalty(150).toFixed(2) }; } }; const audioAds = [ { name: 'Реклама Партнера 1', duration: 30, src: null }, { name: 'Реклама Партнера 2', duration: 15, src: null }, { name: 'Balabony Promo', duration: 20, src: null } ]; let isSocialPlan = false; function playAudioAd() { if (!isSocialPlan) return; const ad = audioAds[Math.floor(Math.random() * audioAds.length)]; console.log('[Ad-Server] Програємо:', ad.name, '(' + ad.duration + ' сек)'); const overlay = document.createElement('div'); overlay.style.cssText = 'position:fixed;bottom:90px;left:0;right:0;background:var(--bg-deep);color:#94a3b8;text-align:center;padding:10px;font-size:12px;z-index:300;border-top:1px solid var(--border);'; overlay.id = 'adOverlay'; overlay.textContent = '📢 Аудіо-реклама: ' + ad.name + ' (' + ad.duration + ' сек)'; document.body.appendChild(overlay); setTimeout(() => { const el = document.getElementById('adOverlay'); if (el) el.remove(); }, ad.duration * 200); } if ('serviceWorker' in navigator) { window.addEventListener('load', function() { console.log('[PWA] Service Worker підтримується. Реєстрація: /sw.js'); }); } const HLS_PROTECTION = { enabled: false, provider: 'Cloudflare Stream / AWS MediaConvert', note: 'Шифрування AES-128, токени з TTL 3600 сек' }; if (typeof modalContent !== 'undefined') { modalContent.wallet = { title: 'Wallet — Нарахування (Брутто / Нетто)', text: 'Брутто: 894 ₴ (ORIGINAL, Pro-Rata 50%). Утримано: ФОП → 0 (автор сплачує сам). Нетто: 894 ₴. Поточний баланс: 745 ₴. Виплата LiqPay/PayPal до 5 квітня 2026.' }; modalContent.contract = { title: 'Договір з автором', text: 'FRANCHISE (Балабони): 0% роялті, 100% платформи. ORIGINAL: 50/50 Pro-Rata. Активація: 500 ₴ разово + Дія.Підпис. Ексклюзивність ORIGINAL: 1 рік.' }; } function buildHeatmap() { const grid = document.getElementById('heatmapGrid'); if (!grid) return; const depths = [100,94,87,79,74,70,67,64,61,58,55,52,50,48,46,44,42,40,38,36]; grid.innerHTML = ''; depths.forEach((v, i) => { const cell = document.createElement('div'); const alpha = 0.15 + (v / 100) * 0.75; cell.style.cssText = ` background: rgba(239,159,39,${alpha.toFixed(2)}); border-radius: 4px; padding: 6px 4px; text-align: center; font-size: 10px; color: ${v > 60 ? '#0f172a' : '#f8fafc'}; font-weight: 700; cursor: default; `; cell.textContent = i + 1; cell.title = `Серія ${i+1}: ${v}% дослухали`; grid.appendChild(cell); }); } const smartPayout = { calcFOP: function(grossAmount) { return { gross: grossAmount, net: grossAmount, note: 'Автор-ФОП: 50%. Податки сам.' }; }, calcIndividual: function(grossAmount) { const pdfo = grossAmount * 0.18; const vz = grossAmount * 0.05; const esv = grossAmount * 0.22; const net = grossAmount - pdfo - vz; return { gross: grossAmount.toFixed(2), pdfo: pdfo.toFixed(2), vz: vz.toFixed(2), esv: esv.toFixed(2), net: net.toFixed(2), note: 'Фізособа: 40%. ПДФО+ВЗ утримує платформа. ЄСВ 22% зверху.' }; }, report: function(amount, type) { return type === 'FOP' ? this.calcFOP(amount) : this.calcIndividual(amount); } }; function calcTopRating(listenMin, rating, saves) { return ((listenMin * 0.6) + (rating * 0.3) + (saves * 0.1)).toFixed(2); } (function() { let amount = 18450; const goal = 50000; const el = document.getElementById('crowdAmount'); if (!el) return; setInterval(() => { amount += Math.floor(Math.random() * 50); if (amount > goal) amount = goal; el.textContent = amount.toLocaleString('uk-UA'); }, 12000); })(); const _origStartAdmin = typeof startAdminDashboard !== 'undefined' ? startAdminDashboard : null; if (_origStartAdmin) { const __origStart = startAdminDashboard; startAdminDashboard = function() { __origStart(); buildHeatmap(); }; } let demoSeriesPlayed = parseInt(localStorage.getItem('balabony_demo_played') || '0'); const DEMO_LIMIT = 2; function startFreeDemo() { if (demoSeriesPlayed >= DEMO_LIMIT) { showUpsellModal(); return; } demoSeriesPlayed++; localStorage.setItem('balabony_demo_played', demoSeriesPlayed); const reader = document.getElementById('reader'); if (reader) reader.scrollIntoView({ behavior: 'smooth' }); if (demoSeriesPlayed >= DEMO_LIMIT) { setTimeout(showUpsellModal, 3000); } } function showUpsellModal() { if (!isPremium) { document.getElementById('upsellModal').classList.add('open'); } } function closeUpsell() { document.getElementById('upsellModal').classList.remove('open'); } document.getElementById('upsellModal').addEventListener('click', function(e) { if (e.target === this) closeUpsell(); }); const priceFeedbackLog = JSON.parse(localStorage.getItem('balabony_price_feedback') || '[]'); function openNegotiatorBot() { closeUpsell(); document.getElementById('botChat').innerHTML = 'Яка ціна на місяць була б для вас прийнятною? Введіть суму в гривнях:'; document.getElementById('priceInput').value = ''; document.getElementById('negotiatorModal').classList.add('open'); } function closeNegotiator() { document.getElementById('negotiatorModal').classList.remove('open'); } document.getElementById('negotiatorModal').addEventListener('click', function(e) { if (e.target === this) closeNegotiator(); }); function negotiatorReply() { const raw = parseInt(document.getElementById('priceInput').value); const chat = document.getElementById('botChat'); if (!raw || raw <= 0) { chat.innerHTML += '
Введіть коректну суму.'; return; } priceFeedbackLog.push({ price: raw, ts: new Date().toISOString() }); localStorage.setItem('balabony_price_feedback', JSON.stringify(priceFeedbackLog)); updatePriceFeedbackPanel(); let reply = ''; if (raw === 1) { reply = '😊 На жаль, без реклами за 1 грн доступ надати неможливо. Спробуйте знайти за такою ціною в іншому місці. Чекаємо вас у Соціальному пакеті — там лише відеореклама!'; chat.innerHTML = reply; } else if (raw < 50) { reply = '🤝 Ми розуміємо! Пропонуємо «поріг виживання» — 50 грн за перший місяць. Далі — 99 грн/міс. Згодні?'; chat.innerHTML = reply; chat.innerHTML += '

'; } else if (raw <= 98) { const offer = Math.max(50, raw); reply = `🎉 Домовились! Перший місяць за ${offer} ₴ (промокод: DEAL${offer}). З наступного — 99 ₴/міс.`; chat.innerHTML = reply; chat.innerHTML += `

`; const convEl = document.getElementById('botConversions'); if (convEl) convEl.textContent = parseInt(convEl.textContent || 0) + 1; } else { reply = '🙂 99 ₴ вже є нашою найкращою ціною! Але ми цінуємо вас — ось 7 днів безкоштовно для ознайомлення.'; chat.innerHTML = reply; chat.innerHTML += '

'; } document.getElementById('priceInput').disabled = true; } function updatePriceFeedbackPanel() { const listEl = document.getElementById('priceFeedbackList'); const countEl = document.getElementById('feedbackCount'); const medEl = document.getElementById('medianPrice'); const avgEl = document.getElementById('avgPrice'); if (!listEl || !priceFeedbackLog.length) return; countEl.textContent = priceFeedbackLog.length + ' відповідей'; const prices = priceFeedbackLog.map(f => f.price).sort((a,b) => a-b); const avg = Math.round(prices.reduce((s,v) => s+v, 0) / prices.length); const med = prices[Math.floor(prices.length / 2)]; if (avgEl) avgEl.textContent = avg; if (medEl) medEl.textContent = med; listEl.innerHTML = ''; priceFeedbackLog.slice(-10).reverse().forEach(f => { const row = document.createElement('div'); row.style.cssText = 'display:flex;justify-content:space-between;align-items:center;padding:6px 10px;background:var(--light);border-radius:6px;font-size:12px;'; const label = f.price === 1 ? '❌ відмова' : f.price < 50 ? '⚠️ нижче порогу' : f.price <= 98 ? '✅ конверсія' : '💬 дорого'; row.innerHTML = `${f.price} ₴ — ${label}${new Date(f.ts).toLocaleDateString('uk-UA')}`; listEl.appendChild(row); }); } window.addEventListener('load', function() { updatePriceFeedbackPanel(); if (demoSeriesPlayed >= DEMO_LIMIT && !isPremium) { } }); window.addEventListener('load', function() { updatePriceFeedbackPanel(); initAuthorBalance(); loadLangCards(); loadAdminData(); checkDiscountTimer(); if (demoSeriesPlayed >= DEMO_LIMIT && !isPremium) { } }); // ============================================================ // БЛОК 1: ПОШТУЧНА КУПІВЛЯ (7 ₴) + WayForPay/LiqPay // ============================================================ let currentBuyItem = null; function updateTryPrice() { const sel = document.getElementById('trySeriesSelect'); const opt = sel.options[sel.selectedIndex]; const price = opt.dataset.price || '7'; document.getElementById('tryPriceDisplay').textContent = price; document.getElementById('tryBtnPrice').textContent = price; currentBuyItem = { id: sel.value, title: opt.text, price: parseInt(price) }; } function buyOneItem() { const sel = document.getElementById('trySeriesSelect'); const opt = sel.options[sel.selectedIndex]; currentBuyItem = { id: sel.value, title: opt.text, price: 7 }; const modal = document.getElementById('buyOneModal'); document.getElementById('buyOneDesc').textContent = opt.text; modal.style.display = 'flex'; } function processBuyOneWFP() { // WayForPay потребує серверного підпису (HMAC-MD5). // Тут відкриваємо симуляцію — реальна інтеграція потребує backend. const item = currentBuyItem || { title: 'Серія', price: 7 }; const wfpUrl = `https://secure.wayforpay.com/pay?` + `merchantAccount=balabony_demo` + `&merchantDomainName=balabony.com` + `&orderReference=order_${Date.now()}` + `&orderDate=${Math.floor(Date.now()/1000)}` + `&amount=${item.price}` + `¤cy=UAH` + `&productName=${encodeURIComponent(item.title)}` + `&productCount=1` + `&productPrice=${item.price}`; // Зберігаємо що куплено (до webhook підтвердження) const bought = JSON.parse(localStorage.getItem('balabony_bought') || '[]'); bought.push({ id: item.id, ts: Date.now() }); localStorage.setItem('balabony_bought', JSON.stringify(bought)); document.getElementById('buyOneModal').style.display = 'none'; alert(`✅ Симуляція WayForPay для: "${item.title}" — 7 ₴\n\nДоступ відкрито локально.\n(Реальна оплата потребує backend-webhook)`); grantLocalAccess(currentBuyItem.id); } function processBuyOneLiqPay() { const item = currentBuyItem || { title: 'Серія', price: 7 }; document.getElementById('buyOneModal').style.display = 'none'; alert(`✅ Симуляція LiqPay для: "${item.title}" — 7 ₴\n\nДоступ відкрито локально.\n(Реальна оплата потребує серверний ключ LiqPay)`); grantLocalAccess(currentBuyItem.id); } function grantLocalAccess(itemId) { const bought = JSON.parse(localStorage.getItem('balabony_bought') || '[]'); if (!bought.find(b => b.id === itemId)) { bought.push({ id: itemId, ts: Date.now() }); localStorage.setItem('balabony_bought', JSON.stringify(bought)); } // Позначаємо серію як доступну в UI const episodeBtn = document.querySelector(`[data-ep="${itemId}"]`); if (episodeBtn) { episodeBtn.style.background = '#10b981'; episodeBtn.style.color = '#fff'; } } function hasLocalAccess(itemId) { const bought = JSON.parse(localStorage.getItem('balabony_bought') || '[]'); return bought.some(b => b.id === itemId); } // ============================================================ // БЛОК 2: КРЕДИТ (ПриватБанк / Ощадбанк) // ============================================================ let creditBank = ''; let creditAmt = 0; function openCreditFlow(bank, amount) { creditBank = bank; creditAmt = amount; const modal = document.getElementById('creditModal'); const icon = document.getElementById('creditModalIcon'); const title = document.getElementById('creditModalTitle'); const desc = document.getElementById('creditModalDesc'); const btn = document.getElementById('creditGoBtn'); const amtEl = document.getElementById('creditAmount'); amtEl.textContent = amount + ' ₴'; if (bank === 'privat') { icon.textContent = '🟢'; title.textContent = 'ПриватБанк — Оплата частинами'; desc.innerHTML = `Перевірка ліміту та підписання угоди на сайті ПриватБанку. Сума: ${amount} ₴`; btn.style.background = '#009444'; btn.textContent = 'Перейти до ПриватБанку →'; } else { icon.textContent = '🔵'; title.textContent = 'Ощадбанк — Розстрочка'; desc.innerHTML = `Оформлення розстрочки на сайті Ощадбанку. Сума: ${amount} ₴`; btn.style.background = '#00529b'; btn.textContent = 'Перейти до Ощадбанку →'; } modal.style.display = 'flex'; } function goToBankCredit() { const urls = { privat: `https://www.privatbank.ua/payments/p24/?MERCHANT_ID=balabony&AMOUNT=${creditAmt}&CURRENCY=UAH&DESCRIPTION=Balabony+підписка`, oschadbank: `https://online.oschadbank.ua/payments/?amount=${creditAmt}&merchant=balabony&desc=Balabony` }; document.getElementById('creditModal').style.display = 'none'; // Відкриваємо у новій вкладці (реальний URL потребує реєстрації мерчанта) window.open(urls[creditBank] || '#', '_blank'); alert(`ℹ️ Для реальної роботи потрібна реєстрація мерчанта в ${creditBank === 'privat' ? 'ПриватБанку' : 'Ощадбанку'}.\n\nURL сформовано коректно — відкриється після реєстрації.`); } // ============================================================ // БЛОК 3: ЛОГІКА "ДОРОГО" × 2 → POP-UP ЗНИЖКИ -20% / 24 год // ============================================================ const priceFeedbackLog = JSON.parse(localStorage.getItem('balabony_price_feedback') || '[]'); let expensiveClickCount = parseInt(localStorage.getItem('balabony_expensive_clicks') || '0'); let discountTimerInterval = null; function recordExpensive() { expensiveClickCount++; localStorage.setItem('balabony_expensive_clicks', expensiveClickCount); // Логуємо в price_feedback priceFeedbackLog.push({ price: 99, tag: 'expensive', ts: new Date().toISOString() }); localStorage.setItem('balabony_price_feedback', JSON.stringify(priceFeedbackLog)); updatePriceFeedbackPanel(); if (expensiveClickCount >= 2) { // Зберігаємо час видачі знижки (діє 24 год) const existingExpiry = localStorage.getItem('balabony_discount_expiry'); if (!existingExpiry) { const expiry = Date.now() + 24 * 60 * 60 * 1000; localStorage.setItem('balabony_discount_expiry', expiry); localStorage.setItem('balabony_discount_code', 'SAVE20'); } showDiscountPopup(); } else { // Після 1 кліку — показуємо бота openNegotiatorBot(); } } function showDiscountPopup() { const popup = document.getElementById('discountPopup'); popup.style.display = 'flex'; startDiscountCountdown(); // Оновлюємо UI ціни в тарифній карточці const badge = document.getElementById('discountBadge'); if (badge) badge.style.display = 'block'; const priceEl = document.getElementById('monthlyPriceDisplay'); if (priceEl) priceEl.innerHTML = '99 79 ₴/міс'; const payBtn = document.getElementById('monthlyPayBtn'); if (payBtn) { payBtn.textContent = 'Оформити за 79 ₴ 🔥'; payBtn.onclick = function() { openPayment('Базовий — 79 ₴/міс (SAVE20)'); }; } } function checkDiscountTimer() { const expiry = localStorage.getItem('balabony_discount_expiry'); if (expiry && Date.now() < parseInt(expiry)) { // Знижка ще активна const badge = document.getElementById('discountBadge'); if (badge) badge.style.display = 'block'; const priceEl = document.getElementById('monthlyPriceDisplay'); if (priceEl) priceEl.innerHTML = '99 79 ₴/міс'; } } function startDiscountCountdown() { const expiry = parseInt(localStorage.getItem('balabony_discount_expiry') || (Date.now() + 86400000)); if (discountTimerInterval) clearInterval(discountTimerInterval); discountTimerInterval = setInterval(() => { const left = expiry - Date.now(); if (left <= 0) { clearInterval(discountTimerInterval); localStorage.removeItem('balabony_discount_expiry'); localStorage.removeItem('balabony_discount_code'); localStorage.setItem('balabony_expensive_clicks', '0'); document.getElementById('discountPopup').style.display = 'none'; // Повертаємо стару ціну const priceEl = document.getElementById('monthlyPriceDisplay'); if (priceEl) priceEl.innerHTML = '99 ₴/міс'; return; } const h = Math.floor(left / 3600000).toString().padStart(2,'0'); const m = Math.floor((left % 3600000) / 60000).toString().padStart(2,'0'); const s = Math.floor((left % 60000) / 1000).toString().padStart(2,'0'); const el = document.getElementById('discountTimer'); if (el) el.textContent = `${h}:${m}:${s}`; }, 1000); } function closeDiscountPopup() { document.getElementById('discountPopup').style.display = 'none'; } function applyDiscountAndPay() { document.getElementById('discountPopup').style.display = 'none'; openPayment('Базовий — 79 ₴/міс (промокод SAVE20)'); } // ============================================================ // БЛОК 4: АВАТАР + КРОППЕР // ============================================================ let cropImg = null; let cropX = 0, cropY = 0; let isDragging = false; let dragStartX, dragStartY, cropStartX, cropStartY; function initAvatarCrop(e) { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(ev) { const container = document.getElementById('cropperContainer'); const canvas = document.getElementById('cropCanvas'); container.style.display = 'block'; cropImg = new Image(); cropImg.onload = function() { const maxW = Math.min(cropImg.width, 400); const scale = maxW / cropImg.width; canvas.width = maxW; canvas.height = cropImg.height * scale; const ctx = canvas.getContext('2d'); ctx.drawImage(cropImg, 0, 0, canvas.width, canvas.height); // Початкова позиція рамки — центр cropX = (canvas.width - 120) / 2; cropY = (canvas.height - 120) / 2; const overlay = document.getElementById('cropOverlay'); overlay.style.left = cropX + 'px'; overlay.style.top = cropY + 'px'; overlay.style.transform = 'none'; }; cropImg.src = ev.target.result; }; reader.readAsDataURL(file); } function startCropDrag(e) { isDragging = true; const touch = e.touches ? e.touches[0] : e; dragStartX = touch.clientX; dragStartY = touch.clientY; cropStartX = cropX; cropStartY = cropY; const move = (e2) => { if (!isDragging) return; const t = e2.touches ? e2.touches[0] : e2; const dx = t.clientX - dragStartX; const dy = t.clientY - dragStartY; const canvas = document.getElementById('cropCanvas'); cropX = Math.max(0, Math.min(canvas.width - 120, cropStartX + dx)); cropY = Math.max(0, Math.min(canvas.height - 120, cropStartY + dy)); const overlay = document.getElementById('cropOverlay'); overlay.style.left = cropX + 'px'; overlay.style.top = cropY + 'px'; }; const up = () => { isDragging = false; document.removeEventListener('mousemove', move); document.removeEventListener('mouseup', up); }; document.addEventListener('mousemove', move); document.addEventListener('mouseup', up); document.addEventListener('touchmove', move); document.addEventListener('touchend', up); } function applyCrop() { if (!cropImg) return; const srcCanvas = document.getElementById('cropCanvas'); const scaleX = cropImg.width / srcCanvas.width; const scaleY = cropImg.height / srcCanvas.height; const out = document.createElement('canvas'); out.width = 120; out.height = 120; const ctx = out.getContext('2d'); ctx.save(); ctx.beginPath(); ctx.arc(60, 60, 60, 0, Math.PI * 2); ctx.clip(); ctx.drawImage(cropImg, cropX * scaleX, cropY * scaleY, 120 * scaleX, 120 * scaleY, 0, 0, 120, 120 ); ctx.restore(); const dataUrl = out.toDataURL('image/jpeg', 0.9); // Показуємо в аватарному колі const preview = document.getElementById('avatarPreview'); preview.innerHTML = ``; localStorage.setItem('balabony_avatar', dataUrl); document.getElementById('cropperContainer').style.display = 'none'; // Перевіряємо чи можна вивести гроші checkWithdrawEligibility(); } // ============================================================ // БЛОК 5: ГАМАНЕЦЬ — реальний поріг 100 ₴ // ============================================================ function initAuthorBalance() { const balance = parseInt(localStorage.getItem('balabony_author_balance') || '74'); updateBalanceUI(balance); } function updateBalanceUI(balance) { const el = document.getElementById('authorBalance'); const bar = document.getElementById('balanceBar'); const needed = document.getElementById('balanceNeeded'); const btn = document.getElementById('withdrawBtn'); if (!el) return; el.textContent = balance + ' ₴'; const pct = Math.min(100, balance); if (bar) bar.style.width = pct + '%'; if (needed) needed.textContent = Math.max(0, 100 - balance) + ' ₴'; if (btn) { if (balance >= 100 && localStorage.getItem('balabony_diia_signed')) { btn.disabled = false; btn.style.cssText = 'width:100%;padding:9px;border-radius:7px;font-size:13px;font-weight:700;cursor:pointer;background:var(--accent-gold);color:#fff;border:none;font-family:Montserrat,sans-serif;'; btn.textContent = '💸 Вивести ' + balance + ' ₴'; } else if (balance >= 100) { btn.disabled = false; btn.style.cssText = 'width:100%;padding:9px;border-radius:7px;font-size:13px;font-weight:700;cursor:pointer;background:#c2410c;color:#fff;border:none;font-family:Montserrat,sans-serif;'; btn.textContent = '⚠️ Підпишіть договір ДІЯ спочатку'; btn.onclick = function() { document.getElementById('withdrawDiiaNote').style.display = 'block'; openDiiaFlow(); }; } else { btn.disabled = true; btn.style.cssText = 'width:100%;padding:9px;border-radius:7px;font-size:13px;font-weight:700;cursor:not-allowed;background:#e2e8f0;color:#94a3b8;border:none;font-family:Montserrat,sans-serif;'; btn.textContent = '🔒 Вивести кошти (потрібно ≥100 ₴)'; } } } function checkWithdrawEligibility() { const balance = parseInt(localStorage.getItem('balabony_author_balance') || '74'); updateBalanceUI(balance); } function requestWithdraw() { const balance = parseInt(localStorage.getItem('balabony_author_balance') || '0'); if (balance < 100) return; if (!localStorage.getItem('balabony_diia_signed')) { openDiiaFlow(); return; } alert(`✅ Запит на виведення ${balance} ₴ прийнято.\nВиплата до 5-го числа наступного місяця на рахунок, вказаний у договорі.`); } // ============================================================ // БЛОК 6: ВИБІР ТИПУ КОНТЕНТУ + МУЛЬТИ-ЗАВАНТАЖЕННЯ СЕРІАЛУ // ============================================================ function setContentType(type) { const storyBtn = document.getElementById('typeStory'); const serialBtn = document.getElementById('typeSerial'); const storyUpload = document.getElementById('uploadStory'); const serialUpload = document.getElementById('uploadSerial'); if (type === 'story') { storyBtn.style.cssText = 'flex:1;padding:7px;border-radius:7px;border:2px solid var(--accent-gold);background:var(--accent-gold);color:#fff;font-size:12px;font-weight:700;cursor:pointer;font-family:Montserrat,sans-serif;'; serialBtn.style.cssText = 'flex:1;padding:7px;border-radius:7px;border:2px solid var(--border);background:transparent;color:var(--muted);font-size:12px;font-weight:700;cursor:pointer;font-family:Montserrat,sans-serif;'; storyUpload.style.display = 'flex'; serialUpload.style.display = 'none'; } else { serialBtn.style.cssText = 'flex:1;padding:7px;border-radius:7px;border:2px solid var(--accent-gold);background:var(--accent-gold);color:#fff;font-size:12px;font-weight:700;cursor:pointer;font-family:Montserrat,sans-serif;'; storyBtn.style.cssText = 'flex:1;padding:7px;border-radius:7px;border:2px solid var(--border);background:transparent;color:var(--muted);font-size:12px;font-weight:700;cursor:pointer;font-family:Montserrat,sans-serif;'; storyUpload.style.display = 'none'; serialUpload.style.display = 'flex'; if (document.getElementById('serialEpisodes').children.length === 0) { addSerialEpisode(); addSerialEpisode(); } } } let episodeCount = 0; function addSerialEpisode() { episodeCount++; const container = document.getElementById('serialEpisodes'); const row = document.createElement('div'); row.style.cssText = 'display:flex;align-items:center;gap:6px;background:var(--white);border:1px solid var(--border);border-radius:6px;padding:6px 8px;'; row.innerHTML = `С${episodeCount} `; container.appendChild(row); } function submitSingleUpload() { alert('✅ Окрему історію надіслано на модерацію.\nОксана перевірить протягом 24 годин.'); } function submitSerialUpload() { const name = document.getElementById('serialName').value || 'Новий серіал'; const count = document.getElementById('serialEpisodes').children.length; alert(`✅ Серіал "${name}" (${count} серій) надіслано на модерацію.\nМарічка перевірить відповідність аудіо тексту.`); } // ============================================================ // БЛОК 7: ПЕРЕМИКАЧ КАБІНЕТІВ АДМІНІВ // ============================================================ function switchAdminRole(role) { document.querySelectorAll('.admin-cabinet').forEach(c => c.style.display = 'none'); document.querySelectorAll('.admin-role-btn').forEach(b => { b.style.background = 'transparent'; b.style.color = 'var(--muted)'; }); const cabinet = document.getElementById('cabinet-' + role); if (cabinet) cabinet.style.display = 'block'; const btn = document.getElementById('roleBtn-' + role); if (btn) { btn.style.background = 'var(--bg-deep)'; btn.style.color = '#fff'; } // Завантажуємо дані кабінету if (role === 'oksana') loadApprovalQueue(); if (role === 'tetiana') { loadAdSlots(); loadPromoAuthors(); } if (role === 'natalia') loadLangCards(); } function loadAdminData() { // Ініціалізація стилю для admin-cabinet класів document.querySelectorAll('.admin-cabinet').forEach((c,i) => { if (i > 0) c.style.display = 'none'; }); } // Кабінет Оксани function loadApprovalQueue() { const queue = [ { title: 'Нова серія — «Загадка дзеркала»', author: 'Іванченко М.', type: '📺 Серіал' }, { title: '«Лист з окопу» (окрема)', author: 'Петренко О.', type: '📖 Окрема' }, ]; const el = document.getElementById('approvalQueue'); if (!el) return; el.innerHTML = ''; queue.forEach(item => { el.innerHTML += `
${item.title}
${item.author} · ${item.type}
`; }); } function approveItem(btn) { btn.closest('div[style]').style.opacity='0.4'; btn.textContent = '✓ Схвалено'; btn.disabled = true; } function rejectItem(btn) { btn.closest('div[style]').style.opacity='0.4'; btn.textContent = '✗ Відхилено'; btn.disabled = true; } function setStoryOfDay() { const sel = document.getElementById('storyOfDaySelect'); localStorage.setItem('balabony_story_of_day', sel.value); const confirm = document.getElementById('storyOfDayConfirm'); if (confirm) { confirm.style.display = 'block'; setTimeout(() => confirm.style.display = 'none', 2000); } } // AI-обкладинка через DALL-E 3 async function generateAICover() { const prompt = document.getElementById('aiCoverPrompt').value.trim(); if (!prompt) { alert('Введіть опис обкладинки'); return; } const key = localStorage.getItem('openai_key') || document.getElementById('openaiKeyInput')?.value; if (!key || !key.startsWith('sk-')) { alert('Введіть коректний API-ключ OpenAI (sk-...)'); return; } document.getElementById('aiCoverLoading').style.display = 'block'; document.getElementById('aiCoverResult').style.display = 'none'; const fullPrompt = `Ukrainian audio platform "Balabony" cover art, ${prompt}. Style: warm golden tones, folk illustration, storytelling atmosphere, book cover design, professional quality`; try { const resp = await fetch('https://api.openai.com/v1/images/generations', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${key}` }, body: JSON.stringify({ model: 'dall-e-3', prompt: fullPrompt, n: 1, size: '1024x1024', quality: 'standard' }) }); const data = await resp.json(); if (data.data && data.data[0]) { const imgUrl = data.data[0].url; document.getElementById('aiCoverImg').src = imgUrl; document.getElementById('aiCoverResult').style.display = 'block'; localStorage.setItem('balabony_last_ai_cover', imgUrl); } else { alert('Помилка DALL-E: ' + (data.error?.message || JSON.stringify(data))); } } catch(e) { alert('Помилка мережі: ' + e.message); } finally { document.getElementById('aiCoverLoading').style.display = 'none'; } } function applyCoverToStory() { const imgSrc = document.getElementById('aiCoverImg').src; // Замінюємо плейсхолдери обкладинок document.querySelectorAll('.series-cover-placeholder').forEach(el => { el.style.backgroundImage = `url(${imgSrc})`; el.style.backgroundSize = 'cover'; el.style.backgroundPosition = 'center'; }); alert('✅ AI-обкладинку застосовано до всіх серій на сторінці.'); } // Кабінет Тетяни function loadAdSlots() { const slots = JSON.parse(localStorage.getItem('balabony_ad_slots') || '[]'); const el = document.getElementById('adSlotsList'); if (!el) return; if (!slots.length) { el.innerHTML = '
Рекламних блоків ще немає
'; return; } el.innerHTML = slots.map((s,i) => `
${s.title}
${s.url}
`).join(''); } function addAdSlot() { const title = document.getElementById('newAdTitle').value.trim(); const url = document.getElementById('newAdUrl').value.trim(); if (!title) { alert('Введіть назву реклами'); return; } const slots = JSON.parse(localStorage.getItem('balabony_ad_slots') || '[]'); slots.push({ title, url, added: new Date().toLocaleDateString('uk-UA'), expiry: new Date(Date.now()+30*86400000).toLocaleDateString('uk-UA') }); localStorage.setItem('balabony_ad_slots', JSON.stringify(slots)); document.getElementById('newAdTitle').value = ''; document.getElementById('newAdUrl').value = ''; loadAdSlots(); } function removeAdSlot(i) { const slots = JSON.parse(localStorage.getItem('balabony_ad_slots') || '[]'); slots.splice(i, 1); localStorage.setItem('balabony_ad_slots', JSON.stringify(slots)); loadAdSlots(); } function loadPromoAuthors() { const authors = JSON.parse(localStorage.getItem('balabony_promo_authors') || '[]'); const el = document.getElementById('promoAuthorsList'); if (!el) return; if (!authors.length) { el.innerHTML = '
Авторів з промо ще немає
'; return; } el.innerHTML = authors.map(a => `
👤 ${a.id} · до ${a.expiry}
`).join(''); } function addPromoAuthor() { const id = document.getElementById('promoAuthorInput').value.trim(); if (!id) { alert('Введіть ID або email автора'); return; } const authors = JSON.parse(localStorage.getItem('balabony_promo_authors') || '[]'); authors.push({ id, expiry: new Date(Date.now()+30*86400000).toLocaleDateString('uk-UA'), is_archival: false }); localStorage.setItem('balabony_promo_authors', JSON.stringify(authors)); document.getElementById('promoAuthorInput').value = ''; loadPromoAuthors(); } // Кабінет Марічки function saveEditorText() { const text = document.getElementById('marichkaTextEditor').value; localStorage.setItem('balabony_edited_text', text); alert('✅ Правки збережено. Автор отримає повідомлення.'); } function approveSeries() { alert('✅ Серію схвалено. Передано Оксані для фінального затвердження.'); } function loadAudioForCheck(e) { const file = e.target.files[0]; if (!file) return; const audio = document.getElementById('marichkaAudioCheck'); audio.src = URL.createObjectURL(file); } function sendEditorComment() { const comment = document.getElementById('marichkaComment').value.trim(); if (!comment) { alert('Введіть коментар'); return; } document.getElementById('commentSent').style.display = 'block'; document.getElementById('marichkaComment').value = ''; setTimeout(() => document.getElementById('commentSent').style.display = 'none', 2000); } // ============================================================ // БЛОК 8: МОВНИЙ МОДУЛЬ НАТАЛІ (1000 карток) // ============================================================ function loadLangCards() { const cards = JSON.parse(localStorage.getItem('balabony_lang_cards') || '[]'); const listEl = document.getElementById('langCardsList'); const countEl = document.getElementById('cardCount'); const barEl = document.getElementById('cardProgressBar'); if (countEl) countEl.textContent = cards.length; if (barEl) barEl.style.width = (cards.length / 10) + '%'; // 10% = 100 cards out of 1000 if (!listEl) return; if (!cards.length) { listEl.innerHTML = '
Карток ще немає. Додайте першу!
'; return; } listEl.innerHTML = cards.slice(-20).reverse().map((c,i) => `
${c.wrong} ${c.right} ${c.audio ? `` : ''}
`).join(''); } function addLanguageCard() { const wrong = document.getElementById('wrongWord').value.trim(); const right = document.getElementById('rightWord').value.trim(); const audioFile = document.getElementById('cardAudioFile').files[0]; if (!wrong || !right) { alert('Заповніть обидва поля'); return; } const cards = JSON.parse(localStorage.getItem('balabony_lang_cards') || '[]'); if (cards.length >= 1000) { alert('База вже містить 1000 карток — ціль досягнута!'); return; } const saveCard = (audioDataUrl) => { cards.push({ wrong, right, audio: audioDataUrl || null, ts: Date.now() }); localStorage.setItem('balabony_lang_cards', JSON.stringify(cards)); document.getElementById('wrongWord').value = ''; document.getElementById('rightWord').value = ''; document.getElementById('cardAudioFile').value = ''; document.getElementById('cardAudioPreview').style.display = 'none'; const msg = document.getElementById('cardAddedMsg'); if (msg) { msg.style.display = 'block'; setTimeout(() => msg.style.display = 'none', 1500); } loadLangCards(); }; if (audioFile) { const reader = new FileReader(); reader.onload = (e) => saveCard(e.target.result); reader.readAsDataURL(audioFile); } else { // Web Speech API для озвучення (якщо немає файлу) if ('speechSynthesis' in window) { const utt = new SpeechSynthesisUtterance(right); utt.lang = 'uk-UA'; speechSynthesis.speak(utt); } saveCard(null); } } function deleteLangCard(idx) { const cards = JSON.parse(localStorage.getItem('balabony_lang_cards') || '[]'); const realIdx = cards.length - 1 - idx; cards.splice(realIdx, 1); localStorage.setItem('balabony_lang_cards', JSON.stringify(cards)); loadLangCards(); } function playCardAudio(dataUrl) { if (!dataUrl) return; const audio = new Audio(dataUrl); audio.play().catch(e => { // Fallback: Web Speech API const word = dataUrl; // якщо це текст, а не dataUrl if ('speechSynthesis' in window) { const utt = new SpeechSynthesisUtterance(word); utt.lang = 'uk-UA'; speechSynthesis.speak(utt); } }); } // Показ аудіо при виборі файлу картки document.addEventListener('change', function(e) { if (e.target.id === 'cardAudioFile') { const file = e.target.files[0]; if (file) { const preview = document.getElementById('cardAudioPreview'); const audio = document.getElementById('cardAudioEl'); if (preview && audio) { audio.src = URL.createObjectURL(file); preview.style.display = 'block'; } } } });