BG
GamesTypeScriptCI status

The Road to a Million

A Bulgarian quiz show in the browser with a host, music, 547 questions and four difficulty levels.

README

Пътят към милиона

🎮 Играй сега · Как се играе · Добави въпрос · Разработка · English

Deploy Въпроси TypeScript Vite Без зависимости MIT

„Пътят към милиона“ е телевизионно куиз шоу в браузъра. Сядаш на стола срещу водещия Ясен Светлев: той те представя, чете въпросите на глас и пита „Последен отговор?“. Светлините угасват, музиката затихва, сърцето бие… и чак тогава разбираш дали си познал.

Няма регистрация, реклами или сървър. Всичко работи в браузъра, а въпросите са обикновени JSON файлове.

Екран от играта

✨ Какво има

🎙️ Водещ Оригинален герой с аватар и над 100 реплики. Говори на глас с най-добрия български глас на браузъра (в Microsoft Edge това е невронният „Borislav Natural“), а гласът може да се смени от настройките. Приятелките по телефона говорят с женски глас, ако има такъв. Без глас водещият „говори“ в балончета.
🎵 Музика и звуци Музиката е композирана в код с Web Audio API: фон, който става по-напрегнат с всяко ниво, сърдечен ритъм при заключен отговор, фанфари. Отгоре има истинска публика: аплодисменти, викове, барабанно тремоло преди големите отговори и звънящ телефон (записи с лиценз CC0).
💡 547 въпроса 13 категории: история, география, наука, изкуство, спорт, кино и музика, България, обща култура, животни, технологии, митология, кухня и „Езикът ни“.
🎚️ 4 трудности От „Лесно“ без таймер до „Експерт“ с 20 секунди, само 50:50 и без сигурни суми.
🃏 4 жокера 50:50, „Питай публиката“ с гласуване на живо, „Обади се на приятел“ с избор кого да потърсиш и 30 секунди разговор, „Смени въпроса“.
💡 Студио Прожектори, които се движат по-бързо с наближаването на милиона, затъмняване при заключен отговор, зелена или червена светлина при отговора, конфети.
💰 Вземи парите Спри когато искаш. После водещият те пита какво щеше да отговориш и ти показва дали щеше да познаеш.
🏆 Зала на славата Обща класация на всички играчи, лична Зала на славата и статистика, която следва профила ти.
📝 Твои въпроси Влез с Google, направи свой пакет с поне 15 въпроса и го играй или го прати на приятели с линк. Публичните пакети се виждат от всички в „Пакети на играчите“.
📱 Навсякъде Работи на телефон, с клавиатура и с екранен четец, минава на цял екран при първото кликване и спазва prefers-reduced-motion.

🎮 Как се играе

Отговаряш на 15 въпроса, всеки по-труден и по-скъп от предишния. Можеш да спреш по всяко време и да вземеш спечеленото. При грешен отговор (или изтекло време) се връщаш до последната сигурна сума 🔒.

💶 Стълбата на наградите
Ниво Награда Ниво Награда Ниво Награда
1 100 € 6 2 500 € 11 75 000 €
2 200 € 7 5 000 € 12 150 000 €
3 300 € 8 10 000 € 13 250 000 €
4 500 € 9 20 000 € 14 500 000 €
5 1 000 € 🔒 10 40 000 € 🔒 15 1 000 000 €

🎚️ Трудности

Режим Таймер Жокери Сигурни суми Въпроси
Лесно без таймер ½ 👥 📞 🔄 нива 5 и 10 по-леки (★ до ★★★★)
Нормално 45 s ½ 👥 📞 нива 5 и 10 от ★ до ★★★★★
Трудно 30 s ½ 👥 📞 само ниво 10 от ★★ до ★★★★★
Експерт 20 s ½ няма от ★★★ до ★★★★★

Таймерът тръгва, след като водещият прочете въпроса, и спира, докато използваш жокер.

🃏 Жокери

Жокер Какво прави
½ 50:50 Премахва два грешни отговора.
👥 Питай публиката Залата гласува. При лесни въпроси почти винаги е права, при трудни понякога се подвежда.
📞 Обади се на приятел Избираш на кого да звъннеш. Всеки приятел има любими теми и там отговаря по-уверено и по-точно.
🔄 Смени въпроса Нов въпрос със същата трудност (само в „Лесно“).

⌨️ Клавиши

Клавиш Действие
1–4 или A–D Избор на отговор. Повторно натискане го заключва.
Enter Потвърди или продължи към следващия въпрос.
Space Пропусни четенето на водещия.
Esc Откажи избора.
W Вземи парите.
L Покажи стълбата (на телефон).
M Звук вкл./изкл.
F Цял екран вкл./изкл.

💡 Добави въпрос

Въпросите са в public/questions/, по един JSON файл за всяка категория:

{
  "id": "geo-047",
  "difficulty": 3,
  "question": "Коя е най-дългата река в Европа?",
  "answers": ["Дунав", "Рейн", "Волга", "Днепър"],
  "correct": 2,
  "explanation": "Волга е дълга около 3530 км и се влива в Каспийско море."
}
Поле Описание
id Уникален, в kebab-case: префиксът на категорията плюс номер.
difficulty От 1 (много лесен) до 5 (експертен).
answers Точно 4 различни отговора. Редът се разбърква при всяка игра.
correct Индексът (0–3) на верния отговор в answers.
explanation По желание: интересен факт, който се показва след отговора.

След промяна пусни npm run validate. Скриптът проверява формата, уникалните id-та, повторените въпроси, смесената кирилица и латиница (например латинско „e“ в българска дума) и дали има достатъчно въпроси за всяка трудност. Същата проверка минава и в CI.

Нямаш време да пишеш JSON? Предложи въпрос с формата в Issues. Повече подробности има в CONTRIBUTING.md.

🔊 Звуци

Музиката и повечето ефекти се генерират в браузъра (src/audio/engine.ts). Записите на публиката, барабаните и телефона са в public/audio/. Те са от BigSoundBank с лиценз CC0, а подробности има в CREDITS.md.

Всичко се описва в public/audio/manifest.json. В samples са записите, които се наслагват върху шоуто. В replace можеш да смениш генериран звук или фон със свой файл:

{
  "samples": { "applause-small": "applause-small.mp3", "drumroll": "drumroll.mp3" },
  "replace": { "wrong": "my-wrong.mp3", "bed:tier3": "tension-loop.mp3" }
}

Звуци, които могат да се сменят: intro, start, reveal, select, lock, correct, correctBig, wrong, safe, win, walk, tick, lifeline, fifty, keypad, ring. Фонове (въртят се в цикъл): bed:menu, bed:tier1, bed:tier2, bed:tier3, bed:suspense, bed:phone. Използвай само звуци, които имаш право да публикуваш.

🎙️ Гласът на водещия

Водещият говори през Web Speech API. Сам избира най-добрия наличен български глас, като предпочита невронните („Natural“). От ⚙️ Настройки можеш да избереш друг глас и да го чуеш. Преди четене текстът се подготвя, за да звучи естествено: „1 000 000 €“ се чете като „милион евро“, „IX век“ като „девети век“, „343 m/s“ като „метра в секунда“ (src/audio/speech-text.ts).

Браузър Глас
Microsoft Edge ✨ Borislav и Kalina (Natural), най-добрият
Safari (Mac, iPhone) Daria
Chrome на Android гласът на Google за български
Chrome или Firefox на Windows Ivan, ако е инсталиран българският езиков пакет

Записан глас с Azure (безплатно)

С безплатен Azure акаунт можеш да запишеш всички реплики и въпроси с невронния глас „Борислав“. Тогава водещият звучи еднакво хубаво във всеки браузър и на всеки телефон.

  1. Направи безплатен акаунт в Azure. Иска карта само за проверка, а безплатният план не таксува.
  2. В портала избери Create a resource → Speech, регион West Europe и план Free F0.
  3. Отвори ресурса, после Keys and Endpoint, и копирай KEY 1 и региона.
  4. Копирай .env.example като .env.local и попълни ключа и региона. Файлът не се качва в git.
  5. Пусни npm run voice:plan, за да видиш плана, после npm run voice -- --limit=3 за проба и накрая npm run voice за всичко. Записът отнема около 35 минути заради лимита от 20 заявки в минута. Може да го спреш и пуснеш пак, готовото се пропуска.
  6. Пусни играта с npm run dev и чуй резултата. После качи public/voice/ с commit и push.

Записват се около 760 фрази и 547 въпроса, общо около 84 000 знака от безплатните 500 000 на месец. Името на участника не може да се запише предварително, затова то остава само в балончето. Когато има запис, отговорите са в записания ред. Каквото липсва (например нов въпрос), се чете с гласа на браузъра, а при следващо пускане на npm run voice се записва само новото.

Azure връща само приблизително кога започва всяка фраза, а след числа може да сгреши с до 2 секунди. Затова скриптът намира паузите в самото аудио и реже по тях. Ако някога промениш логиката за рязане, npm run voice -- --retrim ще нареже наново вече записаните файлове, без Azure.

📝 Акаунти, твои въпроси и администрация (Firebase)

Сайтът живее във Firebase: хостинг, акаунти и база данни (Firestore).

  • Вход с Google или с имейл и парола (с регистрация и „Забравена парола“).
  • Прогресът следва профила. Настройките, статистиката и личната Зала на славата се пазят в акаунта и се сливат с тези от браузъра при вход.
  • Обща класация. Най-добрият резултат на всеки играч за всеки режим. Правилата приемат само истински суми от стълбата и само подобрения, а администраторите могат да махат фалшиви резултати.
  • Твои въпроси. Пакети, които са лични, с линк (?pack=<id>) или публични. Споделят се само пакети с поне 15 въпроса. В редактора може да се внесе и изнесе JSON. Тези игри не влизат в класациите.
  • Официалните въпроси са във Firestore, по един документ на категория. Браузърът ги кешира и при всяко посещение прави само едно малко четене (meta/bank), освен ако нещо е променено. Ако базата е празна или недостъпна, играта ползва JSON файловете в public/questions/.
  • Администрация (бутон в „Мои въпроси“). Редакция, добавяне и триене на въпроси и категории, търсене и преглед на докладваните пакети. Администратор е всеки, за когото има документ admins/<uid> във Firestore. Създава се на ръка в конзолата, а uid се вижда в Authentication → Users.

Записаният глас е за въпросите в репото. След промени от администрацията npm run questions:pull сваля въпросите от базата в public/questions/, а после npm run voice записва само новото. Дотогава новите и променените въпроси се четат с гласа на браузъра.

Всичко се побира в безплатния план (Spark): 50 000 четения на ден. Правилата за достъп в firestore.rules имат собствени тестове (npm run test:rules).

Настройка на нов проект (веднъж):

  1. В console.firebase.google.com създай проект.
  2. Authentication → Sign-in method: включи Google и Email/Password.
  3. Firestore Database → Create database, локация eur3 (Europe), режим production.
  4. Project settings → Your apps → Web (</>). Сложи apiKey, authDomain, projectId и appId в .env.local като VITE_FIREBASE_API_KEY, VITE_FIREBASE_AUTH_DOMAIN, VITE_FIREBASE_PROJECT_ID и VITE_FIREBASE_APP_ID. Не са тайни: данните пазят правилата.
  5. Authentication → Settings → Authorized domains: добави домейна на сайта (тук patyat-kam-miliona.web.app).
  6. npx firebase login, npx firebase use --add, npm run firebase:deploy (правила и индекси) и npm run deploy (сайтът).
  7. Влез в сайта, вземи своя uid от Authentication → Users и създай документ admins/<uid> във Firestore. После в „Администрация“ натисни „Качи въпросите в базата“.

Локално без истински проект: npm run emulators в един терминал и npm run dev:cloud в друг (трябва Java 21+).

🛠 Локално стартиране

Трябва ти Node.js 22.12 или по-нов.

git clone https://github.com/Krumqnkata/become-millionaire.git
cd become-millionaire
npm install
npm run dev
Скрипт Какво прави
npm run dev Сървър за разработка с hot reload.
npm run build Проверка на типовете и production build в dist/.
npm run preview Пуска production build-а локално.
npm test Unit тестове с Vitest.
npm run validate Проверка на всички въпроси.
npm run lint ESLint.
npm run format Prettier.
npm run check Lint, типове, въпроси и тестове наведнъж.
npm run dev:cloud Сървър за разработка с акаунтите в локалните емулатори.
npm run emulators Локалните емулатори на Firebase (акаунти и база).
npm run test:rules Тестове на правилата за достъп до базата.
npm run firebase:deploy Качва правилата и индексите във Firebase.
npm run deploy Build и качване на сайта във Firebase Hosting.
npm run questions:pull Сваля официалните въпроси от базата в public/questions/.

🧠 Как е направено

Няма framework, само TypeScript в strict режим и малък помощник h() за DOM. Логиката на играта е отделена от интерфейса, така че може да се тества без браузър.

src/
├── game/        чиста логика без DOM, покрита с тестове
│   ├── engine.ts      Game: state machine на една игра
│   ├── questions.ts   зареждане, валидиране и избор на въпроси
│   ├── lifelines.ts   50:50, публика, приятели
│   ├── ladder.ts      награди и сигурни суми
│   ├── modes.ts       четирите трудности
│   ├── packs.ts       пакетите на играчите: проверка и превръщане във въпроси
│   └── random.ts      seedable RNG (Mulberry32) за тестовете
├── show/script.ts     репликите на водещия
├── audio/
│   ├── engine.ts      синтезатор, музика и звуци (Web Audio API)
│   └── voice.ts       гласът на водещия (Speech Synthesis API)
├── cloud/       Firebase: акаунти, пакети, класация, синхронизация, въпроси от базата
├── storage/stats.ts   настройки, статистика, Зала на славата
├── ui/                екрани и компоненти
└── styles/            CSS: токени, компоненти, студио
public/questions/      въпросите (JSON)
scripts/               валидатор на въпросите
tests/                 Vitest

Една игра е малка state machine в src/game/engine.ts:

stateDiagram-v2
    [*] --> question
    question --> locked: lockIn()
    question --> finished: walkAway() / timeout()
    locked --> correct: reveal() ✔
    locked --> finished: reveal() ✖ или ✔ на 15-ия въпрос
    correct --> question: next()
    finished --> [*]
Loading

Подбележки:

  • Избор на въпроси. Взема се точната трудност от избраните категории. Ако такава няма, ограниченията се разхлабват стъпка по стъпка: ±1 трудност, после всички категории. Въпросите от последните игри се избягват.
  • Публиката и приятелите не са просто „верен отговор + шум“. Точността им зависи от трудността, а приятелите знаят повече в любимите си теми. Тестовете проверяват статистиката с фиксиран seed.
  • Музиката се планира с look-ahead scheduler, а не със setInterval, затова не се разминава с ритъма. Звукът минава през компресор и генериран reverb.
  • localStorage се чете през проверки и try/catch. Повредени данни или блокирано хранилище не чупят играта.

🚀 Деплой

Сайтът е на Firebase Hosting: https://patyat-kam-miliona.web.app. Всеки push в main пуска deploy.yml. Той прави lint, проверка на въпросите, тестове (и на правилата за достъп), build и качване във Firebase. Pull request-ите минават през същите проверки в ci.yml. Старият адрес в GitHub Pages само препраща към новия и запазва линковете към пакети.

За автоматичното качване трябва secret FIREBASE_SERVICE_ACCOUNT_STANI_BOGAT. Създава се с npx firebase init hosting:github; генерираните от него workflow файлове не ти трябват. Без него сайтът се качва ръчно с npm run deploy. Настройките на Firebase идват от GitHub променливите FIREBASE_API_KEY, FIREBASE_AUTH_DOMAIN, FIREBASE_PROJECT_ID и FIREBASE_APP_ID.

⚖️ Бележка

„Пътят към милиона“ е оригинален проект в жанра на телевизионните куиз игри. Не е свързан с никое телевизионно предаване. Името, водещият, музиката, звуците и дизайнът са създадени специално за тази игра. Записите на публиката са с лиценз CC0 от BigSoundBank (Joseph Sardin).

📄 Лиценз

MIT © 2026 Krum


English

The Road to the Million is a TV-style quiz show that runs in the browser, in Bulgarian. A virtual host introduces you, reads every question aloud through the browser's speech synthesis, asks “Final answer?” and keeps you waiting while the lights dim. Climb 15 questions to €1,000,000 with four lifelines (50:50, ask the audience, phone a friend, switch the question) across four difficulty modes. The bank has 547 questions in 13 categories.

Under the hood it is strict TypeScript and Vite with no UI framework. The game logic is a framework-free state machine with unit tests. The whole soundtrack is synthesized live with the Web Audio API. Questions are plain JSON, validated in CI. It runs on Firebase: Hosting, Auth (Google or e-mail) and Firestore. Players' progress follows their account, there is a global leaderboard, players build and share their own question packs, and the official questions are edited in an admin panel and cached in the browser. The security rules have their own emulator tests. Run it with npm install && npm run dev.