509 lines
38 KiB
JavaScript
509 lines
38 KiB
JavaScript
/* ===== Данные клинических кейсов ===== */
|
||
const CASES = [
|
||
{
|
||
id:"stemi",
|
||
theme:"aks", themeLabel:"ОКС / инфаркт", badge:"b-aks",
|
||
title:"Острый инфаркт миокарда с подъёмом ST",
|
||
severity:3,
|
||
patient:{avatar:"👨🦰", name:"Пациент А.", age:"58 лет, мужчина", ctx:"Поступил по скорой, ~2 ч от начала боли"},
|
||
complaint:"Доктор, часа два назад как прижало за грудиной — давит сильно, будто слон на грудь сел. Отдаёт в левую руку и в челюсть. Дышать тяжело, бросило в холодный пот, стало страшно. Таблетку нитроглицерина под язык клал — не отпускает.",
|
||
anamnesis:[
|
||
"Боль возникла в покое, после эмоционального стресса на работе.",
|
||
"Курит 30 лет по пачке в день. Артериальная гипертензия 8 лет.",
|
||
"Отец умер от инфаркта миокарда в 60 лет.",
|
||
"Ранее подобных приступов не было. Регулярно терапии не получает."
|
||
],
|
||
objective:{
|
||
vitals:{hr:"102", bp:"150/95", rr:"22", spo2:"93%"},
|
||
general:"Кожа бледная, влажная, холодный пот. Пациент тревожен, ищет удобное положение.",
|
||
heart:"Тоны сердца приглушены, тахикардия, ритм правильный.",
|
||
lungs:"В нижних отделах с обеих сторон — мелкопузырчатые незвучные хрипы.",
|
||
other:"Шумов на сонных артериях нет. Отёков нет."
|
||
},
|
||
extra:{title:"ЭКГ и биомаркеры", text:"ЭКГ: подъём сегмента ST в отведениях I, aVL, V1–V4; реципрокная депрессия в II, III, aVF. Патологический зубец Q начинает формироваться. Тропонин I повышен."},
|
||
quiz:{
|
||
q:"Какой наиболее вероятный диагноз и тактика?",
|
||
options:[
|
||
{t:"Острый перикардит — НПВС, наблюдение", c:false},
|
||
{t:"STEMI (передне-перегородочный) — экстренная реперфузия", c:true},
|
||
{t:"ТЭЛА — антикоагуляция, тромболизис", c:false},
|
||
{t:"Расслоение аорты — контроль АД, экстренная хирургия", c:false}
|
||
],
|
||
feedback:{
|
||
ok:"Верно. Типичный болевой синдром, подъём ST в передних отведениях и реципрокные изменения — это STEMI. Время мышления = гибнущий миокард: показана экстренная реперфузия.",
|
||
no:"Не совсем. Подъём ST в V1–V4 с реципрокной депрессией и типичной клиникой — это STEMI, требующий немедленной реперфузии."
|
||
}
|
||
},
|
||
diagnosis:"Острый инфаркт миокарда с подъёмом сегмента ST (STEMI), передне-перегородочная локализация. Острая левожелудочковая недостаточность (Killip II).",
|
||
algorithm:[
|
||
{s:"Вызовите реанимационную бригаду, активируйте STEMI-маршрут (door-to-balloon ≤ 90 мин)."},
|
||
{s:"Мониторинг ЭКГ, пульсоксиметрия, 2 венозных доступа."},
|
||
{s:"Обезболивание (морфин), кислород при SpO₂ < 90%, нитраты при сохраняющемся болевом синдроме и АД > 100."},
|
||
{s:"Двойная антитромбоцитарная терапия: аспирин + ингибитор P2Y12 (тикагрелор/прасугрел). Антикоагулянт (эноксапарин/НФГ)."},
|
||
{s:"Реперфузия — первичное ЧКВ предпочтительно; при недоступности ≤ 120 мин — тромболизис с последующим переводом."},
|
||
{s:"Бета-блокатор, статин в высокой дозе, ингибитор АПФ — после стабилизации."}
|
||
],
|
||
keypoints:[
|
||
{t:"good",x:"Время = миокард: чем раньше реперфузия, тем больше спасённой мышцы."},
|
||
{t:"bad",x:"Не откладывайте реперфузию, ожидая тропонин — ЭКГ и клиника достаточны для STEMI."},
|
||
{t:"bad",x:"Осторожно с нитратами при инфаркте ПЖ и гипотонии — оцените состояние."}
|
||
]
|
||
},
|
||
|
||
{
|
||
id:"nstemi",
|
||
theme:"aks", themeLabel:"ОКС / инфаркт", badge:"b-aks",
|
||
title:"Острый коронарный синдром без подъёма ST",
|
||
severity:2,
|
||
patient:{avatar:"👩🦳", name:"Пациентка Б.", age:"65 лет, женщина", ctx:"Поступила через сутки от начала симптомов"},
|
||
complaint:"Вчера ночью проснулась от боли за грудиной — давящая, минут двадцать держалась, отдала в спину. Сегодня повторилось дважды, уже при небольшой нагрузке — просто по квартире походила. Раньше такого никогда не было. Как-то страшно, думаю, это сердце.",
|
||
anamnesis:[
|
||
"Сахарный диабет 2 типа 12 лет, ожирение, гиперхолестеринемия.",
|
||
"Гипертония, принимает периодически.",
|
||
"Боли провоцируются нагрузкой, купируются в покое за 10–15 мин (прогрессирующая стенокардия).",
|
||
"За последний месяц снизилась толерантность к нагрузке."
|
||
],
|
||
objective:{
|
||
vitals:{hr:"88", bp:"140/85", rr:"18", spo2:"96%"},
|
||
general:"Состояние средней тяжести. Кожные покровы обычной окраски. ИМТ 32.",
|
||
heart:"Тоны ритмичные, приглушены. Шумов нет.",
|
||
lungs:"Дыхание везикулярное, хрипов нет.",
|
||
other:"Пульс на периферических артериях сохранён. Отёков нет."
|
||
},
|
||
extra:{title:"ЭКГ и лаборатория", text:"ЭКГ: горизонтальная депрессия ST в V4–V6, инверсия зубца T. Тропонин I повышен (>99-го перцентиля), динамика нарастания. Оценка по шкале GRACE — высокий риск."},
|
||
quiz:{
|
||
q:"Какая тактика наиболее оправдана?",
|
||
options:[
|
||
{t:"Стабильная стенокардия — амбулаторное наблюдение", c:false},
|
||
{t:"NSTEMI высокого риска — ранняя инвазивная стратегия ≤ 24 ч", c:true},
|
||
{t:"Костохондрит — НПВС и покой", c:false},
|
||
{t:"ГЭРБ — ингибиторы протонной помпы", c:false}
|
||
],
|
||
feedback:{
|
||
ok:"Верно. Депрессия ST + подъём тропонина + высокий риск по GRACE = NSTEMI. Показана ранняя инвазивная стратегия.",
|
||
no:"Не совсем. У пациентки депрессия ST, рост тропонина и высокий риск — это NSTEMI, требующий раннего ЧКВ."
|
||
}
|
||
},
|
||
diagnosis:"Острый коронарный синдром без подъёма ST (NSTEMI). Высокий риск по шкале GRACE.",
|
||
algorithm:[
|
||
{s:"Госпитализация в ОРИТ/кардиологию, мониторинг ЭКГ и ритма."},
|
||
{s:"Двойная антиагрегантная терапия (аспирин + P2Y12-ингибитор) + антикоагулянт."},
|
||
{s:"Стратификация риска (GRACE/ESC). Высокий риск → ранняя инвазивная стратегия (ЧКВ ≤ 24 ч)."},
|
||
{s:"Симптоматически: нитраты при боли, бета-блокатор при отсутствии противопоказаний."},
|
||
{s:"Статины в высокой дозе, ингибитор АПФ, контроль гликемии."},
|
||
{s:"Вторичная профилактика и кардиореабилитация."}
|
||
],
|
||
keypoints:[
|
||
{t:"good",x:"Женщины и диабетики часто имеют атипичную клинику — не сбрасывайте со счетов."},
|
||
{t:"bad",x:"Не ставьте «стабильную стенокардию» при недавнем изменении/прогрессии симптомов — это ОКС."},
|
||
{t:"good",x:"Решение о раннем ЧКВ принимает стратификация риска, а не один тропонин."}
|
||
]
|
||
},
|
||
|
||
{
|
||
id:"asthma",
|
||
theme:"odn", themeLabel:"ОДН / астма / ХОБЛ", badge:"b-odn",
|
||
title:"Острый тяжёлый приступ бронхиальной астмы",
|
||
severity:3,
|
||
patient:{avatar:"👩", name:"Пациентка В.", age:"24 года, женщина", ctx:"Доставлена СМП, одышка 1 час"},
|
||
complaint:"Не могу... выдохнуть... свистит вся грудь... началось час назад... ингалятор... уже третий раз пшикала — не помогает... тяжело... говорит по словам... страшно задохнуться.",
|
||
anamnesis:[
|
||
"Бронхиальная астма с детства, базисную терапию (ИГКС) принимает нерегулярно.",
|
||
"Неделю назад перенесла ОРВИ с кашлем.",
|
||
"За последние дни ингалятор сальбутамола использовала всё чаще (более 10 раз в сутки).",
|
||
"Аллергия на пыльцу, домашнюю пыль."
|
||
],
|
||
objective:{
|
||
vitals:{hr:"120", bp:"135/85", rr:"30", spo2:"91%"},
|
||
general:"Положение ортопноэ, опирается на руки. Речь прерывистая, произносит отдельные фразы. Выраженная работа вспомогательной мускулатуры.",
|
||
lungs:"Выдох резко удлинён, свистящие сухие хрипы по всем полям. Участие в акте дыхания всех групп мышц.",
|
||
heart:"Тахикардия. Гиперзвуковой перкуторный звук над лёгкими.",
|
||
other:"Втянутые межрёберные промежутки, парадоксальный пульс."
|
||
},
|
||
extra:{title:"Дополнительные признаки", text:"Пиковая скорость выдоха (ПСВ) < 50% от должного. При угрожающем жизни статусе — «немое лёгкое» (исчезновение хрипов при нарастающей дыхательной недостаточности), брадикардия, нарушение сознания."},
|
||
quiz:{
|
||
q:"Что является приоритетом в неотложной помощи?",
|
||
options:[
|
||
{t:"Дать седативное, уложить пациента — panic attack", c:false},
|
||
{t:"Кислород + повторные небулайзеры Сальбутамола + системные ГКС", c:true},
|
||
{t:"Антибиотики широкого спектра внутривенно", c:false},
|
||
{t:"Наркотический анальгетик от боли в груди", c:false}
|
||
],
|
||
feedback:{
|
||
ok:"Верно. Тяжёлый приступ астмы: оксигенация, частые бронходилататоры через небулайзер, системные ГКС, при ухудшении — ИВЛ.",
|
||
no:"Не совсем. Седация и наркотики угнетают дыхание и противопоказаны. Основа — бронходилататоры и ГКС."
|
||
}
|
||
},
|
||
diagnosis:"Острый тяжёлый приступ бронхиальной астмы. Угроза астматического статуса.",
|
||
algorithm:[
|
||
{s:"Оценка ABCD. Мониторинг SpO₂, ЧДД, ЧСС. Кислород через маску, титровать до SpO₂ 94–98%."},
|
||
{s:"Сальбутамол 5 мг через небулайзер, при тяжёлом — повторно каждые 15–20 мин (или непрерывно). Добавить ипратропия бромид."},
|
||
{s:"Системные ГКС как можно раньше: преднизолон 40–60 мг внутрь или гидрокортизон в/в."},
|
||
{s:"Магния сульфат 2 г в/в при тяжёлом приступе, не отвечающем на терапию."},
|
||
{s:"Критерии жизнеугрожающего статуса: «немое лёгкое», SpO₂ < 92%, нарушение сознания, брадикардия → ИВЛ, перевод в ОРИТ."},
|
||
{s:"После стабилизации — пересмотр базисной терапии (ступенчатое лечение, ИГКС), обучение пациента."}
|
||
],
|
||
keypoints:[
|
||
{t:"bad",x:"Никогда не давайте седативные/опиоиды при астме — это угнетает дыхание и ведёт к остановке."},
|
||
{t:"good",x:"Системные ГКС работают через 4–6 ч, поэтому вводите их сразу, не ждите ухудшения."},
|
||
{t:"bad",x:"«Немое лёгкое» = плохо, а не хорошо: хрипы исчезли из-за отсутствия дыхательного резерва — это предлетальное состояние."}
|
||
]
|
||
},
|
||
|
||
{
|
||
id:"copd",
|
||
theme:"odn", themeLabel:"ОДН / астма / ХОБЛ", badge:"b-odn",
|
||
title:"Обострение ХОБЛ с острой дыхательной недостаточностью",
|
||
severity:2,
|
||
patient:{avatar:"👴", name:"Пациент Г.", age:"68 лет, мужчина", ctx:"Направлен врачом общей практики"},
|
||
complaint:"Уже неделю кашель усилился, мокрота стала густая, жёлто-зелёная. Дышать совсем тяжело, особенно со вчера. Раньше хоть по дому ходил, а сейчас до туалета не дойти — задыхаюсь. Сплю полусидя, две подушки подкладываю. Лихорадит три дня.",
|
||
anamnesis:[
|
||
"ХОБЛ диагностирована 10 лет назад. Курит 40 лет (40 пачко-лет).",
|
||
"Последние 3 дня — лихорадка до 38.5 °C, гнойная мокрота.",
|
||
"Базовая SpO₂ целевая 88–92% (компенсированная гиперкапния).",
|
||
"Дома — ипратропия бромид/фенотерол, длительные оксигены не получал."
|
||
],
|
||
objective:{
|
||
vitals:{hr:"110", bp:"160/90", rr:"28", spo2:"86%", temp:"38.3°C"},
|
||
general:"Состояние тяжёлое. Цианоз губ, акроцианоз. Разговорчив с трудом.",
|
||
lungs:"Грудная клетка бочкообразная, перкуторно — коробочный звук. Сухие свистящие хрипы на выдохе, ослабленное дыхание.",
|
||
heart:"Громкий II тон над лёгочной артерией. Тахикардия.",
|
||
other:"Периферические отёки голеней — признаки cor pulmonale."
|
||
},
|
||
extra:{title:"Газометрия артериальной крови", text:"PaO₂ 52 мм рт.ст. ↓, PaCO₂ 68 мм рт.ст. ↑, pH 7.31 (дыхательный ацидоз). Гиперкапническая ОДН 2 типа. Рентген: усиление лёгочного рисунка, без очаговой инфильтрации."},
|
||
quiz:{
|
||
q:"Какой подход к кислородотерапии правильный?",
|
||
options:[
|
||
{t:"Максимальный O₂ через маску до SpO₂ 99–100%", c:false},
|
||
{t:"Титровать O₂ до целевого SpO₂ 88–92% + NIV при тяжёлой ОДН", c:true},
|
||
{t:"Кислород не давать вообще — опасно", c:false},
|
||
{t:"Только ингаляции эфирных масел", c:false}
|
||
],
|
||
feedback:{
|
||
ok:"Верно. При ХОБЛ с гиперкапнией избыток кислорода снимает гипоксический драйв дыхания → рост PaCO₂ и кома. Титруйте до 88–92%.",
|
||
no:"Не совсем. Неконтролируемый O₂ при ХОБЛ ведёт к нарастанию гиперкапнии и CO₂-наркозу. Нужен титрованный кислород и NIV."
|
||
}
|
||
},
|
||
diagnosis:"Обострение ХОБЛ. Острая дыхательная недостаточность 2 типа (гиперкапническая), декомпенсация cor pulmonale.",
|
||
algorithm:[
|
||
{s:"Контролируемая оксигенотерапия: начать с 24–28% через Venturi-маску, титровать до SpO₂ 88–92%."},
|
||
{s:"Бронходилататоры через небулайзер (ипратропия бромид + фенотерол/сальбутамол)."},
|
||
{s:"Системные ГКС: преднизолон 30–40 мг/сут на 5–7 дней."},
|
||
{s:"Антибиотики при признаках инфекции (гнойная мокрота, лихорадка) — амоксициллин/клавуланат или макролид."},
|
||
{s:"При тяжёлой ОДН с гиперкапнией/ацидозом (pH < 7.35) — неинвазивная вентиляция (NIV/BiPAP)."},
|
||
{s:"Контроль газометрии, мониторинг. При отсутствии эффекта от NIV — интубация и ИВЛ."}
|
||
],
|
||
keypoints:[
|
||
{t:"bad",x:"Не давайте 100% O₂ «на всякий случай» при ХОБЛ — это классическая ошибка, ведущая к CO₂-наркозу."},
|
||
{t:"good",x:"NIV снижает потребность в интубации и летальность при обострении ХОБЛ с гиперкапнией."},
|
||
{t:"good",x:"Целевой SpO₂ для пациента с ХОБЛ — 88–92%, а не 94–98% как у большинства."}
|
||
]
|
||
},
|
||
|
||
{
|
||
id:"hemoshock",
|
||
theme:"shock", themeLabel:"Шок и травма", badge:"b-shock",
|
||
title:"Травматический геморрагический шок",
|
||
severity:3,
|
||
patient:{avatar:"🧑", name:"Пациент Д.", age:"30 лет, мужчина", ctx:"ДТП, доставлен бригадой СМП"},
|
||
complaint:"Всё кружится... холодно почему-то... пить очень хочу... ногу не чувствую... где я... ничего не вижу ясно...",
|
||
anamnesis:[
|
||
"ДТП: лобовое столкновение, пациент за рулём, не пристёгнут.",
|
||
"Закрытый перелом бедра, подозрение на перелом таза.",
|
||
"Видимое наружное кровотечение из раны бедра, обширная гематома.",
|
||
"Время от травмы до поступления ~40 минут."
|
||
],
|
||
objective:{
|
||
vitals:{hr:"130", bp:"80/40", rr:"28", spo2:"95%"},
|
||
general:"Сознание оглушение (GCS 13). Резкая бледность, кожа покрыта холодным липким потом. Акроцианоз.",
|
||
heart:"Тоны глухие, выраженная тахикардия, нитевидный пульс.",
|
||
lungs:"Дыхание проводится по всем полям, хрипов нет.",
|
||
other:"Левое бедро деформировано, укорочено, обширная пульсирующая гематома. Кровь на одежде. Живот мягкий, без перитонеальных знаков."
|
||
},
|
||
extra:{title:"Оценка шока", text:"Класс геморрагического шока III–IV (потеря > 30% ОЦК). Дефицит базы > −6 ммоль/л, лактат повышен. Шоковый индекс (ЧСС/САД) = 130/80 = 1.6 — тяжёлый шок."},
|
||
quiz:{
|
||
q:"Что является абсолютным приоритетом?",
|
||
options:[
|
||
{t:"Полное рентген-обследование до любой терапии", c:false},
|
||
{t:"Остановка кровотечения + венозный доступ + инфузия/гемотрансфузия", c:true},
|
||
{t:"Введение обезболивающего и ожидание хирурга", c:false},
|
||
{t:"Только внутривенно коллоиды в большой дозе", c:false}
|
||
],
|
||
feedback:{
|
||
ok:"Верно. Принцип damage control: немедленная остановка кровотечения, восстановление ОЦК, раннее переливание крови в соотношении 1:1:1.",
|
||
no:"Не совсем. До полного обследования пациент может погибнуть от гиповолемии. Сначала — остановка кровотечения и инфузия."
|
||
}
|
||
},
|
||
diagnosis:"Травматический геморрагический шок III–IV степени. Закрытый перелом бедра, перелом таза. Массивная кровопотеря.",
|
||
algorithm:[
|
||
{s:"ABCDE. Обеспечение проходимости дыхательных путей, оксигенация, иммобилизация шейного отдела."},
|
||
{s:"Немедленная остановка наружного кровотечения: прямое давление, тазовый бинт, жгут при артериальном кровотечении."},
|
||
{s:"Два крупных венозных доступа. Пробы на группу крови, кросс-матч, лактат."},
|
||
{s:"Согревание пациента, профилактика триады смерти: гипотермия — ацидоз — коагулопатия."},
|
||
{s:"Сбалансированная инфузия кристаллоидов + ранняя гемотрансфузия (эритроциты/плазма/тромбоциты ~1:1:1). Вазопрессоры временно, если инфузия неэффективна."},
|
||
{s:"Экстренный хирургический/ортопедический гемостаз (damage control surgery). Контроль внутреннего кровотечения."}
|
||
],
|
||
keypoints:[
|
||
{t:"bad",x:"Не тратьте время на полное обследование при нестабильной гемодинамике — пациент гибнет от кровопотери."},
|
||
{t:"good",x:"Боритесь с триадой смерти (гипотермия + ацидоз + коагулопатия) — это определяет выживаемость."},
|
||
{t:"bad",x:"Избыток кристаллоидов до гемотрансфузии усугубляет коагулопатию и гипотермию — не «заливайте» пациента."}
|
||
]
|
||
},
|
||
|
||
{
|
||
id:"sepsis",
|
||
theme:"shock", themeLabel:"Шок и травма", badge:"b-shock",
|
||
title:"Септический шок",
|
||
severity:3,
|
||
patient:{avatar:"🧔", name:"Пациент Е.", age:"55 лет, мужчина", ctx:"Направлен из приёмного покоя"},
|
||
complaint:"Три дня назад начался озноб, температура под тридцать девять, кашель с мокротой. Со вчера стало хуже — давление упало, в себя прихожу с трудом. Раньше ноги никогда так не отекали, а сейчас задыхаюсь. Пить хочу, но тошнит.",
|
||
anamnesis:[
|
||
"Сахарный диабет 2 типа, плохо компенсирован.",
|
||
"Заболел как ОРВИ 5 дней назад, затем присоединился кашель с гнойной мокротой.",
|
||
"Самостоятельно принимал парацетамол и «антибиотик из аптечки».",
|
||
"Аллергия на пенициллин."
|
||
],
|
||
objective:{
|
||
vitals:{hr:"125", bp:"85/50", rr:"26", spo2:"92%", temp:"38.9°C"},
|
||
general:"Состояние тяжёлое. Кожа горячая, пятнистая сыпь. Сознание спутано, вялый.",
|
||
heart:"Тоны глухие, тахикардия. САД 85 после инфузии кристаллоидов.",
|
||
lungs:"В правой нижней доле — ослабленное дыхание, влажные мелкопузырчатые хрипы, притупление перкуторно.",
|
||
other:"Олигурия (< 0.5 мл/кг/ч). Капиллярное наполнение > 3 сек. Холодные на ощупь конечности на поздней стадии."
|
||
},
|
||
extra:{title:"Лаборатория и визуализация", text:"Лактат 4.2 ммоль/л ↑. Лейкоциты 22×10⁹/л, сдвиг формулы. Креатинин повышен. Рентген ОГК: инфильтрация нижней доли справа, плевральный выпот. Гемокультуры взяты до антибиотиков."},
|
||
quiz:{
|
||
q:"Что должно быть выполнено в первый час (Sepsis 1-hour bundle)?",
|
||
options:[
|
||
{t:"Только ждать результаты посевов 24 часа", c:false},
|
||
{t:"Лактат + гемокультуры + антибиотики + инфузия 30 мл/кг + вазопрессоры при гипотонии", c:true},
|
||
{t:"Только вазопрессоры без инфузии", c:false},
|
||
{t:"Только жаропонижающие и наблюдение", c:false}
|
||
],
|
||
feedback:{
|
||
ok:"Верно. Hour-1 bundle: измерить лактат, взять культуры до антибиотиков, ввести широкопрофильные антибиотики, инфузия 30 мл/кг кристаллоидов, норадреналин при стойкой гипотонии.",
|
||
no:"Не совсем. Задержка с антибиотиками и инфузией每小时 повышает летальность. Все компоненты выполняются в первый час."
|
||
}
|
||
},
|
||
diagnosis:"Септический шок. Внебольничная пневмония нижней доли справа — вероятный источник. Острое почечное повреждение.",
|
||
algorithm:[
|
||
{s:"Sepsis hour-1 bundle: лактат, забор гемокультур до антибиотиков."},
|
||
{s:"Антибиотики широкого спектра в/в в первый час (учесть аллергию на пенициллин — например, цефалоспорин/карбапенем + макролид)."},
|
||
{s:"Инфузия кристаллоидов 30 мл/кг за 3 ч при гипотензии или лактате ≥ 4 ммоль/л."},
|
||
{s:"При стойкой гипотензии после инфузии — норадреналин титровать до САД ≥ 65 мм рт.ст. Вазопрессином добавить при необходимости."},
|
||
{s:"Контроль источника инфекции (дренирование выпота, лечение пневмонии). Мониторинг: лактат, диурез, АД."},
|
||
{s:"Поддерживающая терапия: оксигенация/ИВЛ по показаниям, контроль гликемии, профилактика тромбозов."}
|
||
],
|
||
keypoints:[
|
||
{t:"bad",x:"Не ждите посевов, чтобы начать антибиотики — каждая час задержки повышает летальность."},
|
||
{t:"good",x:"Норадреналин — препарат первого ряда при септическом шоке после адекватной инфузии."},
|
||
{t:"good",x:"Контроль источника инфекции так же важен, как антибиотики — без него шок не купируется."}
|
||
]
|
||
}
|
||
];
|
||
|
||
/* ===== Рендер карточек ===== */
|
||
const grid = document.getElementById("caseGrid");
|
||
|
||
function sevDots(n){
|
||
let s="";for(let i=0;i<3;i++) s+=`<span class="sev-dot ${i<n?'on':''}"></span>`;
|
||
return s;
|
||
}
|
||
|
||
function renderCards(filter="all"){
|
||
grid.innerHTML="";
|
||
CASES.filter(c=>filter==="all"||c.theme===filter).forEach(c=>{
|
||
const el=document.createElement("article");
|
||
el.className=`case-card t-${c.theme}`;
|
||
el.innerHTML=`
|
||
<div class="case-head">
|
||
<div class="case-avatar">${c.patient.avatar}</div>
|
||
<div>
|
||
<h3>${c.title}</h3>
|
||
<div class="case-sub">${c.patient.name}, ${c.patient.age.split(',')[0]}</div>
|
||
</div>
|
||
</div>
|
||
<p class="case-complaint">«${c.complaint.length>130?c.complaint.slice(0,130)+'…':c.complaint}»</p>
|
||
<div class="case-foot">
|
||
<span class="playhint">▶ Прослушать и разобрать</span>
|
||
<span class="severity" title="Тяжесть">${sevDots(c.severity)}</span>
|
||
</div>`;
|
||
el.addEventListener("click",()=>openCase(c));
|
||
grid.appendChild(el);
|
||
});
|
||
}
|
||
|
||
/* ===== Фильтры ===== */
|
||
document.getElementById("filters").addEventListener("click",e=>{
|
||
const b=e.target.closest(".filter");if(!b)return;
|
||
document.querySelectorAll(".filter").forEach(f=>f.classList.remove("is-active"));
|
||
b.classList.add("is-active");
|
||
renderCards(b.dataset.filter);
|
||
});
|
||
|
||
/* ===== Модальное окно ===== */
|
||
const modal=document.getElementById("modal");
|
||
const modalBody=document.getElementById("modalBody");
|
||
|
||
function vitalsHTML(v){
|
||
let extras="";
|
||
if(v.temp) extras=`<div class="vital v-temp"><b>${v.temp}</b><span>Температура</span></div>`;
|
||
return `<div class="vitals">
|
||
<div class="vital v-hr"><b>${v.hr}</b><span>ЧСС</span></div>
|
||
<div class="vital v-bp"><b>${v.bp}</b><span>АД</span></div>
|
||
<div class="vital v-rr"><b>${v.rr}</b><span>ЧДД</span></div>
|
||
<div class="vital v-spo"><b>${v.spo2}</b><span>SpO₂</span></div>
|
||
${extras}
|
||
</div>`;
|
||
}
|
||
|
||
function examHTML(o){
|
||
return `
|
||
<h4>Жизненные показатели</h4>
|
||
${vitalsHTML(o.vitals)}
|
||
<h4>Общий осмотр</h4><p>${o.general}</p>
|
||
<h4>Сердечно-сосудистая система</h4><p>${o.heart}</p>
|
||
<h4>Дыхательная система</h4><p>${o.lungs}</p>
|
||
<h4>Прочее</h4><p>${o.other}</p>`;
|
||
}
|
||
|
||
function algoHTML(a){
|
||
return `<ol class="algo">${a.map(s=>`<li>${s.s}</li>`).join("")}</ol>`;
|
||
}
|
||
function kpHTML(k){
|
||
return `<div class="keypoints">${k.map(p=>`<div class="kp kp-${p.t==='good'?'good':'bad'}"><span class="kp-ico">${p.t==='good'?'✅':'⚠️'}</span><span>${p.x}</span></div>`).join("")}</div>`;
|
||
}
|
||
|
||
function openCase(c){
|
||
const qOpts=c.quiz.options.map((o,i)=>`<button class="quiz-opt" data-i="${i}" data-c="${o.c}">${o.t}</button>`).join("");
|
||
modalBody.innerHTML=`
|
||
<div class="patient">
|
||
<div class="patient-top">
|
||
<div class="patient-avatar" id="pAvatar">${c.patient.avatar}</div>
|
||
<div class="patient-info">
|
||
<h2 id="mTitle">${c.title}</h2>
|
||
<div class="meta">${c.patient.name} · ${c.patient.age} · ${c.patient.ctx}</div>
|
||
<div class="patient-badges"><span class="badge ${c.badge}">${c.themeLabel}</span></div>
|
||
</div>
|
||
</div>
|
||
<div class="speak-bar">
|
||
<span class="speak-ico">🔊</span>
|
||
<span class="speak-text"><b>Жалобы пациента</b><small>Нажмите, чтобы услышать голос</small></span>
|
||
<button class="speak-btn" id="speakBtn">▶ Прослушать</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mbody">
|
||
<div class="complaint">«${c.complaint}»</div>
|
||
|
||
<details class="acc" open>
|
||
<summary>📋 Анамнез заболевания и жизни</summary>
|
||
<div class="acc-body"><ul>${c.anamnesis.map(a=>`<li>${a}</li>`).join("")}</ul></div>
|
||
</details>
|
||
|
||
<details class="acc" open>
|
||
<summary>🩺 Объективный осмотр</summary>
|
||
<div class="acc-body">${examHTML(c.objective)}</div>
|
||
</details>
|
||
|
||
<details class="acc">
|
||
<summary>🔬 Дополнительные данные</summary>
|
||
<div class="acc-body"><p>${c.extra.text}</p></div>
|
||
</details>
|
||
|
||
<div class="quiz">
|
||
<h3>🧠 Поставьте диагноз и выберите тактику</h3>
|
||
<p class="q-sub">${c.quiz.q}</p>
|
||
<div class="quiz-opts">${qOpts}</div>
|
||
<div class="quiz-feedback" id="quizFb"></div>
|
||
</div>
|
||
|
||
<details class="acc">
|
||
<summary>✅ Правильный диагноз</summary>
|
||
<div class="acc-body"><p>${c.diagnosis}</p></div>
|
||
</details>
|
||
|
||
<details class="acc">
|
||
<summary>🚑 Алгоритм неотложной помощи</summary>
|
||
<div class="acc-body">${algoHTML(c.algorithm)}</div>
|
||
</details>
|
||
|
||
<details class="acc">
|
||
<summary>💡 Ключевые моменты и типичные ошибки</summary>
|
||
<div class="acc-body">${kpHTML(c.keypoints)}</div>
|
||
</details>
|
||
</div>`;
|
||
|
||
// озвучка
|
||
const speakBtn=document.getElementById("speakBtn");
|
||
const pAvatar=document.getElementById("pAvatar");
|
||
speakBtn.addEventListener("click",()=>toggleSpeak(c.complaint,speakBtn,pAvatar));
|
||
|
||
// квиз
|
||
const opts=modalBody.querySelectorAll(".quiz-opt");
|
||
const fb=document.getElementById("quizFb");
|
||
opts.forEach(o=>{
|
||
o.addEventListener("click",()=>{
|
||
opts.forEach(x=>x.disabled=true);
|
||
const correct=o.dataset.c==="true";
|
||
if(correct){o.classList.add("correct")}else{o.classList.add("wrong");
|
||
// подсветить верный
|
||
opts.forEach(x=>{if(x.dataset.c==="true")x.classList.add("correct")});
|
||
}
|
||
fb.className=`quiz-feedback show ${correct?'ok':'no'}`;
|
||
fb.innerHTML=correct?`<b>Верно!</b>${c.quiz.feedback.ok}`:`<b>Не совсем так</b>${c.quiz.feedback.no}`;
|
||
});
|
||
});
|
||
|
||
modal.hidden=false;
|
||
document.body.style.overflow="hidden";
|
||
}
|
||
|
||
function closeModal(){
|
||
window.speechSynthesis.cancel();
|
||
modal.hidden=true;
|
||
document.body.style.overflow="";
|
||
}
|
||
modal.addEventListener("click",e=>{if(e.target.hasAttribute("data-close"))closeModal()});
|
||
document.addEventListener("keydown",e=>{if(e.key==="Escape"&&!modal.hidden)closeModal()});
|
||
|
||
/* ===== Озвучка (Web Speech API) ===== */
|
||
let voicesReady=false;
|
||
function loadVoices(){
|
||
voicesReady=window.speechSynthesis.getVoices().length>0;
|
||
}
|
||
if("speechSynthesis" in window){
|
||
loadVoices();
|
||
window.speechSynthesis.onvoiceschanged=loadVoices;
|
||
}
|
||
|
||
function toggleSpeak(text,btn,avatar){
|
||
const synth=window.speechSynthesis;
|
||
if(!synth){alert("Озвучка не поддерживается этим браузером.");return}
|
||
// если уже играет — остановить
|
||
if(btn.classList.contains("is-playing")){
|
||
synth.cancel();
|
||
btn.classList.remove("is-playing");
|
||
btn.textContent="▶ Прослушать";
|
||
avatar.classList.remove("is-speaking");
|
||
return;
|
||
}
|
||
synth.cancel();
|
||
const u=new SpeechSynthesisUtterance(text);
|
||
u.lang="ru-RU";u.rate=0.97;u.pitch=1;
|
||
const voices=synth.getVoices();
|
||
const ru=voices.find(v=>v.lang&&v.lang.toLowerCase().startsWith("ru"));
|
||
if(ru)u.voice=ru;
|
||
u.onend=()=>{btn.classList.remove("is-playing");btn.textContent="▶ Прослушать";avatar.classList.remove("is-speaking")};
|
||
u.onerror=()=>{btn.classList.remove("is-playing");btn.textContent="▶ Прослушать";avatar.classList.remove("is-speaking")};
|
||
btn.classList.add("is-playing");
|
||
btn.textContent="⏹ Остановить";
|
||
avatar.classList.add("is-speaking");
|
||
synth.speak(u);
|
||
}
|
||
|
||
/* остановить речь при перерисовке */
|
||
window.addEventListener("beforeunload",()=>{if("speechSynthesis" in window)window.speechSynthesis.cancel()});
|
||
|
||
/* старт */
|
||
renderCards();
|