{"id":19,"date":"2026-06-15T01:05:02","date_gmt":"2026-06-15T01:05:02","guid":{"rendered":"https:\/\/bezdechkurs.pl\/?page_id=19"},"modified":"2026-06-15T01:05:02","modified_gmt":"2026-06-15T01:05:02","slug":"19-2","status":"publish","type":"page","link":"https:\/\/bezdechkurs.pl\/index.php\/19-2\/","title":{"rendered":""},"content":{"rendered":"\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><code><div id=\"poligrafia-app\">\n<style>\n:root{--p:#1a6fa8;--pd:#124e78;--s:#2ecc9a;--acc:#e74c3c;--w:#f39c12;--bg:#f0f4f8;--card:#fff;--tx:#1e2a38;--mu:#6b7a8d;--bd:#d1dae5;--r:12px;--sh:0 2px 12px rgba(0,0,0,.08)}\n#poligrafia-app *{box-sizing:border-box;margin:0;padding:0}\n#poligrafia-app{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(--tx);min-height:100vh;width:100%;max-width:none;position:relative;overflow-x:hidden}\nnav{background:var(--pd);color:#fff;position:sticky;top:0;z-index:100;display:flex;align-items:center;gap:4px;padding:0 12px;height:52px;box-shadow:0 2px 8px rgba(0,0,0,.2);overflow-x:auto}\nnav .logo{font-weight:700;font-size:.95rem;white-space:nowrap;margin-right:12px;flex-shrink:0}\nnav button{background:none;border:none;color:rgba(255,255,255,.75);padding:6px 11px;border-radius:6px;cursor:pointer;font-size:.8rem;white-space:nowrap;flex-shrink:0;transition:all .2s}\nnav button:hover,nav button.active{background:rgba(255,255,255,.15);color:#fff}\n.poli-main{width:100%;max-width:1680px;margin:0 auto;padding:28px 24px}\n.section{display:none}.section.active{display:block}\n.card{background:var(--card);border-radius:var(--r);padding:24px;margin-bottom:20px;box-shadow:var(--sh);border:1px solid var(--bd)}\nh1{font-size:1.7rem;color:var(--pd);margin-bottom:8px}\nh2{font-size:1.25rem;color:var(--p);margin-bottom:14px;padding-bottom:8px;border-bottom:2px solid var(--bd)}\nh3{font-size:1rem;color:var(--pd);margin:14px 0 8px}\np{line-height:1.7;margin-bottom:10px}\nul,ol{padding-left:20px;margin-bottom:10px}\nli{line-height:1.7;margin-bottom:3px}\ntable{width:100%;border-collapse:collapse;margin:14px 0;font-size:.88rem}\nth{background:var(--p);color:#fff;padding:9px 12px;text-align:left}\ntd{padding:9px 12px;border-bottom:1px solid var(--bd)}\ntr:nth-child(even) td{background:var(--bg)}\n.hero{background:linear-gradient(135deg,var(--pd) 0%,var(--p) 65%,#2980b9 100%);color:#fff;border-radius:var(--r);padding:36px 28px;margin-bottom:24px;text-align:center}\n.hero h1{color:#fff;font-size:1.9rem;margin-bottom:10px}\n.hero p{color:rgba(255,255,255,.85);font-size:1rem;max-width:660px;margin:0 auto 20px}\n.hero-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px;margin-top:20px}\n.hero-stat{background:rgba(255,255,255,.12);border-radius:10px;padding:14px 10px}\n.hero-stat .num{font-size:1.9rem;font-weight:700}\n.hero-stat .lbl{font-size:.75rem;color:rgba(255,255,255,.75);margin-top:3px}\n.info-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:10px;margin:16px 0}\n.info-box{border-radius:10px;padding:14px;text-align:center;border:2px solid transparent}\n.info-box .label{font-size:.75rem;font-weight:700;letter-spacing:.4px;text-transform:uppercase;margin-bottom:5px}\n.info-box .value{font-size:.95rem;font-weight:600}\n.blue{background:#e8f0f9;border-color:var(--p);color:var(--p)}\n.green{background:#e6f9f2;border-color:var(--s);color:#1a8a6a}\n.red{background:#fdecea;border-color:var(--acc);color:#c0392b}\n.orange{background:#fef5e6;border-color:var(--w);color:#c87900}\n.gray{background:#f0f4f8;border-color:var(--bd);color:var(--mu)}\n.alert{border-radius:10px;padding:12px 16px;margin:14px 0;display:flex;gap:10px;align-items:flex-start;border-left:4px solid}\n.alert.info{background:#e8f0f9;border-color:var(--p)}\n.alert.warn{background:#fef5e6;border-color:var(--w)}\n.alert.danger{background:#fdecea;border-color:var(--acc)}\n.alert.success{background:#e6f9f2;border-color:var(--s)}\n.alert .icon{font-size:1.1rem;flex-shrink:0;margin-top:1px}\n.btn{display:inline-flex;align-items:center;gap:7px;padding:9px 18px;border-radius:8px;border:none;font-size:.88rem;font-weight:600;cursor:pointer;transition:all .2s;text-decoration:none}\n.btn-primary{background:var(--p);color:#fff}.btn-primary:hover{background:var(--pd)}\n.btn-secondary{background:var(--s);color:#fff}.btn-secondary:hover{background:#27ae80}\n.btn-outline{background:transparent;color:var(--p);border:2px solid var(--p)}.btn-outline:hover{background:var(--p);color:#fff}\n.btn-lg{padding:13px 26px;font-size:.95rem}\n.module-item{display:flex;align-items:center;gap:14px;background:var(--bg);border-radius:10px;padding:13px 16px;border:1px solid var(--bd);cursor:pointer;transition:all .2s;margin-bottom:8px}\n.module-item:hover{border-color:var(--p);background:#e8f0f9}\n.module-item .icon{font-size:1.4rem;width:38px;text-align:center;flex-shrink:0}\n.module-item .info .title{font-weight:600;color:var(--pd)}\n.module-item .info .sub{font-size:.8rem;color:var(--mu);margin-top:2px}\n.badge{margin-left:auto;font-size:.72rem;padding:3px 8px;border-radius:20px;background:#e8f0f9;color:var(--p);font-weight:600;white-space:nowrap}\n.badge.pr{background:#e6f9f2;color:#1a8a6a}\n.prog-bar{height:7px;background:var(--bd);border-radius:4px;overflow:hidden}\n.prog-fill{height:100%;border-radius:4px;background:var(--s);transition:width .4s}\n.chapter-nav{display:flex;gap:8px;justify-content:flex-end;margin-top:20px}\n.chapter-figure{margin:16px 0 22px;background:#fff;border:1px solid var(--bd);border-radius:12px;padding:12px;box-shadow:var(--sh)}\n.chapter-figure img{display:block;width:100%;height:auto;border-radius:10px;border:1px solid #e7edf5}\n.chapter-figure figcaption{font-size:.82rem;line-height:1.55;color:var(--mu);margin-top:8px}\n.chapter-2col{display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:start;margin:14px 0}\n.callout-list{background:var(--bg);border:1px solid var(--bd);border-radius:10px;padding:14px 16px;margin:14px 0}\n.callout-list li{margin-bottom:6px}\n@media(max-width:800px){.chapter-2col{grid-template-columns:1fr}}\n\n\/* case report patient card *\/\n.ci-stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(100px,1fr));gap:8px;margin:14px 0}\n.ci-stat{background:rgba(255,255,255,.7);border:1px solid var(--bd);border-radius:8px;padding:10px;text-align:center}\n.ci-key{font-size:.7rem;color:var(--mu);font-weight:700;text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px}\n.ci-val{font-size:1.05rem;font-weight:700;color:var(--pd)}\n\/* event cards in ch7 *\/\n.ev-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px;margin:16px 0}\n.ev-card{border-radius:12px;padding:16px;border:2px solid}\n.ev-card .et{font-weight:700;font-size:.92rem;margin-bottom:6px}\n.ev-card .ed{font-size:.8rem;line-height:1.5}\n.ev-oa{border-color:#e74c3c;background:#fdecea}.ev-oa .et{color:#c0392b}\n.ev-ca{border-color:#3498db;background:#e8f0f9}.ev-ca .et{color:#1a6fa8}\n.ev-ma{border-color:#9b59b6;background:#f3e8fd}.ev-ma .et{color:#7d3c98}\n.ev-oh{border-color:#f39c12;background:#fef5e6}.ev-oh .et{color:#c87900}\n.ev-ch{border-color:#00b894;background:#e6f9f2}.ev-ch .et{color:#007a5e}\n.ev-ds{border-color:#6c5ce7;background:#f3eaff}.ev-ds .et{color:#4a3ab8}\n\/* scoring *\/\n.scoring-toolbar{display:flex;flex-wrap:wrap;gap:7px;margin:10px 0;padding:10px;background:var(--bg);border-radius:10px;align-items:center}\n.ev-btn{padding:5px 12px;border-radius:20px;border:2px solid;font-size:.78rem;font-weight:700;cursor:pointer;transition:all .2s;background:transparent}\n.ev-btn.active{color:#fff!important}\n.ev-btn.oa{border-color:#e74c3c;color:#c0392b}.ev-btn.oa.active{background:#e74c3c}\n.ev-btn.ca{border-color:#3498db;color:#1a6fa8}.ev-btn.ca.active{background:#3498db}\n.ev-btn.ma{border-color:#9b59b6;color:#7d3c98}.ev-btn.ma.active{background:#9b59b6}\n.ev-btn.oh{border-color:#f39c12;color:#c87900}.ev-btn.oh.active{background:#f39c12}\n.ev-btn.ch{border-color:#00b894;color:#007a5e}.ev-btn.ch.active{background:#00b894}\n.ev-btn.ds{border-color:#6c5ce7;color:#4a3ab8}.ev-btn.ds.active{background:#6c5ce7}\n.ev-btn.snore{border-color:#2ecc71;color:#1a8a4a}.ev-btn.snore.active{background:#2ecc71}\n.ev-btn.brady{border-color:#00cec9;color:#007a77}.ev-btn.brady.active{background:#00cec9}\n.ev-btn.tachy{border-color:#fd79a8;color:#c0396a}.ev-btn.tachy.active{background:#fd79a8}\n.mark-pending-tip{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:#f39c12;color:#fff;padding:8px 18px;border-radius:20px;font-size:.85rem;font-weight:700;z-index:200;pointer-events:none;box-shadow:0 4px 16px rgba(0,0,0,.3);animation:pulse .8s infinite alternate}\n@keyframes pulse{from{opacity:.85}to{opacity:1}}\n.win-btns{display:flex;gap:5px;align-items:center;flex-wrap:wrap;margin:8px 0}\n.win-btn{padding:5px 12px;border-radius:6px;border:1px solid var(--bd);background:var(--card);cursor:pointer;font-size:.8rem;font-weight:600;color:var(--mu);transition:all .2s}\n.win-btn.active{background:var(--pd);color:#fff;border-color:var(--pd)}\n.win-hint{font-size:.78rem;color:var(--mu);padding:5px 10px;background:var(--bg);border-radius:6px;margin-left:4px;border-left:3px solid var(--p)}\n.stat-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(110px,1fr));gap:8px;margin:10px 0}\n.stat-box{background:var(--card);border-radius:8px;padding:10px;text-align:center;border:1px solid var(--bd)}\n.stat-box .sv{font-size:1.35rem;font-weight:700;color:var(--p)}\n.stat-box .sl{font-size:.7rem;color:var(--mu);margin-top:2px}\n.compare-area{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin:14px 0}\n.cp{border-radius:10px;padding:12px;border:2px solid}\n.cp.st{border-color:var(--p);background:#e8f0f9}\n.cp.ma{border-color:var(--s);background:#e6f9f2}\n.certificate{background:linear-gradient(135deg,#fff8e1 0%,#fff 60%);border:3px solid #c9a227;border-radius:16px;padding:36px;text-align:center;margin:20px 0;box-shadow:0 4px 24px rgba(201,162,39,.2)}\n\/* quality check boxes *\/\n.qc-item{display:flex;gap:12px;padding:12px;border-radius:10px;background:var(--bg);border:1px solid var(--bd);margin-bottom:8px;align-items:flex-start}\n.qc-icon{font-size:1.3rem;flex-shrink:0;width:32px;text-align:center}\n.qc-title{font-weight:600;color:var(--pd);margin-bottom:4px;font-size:.92rem}\n.qc-detail{font-size:.82rem;color:var(--mu);line-height:1.5}\n.qc-detail .ok{color:#1a8a6a}.qc-detail .bad{color:#c0392b}.qc-detail .warn{color:#c87900}\n@media(max-width:900px){.poli-main{padding:18px 10px}}\n@media(max-width:600px){.compare-area{grid-template-columns:1fr}.hero h1{font-size:1.4rem}.poli-main{padding:12px 6px}.card{padding:16px}}\n<\/style>\n<nav>\n  <span class=\"logo\">\ud83e\udec1 Poligrafia III<\/span>\n  <button onclick=\"showSection('home')\" id=\"nav-home\" class=\"active\">\ud83c\udfe0 Start<\/button>\n  <button onclick=\"showSection('teoria')\" id=\"nav-teoria\">\ud83d\udcda Teoria<\/button>\n  <button onclick=\"showSection('praktyka')\" id=\"nav-praktyka\">\ud83c\udfaf Praktyka<\/button>\n  <button onclick=\"showSection('cert')\" id=\"nav-cert\">\ud83c\udfc6 Za\u015bwiadczenie<\/button>\n<\/nav>\n<div class=\"poli-main\">\n\n<!-- HOME -->\n<div id=\"sec-home\" class=\"section active\">\n  <div class=\"hero\">\n    <h1>Poligrafia typu III<\/h1>\n    <p>Od teorii do samodzielnego opisu badania snu \u2013 kurs przygotowuj\u0105cy do niezale\u017cnej analizy zapis\u00f3w poligraficznych<\/p>\n    <div style=\"display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:18px\">\n      <button class=\"btn btn-secondary btn-lg\" onclick=\"showSection('teoria')\">\u25b6 Rozpocznij kurs<\/button>\n      <button class=\"btn btn-outline btn-lg\" style=\"color:#fff;border-color:#fff\" onclick=\"showSection('praktyka')\">\ud83c\udfaf Cz\u0119\u015b\u0107 praktyczna<\/button>\n    <\/div>\n    <div class=\"hero-grid\">\n      <div class=\"hero-stat\"><div class=\"num\">10<\/div><div class=\"lbl\">Rozdzia\u0142\u00f3w teorii<\/div><\/div>\n      <div class=\"hero-stat\"><div class=\"num\">12<\/div><div class=\"lbl\">Bada\u0144 do scoringu<\/div><\/div>\n      <div class=\"hero-stat\"><div class=\"num\">6<\/div><div class=\"lbl\">Kana\u0142\u00f3w sygna\u0142u<\/div><\/div>\n      <div class=\"hero-stat\"><div class=\"num\">\ud83c\udfc6<\/div><div class=\"lbl\">Za\u015bwiadczenie<\/div><\/div>\n    <\/div>\n  <\/div>\n  <div class=\"card\">\n    <h2>\ud83d\udccb Program kursu<\/h2>\n    <div id=\"home-modules\"><\/div>\n  <\/div>\n<\/div>\n\n<!-- TEORIA -->\n<div id=\"sec-teoria\" class=\"section\">\n  <div class=\"card\" style=\"padding:14px 20px;margin-bottom:16px;display:flex;align-items:center;gap:14px\">\n    <span style=\"font-size:.82rem;color:var(--mu);white-space:nowrap\">Post\u0119p teorii:<\/span>\n    <div class=\"prog-bar\" style=\"flex:1\"><div class=\"prog-fill\" id=\"th-prog\" style=\"width:0%\"><\/div><\/div>\n    <span id=\"th-pct\" style=\"font-size:.82rem;font-weight:700;color:var(--p)\">0%<\/span>\n  <\/div>\n  <div id=\"chapters-container\"><\/div>\n<\/div>\n\n<!-- PRAKTYKA -->\n<div id=\"sec-praktyka\" class=\"section\">\n  <div class=\"card\">\n    <h2>\ud83c\udfaf Cz\u0119\u015b\u0107 praktyczna \u2013 Samodzielny Scoring<\/h2>\n    <div style=\"display:flex;align-items:center;gap:14px;margin-bottom:16px\">\n      <span style=\"font-size:.82rem;color:var(--mu)\">Post\u0119p praktyki:<\/span>\n      <div class=\"prog-bar\" style=\"flex:1\"><div class=\"prog-fill\" id=\"pr-prog\" style=\"width:0%\"><\/div><\/div>\n      <span id=\"pr-pct\" style=\"font-size:.82rem;font-weight:700;color:var(--s)\">0\/12<\/span>\n    <\/div>\n    <div style=\"display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:18px\">\n      <div class=\"info-box blue\" id=\"si1\"><div class=\"label\">Etap 1<\/div><div class=\"value\" style=\"font-size:.82rem\">3\u00d7 fragmenty po 30 min<\/div><div style=\"font-size:.72rem;margin-top:4px;color:var(--mu)\" id=\"s1s\">0\/3 uko\u0144czone<\/div><\/div>\n      <div class=\"info-box gray\" id=\"si2\"><div class=\"label\">Etap 2<\/div><div class=\"value\" style=\"font-size:.82rem\">Etap 2 \u2013 po te\u015bcie<\/div><div style=\"font-size:.72rem;margin-top:4px;color:var(--mu)\" id=\"s2s\">Odblokowane po etapie 1<\/div><\/div>\n    <\/div>\n    <div id=\"study-selector\">\n      <div class=\"module-list\" id=\"studies-list\"><\/div>\n      <div id=\"case-reports-section\" style=\"display:none;margin-top:24px\">\n        <div style=\"display:flex;align-items:center;gap:10px;margin-bottom:10px;padding-bottom:8px;border-top:2px dashed var(--bd);padding-top:16px\">\n          <span style=\"font-size:1.4rem\">\ud83e\ude7a<\/span>\n          <div>\n            <div style=\"font-weight:700;color:var(--pd);font-size:1rem\">Etap 3 \u2013 Przypadki kliniczne<\/div>\n            <div style=\"font-size:.8rem;color:var(--mu)\">Zintegrowane opisy z kontekstem klinicznym \u00b7 3 przypadki<\/div>\n          <\/div>\n        <\/div>\n        <div id=\"cr-list\"><\/div>\n      <\/div>\n    <\/div>\n    <div id=\"scoring-interface\" style=\"display:none\">\n      <div style=\"display:flex;align-items:center;gap:10px;margin-bottom:12px;flex-wrap:wrap\">\n        <button class=\"btn btn-outline\" onclick=\"backToSelector()\" style=\"font-size:.78rem;padding:5px 12px\">\u2190 Lista<\/button>\n        <h3 id=\"study-title\" style=\"margin:0\"><\/h3>\n        <span id=\"study-badge\" class=\"badge\"><\/span>\n        <span style=\"margin-left:auto;font-size:.8rem;color:var(--mu)\" id=\"sc-timer\">\u23f1 00:00<\/span>\n      <\/div>\n      <div class=\"scoring-toolbar\">\n        <span style=\"font-size:.72rem;font-weight:700;color:var(--mu);letter-spacing:.3px\">ZDARZENIA ODDECHOWE:<\/span>\n        <button class=\"ev-btn oa active\" onclick=\"selEv('OA',this)\">OA<\/button>\n        <button class=\"ev-btn ca\" onclick=\"selEv('CA',this)\">CA<\/button>\n        <button class=\"ev-btn ma\" onclick=\"selEv('MA',this)\">MA<\/button>\n        <button class=\"ev-btn oh\" onclick=\"selEv('OH',this)\">OH<\/button>\n        <button class=\"ev-btn ch\" onclick=\"selEv('CH',this)\">CH<\/button>\n        <button class=\"ev-btn ds\" onclick=\"selEv('DESAT',this)\">Desaturation<\/button>\n        <span style=\"width:1px;height:22px;background:var(--bd);margin:0 4px\"><\/span>\n        <span style=\"font-size:.72rem;font-weight:700;color:var(--mu);letter-spacing:.3px\">ADNOTACJE:<\/span>\n        <button class=\"ev-btn snore\" onclick=\"selEv('SNORE',this)\">Chrapanie<\/button>\n        <button class=\"ev-btn brady\" onclick=\"selEv('BRADY',this)\">Bradykardia<\/button>\n        <button class=\"ev-btn tachy\" onclick=\"selEv('TACHY',this)\">Tachykardia<\/button>\n        <span style=\"width:1px;height:22px;background:var(--bd);margin:0 4px\"><\/span>\n        <button id=\"cancel-mark-btn\" onclick=\"cancelMark()\" style=\"display:none;padding:5px 12px;border-radius:6px;border:2px solid #f39c12;background:#fef5e6;color:#c87900;cursor:pointer;font-size:.78rem;font-weight:700\">\u2715 Anuluj<\/button>\n        <button onclick=\"undoEv()\" style=\"margin-left:auto;padding:5px 10px;border-radius:6px;border:1px solid var(--bd);background:var(--card);cursor:pointer;font-size:.78rem\">\u21a9 Cofnij<\/button>\n        <button onclick=\"clearEvs()\" style=\"padding:5px 10px;border-radius:6px;border:1px solid var(--acc);background:#fdecea;color:var(--acc);cursor:pointer;font-size:.78rem\">\ud83d\uddd1 Wyczy\u015b\u0107<\/button>\n      <\/div>\n      <div id=\"mark-instruction\" style=\"font-size:.78rem;color:#c87900;font-weight:600;padding:4px 8px;background:#fef5e6;border-radius:6px;display:none;margin-bottom:4px\">\n        \ud83d\udccd Kliknij na <strong>pocz\u0105tku<\/strong> zdarzenia, potem na <strong>ko\u0144cu<\/strong> \u2014 zostanie zaznaczony zakres.\n      <\/div>\n      <div class=\"win-btns\">\n        <span style=\"font-size:.75rem;color:var(--mu);font-weight:600\">Okno:<\/span>\n        <button class=\"win-btn\" onclick=\"setWin(30,this)\">30s<\/button>\n        <button class=\"win-btn\" onclick=\"setWin(120,this)\">2 min<\/button>\n        <button class=\"win-btn active\" onclick=\"setWin(300,this)\">5 min<\/button>\n        <button class=\"win-btn\" onclick=\"setWin(600,this)\">10 min<\/button>\n        <span class=\"win-hint\" id=\"win-hint\">5 min \u2013 standardowe okno scoringu: wida\u0107 kilka zdarze\u0144 z kontekstem SpO\u2082<\/span>\n      <\/div>\n      <div style=\"position:relative;background:#0a0e14;border-radius:12px;overflow:hidden;border:2px solid var(--bd);cursor:crosshair\">\n        <canvas id=\"recCanvas\" height=\"400\"><\/canvas>\n        <div id=\"loading-overlay\" style=\"display:none;position:absolute;inset:0;background:rgba(10,14,20,.88);align-items:center;justify-content:center;flex-direction:column;gap:14px;z-index:50\">\n          <div style=\"width:48px;height:48px;border:4px solid #1a6fa8;border-top-color:#2ecc9a;border-radius:50%;animation:spin 0.9s linear infinite\"><\/div>\n          <div id=\"loading-msg\" style=\"color:#aabbcc;font-size:.9rem;font-family:monospace\">Wczytywanie zapisu EDF\u2026<\/div>\n        <\/div>\n      <\/div>\n      <style>@keyframes spin{to{transform:rotate(360deg)}}<\/style>\n      <div style=\"display:flex;align-items:center;gap:8px;margin:8px 0\">\n        <button onclick=\"scrollRec(-1)\" style=\"padding:4px 12px;border-radius:6px;border:1px solid var(--bd);background:var(--card);cursor:pointer\">\u25c0<\/button>\n        <div style=\"flex:1;background:var(--bd);border-radius:4px;height:6px;position:relative;cursor:pointer\" id=\"tl-bar\" onclick=\"seekTl(event)\">\n          <div style=\"height:100%;border-radius:4px;background:var(--p);transition:width .1s\" id=\"tl-fill\"><\/div>\n        <\/div>\n        <button onclick=\"scrollRec(1)\" style=\"padding:4px 12px;border-radius:6px;border:1px solid var(--bd);background:var(--card);cursor:pointer\">\u25b6<\/button>\n        <span style=\"font-size:.78rem;color:var(--mu);white-space:nowrap\" id=\"td-display\">0:00 \/ 30:00<\/span>\n      <\/div>\n      <div class=\"stat-grid\">\n        <div class=\"stat-box\"><div class=\"sv\" id=\"ss-oa\" style=\"color:#e74c3c\">0<\/div><div class=\"sl\">OA<\/div><\/div>\n        <div class=\"stat-box\"><div class=\"sv\" id=\"ss-ca\" style=\"color:#3498db\">0<\/div><div class=\"sl\">CA<\/div><\/div>\n        <div class=\"stat-box\"><div class=\"sv\" id=\"ss-ma\" style=\"color:#9b59b6\">0<\/div><div class=\"sl\">MA<\/div><\/div>\n        <div class=\"stat-box\"><div class=\"sv\" id=\"ss-oh\" style=\"color:#f39c12\">0<\/div><div class=\"sl\">OH<\/div><\/div>\n        <div class=\"stat-box\"><div class=\"sv\" id=\"ss-ch\" style=\"color:#00b894\">0<\/div><div class=\"sl\">CH<\/div><\/div>\n        <div class=\"stat-box\"><div class=\"sv\" id=\"ss-ds\" style=\"color:#6c5ce7\">0<\/div><div class=\"sl\">Desat<\/div><\/div>\n        <div class=\"stat-box\"><div class=\"sv\" id=\"ss-ahi\" style=\"color:var(--acc)\">0.0<\/div><div class=\"sl\">AHI \/h<\/div><\/div>\n        <div class=\"stat-box\"><div class=\"sv\" id=\"ss-odi\" style=\"color:#6c5ce7\">0.0<\/div><div class=\"sl\">ODI \/h<\/div><\/div>\n        <div class=\"stat-box\" style=\"border-top:2px solid #2ecc71\"><div class=\"sv\" id=\"ss-snore\" style=\"color:#2ecc71\">0<\/div><div class=\"sl\" style=\"color:#2ecc71\">Chrap.<\/div><\/div>\n        <div class=\"stat-box\" style=\"border-top:2px solid #00cec9\"><div class=\"sv\" id=\"ss-brady\" style=\"color:#00cec9\">0<\/div><div class=\"sl\" style=\"color:#00cec9\">Brady<\/div><\/div>\n        <div class=\"stat-box\" style=\"border-top:2px solid #fd79a8\"><div class=\"sv\" id=\"ss-tachy\" style=\"color:#fd79a8\">0<\/div><div class=\"sl\" style=\"color:#fd79a8\">Tachy<\/div><\/div>\n      <\/div>\n      <div style=\"display:flex;gap:10px;margin-top:14px;flex-wrap:wrap\">\n        <button class=\"btn btn-outline\" onclick=\"showHint()\" id=\"hint-btn\">\ud83d\udca1 Podpowied\u017a<\/button>\n        <button class=\"btn btn-primary btn-lg\" onclick=\"submitScoring()\" style=\"margin-left:auto\">Zako\u0144cz i por\u00f3wnaj \u2192<\/button>\n      <\/div>\n      <div id=\"hint-panel\" class=\"alert warn\" style=\"display:none;margin-top:10px\"><span class=\"icon\">\ud83d\udca1<\/span><div id=\"hint-txt\"><\/div><\/div>\n    <\/div>\n    <div id=\"compare-results\" style=\"display:none\">\n      <h3 id=\"cmp-title\"><\/h3>\n      <div class=\"compare-area\">\n        <div class=\"cp st\"><h4 style=\"margin-bottom:8px;font-size:.85rem\">\ud83d\udc64 Tw\u00f3j wynik<\/h4><div id=\"st-res\"><\/div><\/div>\n        <div class=\"cp ma\"><h4 style=\"margin-bottom:8px;font-size:.85rem\">\ud83c\udfc5 Master<\/h4><div id=\"ms-res\"><\/div><\/div>\n      <\/div>\n      <div class=\"card\" style=\"text-align:center;padding:18px\">\n        <div style=\"font-size:2rem;font-weight:700\" id=\"acc-pct\">--<\/div>\n        <div style=\"color:var(--mu);font-size:.82rem\">Zgodno\u015b\u0107 z masterem<\/div>\n        <div class=\"prog-bar\" style=\"margin:10px 0\"><div class=\"prog-fill\" id=\"acc-fill\"><\/div><\/div>\n        <div id=\"acc-msg\" style=\"font-size:.88rem;margin-top:6px\"><\/div>\n      <\/div>\n      <div id=\"case-result-panel\" style=\"display:none\"><\/div>\n      <div style=\"display:flex;gap:10px;flex-wrap:wrap;margin-top:10px\">\n        <button class=\"btn btn-outline\" onclick=\"retryStudy()\">\ud83d\udd04 Spr\u00f3buj ponownie<\/button>\n        <button class=\"btn btn-primary\" onclick=\"nextStudy()\" id=\"next-btn\" style=\"margin-left:auto\">Nast\u0119pne badanie \u2192<\/button>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n<!-- CERT -->\n<div id=\"sec-cert\" class=\"section\">\n  <div class=\"card\" style=\"text-align:center\">\n    <div id=\"cert-locked\" style=\"padding:36px\">\n      <div style=\"font-size:3.5rem\">\ud83d\udd12<\/div>\n      <h2 style=\"margin:14px 0 8px\">Za\u015bwiadczenie niedost\u0119pne<\/h2>\n      <p style=\"color:var(--mu)\">Uko\u0144cz wszystkie 12 bada\u0144 (\u226550% zgodno\u015bci), aby odblokowa\u0107 za\u015bwiadczenie.<\/p>\n      <button class=\"btn btn-primary\" style=\"margin-top:14px\" onclick=\"showSection('praktyka')\">Przejd\u017a do \u0107wicze\u0144 \u2192<\/button>\n    <\/div>\n    <div id=\"cert-unlocked\" style=\"display:none\">\n      <div class=\"certificate\">\n        <div style=\"font-size:2rem\">\ud83c\udfc6<\/div>\n        <h2 style=\"color:#8b6914;margin:10px 0\">Za\u015bwiadczenie uko\u0144czenia kursu<\/h2>\n        <p style=\"color:#8b6914;font-size:.9rem\">Niniejszym za\u015bwiadcza si\u0119, \u017ce<\/p>\n        <div style=\"font-size:1.7rem;font-weight:700;color:var(--pd);margin:14px 0\">Uczestnik Kursu<\/div>\n        <p style=\"color:#8b6914;font-size:.9rem\">uko\u0144czy\u0142\/a kurs<\/p>\n        <p style=\"font-size:1.05rem;font-weight:700;color:var(--pd);margin:6px 0\">\u201ePoligrafia typu III \u2013 od teorii do samodzielnego opisu badania\"<\/p>\n        <p style=\"color:#8b6914;font-size:.85rem;margin-top:6px\">Data: <span id=\"cert-date\"><\/span><\/p>\n      <\/div>\n      <button class=\"btn btn-primary\" style=\"margin-top:14px\" onclick=\"window.print()\">\ud83d\udda8 Drukuj<\/button>\n    <\/div>\n  <\/div>\n<\/div>\n\n<\/div>\n<script>\nwindow.POLI_API      = \"https:\\\/\\\/bezdechkurs.pl\\\/index.php\\\/wp-json\\\/poligrafia\\\/v1\";\nwindow.POLI_STUDY_ID = \"szkolenie-obs-demo\";\nconst POLI_ASSET_BASE = \"https:\\\/\\\/bezdechkurs.pl\\\/wp-content\\\/plugins\\\/poligrafia-kurs\\\/assets\\\/\";\n\/\/ \u2500\u2500 STATE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst TRIAL_MODE = true;\nconst S = {\n  chapter: 1, maxChapter: 10, theoryDone: new Set(),\n  study: null, marked: [], evType: 'OA',\n  offset: 0, winDur: 300,\n  completed: {}, timer: null, elapsed: 0,\n  markStart: null, markCursor: null  \/\/ two-click marking: null=idle, number=pending start\n};\n\n\/\/ \u2500\u2500 RNG \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction rng(seed){ let s=seed; return ()=>{ s=(s*1664525+1013904223)&0xffffffff; return (s>>>0)\/0xffffffff; }; }\n\n\/\/ \u2500\u2500 STUDY DATA (real EDF recordings) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\/\/ tStart \/ dur are in seconds relative to EDF recording start\nconst STUDIES=[\n  \/\/ Stage 1: Training \u2014 3 non-overlapping 30-min windows from start of TST\n  {id:'t1',diff:'easy',   tStart:2400, dur:1800, label:'Trening 1 \u2013 sp\u0142ycenia i pierwsze bezdechy', stage:1},\n  {id:'t2',diff:'medium', tStart:4200, dur:1800, label:'Trening 2 \u2013 liczne bezdechy obturacyjne', stage:1},\n  {id:'t3',diff:'hard',   tStart:6000, dur:1800, label:'Trening 3 \u2013 bezdechy, hipopnoe i zdarzenia centralne', stage:1},\n  \/\/ Stage 2: Full TST scoring\n  {id:'e1',diff:'easy',   tStart:240,  dur:7470, label:'Badanie 1 (\u0142atwe)',         stage:2},\n  {id:'e2',diff:'easy',   tStart:240,  dur:7470, label:'Badanie 2 (\u0142atwe)',         stage:2},\n  {id:'e3',diff:'easy',   tStart:240,  dur:7470, label:'Badanie 3 (\u0142atwe)',         stage:2},\n  {id:'m1',diff:'medium', tStart:240,  dur:7470, label:'Badanie 4 (\u015brednie)',        stage:2},\n  {id:'m2',diff:'medium', tStart:240,  dur:7470, label:'Badanie 5 (\u015brednie)',        stage:2},\n  {id:'m3',diff:'medium', tStart:240,  dur:7470, label:'Badanie 6 (\u015brednie)',        stage:2},\n  {id:'h1',diff:'hard',   tStart:240,  dur:7470, label:'Badanie 7 (trudne)',         stage:2},\n  {id:'h2',diff:'hard',   tStart:240,  dur:7470, label:'Badanie 8 (trudne)',         stage:2},\n  {id:'h3',diff:'hard',   tStart:240,  dur:7470, label:'Badanie 9 (trudne)',         stage:2},\n  \/\/ Stage 3: Clinical case reports\n  {id:'cr1',diff:'easy',  tStart:240,  dur:7470, label:'Przypadek 1: Pan M., 52 l.',stage:3},\n  {id:'cr2',diff:'medium',tStart:240,  dur:7470, label:'Przypadek 2: Pani A., 58 l.',stage:3},\n  {id:'cr3',diff:'hard',  tStart:240,  dur:7470, label:'Przypadek 3: Pan T., 67 l.',stage:3},\n];\n\n\/\/ Helper: get master events for a study window (relative time)\nfunction getMasterForStudy(study){\n  if(!MASTER_EVENTS_ALL)return[];\n  const t0=study.tStart, t1=study.tStart+study.dur;\n  return MASTER_EVENTS_ALL\n    .filter(e=>e.start>=t0&&e.start+e.duration<=t1)\n    .map(e=>({...e,start:e.start-t0}));\n}\n\nconst CASE_INFO={\n  cr1:{\n    title:'Przypadek 1 \u2013 Pan M., lat 52',\n    badge:'Ci\u0119\u017cki OBS',badgeColor:'#e74c3c',\n    stats:[['Wiek','52 l.'],['P\u0142e\u0107','M'],['BMI','33,2'],['Obw\u00f3d szyi','45 cm'],['ESS','14\/24'],['STOP-BANG','6\/8']],\n    symptoms:'Nasilone g\u0142o\u015bne chrapanie od ~4 lat. Partnerka obserwuje bezdechy trwaj\u0105ce do 30 sekund. Senno\u015b\u0107 za kierownic\u0105 (usn\u0105\u0142 na czerwonym \u015bwietle). Poranne b\u00f3le g\u0142owy. Uczucie niewyspania mimo 8h snu.',\n    history:'NT t\u0119tnicze od 5 lat (leczony amlodypin\u0105 5 mg, bisoprololem 5 mg) \u2013 s\u0142aba kontrola (RR ~155\/95). Nie pali. Alkohol sporadycznie. Pracuje jako kierowca TIR-a. BMI wzros\u0142o o 8 kg w ci\u0105gu ostatnich 3 lat.',\n    question:'Wykonaj scoring badania. Oblicz AHI i ODI. Jaki stopie\u0144 ci\u0119\u017cko\u015bci OBS? Jakie leczenie zalecisz?',\n    expected:'Oczekiwane: AHI ~35\u201350\/h, przewaga OA z desaturacjami. Ci\u0119\u017cki OBS \u2013 wskazanie do CPAP. Poprawa kontroli NT po leczeniu OBS udokumentowana w badaniach. Ograniczenie jazdy do czasu ustawienia CPAP.',\n    color:'#fdecea',borderColor:'#e74c3c'\n  },\n  cr2:{\n    title:'Przypadek 2 \u2013 Pani A., lat 58',\n    badge:'Umiarkowany OBS',badgeColor:'#f39c12',\n    stats:[['Wiek','58 l.'],['P\u0142e\u0107','K'],['BMI','27,1'],['Obw\u00f3d szyi','38 cm'],['ESS','9\/24'],['STOP-BANG','3\/8']],\n    symptoms:'Zm\u0119czenie poranne mimo d\u0142ugiego snu. Nocne budzenia (2\u20133\u00d7). B\u00f3le g\u0142owy rano. Partnerka nie obserwuje bezdechu \u2013 pacjentka \u015bpi sama. Chrapanie: okazjonalne, niezbyt g\u0142o\u015bne wg domownik\u00f3w. Depresja leczona sertralin\u0105.',\n    history:'Menopauza od 3 lat (bez HRT). Niedoczynno\u015b\u0107 tarczycy (eutyreoza na lewotyroksynie). Brak NT, brak cukrzycy. STOP-BANG 3\/8 \u2013 niskie\u2013umiarkowane ryzyko, ALE kobieta po menopauzie = podwy\u017cszone ryzyko atypowego OBS.',\n    question:'Scoreuj badanie. Czy spodziewasz si\u0119 przewagi OA czy CA? Kt\u00f3re zdarzenia dominuj\u0105 u kobiet po menopauzie? Czy ESS 9 wyklucza OBS?',\n    expected:'Oczekiwane: AHI ~15\u201325\/h, mieszane OA+OH, mniej nasilone desaturacje. Umiarkowany OBS o atypowym przebiegu. ESS mo\u017ce by\u0107 niski u kobiet z OBS \u2013 nie wyklucza rozpoznania. CPAP lub MAD (mandibular advancement device).',\n    color:'#fef5e6',borderColor:'#f39c12'\n  },\n  cr3:{\n    title:'Przypadek 3 \u2013 Pan T., lat 67',\n    badge:'OBS + CSA \/ overlap',badgeColor:'#3498db',\n    stats:[['Wiek','67 l.'],['P\u0142e\u0107','M'],['BMI','28,4'],['EF serca','32%'],['ESS','12\/24'],['STOP-BANG','5\/8']],\n    symptoms:'Opadanie ze snu w dzie\u0144, nocne duszno\u015bci (wybudzenia z uczuciem braku tchu), ortopnoea (\u015bpi na 2 poduszkach). Obrz\u0119ki podudzi. Chrapanie nieregularne. Partnerka: \u201eoddycha nieregularnie przez ca\u0142\u0105 noc, czasem jakby prawie nie oddycha\u0142\".',\n    history:'Niewydolno\u015b\u0107 serca (EF 32%, NYHA III) po MI 4 lata temu. Migotanie przedsionk\u00f3w (przewlek\u0142e, kontrola rytmu). Leki: karwedilol, sakubitrylen\/walsartan, dapagliflozyna, diuretyk. NT kompensowane. Nefrologia: eGFR 52.',\n    question:'Scoreuj badanie. Oce\u0144 proporcj\u0119 OA do CA. Czy wida\u0107 wzorzec Cheyne-Stokesa? Jakie leczenie jest wskazane \u2013 CPAP czy ASV?',\n    expected:'Oczekiwane: AHI ~20\u201330\/h z mieszanym obrazem: CA\/CH dominuj\u0105 lub wsp\u00f3\u0142istniej\u0105 z OA (overlap\/CSA). Wzorzec crescendo-decrescendo na kanale przep\u0142ywu sugeruje CSR. W NS z EF&lt;45% ASV jest przeciwwskazane (SERVE-HF trial). Optymalizacja leczenia NS jako priorytet. Rozwa\u017cy\u0107 O\u2082 lub CPAP po konsultacji kardio.',\n    color:'#e8f0f9',borderColor:'#3498db'\n  }\n};\n\n\/\/ \u2500\u2500 REAL WAVEFORM DATA \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\/\/ Global state for fetched data\nlet WAVEFORM_CACHE = null;   \/\/ {flow,snore,tho,spo2,hr,pleth, _tStart, _fs}\nlet MASTER_EVENTS_ALL = null; \/\/ all scoring events from RML (absolute times)\nlet STUDY_INFO_SERVER = null; \/\/ {duration, lightsOff, lightsOn, bmi, ...}\n\n\/\/ Fetch waveform for current study window from server\nasync function fetchStudyData(study){\n  showLoadingOverlay(true, 'Wczytywanie zapisu EDF\u2026');\n  try {\n    const studySuffix = window.POLI_STUDY_ID ? `&study=${encodeURIComponent(window.POLI_STUDY_ID)}` : '';\n    const resp = await fetch(window.POLI_API + `\/waveform?t=${study.tStart}&dur=${study.dur}&fs=25${studySuffix}`);\n    if(!resp.ok) throw new Error('HTTP '+resp.status);\n    const json = await resp.json();\n    WAVEFORM_CACHE = json.channels;\n    WAVEFORM_CACHE._tStart = study.tStart;\n    WAVEFORM_CACHE._fs = json.fs || 25;\n    showLoadingOverlay(false);\n    return true;\n  } catch(e) {\n    console.warn('B\u0142\u0105d pobierania danych EDF:', e);\n    showLoadingOverlay(false, 'B\u0142\u0105d: '+e.message);\n    return false;\n  }\n}\n\nfunction showLoadingOverlay(show, msg){\n  let ov = document.getElementById('loading-overlay');\n  if(!ov) return;\n  ov.style.display = show ? 'flex' : 'none';\n  const m = document.getElementById('loading-msg');\n  if(m && msg) m.textContent = msg;\n}\n\n\/\/ Extract a window from cached waveform data, normalized for canvas rendering\nfunction extractWindow(winAbsStart, winDur){\n  const cache = WAVEFORM_CACHE;\n  if(!cache) return null;\n  const cacheAbsStart = cache._tStart || 0;\n  const HIGH_FS = cache._fs || 25;\n  const LOW_FS  = 1;\n\n  const t_rel = winAbsStart - cacheAbsStart;\n  const i0_h  = Math.max(0, Math.round(t_rel * HIGH_FS));\n  const n_h   = Math.round(winDur * HIGH_FS);\n  const i0_l  = Math.max(0, Math.round(t_rel * LOW_FS));\n  const n_l   = Math.round(winDur * LOW_FS);\n\n  const slH = arr => (arr||[]).slice(i0_h, i0_h + n_h);\n  const slL = arr => (arr||[]).slice(i0_l, i0_l + n_l);\n\n  \/\/ Center a signal (remove DC drift) then scale\n  function center(arr, scale){\n    if(!arr.length) return arr;\n    const mean = arr.reduce((s,v)=>s+v,0) \/ arr.length;\n    return arr.map(v => (v - mean) * scale);\n  }\n\n  const rawFlow  = slH(cache.flow);\n  const rawSnore = slH(cache.snore);\n  const rawTho   = slH(cache.tho);\n  const rawPleth = slH(cache.pleth);\n  const rawSpo2  = slL(cache.spo2);\n  const rawHr    = slL(cache.hr);\n\n  \/\/ Normalize each channel to ~[-1,1] range for drawing functions\n  \/\/ Flow Patient: physical units \u00b1100 (% max), typical signal \u00b130-50\n  const flow = rawFlow.map(v => v \/ 55);\n  \/\/ Snore: raw \u00b15 range, take absolute envelope\n  const snoring = rawSnore.map(v => Math.abs(v) \/ 4);\n  \/\/ Effort THO: has DC offset (~-28), center it and scale by oscillation range\n  const chest   = center(rawTho, 0.08);\n  \/\/ No ABD channel in Alice NightOne \u2014 replicate THO slightly phase-shifted\n  const abdomen = center(rawTho, 0.072);\n  \/\/ Pleth: \u00b180 range\n  const pulse   = rawPleth.map(v => v \/ 70);\n  \/\/ SpO2 and HR: kept in natural units (%, BPM)\n  const spo2    = rawSpo2;\n  const hr      = rawHr;\n\n  return {flow, snoring, chest, abdomen, spo2, pulse, hr};\n}\n\n\/\/ \u2500\u2500 SYNTHETIC FALLBACK (for demo canvas only) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst SR = 20; \/\/ samples\/sec for synthetic\nfunction genChannels(study, winStart, winDur){\n  const seed = study.seed||42;\n  const N=Math.round(winDur*SR);\n  const r=rng(seed+Math.floor(winStart));\n  const n=()=>(r()-0.5)*0.07;\n  const BF=2*Math.PI*0.22; const PF=2*Math.PI*1.05;\n  let spo2=97.2;\n  const ch={snoring:new Array(N),flow:new Array(N),chest:new Array(N),abdomen:new Array(N),spo2:new Array(N),pulse:new Array(N),hr:new Array(N)};\n  const evs=study.events||[];\n  for(let i=0;i<N;i++){\n    const t=winStart+i\/SR;\n    let ev=null;\n    for(const e of evs){if(t>=e.start&&t<e.start+e.duration){ev=e;break;}}\n    let spo2Target=97.2;\n    if(!ev){for(const e of evs){const re=e.start+e.duration;if(t>=re&&t<re+30){const p=(t-re)\/30;spo2Target=Math.max(97.2,90+p*7.2);break;}}}\n    const b=Math.sin(BF*t),ba=Math.abs(b),p=Math.sin(PF*t);\n    if(!ev){\n      ch.flow[i]=b*0.82+n();ch.chest[i]=ba*0.75+n();ch.abdomen[i]=ba*0.70+n();\n      ch.snoring[i]=Math.sin(t*0.07)>0.55?Math.abs(Math.sin(BF*10*t))*0.52+Math.abs(n())*0.3:Math.abs(n())*0.12;\n      spo2+=(spo2Target-spo2)*0.018;ch.spo2[i]=spo2+(r()-0.5)*0.15;ch.pulse[i]=p*0.8+n();\n    } else {\n      const pg=(t-ev.start)\/ev.duration;\n      if(ev.type==='OA'){ch.flow[i]=n()*0.08;const ea=0.65+pg*0.55;ch.chest[i]=Math.sin(BF*1.35*t)*ea*0.72+n();ch.abdomen[i]=-Math.sin(BF*1.35*t)*ea*0.65+n();ch.snoring[i]=Math.abs(n())*0.1;const dp=Math.max(0,pg-0.28);spo2+=(97.2-dp\/(1-0.28)*9-spo2)*0.028;ch.spo2[i]=spo2;ch.pulse[i]=p*0.7+n();}\n      else if(ev.type==='CA'){ch.flow[i]=n()*0.06;ch.chest[i]=n()*0.07;ch.abdomen[i]=n()*0.07;ch.snoring[i]=Math.abs(n())*0.08;if(pg>0.2)spo2+=(97.2-(pg-0.2)\/0.8*7-spo2)*0.026;ch.spo2[i]=spo2;ch.pulse[i]=p*0.65+n();}\n      else if(ev.type==='OH'){ch.flow[i]=b*0.27+n()*0.45;ch.chest[i]=ba*(1.05+pg*0.28)+n();ch.abdomen[i]=ba*(0.95+pg*0.22)+n();ch.snoring[i]=Math.abs(Math.sin(BF*8*t))*0.58+Math.abs(n())*0.18;if(pg>0.38)spo2+=(97.2-(pg-0.38)\/0.62*4.5-spo2)*0.032;ch.spo2[i]=spo2;ch.pulse[i]=p*0.82+n();}\n      else{ch.flow[i]=b*0.78+n();ch.chest[i]=ba*0.72+n();ch.abdomen[i]=ba*0.66+n();ch.snoring[i]=Math.abs(n())*0.18;spo2+=(97.2-Math.sin(pg*Math.PI)*5-spo2)*0.045;ch.spo2[i]=spo2;ch.pulse[i]=p*0.82+n();}\n    }\n  }\n  const r2=rng(seed+Math.floor(winStart)+77777);let hrSmooth=60;\n  for(let i=0;i<N;i++){const t=winStart+i\/SR;let hrTarget=60;for(const e of evs){if(t>=e.start&&t<e.start+e.duration){hrTarget=54;break;}const re=e.start+e.duration;if(t>=re&&t<re+18){hrTarget=67-(t-re)\/18*7;break;}}hrSmooth=hrSmooth*0.98+hrTarget*0.02;ch.hr[i]=hrSmooth+(r2()-0.5)*4;}\n  return ch;\n}\n\n\/\/ \u2500\u2500 CANVAS RENDERING \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst EV_COLORS={OA:'#e74c3c',CA:'#3498db',MA:'#9b59b6',OH:'#f39c12',CH:'#00b894',DESAT:'#6c5ce7',SNORE:'#2ecc71',BRADY:'#00cec9',TACHY:'#fd79a8'};\n\/\/ Event types that DON'T count toward AHI (annotations only)\nconst EV_ANNOT=new Set(['SNORE','BRADY','TACHY']);\n\/\/ Map each event type to its channel row for per-channel box rendering\nconst EV_TO_CHAN={OA:'flow',CA:'flow',MA:'flow',OH:'flow',CH:'flow',DESAT:'spo2',SNORE:'snore',BRADY:'pulse',TACHY:'pulse'};\n\nfunction roundRect(c,x,y,w,h,r){\n  r=Math.min(r,Math.max(0,w\/2),Math.max(0,h\/2));\n  c.beginPath();\n  c.moveTo(x+r,y);c.lineTo(x+w-r,y);c.quadraticCurveTo(x+w,y,x+w,y+r);\n  c.lineTo(x+w,y+h-r);c.quadraticCurveTo(x+w,y+h,x+w-r,y+h);\n  c.lineTo(x+r,y+h);c.quadraticCurveTo(x,y+h,x,y+h-r);\n  c.lineTo(x,y+r);c.quadraticCurveTo(x,y,x+r,y);\n  c.closePath();\n}\nconst CHDEFS=[\n  {name:'Chrapanie',key:'snoring',color:'#2ecc71',frac:0.09,type:'snore'},\n  {name:'Przep\u0142yw',key:'flow',color:'#00d4aa',frac:0.16,type:'flow'},\n  {name:'Wysi\u0142ek kl.',key:'chest',color:'#f39c12',frac:0.16,type:'effort'},\n  {name:'Wysi\u0142ek brz.',key:'abdomen',color:'#e08c20',frac:0.16,type:'effort'},\n  {name:'SpO\u2082 %',key:'spo2',color:'#6c5ce7',frac:0.29,type:'spo2'},\n  {name:'T\u0119tno',key:'pulse',color:'#e74c3c',frac:0.14,type:'pulse'},\n];\n\nfunction drawRec(cid,study,winStart,winDur,marked,master,showMaster){\n  const cv=document.getElementById(cid);if(!cv)return;\n  const dpr=window.devicePixelRatio||1;\n  const W=cv.parentElement.clientWidth||800;\n  const H=400,BTMPAD=20,LP=82,PW=W-LP-4;\n  cv.width=W*dpr;cv.height=H*dpr;cv.style.width=W+'px';cv.style.height=H+'px';\n  const c=cv.getContext('2d');c.scale(dpr,dpr);\n  c.fillStyle='#0a0e14';c.fillRect(0,0,W,H);\n  const PH=H-BTMPAD;\n  \/\/ Use real EDF data if available, else synthetic fallback (demo only)\n  const winAbsStart = (study.tStart !== undefined) ? study.tStart + winStart : winStart;\n  const cdata = (WAVEFORM_CACHE && study.tStart !== undefined)\n    ? extractWindow(winAbsStart, winDur)\n    : genChannels(study, winStart, winDur);\n  let yc=0;\n  CHDEFS.forEach((ch,ci)=>{\n    const chH=Math.round(ch.frac*PH);\n    const yT=yc;yc+=chH;\n    c.fillStyle=ci%2===0?'#0d1218':'#0a0e14';\n    c.fillRect(LP,yT,PW,chH);\n    c.fillStyle=ch.color;c.font='bold 9px monospace';c.textAlign='right';\n    c.fillText(ch.name,LP-3,yT+chH\/2+3);\n    c.strokeStyle='#1a2535';c.lineWidth=1;\n    c.beginPath();c.moveTo(LP,yT+chH);c.lineTo(W,yT+chH);c.stroke();\n    const d=cdata[ch.key];\n    if(!d||d.length===0)return;\n    if(ch.type==='spo2') drawSpo2(c,d,ch,yT,chH,LP,PW);\n    else if(ch.type==='effort') drawEffort(c,d,ch,yT,chH,LP,PW);\n    else if(ch.type==='snore') drawSnore(c,d,ch,yT,chH,LP,PW);\n    else if(ch.type==='flow') drawFlow(c,d,ch,yT,chH,LP,PW);\n    else if(ch.type==='pulse') drawPulse(c,d,cdata.hr,ch,yT,chH,LP,PW,winStart,winDur);\n    else drawStd(c,d,ch,yT,chH,LP,PW);\n  });\n  \/\/ time axis\n  c.fillStyle='#111b28';c.fillRect(LP,PH,PW,BTMPAD);\n  let tick=winDur<=60?5:winDur<=180?15:winDur<=600?60:300;\n  const ft=Math.ceil(winStart\/tick)*tick;\n  for(let t=ft;t<=winStart+winDur;t+=tick){\n    const x=LP+((t-winStart)\/winDur)*PW;\n    const mn=Math.floor(t\/60),sc=Math.round(t%60);\n    c.strokeStyle='#253545';c.lineWidth=1;c.beginPath();c.moveTo(x,0);c.lineTo(x,PH);c.stroke();\n    c.fillStyle='#6a7a8a';c.font='9px monospace';c.textAlign='center';\n    c.fillText(`${mn}:${String(sc).padStart(2,'0')}`,x,H-4);\n  }\n  \/\/ event overlays\n  if(showMaster&&master) drawEvs(c,master,winStart,winDur,LP,PW,PH,0.13,true);\n  drawEvs(c,marked,winStart,winDur,LP,PW,PH,0.32,false);\n  \/\/ Ghost preview for pending two-click marking\n  if(S.markStart!==null){\n    const col=EV_COLORS[S.evType]||'#f39c12';\n    const tS=S.markStart,tC=S.markCursor||tS;\n    const t0=Math.min(tS,tC),t1=Math.max(tS,tC);\n    \/\/ Ghost rectangle\n    if(t1>t0&&t1>=winStart&&t0<=winStart+winDur){\n      const gx1=LP+Math.max(0,(t0-winStart)\/winDur)*PW;\n      const gx2=LP+Math.min(1,(t1-winStart)\/winDur)*PW;\n      c.fillStyle=col+'40';c.fillRect(gx1,0,gx2-gx1,PH);\n      c.strokeStyle=col;c.lineWidth=2;c.setLineDash([6,4]);\n      c.strokeRect(gx1,0,gx2-gx1,PH);c.setLineDash([]);\n      \/\/ Duration label\n      const dur=t1-t0;\n      const lx=(gx1+gx2)\/2;\n      c.fillStyle=col;c.font='bold 12px sans-serif';c.textAlign='center';\n      c.fillText(`${dur.toFixed(1)}s`,lx,PH\/2);\n    }\n    \/\/ Start marker vertical line\n    if(tS>=winStart&&tS<=winStart+winDur){\n      const sx=LP+((tS-winStart)\/winDur)*PW;\n      c.strokeStyle=col;c.lineWidth=2.5;c.setLineDash([]);\n      c.beginPath();c.moveTo(sx,0);c.lineTo(sx,PH);c.stroke();\n      c.fillStyle=col;c.font='bold 10px sans-serif';c.textAlign='center';\n      c.fillText('\u25bc START',sx,PH-8);\n    }\n    \/\/ Cursor position line\n    if(tC!==tS&&tC>=winStart&&tC<=winStart+winDur){\n      const cx2=LP+((tC-winStart)\/winDur)*PW;\n      c.strokeStyle=col+'aa';c.lineWidth=1.5;c.setLineDash([3,4]);\n      c.beginPath();c.moveTo(cx2,0);c.lineTo(cx2,PH);c.stroke();c.setLineDash([]);\n      c.fillStyle=col;c.font='10px sans-serif';c.textAlign='center';\n      c.fillText('\u25b2 KONIEC',cx2,12);\n    }\n  }\n}\n\nfunction drawSpo2(c,d,ch,yT,chH,LP,PW){\n  const SMIN=84,SMAX=100,SR2=SMAX-SMIN;\n  [84,88,90,92,95,98,100].forEach(v=>{\n    const y=yT+chH-((v-SMIN)\/SR2)*chH;\n    const is90=v===90;\n    c.strokeStyle=is90?'rgba(231,76,60,0.55)':'rgba(40,60,80,0.45)';\n    c.lineWidth=is90?1.5:0.5;c.setLineDash(is90?[5,4]:[2,5]);\n    c.beginPath();c.moveTo(LP,y);c.lineTo(LP+PW,y);c.stroke();c.setLineDash([]);\n    if([84,90,95,100].includes(v)){\n      c.fillStyle=is90?'#e74c3c':'#556070';c.font='8px monospace';c.textAlign='right';\n      c.fillText(v+'%',LP-3,y+3);\n    }\n  });\n  \/\/ Colored waveform: purple\u226595, orange\u226590, red<90\n  for(let i=1;i<d.length;i++){\n    const x1=LP+((i-1)\/d.length)*PW,x2=LP+(i\/d.length)*PW;\n    const v1=Math.max(SMIN,Math.min(SMAX,d[i-1])),v2=Math.max(SMIN,Math.min(SMAX,d[i]));\n    const y1=yT+chH-((v1-SMIN)\/SR2)*chH,y2=yT+chH-((v2-SMIN)\/SR2)*chH;\n    const avg=(v1+v2)\/2;\n    c.strokeStyle=avg>=95?'#6c5ce7':avg>=90?'#f39c12':'#e74c3c';\n    c.lineWidth=2;c.beginPath();c.moveTo(x1,y1);c.lineTo(x2,y2);c.stroke();\n  }\n  \/\/ fill\n  c.beginPath();\n  d.forEach((v,i)=>{const x=LP+(i\/d.length)*PW,y=yT+chH-((Math.max(SMIN,Math.min(SMAX,v))-SMIN)\/SR2)*chH;i===0?c.moveTo(x,y):c.lineTo(x,y);});\n  c.lineTo(LP+PW,yT+chH);c.lineTo(LP,yT+chH);c.closePath();\n  const g=c.createLinearGradient(0,yT,0,yT+chH);g.addColorStop(0,'rgba(108,92,231,.22)');g.addColorStop(1,'rgba(108,92,231,.04)');\n  c.fillStyle=g;c.fill();\n  \/\/ Numeric SpO\u2082 labels at regular intervals (SleepWare style)\n  const winDur_=S.winDur||300,winStart_=S.offset||0;\n  const step_=winDur_<=60?10:winDur_<=180?15:30;\n  const SR__=d.length\/winDur_;\n  for(let t=Math.ceil((winStart_+0.01)\/step_)*step_;t<=winStart_+winDur_-1;t+=step_){\n    const idx=Math.min(d.length-1,Math.round((t-winStart_)*SR__));\n    if(idx<0)continue;\n    const v=Math.max(SMIN,Math.min(SMAX,d[idx]));\n    const x=LP+((t-winStart_)\/winDur_)*PW;\n    const y=yT+chH-((v-SMIN)\/SR2)*chH;\n    const col=v>=95?'#6c5ce7':v>=90?'#f39c12':'#e74c3c';\n    \/\/ Dark pill\n    c.fillStyle='rgba(10,14,20,0.85)';\n    c.fillRect(x-12,y-15,24,14);\n    c.fillStyle=col;c.font='bold 10px monospace';c.textAlign='center';\n    c.fillText(Math.round(v),x,y-4);\n  }\n  \/\/ Current value at right edge\n  const lv=d[d.length-1]||97;\n  const ly=yT+chH-((Math.max(SMIN,Math.min(SMAX,lv))-SMIN)\/SR2)*chH;\n  c.fillStyle=lv>=95?'#6c5ce7':lv>=90?'#f39c12':'#e74c3c';\n  c.font='bold 11px monospace';c.textAlign='left';\n  c.fillText(Math.round(lv)+'%',LP+PW+3,ly+4);\n}\n\nfunction drawEffort(c,d,ch,yT,chH,LP,PW){\n  const yM=yT+chH\/2,amp=chH*0.44;\n  \/\/ fill\n  c.beginPath();\n  d.forEach((v,i)=>{const x=LP+(i\/d.length)*PW,y=yM-v*amp;i===0?c.moveTo(x,y):c.lineTo(x,y);});\n  c.lineTo(LP+PW,yM);c.lineTo(LP,yM);c.closePath();\n  const g=c.createLinearGradient(0,yT,0,yT+chH);\n  g.addColorStop(0,ch.color+'55');g.addColorStop(0.5,ch.color+'22');g.addColorStop(1,ch.color+'08');\n  c.fillStyle=g;c.fill();\n  \/\/ baseline\n  c.strokeStyle=ch.color+'35';c.lineWidth=0.5;c.setLineDash([3,5]);\n  c.beginPath();c.moveTo(LP,yM);c.lineTo(LP+PW,yM);c.stroke();c.setLineDash([]);\n  \/\/ line\n  c.beginPath();c.strokeStyle=ch.color;c.lineWidth=2.5;c.lineJoin='round';\n  d.forEach((v,i)=>{const x=LP+(i\/d.length)*PW,y=yM-v*amp;i===0?c.moveTo(x,y):c.lineTo(x,y);});\n  c.stroke();\n}\n\nfunction drawSnore(c,d,ch,yT,chH,LP,PW){\n  const yB=yT+chH-3,maxH=chH-6;\n  const bw=Math.max(1,PW\/d.length);\n  d.forEach((v,i)=>{\n    const x=LP+(i\/d.length)*PW,h=Math.max(0,Math.min(1,v))*maxH;\n    c.fillStyle=ch.color+'99';c.fillRect(x,yB-h,bw,h);\n  });\n  c.beginPath();c.strokeStyle=ch.color;c.lineWidth=1;\n  d.forEach((v,i)=>{const x=LP+(i\/d.length)*PW,y=yB-Math.max(0,Math.min(1,v))*maxH;i===0?c.moveTo(x,y):c.lineTo(x,y);});\n  c.stroke();\n}\n\nfunction drawFlow(c,d,ch,yT,chH,LP,PW){\n  const yM=yT+chH\/2,amp=chH*0.42;\n  c.strokeStyle='#253545';c.lineWidth=0.5;c.setLineDash([3,6]);\n  c.beginPath();c.moveTo(LP,yM);c.lineTo(LP+PW,yM);c.stroke();c.setLineDash([]);\n  c.beginPath();c.strokeStyle=ch.color;c.lineWidth=2;\n  d.forEach((v,i)=>{const x=LP+(i\/d.length)*PW,y=yM-v*amp;i===0?c.moveTo(x,y):c.lineTo(x,y);});\n  c.stroke();\n}\n\nfunction drawStd(c,d,ch,yT,chH,LP,PW){\n  const yM=yT+chH\/2,amp=chH*0.38;\n  c.beginPath();c.strokeStyle=ch.color;c.lineWidth=1.5;\n  d.forEach((v,i)=>{const x=LP+(i\/d.length)*PW,y=yM-v*amp;i===0?c.moveTo(x,y):c.lineTo(x,y);});\n  c.stroke();\n}\n\nfunction drawPulse(c,pleth,hr,ch,yT,chH,LP,PW,winStart,winDur){\n  \/\/ Plethysmography waveform\n  const yM=yT+chH\/2,amp=chH*0.36;\n  c.beginPath();c.strokeStyle=ch.color+'cc';c.lineWidth=1.2;\n  pleth.forEach((v,i)=>{const x=LP+(i\/pleth.length)*PW,y=yM-v*amp;i===0?c.moveTo(x,y):c.lineTo(x,y);});\n  c.stroke();\n  \/\/ HR numeric labels (SleepWare style)\n  if(!hr||!hr.length)return;\n  const step=winDur<=60?10:winDur<=180?15:30;\n  const SR_=hr.length\/winDur;\n  for(let t=Math.ceil((winStart+0.01)\/step)*step;t<=winStart+winDur-1;t+=step){\n    const idx=Math.min(hr.length-1,Math.round((t-winStart)*SR_));\n    if(idx<0)continue;\n    const hrVal=Math.round(hr[idx]);\n    const x=LP+((t-winStart)\/winDur)*PW;\n    \/\/ Dark pill background\n    c.fillStyle='rgba(10,14,20,0.8)';\n    c.fillRect(x-11,yT+3,22,15);\n    c.fillStyle=ch.color;c.font='bold 10px monospace';c.textAlign='center';\n    c.fillText(hrVal,x,yT+15);\n  }\n}\n\nfunction drawEvs(c,evs,winStart,winDur,LP,PW,PH,alpha,isMaster){\n  \/\/ Build channel position map\n  const chPos={};let yc=0;\n  CHDEFS.forEach(cd=>{const chH=Math.round(cd.frac*PH);chPos[cd.type]={yT:yc,chH};yc+=chH;});\n\n  evs.forEach(ev=>{\n    if(ev.start+ev.duration<winStart||ev.start>winStart+winDur)return;\n    const x1=LP+Math.max(0,(ev.start-winStart)\/winDur)*PW;\n    const x2=LP+Math.min(1,(ev.start+ev.duration-winStart)\/winDur)*PW;\n    const w=Math.max(2,x2-x1);\n    const col=EV_COLORS[ev.type]||'#aaa';\n    const chanType=EV_TO_CHAN[ev.type]||'flow';\n    const cp=chPos[chanType]||{yT:0,chH:60};\n    const {yT,chH}=cp;\n\n    if(isMaster){\n      \/\/ Master: dashed outline in the correct channel row\n      c.strokeStyle=col+'cc';c.lineWidth=1.5;c.setLineDash([5,4]);\n      c.strokeRect(x1,yT+2,w,chH-4);c.setLineDash([]);\n      if(w>22){c.fillStyle=col+'cc';c.font='bold 8px sans-serif';c.textAlign='center';c.fillText(ev.type,(x1+x2)\/2,yT+13);}\n    } else {\n      \/\/ Student: SleepWare-style labeled pill in the relevant channel row\n      \/\/ 1. Subtle tint across channel height\n      c.fillStyle=col+'1a';c.fillRect(x1,yT,w,chH);\n      \/\/ 2. Thin vertical start\/end lines\n      c.strokeStyle=col+'77';c.lineWidth=1;\n      c.beginPath();c.moveTo(x1,yT);c.lineTo(x1,yT+chH);c.stroke();\n      c.beginPath();c.moveTo(x2,yT);c.lineTo(x2,yT+chH);c.stroke();\n      \/\/ 3. Colored pill box at top of channel\n      const bH=Math.min(20,Math.round(chH*0.52));\n      const bY=yT+3;\n      const bW=Math.max(1,w-1);\n      c.fillStyle=col;\n      roundRect(c,x1+0.5,bY,bW,bH,4);c.fill();\n      \/\/ 4. Highlight sheen\n      const hg=c.createLinearGradient(0,bY,0,bY+bH\/2);\n      hg.addColorStop(0,'rgba(255,255,255,0.2)');hg.addColorStop(1,'rgba(255,255,255,0)');\n      c.fillStyle=hg;roundRect(c,x1+0.5,bY,bW,bH,4);c.fill();\n      \/\/ 5. Type + duration label\n      if(w>14){\n        c.fillStyle='#fff';c.textAlign='center';const cx=(x1+x2)\/2;\n        if(w>72){\n          c.font='bold 9px sans-serif';c.fillText(ev.type,cx,bY+8);\n          c.font='8px sans-serif';c.fillText(ev.duration+'s',cx,bY+17);\n        } else if(w>36){\n          c.font='bold 9px sans-serif';c.fillText(ev.type,cx,bY+bH\/2+3);\n        } else if(w>18){\n          c.font='bold 8px sans-serif';c.fillText(ev.type.slice(0,2),cx,bY+bH\/2+3);\n        }\n      }\n    }\n  });\n}\n\n\/\/ \u2500\u2500 WINDOW SIZE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst WIN_HINTS={30:'30s \u2013 szczeg\u00f3\u0142owa morfologia: kszta\u0142t bezdechu, weryfikacja kryteri\u00f3w AASM \u226510s',120:'2 min \u2013 analiza 1\u20132 zdarze\u0144 z widokiem desaturacji i powrotu SpO\u2082',300:'5 min \u2013 standardowe okno scoringu: kilka zdarze\u0144 w kontek\u015bcie, wzorzec SpO\u2082',600:'10 min \u2013 przegl\u0105d wzorc\u00f3w: klasteryzacja, zale\u017cno\u015b\u0107 pozycyjna, trendy SpO\u2082'};\nfunction setWin(dur,btn){\n  S.winDur=dur;\n  document.querySelectorAll('.win-btn').forEach(b=>b.classList.remove('active'));\n  btn.classList.add('active');\n  const h=document.getElementById('win-hint');if(h)h.textContent=WIN_HINTS[dur]||'';\n  renderScoring();\n}\n\nfunction showSection(name){\n  document.querySelectorAll('.section').forEach(s=>s.classList.remove('active'));\n  document.querySelectorAll('nav button').forEach(b=>b.classList.remove('active'));\n  document.getElementById('sec-'+name).classList.add('active');\n  const nb=document.getElementById('nav-'+name);if(nb)nb.classList.add('active');\n  if(name==='teoria'){\n    if(!document.getElementById('ch1'))initChapters();\n    showCh(S.chapter||1);\n  }\n  if(name==='praktyka')renderStudyList();\n  if(name==='cert')checkCert();\n  window.scrollTo(0,0);\n}\nfunction showCh(n){\n  document.querySelectorAll('.chapter').forEach(c=>c.style.display='none');\n  const ch=document.getElementById('ch'+n);if(ch)ch.style.display='block';\n  S.chapter=n;S.theoryDone.add(n);\n  const pct=Math.round(S.theoryDone.size\/S.maxChapter*100);\n  document.getElementById('th-prog').style.width=pct+'%';\n  document.getElementById('th-pct').textContent=pct+'%';\n  if(n===10)initDemo();\n}\nfunction nextCh(n){if(n<S.maxChapter)showCh(n+1);}\nfunction prevCh(n){if(n>1)showCh(n-1);}\n\n\/\/ \u2500\u2500 DEMO RECORDING (CH 9) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\/\/ \u2500\u2500 PRACTICE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction stage1Done(){return STUDIES.filter(s=>s.stage===1&&S.completed[s.id]).length>=3;}\n\nfunction renderStudyList(){\n  const done=stage1Done();\n  const allStg2Done=STUDIES.filter(s=>s.stage===2).every(s=>S.completed[s.id]);\n  const s1=STUDIES.filter(s=>s.stage===1),s2=STUDIES.filter(s=>s.stage===2),s3=STUDIES.filter(s=>s.stage===3);\n  document.getElementById('s1s').textContent=`${s1.filter(s=>S.completed[s.id]).length}\/3 uko\u0144czone`;\n  document.getElementById('s2s').textContent=TRIAL_MODE?'Zablokowane w wersji pr\u00f3bnej':(done?`${s2.filter(s=>S.completed[s.id]).length}\/9 uko\u0144czone`:'Odblokowane po etapie 1');\n  document.getElementById('si1').className='info-box '+(done?'green':'blue');\n  document.getElementById('si2').className='info-box '+(done?'blue':'gray');\n\n  \/\/ Stage 1 & 2 display: in trial mode only stage 1 is active.\n  const stg=(done && !TRIAL_MODE)?2:1;\n  const show=STUDIES.filter(s=>s.stage===stg);\n  const list=document.getElementById('studies-list');list.innerHTML='';\n  if(done && TRIAL_MODE){\n    list.innerHTML='<div class=\"alert success\"><span class=\"icon\">\u2705<\/span><div><strong>Etap pr\u00f3bny uko\u0144czony.<\/strong><br>Etap 2 pozostaje zablokowany w tej wersji. Najpierw sprawdzamy dzia\u0142anie trzech fragment\u00f3w treningowych i zgodno\u015b\u0107 z master scoringiem.<\/div><\/div>';\n  }\n  show.forEach(study=>{\n    const c=S.completed[study.id],acc=c?Math.round(c.acc*100):null;\n    const div=document.createElement('div');div.className='module-item';\n    const dlab={easy:'\ud83d\udfe2 \u0142atwe',medium:'\ud83d\udfe1 \u015brednie',hard:'\ud83d\udd34 trudne'};\n    const mCount=getMasterForStudy(study).filter(e=>!EV_ANNOT.has(e.type)).length;\n    const durLabel=study.dur<7200?Math.round(study.dur\/60)+' min':Math.round(study.dur\/360)\/10+'h';\n    div.innerHTML=`<div class=\"icon\">${c?(acc>=70?'\u2705':'\u26a0\ufe0f'):'\ud83d\udccb'}<\/div><div class=\"info\"><div class=\"title\">${study.label}<\/div><div class=\"sub\">${durLabel} \u00b7 ${dlab[study.diff]||study.diff}${mCount?' \u00b7 '+mCount+' zd. (master)':''}<\/div><\/div>${c?`<span class=\"badge ${acc>=70?'pr':''}\">Wynik: ${acc}%<\/span>`:'<span class=\"badge\">Do zrobienia<\/span>'}`;\n    div.onclick=()=>startStudy(study.id);list.appendChild(div);\n  });\n\n  \/\/ Stage 3: case reports \u2013 show only when stage 2 done (or always if we want them accessible)\n  const crSection=document.getElementById('case-reports-section');\n  if(crSection){\n    crSection.style.display=(done&&!TRIAL_MODE)?'block':'none';\n    const crList=document.getElementById('cr-list');crList.innerHTML='';\n    s3.forEach(study=>{\n      const ci=CASE_INFO[study.id];\n      const c=S.completed[study.id],acc=c?Math.round(c.acc*100):null;\n      const div=document.createElement('div');div.className='module-item';\n      div.style.cssText='border-left:4px solid '+(ci?ci.borderColor:'var(--p)')+';background:'+(ci?ci.color+'88':'');\n      const dlab={easy:'\ud83d\udfe2 \u0142atwy',medium:'\ud83d\udfe1 \u015bredni',hard:'\ud83d\udd34 trudny'};\n      div.innerHTML=`<div class=\"icon\" style=\"font-size:1.6rem\">${c?(acc>=70?'\u2705':'\u26a0\ufe0f'):'\ud83e\ude7a'}<\/div><div class=\"info\"><div class=\"title\">${study.label}<\/div><div class=\"sub\">${ci?ci.badge+' \u00b7 ':''} 30 min \u00b7 ${dlab[study.diff]||study.diff}<\/div><\/div>${c?`<span class=\"badge ${acc>=70?'pr':''}\">Wynik: ${acc}%<\/span>`:'<span class=\"badge\">Przypadek kliniczny<\/span>'}`;\n      div.onclick=()=>startStudy(study.id);crList.appendChild(div);\n    });\n  }\n\n  const tot=STUDIES.filter(s=>S.completed[s.id]).length;\n  const total=STUDIES.length;\n  document.getElementById('pr-prog').style.width=Math.round(tot\/total*100)+'%';\n  document.getElementById('pr-pct').textContent=tot+'\/'+total;\n}\n\nfunction startStudy(id){\n  const study=STUDIES.find(s=>s.id===id);if(!study)return;\n  \/\/ Stage 3: show patient card before scoring\n  if(study.stage===3 && CASE_INFO[id]){showCaseInfo(id);return;}\n  _startScoringView(study);\n}\n\nfunction showCaseInfo(id){\n  const study=STUDIES.find(s=>s.id===id);if(!study)return;\n  const ci=CASE_INFO[id];\n  document.getElementById('study-selector').style.display='none';\n  document.getElementById('scoring-interface').style.display='none';\n  document.getElementById('compare-results').style.display='none';\n  let casePanel=document.getElementById('case-info-panel');\n  if(!casePanel){\n    casePanel=document.createElement('div');casePanel.id='case-info-panel';\n    const ref=document.getElementById('study-selector');\n    ref.parentNode.insertBefore(casePanel,ref.nextSibling);\n  }\n  casePanel.style.display='block';\n  const statsHtml=ci.stats.map(([k,v])=>`<div class=\"ci-stat\"><div class=\"ci-key\">${k}<\/div><div class=\"ci-val\">${v}<\/div><\/div>`).join('');\n  casePanel.innerHTML=`\n<div class=\"card\" style=\"border-top:4px solid ${ci.borderColor};background:${ci.color}22\">\n  <div style=\"display:flex;align-items:center;gap:12px;margin-bottom:14px\">\n    <span style=\"font-size:2rem\">\ud83e\ude7a<\/span>\n    <div>\n      <h2 style=\"margin:0;border:none\">${ci.title}<\/h2>\n      <span style=\"background:${ci.borderColor};color:#fff;padding:3px 10px;border-radius:20px;font-size:.8rem;font-weight:700\">${ci.badge}<\/span>\n    <\/div>\n  <\/div>\n  <div class=\"ci-stats-grid\">${statsHtml}<\/div>\n  <h3 style=\"margin-top:18px\">\ud83d\udccb Objawy i wywiad<\/h3>\n  <p>${ci.symptoms}<\/p>\n  <h3>\ud83c\udfe5 Historia medyczna<\/h3>\n  <p>${ci.history}<\/p>\n  <div class=\"alert warn\" style=\"margin-top:16px\"><span class=\"icon\">\u2753<\/span><div><strong>Pytanie kliniczne:<\/strong><br>${ci.question}<\/div><\/div>\n  <div style=\"display:flex;gap:10px;justify-content:flex-end;margin-top:20px;flex-wrap:wrap\">\n    <button class=\"btn btn-outline\" onclick=\"hideCaseInfo()\">\u2190 Wr\u00f3\u0107 do listy<\/button>\n    <button class=\"btn btn-primary btn-lg\" onclick=\"hideCaseInfo();_startScoringView(STUDIES.find(s=>s.id==='${id}'))\">\ud83d\udcdd Rozpocznij scoring \u2192<\/button>\n  <\/div>\n<\/div>`;\n}\n\nfunction hideCaseInfo(){\n  const p=document.getElementById('case-info-panel');if(p)p.style.display='none';\n  document.getElementById('study-selector').style.display='block';\n  renderStudyList();\n}\n\nfunction showCaseResult(id){\n  const ci=CASE_INFO[id];if(!ci)return;\n  const panel=document.getElementById('case-result-panel');\n  if(!panel)return;\n  panel.style.display='block';\n  panel.innerHTML=`\n<div class=\"card\" style=\"border-top:4px solid ${ci.borderColor};background:${ci.color}22;margin-top:16px\">\n  <h3>\ud83e\ude7a Interpretacja kliniczna \u2013 ${ci.title}<\/h3>\n  <div class=\"alert success\"><span class=\"icon\">\u2705<\/span><div>${ci.expected}<\/div><\/div>\n<\/div>`;\n}\n\nasync function _startScoringView(study){\n  S.study=study;S.marked=[];S.offset=0;S.elapsed=0;S.winDur=300;\n  S.markStart=null;S.markCursor=null;setMarkPending(false);\n  WAVEFORM_CACHE=null; \/\/ clear previous study cache\n  const cip=document.getElementById('case-info-panel');if(cip)cip.style.display='none';\n  document.getElementById('study-selector').style.display='none';\n  document.getElementById('scoring-interface').style.display='block';\n  document.getElementById('compare-results').style.display='none';\n  document.getElementById('hint-panel').style.display='none';\n  document.getElementById('study-title').textContent=study.label;\n  const dlab={easy:'\ud83d\udfe2 \u0141atwe',medium:'\ud83d\udfe1 \u015arednie',hard:'\ud83d\udd34 Trudne'};\n  document.getElementById('study-badge').textContent=study.stage===1?'\ud83c\udfcb Trening':study.stage===3?'\ud83e\ude7a Przypadek':dlab[study.diff];\n  document.getElementById('hint-btn').style.display=study.stage===1?'inline-flex':'none';\n  if(S.timer)clearInterval(S.timer);\n  S.timer=setInterval(()=>{S.elapsed++;const m=Math.floor(S.elapsed\/60).toString().padStart(2,'0'),s=(S.elapsed%60).toString().padStart(2,'0');const el=document.getElementById('sc-timer');if(el)el.textContent=`\u23f1 ${m}:${s}`;},1000);\n  document.querySelectorAll('.win-btn').forEach(b=>{b.classList.toggle('active',b.textContent.trim()==='5 min');});\n  \/\/ Fetch real EDF data from server\n  await fetchStudyData(study);\n  renderScoring();updateStats();\n}\n\nfunction renderScoring(){\n  const study=S.study;if(!study)return;\n  const total=study.dur;\n  drawRec('recCanvas',study,S.offset,S.winDur,S.marked,[],false);\n  const pct=S.offset\/Math.max(1,total-S.winDur);\n  const tf=document.getElementById('tl-fill');if(tf)tf.style.width=(pct*100)+'%';\n  const mn=Math.floor(S.offset\/60),sc=Math.round(S.offset%60);\n  const tm=Math.floor(total\/60),ts=Math.round(total%60);\n  const disp=document.getElementById('td-display');\n  if(disp)disp.textContent=`${mn}:${String(sc).padStart(2,'0')} \/ ${String(tm).padStart(2,'0')}:${String(ts).padStart(2,'0')}`;\n}\n\nfunction scrollRec(dir){\n  if(!S.study)return;\n  const total=S.study.dur;\n  S.offset=Math.max(0,Math.min(total-S.winDur,S.offset+dir*S.winDur*0.75));\n  renderScoring();\n}\n\nfunction seekTl(e){\n  const bar=document.getElementById('tl-bar'),rect=bar.getBoundingClientRect();\n  const f=Math.max(0,Math.min(1,(e.clientX-rect.left)\/rect.width));\n  const total=S.study.dur;\n  S.offset=Math.round(f*(total-S.winDur));renderScoring();\n}\n\n\/\/ \u2500\u2500 TWO-CLICK MARKING \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction getCanvasTime(e){\n  const cv=document.getElementById('recCanvas'),rect=cv.getBoundingClientRect();\n  const x=e.clientX-rect.left,W=rect.width,LP=82,PW=W-LP-4;\n  if(x<LP)return null;\n  return S.offset+(x-LP)\/PW*S.winDur;\n}\n\nfunction setMarkPending(state){\n  const btn=document.getElementById('cancel-mark-btn');\n  const tip=document.getElementById('mark-instruction');\n  if(btn)btn.style.display=state?'inline-block':'none';\n  if(tip)tip.style.display=state?'block':'none';\n  \/\/ Show floating toast\n  let toast=document.getElementById('mark-toast');\n  if(state){\n    if(!toast){toast=document.createElement('div');toast.id='mark-toast';toast.className='mark-pending-tip';document.body.appendChild(toast);}\n    const col=EV_COLORS[S.evType]||'#f39c12';\n    toast.style.background=col;\n    toast.textContent=`\ud83d\udccd Oznaczasz: ${S.evType} \u2014 kliknij KONIEC zdarzenia`;\n    toast.style.display='block';\n  } else {\n    if(toast)toast.style.display='none';\n  }\n}\n\nfunction cancelMark(){S.markStart=null;S.markCursor=null;setMarkPending(false);renderScoring();}\n\ndocument.addEventListener('click',e=>{\n  if(e.target.id!=='recCanvas')return;\n  const tClick=getCanvasTime(e);\n  if(tClick===null)return;\n\n  \/\/ Click on existing event when idle = DELETE it\n  if(S.markStart===null){\n    const idx=S.marked.findIndex(ev=>tClick>=ev.start&&tClick<=ev.start+ev.duration);\n    if(idx>=0){S.marked.splice(idx,1);updateStats();renderScoring();return;}\n  }\n\n  if(S.markStart===null){\n    \/\/ FIRST click \u2014 set start\n    S.markStart=tClick;\n    S.markCursor=tClick;\n    setMarkPending(true);\n    renderScoring();\n  } else {\n    \/\/ SECOND click \u2014 create event\n    const t0=Math.min(S.markStart,tClick);\n    const t1=Math.max(S.markStart,tClick);\n    const dur=t1-t0;\n    \/\/ Minimum durations: respiratory events \u226510s, annotations \u22653s\n    const minDur=EV_ANNOT.has(S.evType)?3:10;\n    if(dur<minDur){\n      const h=document.getElementById('hint-panel'),t=document.getElementById('hint-txt');\n      if(h&&t){t.innerHTML=`<strong>\u26a0\ufe0f Za kr\u00f3tkie zdarzenie (${dur.toFixed(1)}s).<\/strong><br>${EV_ANNOT.has(S.evType)?'Adnotacje musz\u0105 trwa\u0107 \u22653s.':'Zdarzenia oddechowe musz\u0105 trwa\u0107 <strong>\u226510 sekund<\/strong> wg AASM.'}`;h.style.display='flex';}\n      S.markStart=null;S.markCursor=null;setMarkPending(false);renderScoring();return;\n    }\n    \/\/ DESAT can't overlap a respiratory event\n    if(S.evType==='DESAT'){\n      const clash=S.marked.find(ev=>!EV_ANNOT.has(ev.type)&&ev.type!=='DESAT'&&ev.start<t1&&ev.start+ev.duration>t0);\n      if(clash){\n        const h=document.getElementById('hint-panel'),t=document.getElementById('hint-txt');\n        if(h&&t){t.innerHTML='<strong>\u26a0\ufe0f Desaturacja nak\u0142ada si\u0119 na zdarzenie oddechowe.<\/strong><br>Wg AASM desaturacja jest <em>kryterium<\/em> sp\u0142ycenia (OH\/CH) \u2013 nie scorujemy jej oddzielnie w miejscu istniej\u0105cego bezdechu\/sp\u0142ycenia.';h.style.display='flex';}\n        S.markStart=null;S.markCursor=null;setMarkPending(false);renderScoring();return;\n      }\n    }\n    S.marked.push({type:S.evType,start:Math.round(t0),duration:Math.round(dur)});\n    S.markStart=null;S.markCursor=null;setMarkPending(false);\n    updateStats();renderScoring();\n  }\n});\n\n\/\/ Ghost preview on mousemove\ndocument.addEventListener('mousemove',e=>{\n  if(e.target.id!=='recCanvas'||S.markStart===null)return;\n  const t=getCanvasTime(e);if(t===null)return;\n  S.markCursor=t;renderScoring();\n});\n\nfunction selEv(t,btn){\n  \/\/ Switching type cancels any pending start mark\n  if(S.markStart!==null){S.markStart=null;S.markCursor=null;setMarkPending(false);}\n  S.evType=t;\n  document.querySelectorAll('.ev-btn').forEach(b=>b.classList.remove('active'));btn.classList.add('active');\n  renderScoring();\n}\nfunction undoEv(){\n  if(S.markStart!==null){cancelMark();return;}\n  S.marked.pop();updateStats();renderScoring();\n}\nfunction clearEvs(){if(confirm('Wyczy\u015bci\u0107 wszystkie oznaczenia?')){S.markStart=null;S.markCursor=null;setMarkPending(false);S.marked=[];updateStats();renderScoring();}}\n\nfunction updateStats(){\n  const cnt=t=>S.marked.filter(e=>e.type===t).length;\n  ['OA','CA','MA','OH','CH'].forEach(t=>{const el=document.getElementById('ss-'+t.toLowerCase());if(el)el.textContent=cnt(t);});\n  const dsEl=document.getElementById('ss-ds');if(dsEl)dsEl.textContent=cnt('DESAT');\n  \/\/ Annotation counts\n  const snEl=document.getElementById('ss-snore');if(snEl)snEl.textContent=cnt('SNORE');\n  const brEl=document.getElementById('ss-brady');if(brEl)brEl.textContent=cnt('BRADY');\n  const taEl=document.getElementById('ss-tachy');if(taEl)taEl.textContent=cnt('TACHY');\n  const st=S.study;if(!st)return;\n  const dh=st.dur\/3600;\n  const ahi=(cnt('OA')+cnt('CA')+cnt('MA')+cnt('OH')+cnt('CH'))\/dh;\n  const odi=cnt('DESAT')\/dh;\n  const ae=document.getElementById('ss-ahi'),oe=document.getElementById('ss-odi');\n  if(ae)ae.textContent=ahi.toFixed(1);if(oe)oe.textContent=odi.toFixed(1);\n}\n\nfunction showHint(){\n  const study=S.study;if(!study||study.stage!==1)return;\n  const master=getMasterForStudy(study);\n  const ev=master.find(e=>!EV_ANNOT.has(e.type)&&e.start>=S.offset&&e.start<=S.offset+S.winDur);\n  const NAMES={OA:'bezdech obturacyjny',CA:'bezdech centralny',MA:'bezdech mieszany',OH:'sp\u0142ycenie obturacyjne',CH:'sp\u0142ycenie centralne',DESAT:'desaturacja izolowana'};\n  const ht=document.getElementById('hint-txt'),hp=document.getElementById('hint-panel');\n  if(ev)ht.innerHTML=`Na tym fragmencie wida\u0107 <strong>${NAMES[ev.type]||ev.type}<\/strong> (~${Math.floor(ev.start\/60)}:${String(Math.round(ev.start%60)).padStart(2,'0')}, ${ev.duration.toFixed(0)}s).`;\n  else ht.innerHTML='Brak zdarze\u0144 oddechowych na tym fragmencie. Przewi\u0144 dalej lub poszukaj chrapania jako wskaz\u00f3wki.';\n  hp.style.display='flex';\n}\n\nfunction backToSelector(){\n  clearInterval(S.timer);\n  document.getElementById('study-selector').style.display='block';\n  document.getElementById('scoring-interface').style.display='none';\n  document.getElementById('compare-results').style.display='none';\n  renderStudyList();\n}\n\nfunction submitScoring(){\n  clearInterval(S.timer);\n  const study=S.study;if(!study)return;\n  const masterEvs=getMasterForStudy(study);\n  const acc=calcAcc(S.marked,masterEvs);\n  S.completed[study.id]={acc,evs:[...S.marked]};\n  document.getElementById('scoring-interface').style.display='none';\n  document.getElementById('compare-results').style.display='block';\n  document.getElementById('cmp-title').textContent='Wyniki: '+study.label;\n  const fmt=(evs,dur_s)=>{\n    const cnt=t=>evs.filter(e=>e.type===t).length,dh=dur_s\/3600;\n    const ahi=(cnt('OA')+cnt('CA')+cnt('MA')+cnt('OH')+cnt('CH'))\/dh;\n    return `<table style=\"width:100%;font-size:.83rem\"><tr><td>OA<\/td><td><b>${cnt('OA')}<\/b><\/td><\/tr><tr><td>CA<\/td><td><b>${cnt('CA')}<\/b><\/td><\/tr><tr><td>MA<\/td><td><b>${cnt('MA')}<\/b><\/td><\/tr><tr><td>OH<\/td><td><b>${cnt('OH')}<\/b><\/td><\/tr><tr><td>CH<\/td><td><b>${cnt('CH')}<\/b><\/td><\/tr><tr><td>Desat<\/td><td><b>${cnt('DESAT')}<\/b><\/td><\/tr><tr style=\"border-top:2px solid #ccc\"><td><b>AHI<\/b><\/td><td><b>${ahi.toFixed(1)}\/h<\/b><\/td><\/tr><\/table>`;\n  };\n  document.getElementById('st-res').innerHTML=fmt(S.marked,study.dur);\n  document.getElementById('ms-res').innerHTML=fmt(masterEvs,study.dur);\n  const ap=Math.round(acc*100);\n  document.getElementById('acc-pct').textContent=ap+'%';\n  const af=document.getElementById('acc-fill');\n  af.style.width=ap+'%';af.style.background=ap>=70?'#2ecc9a':ap>=50?'#f39c12':'#e74c3c';\n  let msg=ap>=90?'\ud83c\udfc6 Doskona\u0142y wynik!':ap>=70?'\u2705 Zaliczone! Dobra zgodno\u015b\u0107.':ap>=50?'\u26a0\ufe0f Prawie \u2013 spr\u00f3buj ponownie lub wr\u00f3\u0107 do teorii.':'\u274c Niska zgodno\u015b\u0107 \u2013 przejrzyj rozdzia\u0142 7\u20139 i powt\u00f3rz.';\n  document.getElementById('acc-msg').textContent=msg;\n  const allDone=STUDIES.every(s=>S.completed[s.id]);\n  const nb=document.getElementById('next-btn');\n  if(allDone){nb.textContent='\ud83c\udfc6 Odbierz za\u015bwiadczenie \u2192';nb.onclick=()=>showSection('cert');}\n  \/\/ Show clinical interpretation for case reports\n  const crPanel=document.getElementById('case-result-panel');\n  if(study.stage===3&&CASE_INFO[study.id]){\n    if(crPanel){showCaseResult(study.id);}\n  } else {if(crPanel)crPanel.style.display='none';}\n  renderStudyList();\n}\n\nfunction calcAcc(stu,mas){\n  const used=new Set();let ok=0;\n  stu.forEach(se=>{\n    let best=-1,bIou=0;\n    mas.forEach((me,mi)=>{\n      if(used.has(mi)||se.type!==me.type)return;\n      const os=Math.max(se.start,me.start),oe=Math.min(se.start+se.duration,me.start+me.duration);\n      const ov=Math.max(0,oe-os);\n      const un=Math.max(se.start+se.duration,me.start+me.duration)-Math.min(se.start,me.start);\n      const iou=ov\/un;if(iou>bIou){bIou=iou;best=mi;}\n    });\n    if(best>=0&&bIou>0.3){ok++;used.add(best);}\n  });\n  return Math.max(0,Math.min(1,ok\/Math.max(mas.length,stu.length,1)));\n}\n\nfunction retryStudy(){const id=S.study.id;document.getElementById('compare-results').style.display='none';startStudy(id);}\nfunction nextStudy(){\n  const idx=STUDIES.findIndex(s=>s.id===S.study.id);\n  const nxt=STUDIES[idx+1];\n  document.getElementById('compare-results').style.display='none';\n  if(nxt)startStudy(nxt.id);else backToSelector();\n}\n\n\/\/ \u2500\u2500 CERTIFICATE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction checkCert(){\n  const ok=STUDIES.every(s=>S.completed[s.id]&&S.completed[s.id].acc>=0.5);\n  document.getElementById('cert-locked').style.display=ok?'none':'block';\n  document.getElementById('cert-unlocked').style.display=ok?'block':'none';\n  if(ok)document.getElementById('cert-date').textContent=new Date().toLocaleDateString('pl-PL',{year:'numeric',month:'long',day:'numeric'});\n}\n\n\/\/ \u2500\u2500 CHAPTER HTML \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst CHAPTERS_HTML = `\n<div id=\"ch1\" class=\"chapter\">\n<div class=\"card\">\n<h2>\ud83c\udf19 Rozdzia\u0142 1 \u2013 Fundamenty snu<\/h2>\n<p><strong>Sen<\/strong> nie jest biernym \u201ewy\u0142\u0105czeniem organizmu\u201d, ale aktywnym procesem biologicznym. W czasie snu dochodzi do regeneracji, konsolidacji pami\u0119ci, stabilizacji nastroju, regulacji hormonalnej oraz prawid\u0142owego funkcjonowania uk\u0142adu kr\u0105\u017cenia i oddychania. Dlatego ka\u017cde zaburzenie struktury snu \u2013 nawet przy pozornie d\u0142ugim \u015bnie \u2013 mo\u017ce prowadzi\u0107 do objaw\u00f3w dziennych.<\/p>\n<div class=\"alert info\"><span class=\"icon\">\ud83d\udca1<\/span><div><strong>Po co zaczynamy od fizjologii snu?<\/strong> \u017beby zrozumie\u0107, dlaczego bezdechy, sp\u0142ycenia oddychania i RERA powoduj\u0105 senno\u015b\u0107, zm\u0119czenie, gorsz\u0105 koncentracj\u0119 oraz przewlek\u0142e niewyspanie.<\/div><\/div>\n<ul class=\"callout-list\">\n  <li><strong>N1<\/strong> \u2013 faza zasypiania, przej\u015bcie z czuwania do snu.<\/li>\n  <li><strong>N2<\/strong> \u2013 najd\u0142u\u017csza faza snu; zawiera wrzeciona snu i kompleksy K.<\/li>\n  <li><strong>N3<\/strong> \u2013 sen g\u0142\u0119boki, najwa\u017cniejszy dla regeneracji fizycznej.<\/li>\n  <li><strong>REM<\/strong> \u2013 faza marze\u0144 sennych, pami\u0119ci i atonii mi\u0119\u015bniowej.<\/li>\n<\/ul>\n<p>Do opisu snu wykorzystujemy przede wszystkim <strong>EEG<\/strong>, <strong>EOG<\/strong> i <strong>EMG<\/strong>. Dzi\u0119ki temu wiemy, kiedy pacjent zasn\u0105\u0142, ile rzeczywi\u015bcie spa\u0142, w jakiej by\u0142 fazie snu oraz kiedy pojawia\u0142y si\u0119 wybudzenia. Graficznym zapisem architektury snu jest <strong>hipnogram<\/strong> \u2013 prawid\u0142owo ma on posta\u0107 powtarzaj\u0105cych si\u0119 cykli NREM\u2192REM, z wi\u0119ksz\u0105 ilo\u015bci\u0105 N3 w pierwszej po\u0142owie nocy i wi\u0119ksz\u0105 ilo\u015bci\u0105 REM w drugiej.<\/p>\n<figure class=\"chapter-figure\">\n  <img decoding=\"async\" src=\"${POLI_ASSET_BASE}grafika-0-fundamenty-snu.png\" alt=\"Fundamenty snu \u2013 fazy snu i prawid\u0142owy hipnogram\">\n  <figcaption><strong>Grafika 0.<\/strong> Podstawowe fazy snu, elementy badania snu oraz przyk\u0142adowy prawid\u0142owy hipnogram osoby doros\u0142ej.<\/figcaption>\n<\/figure>\n<div class=\"chapter-nav\"><button class=\"btn btn-primary\" onclick=\"nextCh(1)\">Dalej \u2192<\/button><\/div>\n<\/div><\/div>\n\n<div id=\"ch2\" class=\"chapter\" style=\"display:none\">\n<div class=\"card\">\n<h2>\ud83e\udec1 Rozdzia\u0142 2 \u2013 Co to jest OBS?<\/h2>\n<p><strong>Obturacyjny bezdech senny (OBS, ang. OSA)<\/strong> to choroba, w kt\u00f3rej podczas snu dochodzi do powtarzaj\u0105cego si\u0119 zw\u0119\u017cania lub zamykania g\u00f3rnych dr\u00f3g oddechowych. Pacjent nadal pr\u00f3buje oddycha\u0107, ale powietrze nie mo\u017ce swobodnie przep\u0142ywa\u0107 przez gard\u0142o. Skutkiem s\u0105 spadki saturacji, wzrost wysi\u0142ku oddechowego oraz kr\u00f3tkie wybudzenia, kt\u00f3rych chory zazwyczaj nie pami\u0119ta.<\/p>\n<div class=\"alert warn\"><span class=\"icon\">\u26a0\ufe0f<\/span><div><strong>OBS to nie tylko chrapanie.<\/strong> To choroba wp\u0142ywaj\u0105ca na uk\u0142ad kr\u0105\u017cenia, metabolizm, m\u00f3zg, nastr\u00f3j, sprawno\u015b\u0107 zawodow\u0105 i bezpiecze\u0144stwo podczas prowadzenia pojazd\u00f3w.<\/div><\/div>\n<div class=\"chapter-2col\">\n  <figure class=\"chapter-figure\">\n    <img decoding=\"async\" src=\"${POLI_ASSET_BASE}grafika-1-prawidlowy-oddech-vs-obs.png\" alt=\"Prawid\u0142owy oddech i obturacyjny bezdech senny\">\n    <figcaption><strong>Grafika 1.<\/strong> W prawid\u0142owym \u015bnie gard\u0142o pozostaje dro\u017cne. W OBS tkanki gard\u0142a zapadaj\u0105 si\u0119 i blokuj\u0105 przep\u0142yw powietrza mimo zachowanego wysi\u0142ku oddechowego.<\/figcaption>\n  <\/figure>\n  <figure class=\"chapter-figure\">\n    <img decoding=\"async\" src=\"${POLI_ASSET_BASE}grafika-2-cykl-epizodu-obs.png\" alt=\"Cykl pojedynczego epizodu OBS\">\n    <figcaption><strong>Grafika 2.<\/strong> Typowy cykl epizodu OBS: za\u015bni\u0119cie, spadek napi\u0119cia mi\u0119\u015bni gard\u0142a, zw\u0119\u017cenie, chrapanie, bezdech lub sp\u0142ycenie oddychania, spadek saturacji, mikrowybudzenie i ponowne za\u015bni\u0119cie.<\/figcaption>\n  <\/figure>\n<\/div>\n<p>To w\u0142a\u015bnie <strong>powtarzalno\u015b\u0107<\/strong> zdarze\u0144 sprawia, \u017ce pacjent mo\u017ce spa\u0107 wiele godzin, a mimo to budzi\u0107 si\u0119 niewyspany. Kr\u00f3tkie mikrowybudzenia przywracaj\u0105 dro\u017cno\u015b\u0107 gard\u0142a, ale jednocze\u015bnie niszcz\u0105 prawid\u0142ow\u0105 architektur\u0119 snu.<\/p>\n<div class=\"chapter-nav\"><button class=\"btn btn-outline\" onclick=\"prevCh(2)\">\u2190 Wstecz<\/button><button class=\"btn btn-primary\" onclick=\"nextCh(2)\">Dalej \u2192<\/button><\/div>\n<\/div><\/div>\n\n<div id=\"ch3\" class=\"chapter\" style=\"display:none\">\n<div class=\"card\">\n<h2>\ud83e\uddd1\u200d\u2695\ufe0f Rozdzia\u0142 3 \u2013 Kto jest szczeg\u00f3lnie nara\u017cony na OBS?<\/h2>\n<p>OBS mo\u017ce wyst\u0105pi\u0107 u osoby w ka\u017cdym wieku i nie dotyczy wy\u0142\u0105cznie m\u0119\u017cczyzn z oty\u0142o\u015bci\u0105. Oty\u0142o\u015b\u0107 jest jednak jednym z najwa\u017cniejszych czynnik\u00f3w ryzyka, a tak\u017ce elementem b\u0142\u0119dnego ko\u0142a: zwi\u0119ksza zapadalno\u015b\u0107 na OBS, a niewyspany pacjent cz\u0119\u015bciej przybiera na wadze.<\/p>\n<div class=\"info-grid\">\n<div class=\"info-box red\"><div class=\"label\">Najsilniejsze czynniki<\/div><div class=\"value\">oty\u0142o\u015b\u0107, zwi\u0119kszony obw\u00f3d szyi, p\u0142e\u0107 m\u0119ska<\/div><\/div>\n<div class=\"info-box blue\"><div class=\"label\">Czynniki anatomiczne<\/div><div class=\"value\">ma\u0142a \u017cuchwa, du\u017cy j\u0119zyk, przerost migda\u0142k\u00f3w, niedro\u017cno\u015b\u0107 nosa<\/div><\/div>\n<div class=\"info-box orange\"><div class=\"label\">Czynniki dodatkowe<\/div><div class=\"value\">alkohol, leki uspokajaj\u0105ce, spanie na plecach<\/div><\/div>\n<div class=\"info-box green\"><div class=\"label\">Nie tylko oty\u0142o\u015b\u0107<\/div><div class=\"value\">tak\u017ce osoby szczup\u0142e mog\u0105 chorowa\u0107 na OBS<\/div><\/div>\n<\/div>\n<table>\n<tr><th>Kategoria<\/th><th>Przyk\u0142ady<\/th><th>Znaczenie kliniczne<\/th><\/tr>\n<tr><td>Anatomiczne<\/td><td>micrognathia, du\u017cy j\u0119zyk, wyd\u0142u\u017cone podniebienie, przerost migda\u0142k\u00f3w<\/td><td>zwi\u0119kszona podatno\u015b\u0107 gard\u0142a na zapadanie<\/td><\/tr>\n<tr><td>Demograficzne<\/td><td>wiek, p\u0142e\u0107 m\u0119ska, rodzinne wyst\u0119powanie OBS<\/td><td>wy\u017csze ryzyko kliniczne<\/td><\/tr>\n<tr><td>Hormonalne<\/td><td>menopauza, niedoczynno\u015b\u0107 tarczycy, akromegalia<\/td><td>cz\u0119stszy atypowy obraz choroby<\/td><\/tr>\n<tr><td>Styl \u017cycia<\/td><td>alkohol przed snem, sedacja, palenie tytoniu, pozycja na plecach<\/td><td>czynniki cz\u0119\u015bciowo modyfikowalne<\/td><\/tr>\n<\/table>\n<div class=\"alert warn\"><span class=\"icon\">\ud83e\udde0<\/span><div><strong>Wa\u017cne:<\/strong> kobiety z OBS cz\u0119\u015bciej zg\u0142aszaj\u0105 zm\u0119czenie, b\u00f3le g\u0142owy i zaburzenia nastroju, a rzadziej klasyczne g\u0142o\u015bne chrapanie. Taki obraz \u0142atwo przeoczy\u0107.<\/div><\/div>\n<div class=\"chapter-nav\"><button class=\"btn btn-outline\" onclick=\"prevCh(3)\">\u2190 Wstecz<\/button><button class=\"btn btn-primary\" onclick=\"nextCh(3)\">Dalej \u2192<\/button><\/div>\n<\/div><\/div>\n\n<div id=\"ch4\" class=\"chapter\" style=\"display:none\">\n<div class=\"card\">\n<h2>\ud83d\ude34 Rozdzia\u0142 4 \u2013 Objawy nocne i dzienne<\/h2>\n<p>Pacjent z OBS mo\u017ce nie pami\u0119ta\u0107 wybudze\u0144, dlatego pierwsze sygna\u0142y cz\u0119sto zauwa\u017ca partner lub domownik. W ci\u0105gu dnia problem mo\u017ce manifestowa\u0107 si\u0119 nie tylko typow\u0105 senno\u015bci\u0105, ale tak\u017ce przewlek\u0142ym zm\u0119czeniem, gorsz\u0105 koncentracj\u0105 czy dra\u017cliwo\u015bci\u0105.<\/p>\n<figure class=\"chapter-figure\">\n  <img decoding=\"async\" src=\"${POLI_ASSET_BASE}grafika-3-objawy-obs.png\" alt=\"Objawy nocne i dzienne obturacyjnego bezdechu sennego\">\n  <figcaption><strong>Grafika 3.<\/strong> Najcz\u0119stsze nocne i dzienne objawy obturacyjnego bezdechu sennego.<\/figcaption>\n<\/figure>\n<figure class=\"chapter-figure\">\n  <img decoding=\"async\" src=\"${POLI_ASSET_BASE}grafika-7-fragmentacja-snu-rera.png\" alt=\"Jak bezdechy, sp\u0142ycenia i RERA niszcz\u0105 sen\">\n  <figcaption><strong>Grafika 7.<\/strong> Bezdechy, sp\u0142ycenia oddychania i RERA prowadz\u0105 do mikrowybudze\u0144, fragmentacji snu i objaw\u00f3w dziennych. Pacjent mo\u017ce spa\u0107 d\u0142ugo, ale sen nie daje pe\u0142nej regeneracji.<\/figcaption>\n<\/figure>\n<div class=\"info-grid\">\n<div class=\"info-box red\"><div class=\"label\">Objawy nocne<\/div><div class=\"value\" style=\"font-size:.84rem\">g\u0142o\u015bne chrapanie, przerwy w oddychaniu, duszenie si\u0119, cz\u0119ste wybudzenia, nokturia, potliwo\u015b\u0107, sucho\u015b\u0107 w ustach<\/div><\/div>\n<div class=\"info-box orange\"><div class=\"label\">Objawy dzienne<\/div><div class=\"value\" style=\"font-size:.84rem\">senno\u015b\u0107, niewyspanie, b\u00f3l g\u0142owy rano, zm\u0119czenie, problemy z pami\u0119ci\u0105 i koncentracj\u0105, dra\u017cliwo\u015b\u0107<\/div><\/div>\n<\/div>\n<p><strong>Dlaczego pacjent ma objawy?<\/strong> Bezdech, hipopnoe i RERA przerywaj\u0105 ci\u0105g\u0142o\u015b\u0107 snu, zmniejszaj\u0105 ilo\u015b\u0107 snu N3 i REM oraz aktywuj\u0105 uk\u0142ad wsp\u00f3\u0142czulny. Chory budzi si\u0119 niewypocz\u0119ty, ma gorsz\u0105 sprawno\u015b\u0107 poznawcz\u0105, a czas reakcji mo\u017ce si\u0119 wyd\u0142u\u017ca\u0107.<\/p>\n<div class=\"chapter-nav\"><button class=\"btn btn-outline\" onclick=\"prevCh(4)\">\u2190 Wstecz<\/button><button class=\"btn btn-primary\" onclick=\"nextCh(4)\">Dalej \u2192<\/button><\/div>\n<\/div><\/div>\n\n<div id=\"ch5\" class=\"chapter\" style=\"display:none\">\n<div class=\"card\">\n<h2>\u26a0\ufe0f Rozdzia\u0142 5 \u2013 Co dzieje si\u0119 w organizmie podczas bezdechu?<\/h2>\n<p>Ka\u017cdy epizod zw\u0119\u017cenia lub zamkni\u0119cia gard\u0142a ogranicza wentylacj\u0119. Powoduje to spadek saturacji, wahania ci\u015bnienia wewn\u0105trz klatki piersiowej, aktywacj\u0119 uk\u0142adu wsp\u00f3\u0142czulnego, zmiany t\u0119tna i ci\u015bnienia t\u0119tniczego oraz mikrowybudzenia. Wielokrotnie powtarzane epizody prowadz\u0105 do <strong>niedotlenienia przerywanego<\/strong> i przewlek\u0142ej fragmentacji snu.<\/p>\n<figure class=\"chapter-figure\">\n  <img decoding=\"async\" src=\"${POLI_ASSET_BASE}grafika-4-wplyw-obs-na-organizm.png\" alt=\"Wp\u0142yw obturacyjnego bezdechu sennego na organizm\">\n  <figcaption><strong>Grafika 4.<\/strong> W OBS zaburzone s\u0105 nie tylko drogi oddechowe. Zmienia si\u0119 utlenowanie krwi, praca serca i ci\u0105g\u0142o\u015b\u0107 snu.<\/figcaption>\n<\/figure>\n<table>\n<tr><th>Obszar<\/th><th>Mo\u017cliwe konsekwencje<\/th><\/tr>\n<tr><td>Uk\u0142ad kr\u0105\u017cenia<\/td><td>nadci\u015bnienie t\u0119tnicze, migotanie przedsionk\u00f3w, choroba wie\u0144cowa, niewydolno\u015b\u0107 serca, udar<\/td><\/tr>\n<tr><td>Metabolizm<\/td><td>oty\u0142o\u015b\u0107, insulinooporno\u015b\u0107, cukrzyca typu 2, dyslipidemia<\/td><\/tr>\n<tr><td>M\u00f3zg i psychika<\/td><td>gorsza pami\u0119\u0107 i koncentracja, obni\u017cenie nastroju, dra\u017cliwo\u015b\u0107, mniejsza odporno\u015b\u0107 psychiczna<\/td><\/tr>\n<tr><td>Bezpiecze\u0144stwo<\/td><td>zasypianie za kierownic\u0105, gorsza koncentracja w pracy, wi\u0119ksze ryzyko wypadk\u00f3w<\/td><\/tr>\n<\/table>\n<div class=\"alert danger\"><span class=\"icon\">\ud83d\ude97<\/span><div><strong>Senno\u015b\u0107 za kierownic\u0105<\/strong> jest objawem alarmowym. Otwieranie okna, kawa czy g\u0142o\u015bna muzyka nie usuwaj\u0105 przyczyny problemu i nie gwarantuj\u0105 bezpiecze\u0144stwa.<\/div><\/div>\n<div class=\"chapter-nav\"><button class=\"btn btn-outline\" onclick=\"prevCh(5)\">\u2190 Wstecz<\/button><button class=\"btn btn-primary\" onclick=\"nextCh(5)\">Dalej \u2192<\/button><\/div>\n<\/div><\/div>\n\n<div id=\"ch6\" class=\"chapter\" style=\"display:none\">\n<div class=\"card\">\n<h2>\ud83d\udd2c Rozdzia\u0142 6 \u2013 Jak diagnozujemy OBS?<\/h2>\n<p>Diagnostyka rozpoczyna si\u0119 od wywiadu i oceny ryzyka (np. <strong>Epworth<\/strong>, <strong>STOP-BANG<\/strong>). Rozpoznanie potwierdza badanie snu \u2013 najcz\u0119\u015bciej <strong>domowa poligrafia oddechowa (HSAT)<\/strong> lub <strong>polisomnografia (PSG)<\/strong>.<\/p>\n<figure class=\"chapter-figure\">\n  <img decoding=\"async\" src=\"${POLI_ASSET_BASE}grafika-5-domowe-badanie-snu.png\" alt=\"Domowe badanie snu\">\n  <figcaption><strong>Grafika 5.<\/strong> Przyk\u0142ad domowej poligrafii: przep\u0142yw powietrza, wysi\u0142ek oddechowy, t\u0119tno i utlenowanie krwi.<\/figcaption>\n<\/figure>\n<div class=\"chapter-2col\">\n  <div>\n    <h3>HSAT \/ poligrafia typu III<\/h3>\n    <ul>\n      <li>rejestruje przep\u0142yw, wysi\u0142ek oddechowy, SpO\u2082, t\u0119tno, cz\u0119sto pozycj\u0119 i chrapanie,<\/li>\n      <li>nie zawiera EEG \u2013 nie okre\u015bla dok\u0142adnie momentu za\u015bni\u0119cia ani stadi\u00f3w snu,<\/li>\n      <li>wynik opisujemy zwykle jako <strong>REI<\/strong>, czyli liczb\u0119 zdarze\u0144 na godzin\u0119 monitorowania.<\/li>\n    <\/ul>\n  <\/div>\n  <div>\n    <h3>Polisomnografia (PSG)<\/h3>\n    <ul>\n      <li>zawiera EEG, EOG, EMG oraz kana\u0142y oddechowe,<\/li>\n      <li>pozwala wyliczy\u0107 <strong>AHI<\/strong> na podstawie rzeczywistego czasu snu,<\/li>\n      <li>umo\u017cliwia ocen\u0119 wybudze\u0144, RERA i hipopnoe zako\u0144czonych arousalem.<\/li>\n    <\/ul>\n  <\/div>\n<\/div>\n<div class=\"alert warn\"><span class=\"icon\">\ud83d\udcc9<\/span><div><strong>Dlaczego poligrafia mo\u017ce zani\u017ca\u0107 nasilenie OBS?<\/strong> Je\u015bli pacjent d\u0142ugo zasypia\u0142, cz\u0119sto si\u0119 wybudza\u0142 lub aparat dzia\u0142a\u0142 d\u0142u\u017cej ni\u017c rzeczywisty sen, to mianownik REI jest zawy\u017cony i wska\u017anik mo\u017ce by\u0107 sztucznie ni\u017cszy od AHI.<\/div><\/div>\n<div class=\"chapter-nav\"><button class=\"btn btn-outline\" onclick=\"prevCh(6)\">\u2190 Wstecz<\/button><button class=\"btn btn-primary\" onclick=\"nextCh(6)\">Dalej \u2192<\/button><\/div>\n<\/div><\/div>\n\n<div id=\"ch7\" class=\"chapter\" style=\"display:none\">\n<div class=\"card\">\n<h2>\ud83d\udccf Rozdzia\u0142 7 \u2013 Jak oceniamy zdarzenia oddechowe?<\/h2>\n<p>W ocenie oddychania podczas snu nie wystarczy \u201ezobaczy\u0107 chrapanie\u201d. Zdarzenie musi spe\u0142nia\u0107 okre\u015blone kryteria dotycz\u0105ce stopnia redukcji przep\u0142ywu, minimalnego czasu trwania, obecno\u015bci wysi\u0142ku oddechowego oraz \u2013 w polisomnografii \u2013 zwi\u0105zku z wybudzeniem.<\/p>\n<table>\n<tr><th>Zdarzenie<\/th><th>Kryteria podstawowe<\/th><th>Co widzimy w kana\u0142ach?<\/th><\/tr>\n<tr><td><strong>Bezdech obturacyjny<\/strong><\/td><td>spadek przep\u0142ywu o \u226590% przez \u226510 s<\/td><td>wysi\u0142ek oddechowy zachowany lub narastaj\u0105cy<\/td><\/tr>\n<tr><td><strong>Bezdech centralny<\/strong><\/td><td>spadek przep\u0142ywu o \u226590% przez \u226510 s<\/td><td>brak wysi\u0142ku oddechowego w ca\u0142ym zdarzeniu<\/td><\/tr>\n<tr><td><strong>Bezdech mieszany<\/strong><\/td><td>spadek przep\u0142ywu o \u226590% przez \u226510 s<\/td><td>pocz\u0105tek centralny, nast\u0119pnie pojawia si\u0119 wysi\u0142ek<\/td><\/tr>\n<tr><td><strong>Hipopnoe \/ sp\u0142ycenie<\/strong><\/td><td>spadek przep\u0142ywu o \u226530% przez \u226510 s<\/td><td>towarzyszy jej desaturacja \u22653% lub arousal w EEG<\/td><\/tr>\n<tr><td><strong>RERA<\/strong><\/td><td>sekwencja \u226510 s z narastaj\u0105cym wysi\u0142kiem lub flow limitation<\/td><td>ko\u0144czy si\u0119 wybudzeniem, ale nie spe\u0142nia kryteri\u00f3w bezdechu\/hipopnoe<\/td><\/tr>\n<\/table>\n<div class=\"alert info\"><span class=\"icon\">\ud83d\udcd8<\/span><div><strong>Wa\u017cne:<\/strong> wym\u00f3g spadku saturacji o 3\u20134 punkty procentowe dotyczy g\u0142\u00f3wnie hipopnoe. <strong>Bezdech mo\u017ce zosta\u0107 zaliczony tak\u017ce bez desaturacji<\/strong>, je\u015bli przep\u0142yw spad\u0142 o \u226590% i trwa\u0142 co najmniej 10 sekund.<\/div><\/div>\n<h3>AHI, REI, RDI i ODI<\/h3>\n<table>\n<tr><th>Wska\u017anik<\/th><th>Co oznacza?<\/th><\/tr>\n<tr><td>AHI<\/td><td>liczba bezdech\u00f3w i hipopnoe na godzin\u0119 rzeczywistego czasu snu<\/td><\/tr>\n<tr><td>REI<\/td><td>liczba zdarze\u0144 oddechowych na godzin\u0119 monitorowania \u2013 najcz\u0119\u015bciej w poligrafii<\/td><\/tr>\n<tr><td>RDI<\/td><td>AHI + zdarzenia RERA; bywa wy\u017cszy ni\u017c AHI u pacjent\u00f3w z cz\u0119stymi wybudzeniami<\/td><\/tr>\n<tr><td>ODI<\/td><td>liczba desaturacji na godzin\u0119; nie jest tym samym co AHI<\/td><\/tr>\n<\/table>\n<p>U doros\u0142ych nasilenie OBS klasyfikuje si\u0119 zwykle nast\u0119puj\u0105co: <strong>\u0142agodne 5\u201314,9\/h<\/strong>, <strong>umiarkowane 15\u201329,9\/h<\/strong>, <strong>ci\u0119\u017ckie \u226530\/h<\/strong>. Opr\u00f3cz samej liczby zdarze\u0144 liczy si\u0119 jednak tak\u017ce g\u0142\u0119boko\u015b\u0107 i czas trwania desaturacji, najni\u017csza saturacja, nasilenie w REM, zale\u017cno\u015b\u0107 od pozycji oraz objawy pacjenta.<\/p>\n<div class=\"chapter-nav\"><button class=\"btn btn-outline\" onclick=\"prevCh(7)\">\u2190 Wstecz<\/button><button class=\"btn btn-primary\" onclick=\"nextCh(7)\">Dalej \u2192<\/button><\/div>\n<\/div><\/div>\n\n<div id=\"ch8\" class=\"chapter\" style=\"display:none\">\n<div class=\"card\">\n<h2>\ud83d\udc8a Rozdzia\u0142 8 \u2013 Leczenie OBS<\/h2>\n<p><strong>CPAP<\/strong> jest metod\u0105 pierwszego wyboru u wi\u0119kszo\u015bci pacjent\u00f3w z istotnym klinicznie OBS. Dodatnie ci\u015bnienie powietrza utrzymuje gard\u0142o otwarte, zapobiega zapadaniu si\u0119 tkanek i eliminuje bezdechy, sp\u0142ycenia oraz chrapanie.<\/p>\n<figure class=\"chapter-figure\">\n  <img decoding=\"async\" src=\"${POLI_ASSET_BASE}grafika-6-jak-dziala-cpap.png\" alt=\"Jak dzia\u0142a CPAP\">\n  <figcaption><strong>Grafika 6.<\/strong> Zasada dzia\u0142ania CPAP: dodatnie ci\u015bnienie utrzymuje dro\u017cno\u015b\u0107 gard\u0142a podczas snu.<\/figcaption>\n<\/figure>\n<table>\n<tr><th>Metoda<\/th><th>Kiedy o niej my\u015blimy?<\/th><\/tr>\n<tr><td>CPAP \/ APAP<\/td><td>umiarkowany i ci\u0119\u017cki OBS, \u0142agodny OBS z wyra\u017anymi objawami lub chorobami wsp\u00f3\u0142istniej\u0105cymi<\/td><\/tr>\n<tr><td>MAD<\/td><td>\u0142agodny\u2013umiarkowany OBS, zw\u0142aszcza przy nietolerancji CPAP<\/td><\/tr>\n<tr><td>Redukcja masy cia\u0142a<\/td><td>u pacjent\u00f3w z nadwag\u0105 lub oty\u0142o\u015bci\u0105 \u2013 zawsze jako element leczenia<\/td><\/tr>\n<tr><td>Leczenie pozycyjne<\/td><td>pozycyjny OBS, gdy zaburzenia nasilaj\u0105 si\u0119 w pozycji na plecach<\/td><\/tr>\n<tr><td>Post\u0119powanie laryngologiczne \/ chirurgiczne<\/td><td>wybrane przypadki z istotnym czynnikiem anatomicznym<\/td><\/tr>\n<\/table>\n<div class=\"alert success\"><span class=\"icon\">\u2705<\/span><div>Dobrze dobrane leczenie poprawia senno\u015b\u0107 dzienn\u0105, jako\u015b\u0107 \u017cycia, kontrol\u0119 ci\u015bnienia t\u0119tniczego i bezpiecze\u0144stwo prowadzenia pojazd\u00f3w.<\/div><\/div>\n<div class=\"chapter-nav\"><button class=\"btn btn-outline\" onclick=\"prevCh(8)\">\u2190 Wstecz<\/button><button class=\"btn btn-primary\" onclick=\"nextCh(8)\">Dalej \u2192<\/button><\/div>\n<\/div><\/div>\n\n<div id=\"ch9\" class=\"chapter\" style=\"display:none\">\n<div class=\"card\">\n<h2>\ud83e\udded Rozdzia\u0142 9 \u2013 Jak my\u015ble\u0107 podczas oceny badania?<\/h2>\n<p>Przed przej\u015bciem do cz\u0119\u015bci praktycznej warto przyj\u0105\u0107 uporz\u0105dkowany schemat oceny. Dzi\u0119ki temu \u0142atwiej unikn\u0105\u0107 pomy\u0142ek i nie przeoczy\u0107 zjawisk takich jak RERA czy zani\u017cony REI.<\/p>\n<ol>\n  <li><strong>Oce\u0144 jako\u015b\u0107 sygna\u0142u<\/strong> \u2013 czy przep\u0142yw, pasy, SpO\u2082 i t\u0119tno s\u0105 wiarygodne?<\/li>\n  <li><strong>Patrz r\u00f3wnocze\u015bnie na kilka kana\u0142\u00f3w<\/strong> \u2013 sam przep\u0142yw nie wystarcza; trzeba zestawi\u0107 go z wysi\u0142kiem i saturacj\u0105.<\/li>\n  <li><strong>Pami\u0119taj o czasie trwania<\/strong> \u2013 zdarzenie oddechowe u doros\u0142ego powinno trwa\u0107 co najmniej 10 sekund.<\/li>\n  <li><strong>Nie licz automatycznie ka\u017cdej desaturacji jako osobnego bezdechu<\/strong> \u2013 ODI i AHI to nie to samo.<\/li>\n  <li><strong>My\u015bl klinicznie<\/strong> \u2013 je\u015bli pacjent ma du\u017ce objawy, a REI jest graniczny, rozwa\u017c ograniczenie poligrafii, flow limitation i RERA.<\/li>\n<\/ol>\n<div class=\"alert warn\"><span class=\"icon\">\ud83e\udde0<\/span><div><strong>Praktyczna wskaz\u00f3wka:<\/strong> u pacjenta z licznymi ograniczeniami przep\u0142ywu, fragmentacj\u0105 snu i objawami dziennymi rzeczywisty <strong>RDI<\/strong> mo\u017ce by\u0107 wy\u017cszy ni\u017c REI uzyskany w prostej poligrafii bez EEG.<\/div><\/div>\n<div class=\"chapter-nav\"><button class=\"btn btn-outline\" onclick=\"prevCh(9)\">\u2190 Wstecz<\/button><button class=\"btn btn-primary\" onclick=\"nextCh(9)\">Dalej \u2192 (Demo scoringu)<\/button><\/div>\n<\/div><\/div>\n\n<div id=\"ch10\" class=\"chapter\" style=\"display:none\">\n<div class=\"card\">\n<h2>\ud83c\udfaf Rozdzia\u0142 10 \u2013 Demo scoringu<\/h2>\n<p>Poni\u017cej widzisz interaktywny zapis poligraficzny. Kliknij na zdarzenie na kanale przep\u0142ywu lub SpO\u2082, aby zobaczy\u0107 opis. To ten sam interfejs co w \u0107wiczeniach praktycznych \u2013 mo\u017cesz oswoi\u0107 si\u0119 z wygl\u0105dem zapisu przed rozpocz\u0119ciem pracy na prawdziwych badaniach EDF.<\/p>\n<div class=\"alert info\"><span class=\"icon\">\ud83d\udca1<\/span><div>W cz\u0119\u015bci praktycznej nagrania s\u0105 prawdziwymi badaniami EDF z systemu Alice NightOne. Scoring polega na zaznaczeniu pocz\u0105tku i ko\u0144ca ka\u017cdego zdarzenia na odpowiednich kana\u0142ach.<\/div><\/div>\n<div style=\"background:#0a0e14;border-radius:12px;overflow:hidden;border:2px solid var(--bd);margin:14px 0;cursor:pointer\">\n  <canvas id=\"demoCanvas\" height=\"320\"><\/canvas>\n<\/div>\n<div id=\"demo-desc\" class=\"alert info\" style=\"display:none\"><span class=\"icon\">\ud83d\udcca<\/span><div><\/div><\/div>\n<p style=\"font-size:.82rem;color:var(--mu);margin-top:10px\">Kana\u0142y (od g\u00f3ry): Chrapanie \u00b7 Przep\u0142yw \u00b7 Wysi\u0142ek kl. \u00b7 Wysi\u0142ek brz. \u00b7 SpO\u2082 \u00b7 T\u0119tno. Kliknij na dowolny fragment zapisu.<\/p>\n<div class=\"chapter-nav\">\n  <button class=\"btn btn-outline\" onclick=\"prevCh(10)\">\u2190 Wstecz<\/button>\n  <button class=\"btn btn-secondary btn-lg\" onclick=\"showSection('praktyka')\">\ud83c\udfcb Przejd\u017a do \u0107wicze\u0144 \u2192<\/button>\n<\/div>\n<\/div><\/div>\n`;\n\nfunction initChapters(){\n  const el=document.getElementById('chapters-container');\n  if(el)el.innerHTML=CHAPTERS_HTML;\n  \/\/ Show first chapter\n  document.querySelectorAll('.chapter').forEach((c,i)=>c.style.display=i===0?'block':'none');\n}\n\n\n\/\/ \u2500\u2500 DEMO RECORDING (CH 9) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst DEMO_STUDY={id:'demo',diff:'medium',seed:55,dur:1800,label:'Demo',stage:0,\n  events:[\n    {type:'OA',start:80,duration:22},{type:'OH',start:200,duration:18},\n    {type:'CA',start:340,duration:15},{type:'OA',start:480,duration:28},\n    {type:'DESAT',start:600,duration:20},{type:'MA',start:720,duration:19},\n  ]\n};\nfunction initDemo(){\n  const draw=()=>drawRec('demoCanvas',DEMO_STUDY,0,1800,[],DEMO_STUDY.events,true);\n  draw();\n  window.addEventListener('resize',draw);\n  const cv=document.getElementById('demoCanvas');\n  if(!cv)return;\n  cv.onclick=e=>{\n    const rect=cv.getBoundingClientRect(),x=e.clientX-rect.left,W=rect.width,LP=82,PW=W-LP-4;\n    if(x<LP)return;\n    const t=(x-LP)\/PW*1800;\n    const ev=DEMO_STUDY.events.find(e=>t>=e.start&&t<e.start+e.duration);\n    const desc=document.getElementById('demo-desc');\n    const DESCS={\n      OA:'<strong>Bezdech obturacyjny (OA):<\/strong> Przep\u0142yw zanik\u0142 \u226590% przez \u226510s. Pasy wysi\u0142kowe AKTYWNE i wzrastaj\u0105. Paradoks kl.\/brz. Gard\u0142o mechanicznie zamkni\u0119te przy zachowanym nap\u0119dzie.',\n      CA:'<strong>Bezdech centralny (CA):<\/strong> Przep\u0142yw zanik\u0142 \u226590%. Pasy wysi\u0142kowe P\u0141ASKIE \u2013 brak nap\u0119du z OUN. SpO\u2082 spada wolniej ni\u017c w OA.',\n      MA:'<strong>Bezdech mieszany (MA):<\/strong> Zaczyna si\u0119 centralnie (pasy p\u0142askie), ko\u0144czy obturacyjnie (pasy aktywne, paradoks).',\n      OH:'<strong>Sp\u0142ycenie obturacyjne (OH):<\/strong> Przep\u0142yw \u2193\u226530% przez \u226510s + desaturacja \u22653% lub przebudzenie. Wysi\u0142ek WZRASTA. Chrapanie.',\n      CH:'<strong>Sp\u0142ycenie centralne (CH):<\/strong> Przep\u0142yw \u2193\u226530% przez \u226510s. Wysi\u0142ek OBNI\u017bONY. Brak chrapania.',\n      DESAT:'<strong>Desaturacja izolowana:<\/strong> SpO\u2082 \u2193\u22653% bez bezdechu\/sp\u0142ycenia. Raportowana jako ODI.'\n    };\n    if(ev){desc.querySelector('div').innerHTML=DESCS[ev.type]||ev.type;desc.style.display='flex';}\n    else{desc.style.display='none';}\n  };\n}\n\n\/\/ \u2500\u2500 APP INIT \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function initApp(){\n  \/\/ Build query param for study selection\n  const studyParam = window.POLI_STUDY_ID ? `?study=${encodeURIComponent(window.POLI_STUDY_ID)}` : '';\n\n  \/\/ Try to load master events and study info from WordPress REST API\n  try {\n    const [infoResp, evResp] = await Promise.all([\n      fetch(window.POLI_API + '\/info' + studyParam),\n      fetch(window.POLI_API + '\/events' + studyParam),\n    ]);\n    if (infoResp.ok) STUDY_INFO_SERVER = await infoResp.json();\n    if (evResp.ok) MASTER_EVENTS_ALL = await evResp.json();\n  } catch (err) {\n    console.error('Poligrafia: b\u0142\u0105d pobierania danych', err);\n    MASTER_EVENTS_ALL = [];\n  }\n\n  initChapters();\n  if (typeof renderStudyList === 'function') renderStudyList();\n  if (typeof updateProgress === 'function') updateProgress();\n}\n\nif (document.readyState === 'loading') {\n  document.addEventListener('DOMContentLoaded', initApp);\n} else {\n  initApp();\n}\n<\/script>\n<\/div>\n<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-19","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/bezdechkurs.pl\/index.php\/wp-json\/wp\/v2\/pages\/19","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bezdechkurs.pl\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/bezdechkurs.pl\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/bezdechkurs.pl\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/bezdechkurs.pl\/index.php\/wp-json\/wp\/v2\/comments?post=19"}],"version-history":[{"count":1,"href":"https:\/\/bezdechkurs.pl\/index.php\/wp-json\/wp\/v2\/pages\/19\/revisions"}],"predecessor-version":[{"id":20,"href":"https:\/\/bezdechkurs.pl\/index.php\/wp-json\/wp\/v2\/pages\/19\/revisions\/20"}],"wp:attachment":[{"href":"https:\/\/bezdechkurs.pl\/index.php\/wp-json\/wp\/v2\/media?parent=19"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}