diff --git a/PRD.md b/PRD.md new file mode 100644 index 0000000..ca16720 --- /dev/null +++ b/PRD.md @@ -0,0 +1,166 @@ +# PRD · ПУЛЬС — Неотложка + +**Игровой тренажёр клинических кейсов по неотложной медицинской помощи** + +Версия документа: 1.0 +Статус: реализовано (MVP), в итеративной доработке +Платформа: статический сайт (HTML + CSS + vanilla JS), публикуется через Git Pages + +--- + +## 1. Концепция + +**ПУЛЬС** — образовательный игровой тренажёр для студентов-медиков 6 курса. Студент работает с «говорящими» видео-пациентами: выслушивает жалобы голосом, самостоятельно назначает обследования и лечение, ставит диагноз и оказывает экстренную помощь на время. За точность начисляется опыт (XP), открываются достижения и уровни, результаты попадают в рейтинг. Алгоритмы решения основаны на клинических протоколах диагностики и лечения МЗ РК. + +**Ценность:** превращает сухую зубрёжку протоколов в тренировку клинического мышления в условиях, близких к приёмному отделению, с мгновенной обратной связью и игровой мотивацией. + +--- + +## 2. Целевая аудитория + +- **Основная:** студенты 6 курса медицинских вузов (интерны, ординаторы) — отработка неотложных состояний. +- **Дополнительная:** врачи СМП и приёмного покоя для повторения алгоритмов; преподаватели — как материал для семинаров. +- **Контекст применения:** Казахстан (протоколы МЗ РК, русскоязычный интерфейс). + +--- + +## 3. Цели и задачи + +### Цели продукта +1. Дать практику клинического мышления по неотложным состояниям в безопасной среде. +2. Закрепить алгоритмы клинических протоколов МЗ РК через повторение. +3. Повысить мотивацию к обучению за счёт геймификации. + +### Метрики успеха (MVP) +- Завершение полного экзамена (6 кейсов) за один заход — основная конверсия. +- Доля повторных прохождений кейсов (replay) — вовлечённость. +- Средний балл по кейсу/экзамену — уровень усвоения. +- Количество разблокированных достижений — глубина погружения. + +--- + +## 4. Функциональные требования + +### 4.1. Профиль игрока и уровни +- Создание профиля: имя + аватар (emoji). +- Система опыта (XP): начисляется за результаты кейсов (пропорционально приросту лучшего балла). +- 8 уровней: Стажёр → Студент → Интерн → Ординатор → Врач → Специалист → Эксперт → Профессор. +- Шапка профиля: аватар, имя, текущий уровень, XP-бар до следующего уровня. +- Сохранение профиля и прогресса в браузере (localStorage). + +### 4.2. Карта прохождения тем +- 3 темы: «ОКС и инфаркт», «ОДН · астма · ХОБЛ», «Шок и травма». +- По каждой теме: прогресс-бар (кейсов пройдено), заработанные звёзды. +- Клик по теме фильтрует кейсы и переносит к списку. + +### 4.3. Кейсы +- 6 клинических кейсов (по 2 на тему). +- Карточка кейса: портрет-аватар (фото), тема, звёзды (1–3), лучший результат, счётчик прохождений, статус «Пройден», кнопка «Играть/Повторить». + +### 4.4. Видео-пациент (говорящий аватар) +- В шаге «Пациент» — видеоплеер с крупным портретом пациента. +- При нажатии «Прослушать»: синтез речи (Web Speech API), портрет «оживает» — звуковые волны, лёгкая анимация говорения, индикатор LIVE. +- **Субтитры** синхронно с речью (по фразам). +- Голос подбирается под пациента: пол, возраст, состояние (pitch/rate/громкость), паузы-«дыхание» между фразами (у астматика — рваная речь с длинными паузами, у шокового — тихая и замедленная). + +### 4.5. Пошаговое решение кейса (экзамен) +Каждый кейс = 4 шага + результат: +1. **Обследования** — множественный выбор из перечня (нужные vs лишние). +2. **Лечение** — множественный выбор препаратов (нужные vs противопоказанные). +3. **Диагноз** — выбор одного верного варианта. +4. **Экстренная помощь ⏱** — за 60 секунд выбрать правильные действия; таймер с обратным отсчётом, автозавершение при истечении. + +Требования к выбору вариантов: +- Варианты кликабельны и доступны для нажатия на всех шагах. +- После «Подтвердить» варианты блокируются и подсвечиваются: верно / лишнее / пропущено / верный диагноз. + +### 4.6. Оценка и обратная связь +- Балл за шаг множественного выбора: `(TP + TN) / всего × 100%`. +- Диагноз: 100% / 0%. +- Вес шагов в кейсе: обследования 20%, лечение 20%, диагноз 30%, экстренная помощь 30%. +- Экран результата кейса: кольцевой индикатор %, грейд (Отлично/Хорошо/Удовл./Неуд), разбивка по шагам, награды (звёзды, XP, новые достижения), разбор: правильный диагноз, **протокол МЗ РК**, ключевые данные, типичные ошибки. + +### 4.7. Геймификация +- **Звёзды за кейс:** ≥85% → 3, ≥70% → 2, ≥55% → 1. +- **Достижения (11 шт.):** «Первый шаг», «Точный диагноз», «Безупречный кейс», «Чистая работа», «Молниеносная реакция», «Мастер ОКС/ОДН/шока», «Великий диагност», «Полное прохождение», «Профессор». +- **Тосты** при разблокировке достижений и повышении уровня. + +### 4.8. Рейтинг игроков +- Топ-10 результатов полного экзамена (имя, аватар, %, дата). +- Сохраняется локально в браузере; результат текущего игрока подсвечен. + +### 4.9. Полный экзамен +- Последовательное прохождение всех 6 кейсов. +- Итоговый экран: средний %, грейд, разбивка по темам и по кейсам. +- Результат добавляется в рейтинг. + +--- + +## 5. Содержание кейсов (по протоколам МЗ РК) + +| # | Тема | Кейс | Протокол МЗ РК | +|---|------|------|----------------| +| 1 | ОКС | Острый инфаркт миокарда с подъёмом ST (STEMI) | «Острый коронарный синдром» | +| 2 | ОКС | ОКС без подъёма ST (NSTEMI) | «Острый коронарный синдром» | +| 3 | ОДН | Острый тяжёлый приступ бронхиальной астмы | «Бронхиальная астма» | +| 4 | ОДН | Обострение ХОБЛ с ОДН 2 типа | «Хроническая обструктивная болезнь лёгких» | +| 5 | Шок | Травматический геморрагический шок | «Шок», «Острое кровотечение» | +| 6 | Шок | Септический шок | «Сепсис и септический шок» | + +Ключевые положения протоколов, отражённые в кейсах: +- **STEMI:** первичное ЧКВ (door-to-balloon ≤ 90 мин); при недоступности ≤ 120 мин — фибринолизис, в т.ч. догоспитальный; двойная антитромбоцитарная терапия + антикоагулянт. +- **NSTEMI:** тромболизис НЕ показан; стратификация по GRACE; ранняя инвазивная стратегия. +- **Астма:** β2-агонисты + ипратропия бромид + системные ГКС + магний; седативные/опиоиды противопоказаны. +- **ХОБЛ:** контролируемая О₂-терапия (целевой SpO₂ 88–92%); NIV при гиперкапнии. +- **Геморрагический шок:** damage control, остановка кровотечения, гемотрансфузия 1:1:1, транексамовая кислота; борьба с триадой смерти. +- **Сепсис:** hour-1 bundle (лактат, гемокультуры, антибиотики в 1-й час, инфузия 30 мл/кг, норадреналин). + +--- + +## 6. Дизайн + +- **Стиль:** выдержанная классическая палитра — графит-navy, кремовый «бумажный» фон, приглушённое золото (акцент), бордо (ОКС), тёмно-бирюзовый (ОДН), кирпичный (шок), хирургический зелёный (верно). Строгий медицинский вид, без неон-яркости. +- **Типографика:** Manrope. +- **Адаптив:** desktop / планшет / мобильный. +- **Иконки:** emoji и inline-элементы. + +--- + +## 7. Технологии и ограничения + +- **Стек:** статические `index.html` + `style.css` + `app.js` (vanilla JS). Без бэкенда, БД, сборщиков, аутентификации. +- **Озвучка:** Web Speech API (SpeechSynthesis), русские голоса браузера. +- **Хранение:** localStorage (профиль, прогресс, рейтинг). +- **Публикация:** Git Pages, ветка `pages`. +- **Ограничения песочницы:** только статика; реальные фото/видео пациентов не используются (этика) — применяется анимированный видео-аватар. + +--- + +## 8. Пользовательский путь + +1. Открывает сайт → видит шапку профиля и hero с уровнем/XP. +2. Создаёт профиль (имя + аватар) через ⚙️. +3. Заходит в «Карту прохождения» или сразу в «Кейсы». +4. Выбирает кейс → видеопациент рассказывает жалобы голосом. +5. Проходит 4 шага (обследования → лечение → диагноз → экстренная помощь на 60 с). +6. Получает результат: %, звёзды, XP, достижения, разбор с протоколом МЗ РК. +7. Идёт к следующему кейсу или запускает полный экзамен → попадает в рейтинг. +8. Растёт в уровне от Стажёра до Профессора. + +--- + +## 9. Roadmap (возможные доработки) + +- Расширение тем: кома и неврология, острый живот, отравления, анафилаксия. +- Режим «Экзамен с таймером на весь кейс». +- Экспорт результата / сертификат прохождения. +- Импорт своих медиа (фото/видео пациентов) для преподавателя. +- Поддержка казахского языка интерфейса. +- Синхронизация прогресса между устройствами (требует бэкенда — вне песочницы). + +--- + +## 10. Ссылки + +- Сайт: https://pages.git.vibe42.kz/saniya_akpanova_91/emergency-cases/ +- Репозиторий: https://git.vibe42.kz/saniya_akpanova_91/emergency-cases diff --git a/app.js b/app.js index 6685bb2..5e80c13 100644 --- a/app.js +++ b/app.js @@ -469,22 +469,24 @@ function pickVoice(preferMale){ return ru[0]; } -let speakState={playing:false, queue:[], idx:0, timer:null, btn:null, avatar:null, opts:null}; +let speakState={playing:false, queue:[], idx:0, timer:null, btn:null, avatar:null, opts:null, player:null, subsEl:null, onPhrase:null, onEnd:null}; function stopSpeak(){ if("speechSynthesis" in window) window.speechSynthesis.cancel(); if(speakState.timer){ clearTimeout(speakState.timer); speakState.timer=null; } speakState.playing=false; - if(speakState.btn){ speakState.btn.classList.remove("is-playing"); speakState.btn.textContent="▶ Прослушать"; } + if(speakState.btn){ speakState.btn.classList.remove("is-playing"); speakState.btn.textContent="▶ Прослушать жалобы"; } if(speakState.avatar) speakState.avatar.classList.remove("is-speaking"); - speakState.queue=[]; speakState.idx=0; + if(speakState.player) speakState.player.classList.remove("is-speaking"); + if(speakState.onEnd){ const cb=speakState.onEnd; speakState.onEnd=null; cb(); } + speakState.queue=[]; speakState.idx=0; speakState.player=null; speakState.subsEl=null; speakState.onPhrase=null; } function splitPhrases(text){ return text.split(/(?<=[.!?…])\s+|(?<=…)\s*/).map(s=>s.trim()).filter(Boolean); } -function speakPatient(text, opts, btn, avatar){ +function speakPatient(text, opts, btn, avatar, ext){ const synth=window.speechSynthesis; if(!synth){ return; } if(speakState.playing){ stopSpeak(); return; } @@ -492,9 +494,14 @@ function speakPatient(text, opts, btn, avatar){ const phrases=splitPhrases(text); if(!phrases.length) return; speakState.queue=phrases; speakState.idx=0; speakState.btn=btn; speakState.avatar=avatar; speakState.opts=opts; + speakState.player=ext&&ext.player||null; + speakState.subsEl=ext&&ext.subsEl||null; + speakState.onPhrase=ext&&ext.onPhrase||null; + speakState.onEnd=ext&&ext.onEnd||null; speakState.playing=true; btn.classList.add("is-playing"); btn.textContent="⏹ Остановить"; if(avatar) avatar.classList.add("is-speaking"); + if(speakState.player) speakState.player.classList.add("is-speaking"); playNext(); } @@ -502,6 +509,8 @@ function playNext(){ const synth=window.speechSynthesis; if(!speakState.playing || speakState.idx>=speakState.queue.length){ stopSpeak(); return; } const phrase=speakState.queue[speakState.idx]; + if(speakState.subsEl) speakState.subsEl.textContent=phrase; + if(speakState.onPhrase) speakState.onPhrase(phrase, speakState.idx); const opts=speakState.opts||{}; const u=new SpeechSynthesisUtterance(phrase); u.lang="ru-RU"; @@ -869,10 +878,18 @@ function patientBlock(c){
${c.themeLabel}
-
- 🔊 - Жалобы пациентаВключите звук и нажмите — пациент говорит голосом - +
+
+
+
${c.patient.avatar}
+ ● ВИДЕО · LIVE + 📷 Фото пациента +
+
Нажмите «Прослушать», чтобы пациент рассказал жалобы
+
+ + 🔊 Включите звук на устройстве +
«${c.complaint}»
`; @@ -888,8 +905,15 @@ function vitalsHTML(v){ ${extra}`; } function bindSpeak(c, root){ - const btn=root.querySelector("#speakBtn"), av=root.querySelector("#pAvatar"); - if(btn) btn.addEventListener("click",()=>speakPatient(c.complaint, c.patient.voice, btn, av)); + const btn=root.querySelector("#speakBtn"), + av=root.querySelector(".vp-avatar"), + player=root.querySelector("#vplayer"), + subs=root.querySelector("#vpSubs"); + if(!btn) return; + btn.addEventListener("click",()=>speakPatient(c.complaint, c.patient.voice, btn, av, { + player, subsEl:subs, + onEnd:()=>{ if(subs) subs.textContent="Нажмите «Прослушать», чтобы пациент рассказал жалобы"; } + })); } function renderPatient(){ diff --git a/index.html b/index.html index 249c34c..86454f1 100644 --- a/index.html +++ b/index.html @@ -3,8 +3,8 @@ -МедИгра · Неотложка — тренажёр для медиков - +ПУЛЬС · Неотложка — игровой тренажёр для медиков + @@ -15,7 +15,7 @@
- 🩺 МедИграНеотложка + 🫀 ПУЛЬСНеотложка
@@ -92,7 +92,7 @@ diff --git a/style.css b/style.css index 633010d..10ed6d9 100644 --- a/style.css +++ b/style.css @@ -280,6 +280,26 @@ h1,h2,h3{letter-spacing:-.4px} .speak-btn.is-playing{background:var(--red)} .complaint{background:rgba(176,141,87,.12);border-left:4px solid var(--gold);border-radius:0 12px 12px 0;padding:16px 18px;font-size:16.5px;line-height:1.7;margin-top:16px;font-style:italic;color:#dfe5ec} +/* видео-аватар пациента */ +.vplayer{background:#0f1828;border-radius:var(--radius-lg);overflow:hidden;margin-bottom:18px;box-shadow:var(--shadow)} +.vp-screen{position:relative;aspect-ratio:16/10;background:radial-gradient(circle at 50% 38%,#2a3a52,#101a2c);display:grid;place-items:center;overflow:hidden} +.vp-waves{position:absolute;inset:0;display:grid;place-items:center;pointer-events:none} +.vp-waves i{position:absolute;width:130px;height:130px;border-radius:50%;border:2px solid rgba(176,141,87,.5);opacity:0} +.vplayer.is-speaking .vp-waves i{animation:vpwave 1.7s ease-out infinite} +.vplayer.is-speaking .vp-waves i:nth-child(2){animation-delay:.55s} +.vplayer.is-speaking .vp-waves i:nth-child(3){animation-delay:1.1s} +@keyframes vpwave{0%{transform:scale(.55);opacity:.7}100%{transform:scale(2.3);opacity:0}} +.vp-avatar{font-size:104px;line-height:1;position:relative;z-index:2;filter:drop-shadow(0 10px 18px rgba(0,0,0,.45))} +.vplayer.is-speaking .vp-avatar{animation:vptalk .45s ease-in-out infinite alternate} +@keyframes vptalk{0%{transform:scale(1) translateY(0)}100%{transform:scale(1.05) translateY(-4px)}} +.vp-live{position:absolute;top:12px;left:12px;background:rgba(0,0,0,.45);color:#fff;font-size:11px;font-weight:800;letter-spacing:.5px;padding:4px 10px;border-radius:6px;z-index:3} +.vplayer.is-speaking .vp-live{color:#ff6b6b} +.vp-photo{position:absolute;top:12px;right:12px;background:rgba(0,0,0,.45);color:#cfd6e0;font-size:11px;font-weight:700;padding:4px 10px;border-radius:6px;z-index:3} +.vp-subs{background:rgba(0,0,0,.6);color:#fff;padding:12px 16px;font-size:14.5px;line-height:1.45;min-height:50px;text-align:center;display:flex;align-items:center;justify-content:center} +.vp-ctrl{padding:12px 16px;display:flex;align-items:center;gap:12px;flex-wrap:wrap;background:#0c1422} +.vp-hint{color:#9ba6b4;font-size:13px} +.vp-ctrl .speak-btn{margin-left:auto} + /* аккордеон */ .acc{border:1px solid var(--line);border-radius:var(--radius);margin-bottom:12px;overflow:hidden;background:var(--white)} .acc summary{list-style:none;cursor:pointer;padding:15px 18px;font-weight:800;font-size:15.5px;display:flex;align-items:center;gap:10px;background:var(--paper);user-select:none} @@ -430,4 +450,5 @@ h1,h2,h3{letter-spacing:-.4px} .hero-stats{gap:20px} .exam-bar,.exam-stage-track,.exam-body{padding-left:16px;padding-right:16px} .toasts{right:12px;left:12px;max-width:none} + .vp-avatar{font-size:72px} }