can validate it in the pov of a slp
| Area | Status |
|---|---|
| Target sound selection | Clinically appropriate |
| Trial data logging | Solid - needs cue hierarchy |
| Dysarthria techniques | Evidence-based |
| Apraxia section | Mostly accurate - rename MPAT to SPT |
| Safari game (phonological awareness) | Mislabeled - not an articulation drill |
| Rocket repeat game | Needs data capture |
| Developmental norms | Missing |
| Phonological process section | Absent |
| Goal-writing / discharge criteria | Absent |
| Assessment tool list | Accurate |
can validate it in the pov of a slp
| Area | Verdict |
|---|---|
| Anatomical accuracy | Accurate - muscles correctly identified |
| Rotary chew pattern | Correct and well illustrated |
| Laryngeal elevation / swallow palpation | Clinically accurate |
| Gauze chewing safety precaution | Correctly placed |
| Buccinator-to-speech bridge | Accurate |
| NSOMES evidence base not addressed | Major gap - needs clinical indication box |
| Level 4 speech integration | Too thin - needs procedural detail |
| Resistive jaw contraindications | Missing - safety risk |
| Exercise sequencing (tongue tap vs. lateral) | Needs review |
| Buccal massage rationale | Conflated with strengthening - needs separation |
| Fixed dosage without adjustment guidance | Minor - add a dosage note |
| Population not specified | Minor - add a scope statement at top |
can develop the webbased app on oromotor exercises and vegetative skills stengthining exercises
mkdir -p /home/daytona/workspace/oromotor-app
~/oromotor-app/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OroTherapy — Oromotor & Vegetative Skills</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,600;0,9..144,700;1,9..144,400&family=Outfit:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--cream:#F7F3EC; --cream2:#EDE8DE; --cream3:#D8D0BE; --ink:#1C1812; --ink2:#3A3328; --ink3:#5C5446; --muted:#9A9080;
--l1:#1A6B5A; --l1b:#E4F4F0; --l1c:#A8DDD4; --l1d:#0d4a3e;
--l2:#8B5E1A; --l2b:#F7EDD8; --l2c:#E8C07A; --l2d:#5e3f10;
--l3:#9B2C2C; --l3b:#FAE8E8; --l3c:#E89A9A; --l3d:#6b1c1c;
--l4:#3D2A8A; --l4b:#EDEAF8; --l4c:#B0A4E0; --l4d:#271a5e;
--veg:#1E5C8A; --vegb:#E4EEF8; --vegc:#90BCD8; --vegd:#123d5e;
--radius:14px; --radius-sm:8px; --radius-lg:20px;
--shadow:0 2px 12px rgba(28,24,18,0.08); --shadow-lg:0 8px 32px rgba(28,24,18,0.14);
--sidebar-w:280px;
}
html{scroll-behavior:smooth}
body{font-family:'Outfit',sans-serif;background:var(--cream);color:var(--ink);min-height:100vh;display:flex;overflow-x:hidden}
/* ─── SIDEBAR ─── */
.sidebar{
width:var(--sidebar-w);min-width:var(--sidebar-w);background:#fff;
border-right:1.5px solid var(--cream2);height:100vh;position:sticky;top:0;
overflow-y:auto;display:flex;flex-direction:column;z-index:100;
box-shadow:2px 0 12px rgba(28,24,18,0.04);
}
.sidebar-brand{padding:28px 22px 20px;border-bottom:1px solid var(--cream2)}
.brand-logo{display:flex;align-items:center;gap:10px;margin-bottom:6px}
.logo-mark{width:36px;height:36px;border-radius:10px;background:linear-gradient(135deg,var(--l1),var(--veg));display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0}
.brand-name{font-family:'Fraunces',serif;font-size:20px;font-weight:700;color:var(--ink);letter-spacing:-0.3px}
.brand-sub{font-size:11px;color:var(--muted);letter-spacing:0.4px;text-transform:uppercase}
.nav-section{padding:14px 12px 8px}
.nav-label{font-size:10px;letter-spacing:1px;text-transform:uppercase;color:var(--muted);padding:0 10px 8px;font-weight:600}
.nav-item{
display:flex;align-items:center;gap:10px;padding:9px 12px;
border-radius:var(--radius-sm);cursor:pointer;font-size:13.5px;font-weight:500;
color:var(--ink3);transition:all 0.15s;margin-bottom:2px;
}
.nav-item:hover{background:var(--cream);color:var(--ink)}
.nav-item.active{color:#fff;font-weight:600}
.nav-item .nav-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.nav-cat-oromotor .nav-item.active{background:var(--l1)}
.nav-cat-veg .nav-item.active{background:var(--veg)}
.nav-badge{margin-left:auto;font-size:10px;padding:2px 7px;border-radius:20px;font-family:'JetBrains Mono',monospace;font-weight:500}
.sidebar-footer{margin-top:auto;padding:16px 22px;border-top:1px solid var(--cream2);font-size:11.5px;color:var(--muted);line-height:1.5}
/* ─── MAIN ─── */
.main{flex:1;overflow-y:auto;min-height:100vh}
/* ─── TOP BAR ─── */
.topbar{
padding:20px 32px 16px;border-bottom:1px solid var(--cream2);
background:rgba(247,243,236,0.92);backdrop-filter:blur(8px);
position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:16px;
}
.topbar-title{font-family:'Fraunces',serif;font-size:26px;font-weight:700;color:var(--ink);flex:1}
.topbar-sub{font-size:13px;color:var(--muted);margin-top:1px}
.filter-row{display:flex;gap:8px;flex-wrap:wrap}
.filter-btn{
font-size:12px;font-weight:500;padding:6px 14px;border-radius:20px;
border:1.5px solid var(--cream3);background:#fff;color:var(--ink3);cursor:pointer;
transition:all 0.15s;font-family:'Outfit',sans-serif;
}
.filter-btn:hover{border-color:var(--ink3);color:var(--ink)}
.filter-btn.active{background:var(--ink);border-color:var(--ink);color:#fff}
/* ─── CONTENT AREA ─── */
.content{padding:32px}
/* ─── SECTION HEADER ─── */
.section-heading{
display:flex;align-items:center;gap:14px;
padding:20px 28px;border-radius:var(--radius-lg);margin-bottom:28px;
}
.section-heading.oromotor{background:linear-gradient(135deg,var(--l1b),#f0faf8)}
.section-heading.vegetative{background:linear-gradient(135deg,var(--vegb),#eaf4fd)}
.sh-icon{width:48px;height:48px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0}
.sh-icon.oromotor{background:var(--l1);color:#fff}
.sh-icon.vegetative{background:var(--veg);color:#fff}
.sh-title{font-family:'Fraunces',serif;font-size:20px;font-weight:700;color:var(--ink)}
.sh-desc{font-size:13px;color:var(--ink3);margin-top:3px;line-height:1.5}
.sh-warning{
margin-top:10px;padding:10px 14px;border-radius:var(--radius-sm);
font-size:12px;line-height:1.6;color:var(--l3d);background:var(--l3b);border-left:3px solid var(--l3);
}
.sh-info{
margin-top:10px;padding:10px 14px;border-radius:var(--radius-sm);
font-size:12px;line-height:1.6;color:var(--vegd);background:var(--vegb);border-left:3px solid var(--veg);
}
/* ─── EXERCISE GRID ─── */
.exercise-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:20px;margin-bottom:40px}
/* ─── EXERCISE CARD ─── */
.ex-card{
background:#fff;border-radius:var(--radius-lg);border:1.5px solid var(--cream2);
box-shadow:var(--shadow);overflow:hidden;transition:box-shadow 0.2s,transform 0.15s;cursor:pointer;
}
.ex-card:hover{box-shadow:var(--shadow-lg);transform:translateY(-2px)}
.ex-card.expanded{box-shadow:var(--shadow-lg)}
.ex-card-top{padding:18px 20px 14px;display:flex;align-items:flex-start;gap:14px}
.ex-num-badge{
width:40px;height:40px;border-radius:10px;display:flex;align-items:center;justify-content:center;
font-family:'Fraunces',serif;font-size:17px;font-weight:700;flex-shrink:0;
}
.ex-info{flex:1}
.ex-title{font-size:15px;font-weight:600;color:var(--ink);margin-bottom:3px}
.ex-muscle{font-size:11.5px;color:var(--muted);line-height:1.4}
.ex-meta-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:6px}
.meta-chip{
font-size:11px;font-family:'JetBrains Mono',monospace;padding:3px 9px;border-radius:6px;
font-weight:500;border:1px solid;
}
.expand-arrow{width:28px;height:28px;border-radius:50%;background:var(--cream);display:flex;align-items:center;justify-content:center;font-size:14px;transition:transform 0.25s;flex-shrink:0;margin-top:4px}
.ex-card.expanded .expand-arrow{transform:rotate(180deg)}
/* ─── ILLUSTRATION ─── */
.ex-illus{
background:var(--cream);border-top:1px solid var(--cream2);border-bottom:1px solid var(--cream2);
display:flex;align-items:center;justify-content:center;padding:20px;min-height:160px;
}
.ex-illus svg{max-width:180px;height:auto}
/* ─── STEPS ─── */
.ex-steps-wrap{padding:16px 20px;display:none}
.ex-card.expanded .ex-steps-wrap{display:block}
.step-row{display:flex;gap:12px;margin-bottom:10px;align-items:flex-start}
.step-n{
width:24px;height:24px;border-radius:6px;display:flex;align-items:center;justify-content:center;
font-size:11px;font-weight:700;flex-shrink:0;margin-top:2px;font-family:'JetBrains Mono',monospace;
}
.step-text{font-size:13.5px;color:var(--ink2);line-height:1.65}
.step-text strong{color:var(--ink);font-weight:500}
/* ─── TIP BOX ─── */
.ex-tip{
padding:10px 16px;font-size:12.5px;line-height:1.6;
display:flex;gap:8px;align-items:flex-start;margin-top:4px;
border-top:1px solid var(--cream2);border-radius:0 0 var(--radius-sm) var(--radius-sm);
}
.ex-tip.warn{background:#fff8e8;color:#7a5310}
.ex-tip.info{background:#eaf4fd;color:var(--vegd)}
.ex-tip.safe{background:#faf0f0;color:var(--l3d)}
/* ─── TIMER PANEL ─── */
.timer-panel{
background:#fff;border-radius:var(--radius-lg);border:1.5px solid var(--cream2);
padding:20px;margin-top:16px;display:none;
}
.ex-card.expanded .timer-panel{display:block}
.timer-title{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:0.8px;color:var(--muted);margin-bottom:12px}
.timer-controls{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.rep-counter{display:flex;align-items:center;gap:10px}
.rep-display{
font-family:'JetBrains Mono',monospace;font-size:28px;font-weight:500;
color:var(--ink);min-width:60px;text-align:center;
}
.rep-label{font-size:11px;color:var(--muted)}
.btn{
padding:8px 18px;border-radius:var(--radius-sm);border:none;font-family:'Outfit',sans-serif;
font-size:13px;font-weight:600;cursor:pointer;transition:all 0.15s;
}
.btn-primary{background:var(--l1);color:#fff}
.btn-primary:hover{background:var(--l1d)}
.btn-veg{background:var(--veg);color:#fff}
.btn-veg:hover{background:var(--vegd)}
.btn-outline{background:#fff;color:var(--ink);border:1.5px solid var(--cream3)}
.btn-outline:hover{border-color:var(--ink3)}
.btn-sm{padding:5px 12px;font-size:12px}
.btn-icon{width:32px;height:32px;padding:0;display:flex;align-items:center;justify-content:center;border-radius:50%;background:var(--cream);border:1.5px solid var(--cream3);cursor:pointer;font-size:16px;transition:all 0.12s}
.btn-icon:hover{background:var(--cream2)}
.hold-timer{
font-family:'JetBrains Mono',monospace;font-size:38px;font-weight:500;
color:var(--ink);text-align:center;margin:8px 0;
}
.hold-ring-wrap{position:relative;width:80px;height:80px;flex-shrink:0}
.hold-ring-wrap svg{position:absolute;top:0;left:0;transform:rotate(-90deg)}
.hold-ring-center{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-family:'JetBrains Mono',monospace;font-size:20px;font-weight:600;color:var(--ink)}
.hold-label{font-size:11px;color:var(--muted);text-align:center;margin-top:4px}
.progress-bar{height:6px;background:var(--cream2);border-radius:3px;overflow:hidden;margin-top:10px}
.progress-fill{height:100%;border-radius:3px;background:var(--l1);transition:width 0.3s}
.progress-fill.veg{background:var(--veg)}
.sets-row{display:flex;gap:6px;margin-top:8px}
.set-dot{width:10px;height:10px;border-radius:50%;background:var(--cream2);transition:background 0.2s}
.set-dot.done{background:var(--l1)}
.set-dot.done-veg{background:var(--veg)}
.status-chip{font-size:11px;font-weight:600;padding:4px 10px;border-radius:20px;font-family:'JetBrains Mono',monospace}
.status-idle{background:var(--cream);color:var(--muted);border:1px solid var(--cream3)}
.status-active{background:var(--l1b);color:var(--l1);border:1px solid var(--l1c)}
.status-active-veg{background:var(--vegb);color:var(--veg);border:1px solid var(--vegc)}
.status-done{background:#e8f8ec;color:#1a7a4a;border:1px solid #8fd4a8}
.status-rest{background:var(--l2b);color:var(--l2);border:1px solid var(--l2c)}
/* ─── INDICATION BOX ─── */
.indication-box{
background:#fff;border-radius:var(--radius-lg);border:1.5px solid var(--cream2);
padding:22px 26px;margin-bottom:32px;box-shadow:var(--shadow);
}
.ib-title{font-family:'Fraunces',serif;font-size:17px;font-weight:700;color:var(--ink);margin-bottom:14px;display:flex;align-items:center;gap:8px}
.ib-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}
.ib-block{border-radius:var(--radius-sm);padding:14px 16px;font-size:13px;line-height:1.6}
.ib-block h4{font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:0.6px;margin-bottom:6px}
.ib-block.green{background:var(--l1b);color:var(--l1d)}
.ib-block.green h4{color:var(--l1)}
.ib-block.red{background:var(--l3b);color:var(--l3d)}
.ib-block.red h4{color:var(--l3)}
.ib-block ul{padding-left:14px}
.ib-block li{margin-bottom:3px}
/* ─── PAGE SECTIONS ─── */
.page{display:none}
.page.active{display:block}
/* ─── OVERVIEW DASHBOARD ─── */
.dash-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px;margin-bottom:32px}
.dash-card{
background:#fff;border-radius:var(--radius);border:1.5px solid var(--cream2);
padding:18px 20px;box-shadow:var(--shadow);
}
.dc-num{font-family:'Fraunces',serif;font-size:32px;font-weight:700;margin-bottom:2px}
.dc-label{font-size:12.5px;color:var(--muted)}
.dc-sub{font-size:11px;color:var(--muted);margin-top:4px}
.quick-start-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:32px}
.qs-card{
background:#fff;border-radius:var(--radius-lg);border:1.5px solid var(--cream2);
padding:22px;cursor:pointer;box-shadow:var(--shadow);transition:all 0.15s;
display:flex;align-items:center;gap:16px;
}
.qs-card:hover{box-shadow:var(--shadow-lg);transform:translateY(-2px)}
.qs-icon{width:52px;height:52px;border-radius:14px;display:flex;align-items:center;justify-content:center;font-size:24px;flex-shrink:0}
.qs-icon.oro{background:linear-gradient(135deg,var(--l1b),var(--l1c))}
.qs-icon.veg{background:linear-gradient(135deg,var(--vegb),var(--vegc))}
.qs-title{font-size:15px;font-weight:600;color:var(--ink);margin-bottom:4px}
.qs-desc{font-size:12.5px;color:var(--ink3);line-height:1.5}
.qs-count{font-size:11px;font-family:'JetBrains Mono',monospace;margin-top:6px;padding:3px 8px;border-radius:6px;display:inline-block}
.qs-count.oro{background:var(--l1b);color:var(--l1)}
.qs-count.veg{background:var(--vegb);color:var(--veg)}
.welcome-banner{
background:linear-gradient(135deg,#0d4a3e,#1A6B5A 60%,#1E5C8A);
border-radius:var(--radius-lg);padding:28px 32px;margin-bottom:28px;color:#fff;
display:flex;align-items:center;gap:24px;
}
.wb-text h2{font-family:'Fraunces',serif;font-size:22px;font-weight:700;margin-bottom:6px}
.wb-text p{font-size:13.5px;opacity:0.8;line-height:1.6;max-width:480px}
.wb-icon{font-size:52px;flex-shrink:0;opacity:0.9}
/* ─── LEVEL COLORS ─── */
.c-l1{background:var(--l1b);color:var(--l1);border-color:var(--l1c)}
.c-l2{background:var(--l2b);color:var(--l2);border-color:var(--l2c)}
.c-l3{background:var(--l3b);color:var(--l3);border-color:var(--l3c)}
.c-l4{background:var(--l4b);color:var(--l4);border-color:var(--l4c)}
.c-veg{background:var(--vegb);color:var(--veg);border-color:var(--vegc)}
.n-l1{background:var(--l1b);color:var(--l1)}
.n-l2{background:var(--l2b);color:var(--l2)}
.n-l3{background:var(--l3b);color:var(--l3)}
.n-l4{background:var(--l4b);color:var(--l4)}
.n-veg{background:var(--vegb);color:var(--veg)}
.sn-l1{background:var(--l1b);color:var(--l1)}
.sn-l2{background:var(--l2b);color:var(--l2)}
.sn-l3{background:var(--l3b);color:var(--l3)}
.sn-l4{background:var(--l4b);color:var(--l4)}
.sn-veg{background:var(--vegb);color:var(--veg)}
/* ─── RESPONSIVE ─── */
@media(max-width:860px){
.sidebar{position:fixed;left:-300px;transition:left 0.3s}
.sidebar.open{left:0}
.main{margin-left:0;width:100%}
.ib-grid{grid-template-columns:1fr}
.quick-start-grid{grid-template-columns:1fr}
.exercise-grid{grid-template-columns:1fr}
.content{padding:16px}
.topbar{padding:14px 16px}
.topbar-title{font-size:20px}
}
.menu-btn{display:none;background:none;border:none;font-size:22px;cursor:pointer;color:var(--ink)}
@media(max-width:860px){.menu-btn{display:flex;align-items:center}}
.overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.35);z-index:90}
.overlay.open{display:block}
</style>
</head>
<body>
<div class="overlay" id="overlay" onclick="closeSidebar()"></div>
<!-- SIDEBAR -->
<nav class="sidebar" id="sidebar">
<div class="sidebar-brand">
<div class="brand-logo">
<div class="logo-mark">🫦</div>
<div>
<div class="brand-name">OroTherapy</div>
<div class="brand-sub">SLP Exercise Library</div>
</div>
</div>
</div>
<div class="nav-section">
<div class="nav-label">Overview</div>
<div class="nav-item active" onclick="showPage('dashboard')" id="nav-dashboard">
<div class="nav-dot" style="background:#1A6B5A"></div> Dashboard
</div>
</div>
<div class="nav-section nav-cat-oromotor">
<div class="nav-label">Oromotor Exercises</div>
<div class="nav-item" onclick="showPage('oro-l1')" id="nav-oro-l1">
<div class="nav-dot" style="background:var(--l1)"></div> Level 1 — Lips
<span class="nav-badge c-l1">3 ex</span>
</div>
<div class="nav-item" onclick="showPage('oro-l2')" id="nav-oro-l2">
<div class="nav-dot" style="background:var(--l2)"></div> Level 2 — Jaw
<span class="nav-badge c-l2">2 ex</span>
</div>
<div class="nav-item" onclick="showPage('oro-l3')" id="nav-oro-l3">
<div class="nav-dot" style="background:var(--l3)"></div> Level 3 — Tongue
<span class="nav-badge c-l3">3 ex</span>
</div>
<div class="nav-item" onclick="showPage('oro-l4')" id="nav-oro-l4">
<div class="nav-dot" style="background:var(--l4)"></div> Level 4 — Integration
<span class="nav-badge c-l4">2 ex</span>
</div>
</div>
<div class="nav-section nav-cat-veg">
<div class="nav-label">Vegetative Skills</div>
<div class="nav-item" onclick="showPage('veg')" id="nav-veg">
<div class="nav-dot" style="background:var(--veg)"></div> All Vegetative Exercises
<span class="nav-badge c-veg">5 ex</span>
</div>
</div>
<div class="sidebar-footer">
<strong>Clinical Note:</strong> Oromotor exercises are indicated for clients with confirmed neuromuscular weakness. Not for functional articulation or phonological disorders in isolation.
</div>
</nav>
<!-- MAIN -->
<div class="main">
<!-- TOP BAR -->
<div class="topbar" id="topbar">
<button class="menu-btn" onclick="openSidebar()">☰</button>
<div>
<div class="topbar-title" id="page-title">Dashboard</div>
<div class="topbar-sub" id="page-sub">OroTherapy — Oromotor & Vegetative Skills Library</div>
</div>
</div>
<!-- CONTENT -->
<div class="content">
<!-- ═══════════════════════════════ DASHBOARD ═══════════════════════════════ -->
<div class="page active" id="page-dashboard">
<div class="welcome-banner">
<div class="wb-icon">🫦</div>
<div class="wb-text">
<h2>Oromotor & Vegetative Skills Library</h2>
<p>A structured SLP exercise reference covering lip, jaw, and tongue strengthening through 4 progressive levels, plus vegetative skills for swallowing readiness.</p>
</div>
</div>
<div class="dash-grid">
<div class="dash-card">
<div class="dc-num" style="color:var(--l1)">10</div>
<div class="dc-label">Oromotor Exercises</div>
<div class="dc-sub">Levels 1–4 · Lips, Jaw, Tongue</div>
</div>
<div class="dash-card">
<div class="dc-num" style="color:var(--veg)">5</div>
<div class="dc-label">Vegetative Exercises</div>
<div class="dc-sub">Chewing · Swallowing readiness</div>
</div>
<div class="dash-card">
<div class="dc-num" style="color:var(--l2)">4</div>
<div class="dc-label">Progressive Levels</div>
<div class="dc-sub">Isolated → Integrated speech</div>
</div>
<div class="dash-card">
<div class="dc-num" style="color:var(--l4)">⏱</div>
<div class="dc-label">Built-in Timers</div>
<div class="dc-sub">Rep & hold tracking per exercise</div>
</div>
</div>
<div class="quick-start-grid">
<div class="qs-card" onclick="showPage('oro-l1')">
<div class="qs-icon oro">💪</div>
<div>
<div class="qs-title">Oromotor Exercises</div>
<div class="qs-desc">Progressive lip, jaw, and tongue strengthening exercises with illustrated instructions and hold timers.</div>
<span class="qs-count oro">10 exercises · 4 levels</span>
</div>
</div>
<div class="qs-card" onclick="showPage('veg')">
<div class="qs-icon veg">🌊</div>
<div>
<div class="qs-title">Vegetative Skills</div>
<div class="qs-desc">Rotary chewing, tongue lateralization, buccal tone, saliva swallowing, and laryngeal elevation exercises.</div>
<span class="qs-count veg">5 exercises</span>
</div>
</div>
</div>
<div class="indication-box">
<div class="ib-title">🩺 Clinical Indication Guide</div>
<div class="ib-grid">
<div class="ib-block green">
<h4>✓ Appropriate for</h4>
<ul>
<li>Flaccid dysarthria (post-stroke, TBI)</li>
<li>Cerebral palsy with hypotonia</li>
<li>Pediatric feeding difficulties</li>
<li>Post-surgical oral weakness</li>
<li>Neurodegenerative conditions (ALS, MS)</li>
</ul>
</div>
<div class="ib-block red">
<h4>✗ Not a substitute for</h4>
<ul>
<li>Functional articulation disorders</li>
<li>Phonological process therapy</li>
<li>Motor speech programming (AOS)</li>
<li>Clients without confirmed weakness</li>
<li>Phonological awareness training</li>
</ul>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════ LEVEL 1 — LIPS ═══════════════════════════════ -->
<div class="page" id="page-oro-l1">
<div class="section-heading oromotor">
<div class="sh-icon oromotor">💋</div>
<div>
<div class="sh-title">Level 1 — Lip Strengthening</div>
<div class="sh-desc">Orbicularis oris, levator labii, depressor labii · Foundation for bilabial consonants /p/, /b/, /m/ and vowel rounding</div>
<div class="sh-warning">⚠️ Progress to Level 2 only after client achieves 80% accuracy with sustained hold on all Level 1 exercises across 2 consecutive sessions.</div>
</div>
</div>
<div class="exercise-grid" id="grid-l1"></div>
</div>
<!-- ═══════════════════════════════ LEVEL 2 — JAW ═══════════════════════════════ -->
<div class="page" id="page-oro-l2">
<div class="section-heading oromotor" style="background:linear-gradient(135deg,var(--l2b),#fdf4e3)">
<div class="sh-icon" style="background:var(--l2);color:#fff">🦷</div>
<div>
<div class="sh-title">Level 2 — Jaw Grading & Range</div>
<div class="sh-desc">Masseter, medial pterygoid, lateral pterygoid, digastric · Jaw excursion and graded control for vowel articulation</div>
<div class="sh-warning">⚠️ Do not perform resistive jaw exercises if the client has TMJ pain, trismus, cervical spine instability, or rheumatoid arthritis. Consult physician or dentist before introducing resistance.</div>
</div>
</div>
<div class="exercise-grid" id="grid-l2"></div>
</div>
<!-- ═══════════════════════════════ LEVEL 3 — TONGUE ═══════════════════════════════ -->
<div class="page" id="page-oro-l3">
<div class="section-heading oromotor" style="background:linear-gradient(135deg,var(--l3b),#fdf0f0)">
<div class="sh-icon" style="background:var(--l3);color:#fff">👅</div>
<div>
<div class="sh-title">Level 3 — Tongue Mobility & Strength</div>
<div class="sh-desc">Genioglossus, hyoglossus, styloglossus, intrinsic tongue muscles · Precision and range for alveolar, palatal, and velar consonants</div>
<div class="sh-warning">⚠️ Tongue exercises should be introduced before tongue tip elevation as tongue lateralization is generally an earlier developmental skill.</div>
</div>
</div>
<div class="exercise-grid" id="grid-l3"></div>
</div>
<!-- ═══════════════════════════════ LEVEL 4 — INTEGRATION ═══════════════════════════════ -->
<div class="page" id="page-oro-l4">
<div class="section-heading oromotor" style="background:linear-gradient(135deg,var(--l4b),#f0eeff)">
<div class="sh-icon" style="background:var(--l4);color:#fff">🗣️</div>
<div>
<div class="sh-title">Level 4 — Speech Integration</div>
<div class="sh-desc">Applying motor gains to real speech · Transition: isolated movement → phoneme → word → phrase → connected speech</div>
<div class="sh-info">ℹ️ Bridge exercises are only effective after adequate muscle activation is established at Levels 1–3. Dosage: 5–10 minutes daily, structured as imitation → delayed production → spontaneous production.</div>
</div>
</div>
<div class="exercise-grid" id="grid-l4"></div>
</div>
<!-- ═══════════════════════════════ VEGETATIVE ═══════════════════════════════ -->
<div class="page" id="page-veg">
<div class="section-heading vegetative">
<div class="sh-icon vegetative">🌊</div>
<div>
<div class="sh-title">Vegetative Skills Strengthening</div>
<div class="sh-desc">Functional feeding and swallowing preparation · Rotary chewing, tongue lateralization, buccal tone, laryngeal elevation</div>
<div class="sh-warning">⚠️ All food-based chewing exercises must only be introduced after safe swallowing is confirmed by clinical or instrumental assessment (MBS/FEES). Use non-food items (gauze, chew tubes) as initial tools.</div>
</div>
</div>
<div class="exercise-grid" id="grid-veg"></div>
</div>
</div><!-- /content -->
</div><!-- /main -->
<script>
// ─── DATA ───────────────────────────────────────────────────────────────────────
const exercises = {
'l1': [
{
id:'l1-1', level:'l1', title:'Lip Press & Hold',
muscles:'Orbicularis oris · Levator labii · Depressor labii',
reps:10, sets:3, holdSec:5,
chips:['10 reps','3 sets','5 sec hold'],
steps:[
{n:'1', text:'Bring lips together into a <strong>firm, flat seal</strong>. No puffing of cheeks — maintain even, symmetrical pressure.'},
{n:'2', text:'Press upper and lower lips hard against each other. <strong>Feel the orbicularis oris working.</strong> Hold for 5 seconds.'},
{n:'3', text:'Release completely. Rest 2 seconds. Repeat.'},
{n:'4', text:'Progress to holding <strong>10 seconds</strong> as lip strength increases over sessions.'},
],
tip:{type:'info', text:'Resistance upgrade: Place a clean tongue depressor between the lips and try to hold it in place while the clinician gently attempts to pull it out.'},
illus: `<svg viewBox="0 0 180 160" xmlns="http://www.w3.org/2000/svg" aria-label="Lips pressed together in firm seal">
<ellipse cx="90" cy="80" rx="70" ry="50" fill="#FDDBB4" opacity="0.6"/>
<path d="M40 78 Q90 68 140 78" stroke="#8B3A35" stroke-width="4" fill="none" stroke-linecap="round"/>
<path d="M40 78 Q90 68 140 78 Q120 73 90 72 Q60 73 40 78 Z" fill="#C0605A"/>
<path d="M40 82 Q90 92 140 82" stroke="#8B3A35" stroke-width="4" fill="none" stroke-linecap="round"/>
<path d="M40 82 Q90 92 140 82 Q120 87 90 88 Q60 87 40 82 Z" fill="#C0605A"/>
<path d="M36 80 L20 80" stroke="#1A6B5A" stroke-width="2.5" stroke-linecap="round" marker-end="url(#arR1)"/>
<path d="M144 80 L160 80" stroke="#1A6B5A" stroke-width="2.5" stroke-linecap="round" marker-end="url(#arL1)"/>
<defs>
<marker id="arR1" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><polygon points="0,0 6,3 0,6" fill="#1A6B5A"/></marker>
<marker id="arL1" markerWidth="6" markerHeight="6" refX="1" refY="3" orient="auto"><polygon points="6,0 0,3 6,6" fill="#1A6B5A"/></marker>
</defs>
<rect x="20" y="130" width="140" height="14" rx="4" fill="#E4F4F0"/>
<text x="90" y="141" text-anchor="middle" font-size="10" font-weight="600" fill="#1A6B5A" font-family="Outfit,sans-serif">PRESS firmly · even seal</text>
</svg>`
},
{
id:'l1-2', level:'l1', title:'Lip Stretch (Smile–Round)',
muscles:'Zygomaticus major · Orbicularis oris · Risorius',
reps:10, sets:2, holdSec:3,
chips:['10 reps','2 sets','3 sec each'],
steps:[
{n:'1', text:'<strong>Smile position:</strong> Retract lips horizontally as wide as possible — "eeeee" shape. Hold 3 seconds.'},
{n:'2', text:'<strong>Round position:</strong> Push lips forward into a tight "O" shape — "oooo". Hold 3 seconds.'},
{n:'3', text:'Alternate smoothly between smile and round without pausing in between. 1 rep = both positions.'},
{n:'4', text:'Focus on full range of motion. Reduce range before increasing speed.'},
],
tip:{type:'info', text:'Use a mirror so the client can monitor symmetry. Asymmetry (one side lagging) indicates unilateral weakness and should be documented.'},
illus: `<svg viewBox="0 0 180 160" xmlns="http://www.w3.org/2000/svg" aria-label="Lips alternating between smile and round positions">
<ellipse cx="52" cy="80" rx="38" ry="30" fill="#FDDBB4" opacity="0.55"/>
<path d="M22 80 Q52 72 82 80 Q66 88 52 89 Q38 88 22 80 Z" fill="#C0605A"/>
<path d="M22 80 Q52 72 82 80" stroke="#8B3A35" stroke-width="2" fill="none"/>
<path d="M20 78 L16 74 M20 82 L16 86" stroke="#8B3A35" stroke-width="1.5"/>
<path d="M82 78 L86 74 M82 82 L86 86" stroke="#8B3A35" stroke-width="1.5"/>
<text x="52" y="122" text-anchor="middle" font-size="10" fill="#8B5E1A" font-family="Outfit">SMILE</text>
<text x="128" y="122" text-anchor="middle" font-size="10" fill="#1A6B5A" font-family="Outfit">ROUND</text>
<text x="90" y="80" text-anchor="middle" font-size="16" fill="#9A9080">⟺</text>
<ellipse cx="128" cy="80" rx="20" ry="28" fill="#FDDBB4" opacity="0.55"/>
<ellipse cx="128" cy="84" rx="14" ry="18" fill="#C0605A" opacity="0.8"/>
<circle cx="128" cy="84" r="8" fill="#8B3A35" opacity="0.6"/>
</svg>`
},
{
id:'l1-3', level:'l1', title:'Lip Pucker & Release',
muscles:'Orbicularis oris · Mentalis',
reps:12, sets:2, holdSec:4,
chips:['12 reps','2 sets','4 sec hold'],
steps:[
{n:'1', text:'<strong>Pucker:</strong> Push lips forward in a tight pucker — like blowing a kiss or whistling. Hold 4 seconds.'},
{n:'2', text:'<strong>Release:</strong> Relax completely. Do not open the mouth — simply release the pucker.'},
{n:'3', text:'Ensure cheeks do not puff. Tension should be centered in the lips only.'},
{n:'4', text:'As strength improves, attempt to pucker and retract lips quickly 5 times in a row (speed drill).'},
],
tip:{type:'info', text:'Pucker strength contributes to /w/ production and liquid management during feeding. Combine with lip press for full orbicularis circuit.'},
illus: `<svg viewBox="0 0 180 160" xmlns="http://www.w3.org/2000/svg" aria-label="Tight lip pucker forward">
<ellipse cx="90" cy="78" rx="60" ry="42" fill="#FDDBB4" opacity="0.6"/>
<ellipse cx="90" cy="82" rx="22" ry="28" fill="#C0605A" opacity="0.85"/>
<circle cx="90" cy="82" r="11" fill="#8B3A35" opacity="0.7"/>
<circle cx="90" cy="82" r="5" fill="#2C1810" opacity="0.4"/>
<path d="M44 80 L64 80" stroke="#1A6B5A" stroke-width="2" stroke-linecap="round" marker-end="url(#arPuck)"/>
<path d="M136 80 L116 80" stroke="#1A6B5A" stroke-width="2" stroke-linecap="round" marker-end="url(#arPuck2)"/>
<defs>
<marker id="arPuck" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><polygon points="0,0 6,3 0,6" fill="#1A6B5A"/></marker>
<marker id="arPuck2" markerWidth="6" markerHeight="6" refX="1" refY="3" orient="auto"><polygon points="6,0 0,3 6,6" fill="#1A6B5A"/></marker>
</defs>
<rect x="20" y="130" width="140" height="14" rx="4" fill="#E4F4F0"/>
<text x="90" y="141" text-anchor="middle" font-size="10" font-weight="600" fill="#1A6B5A" font-family="Outfit,sans-serif">PUCKER forward · hold 4 sec</text>
</svg>`
}
],
'l2': [
{
id:'l2-1', level:'l2', title:'Jaw Open–Close with Grading',
muscles:'Masseter · Medial pterygoid · Lateral pterygoid · Temporalis',
reps:8, sets:3, holdSec:0,
chips:['8 reps','3 sets','graded range'],
steps:[
{n:'1', text:'Begin with mouth <strong>closed and relaxed</strong>. Jaw should be in neutral, not clenched.'},
{n:'2', text:'Open jaw <strong>slowly to ¼ range</strong>. Hold 1 second. Continue to ½ range. Hold 1 second. Continue to maximum comfortable opening. Hold 2 seconds.'},
{n:'3', text:'Close jaw slowly, reversing through each position. Avoid snapping shut.'},
{n:'4', text:'Goal: controlled, smooth excursion — no jaw deviation to either side. Use mirror for feedback.'},
],
tip:{type:'warn', text:'Stop immediately if client reports clicking, pain, or locking. Document and refer for TMJ evaluation before continuing jaw exercises.'},
illus: `<svg viewBox="0 0 180 180" xmlns="http://www.w3.org/2000/svg" aria-label="Jaw opening with graded range markers">
<ellipse cx="90" cy="84" rx="60" ry="52" fill="#FDDBB4" opacity="0.7"/>
<path d="M50 78 Q90 68 130 78 Q115 74 90 73 Q65 74 50 78Z" fill="#C0605A"/>
<path d="M60 98 Q90 108 120 98 Q106 104 90 105 Q74 104 60 98Z" fill="#C0605A"/>
<path d="M60 95 Q90 120 120 95" stroke="#8B3A35" stroke-width="1.5" fill="none" stroke-dasharray="4 3"/>
<path d="M76 130 L76 110" stroke="#8B5E1A" stroke-width="1.8" stroke-dasharray="3 3"/>
<path d="M90 136 L90 110" stroke="#8B5E1A" stroke-width="1.8" stroke-dasharray="3 3"/>
<path d="M104 130 L104 110" stroke="#8B5E1A" stroke-width="1.8" stroke-dasharray="3 3"/>
<text x="68" y="148" text-anchor="middle" font-size="9" fill="#8B5E1A" font-family="Outfit">¼</text>
<text x="90" y="152" text-anchor="middle" font-size="9" fill="#8B5E1A" font-family="Outfit">½</text>
<text x="112" y="148" text-anchor="middle" font-size="9" fill="#8B5E1A" font-family="Outfit">max</text>
<defs><marker id="arJaw" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto"><polygon points="0,0 6,3 0,6" fill="#8B5E1A"/></marker></defs>
<path d="M90 108 L90 122" stroke="#8B5E1A" stroke-width="2" marker-end="url(#arJaw)"/>
<rect x="18" y="162" width="144" height="14" rx="4" fill="#F7EDD8"/>
<text x="90" y="173" text-anchor="middle" font-size="10" font-weight="600" fill="#8B5E1A" font-family="Outfit,sans-serif">Graded open · smooth close</text>
</svg>`
},
{
id:'l2-2', level:'l2', title:'Resistive Jaw Opening',
muscles:'Lateral pterygoid · Digastric · Mylohyoid',
reps:10, sets:2, holdSec:3,
chips:['10 reps','2 sets','3 sec resist'],
steps:[
{n:'1', text:'<strong>Clinician places two fingers under the chin</strong> (or client uses own hand). Apply firm upward resistance.'},
{n:'2', text:'Client opens jaw slowly against the resistance. Hold at maximum comfortable opening for 3 seconds.'},
{n:'3', text:'Clinician maintains steady pressure throughout. Do not allow the jaw to snap open.'},
{n:'4', text:'Close jaw slowly. Rest 2 seconds. Repeat.'},
],
tip:{type:'safe', text:'⚠️ Contraindications: TMJ disorder, trismus, cervical spine instability (rheumatoid arthritis, Down syndrome, ALS), post-surgical jaw fixation. Confirm with physician before introducing resistance.'},
illus: `<svg viewBox="0 0 180 180" xmlns="http://www.w3.org/2000/svg" aria-label="Hand applying resistance under chin during jaw opening">
<ellipse cx="90" cy="72" rx="58" ry="50" fill="#FDDBB4" opacity="0.8"/>
<path d="M44 68 Q90 58 136 68 Q118 64 90 63 Q62 64 44 68Z" fill="#C0605A"/>
<path d="M56 96 Q90 106 124 96" stroke="#8B3A35" stroke-width="2.5" fill="none"/>
<path d="M56 96 Q90 106 124 96 Q108 112 90 113 Q72 112 56 96Z" fill="#C0605A"/>
<rect x="64" y="120" width="52" height="18" rx="9" fill="#FDDBB4" stroke="#D4956A" stroke-width="1.5"/>
<circle cx="72" cy="129" r="4" fill="#D4956A" opacity="0.7"/>
<circle cx="82" cy="129" r="4" fill="#D4956A" opacity="0.7"/>
<circle cx="92" cy="129" r="4" fill="#D4956A" opacity="0.7"/>
<circle cx="102" cy="129" r="4" fill="#D4956A" opacity="0.7"/>
<defs><marker id="arResist" markerWidth="5" markerHeight="5" refX="2.5" refY="1" orient="auto"><polygon points="0,5 2.5,0 5,5" fill="#9B2C2C"/></marker></defs>
<path d="M90 138 L90 128" stroke="#9B2C2C" stroke-width="2.5" stroke-linecap="round" marker-end="url(#arResist)"/>
<text x="90" y="157" text-anchor="middle" font-size="9" fill="#9B2C2C" font-weight="600" font-family="Outfit">RESIST ↑ while opening ↓</text>
<rect x="18" y="162" width="144" height="14" rx="4" fill="#FAE8E8"/>
<text x="90" y="173" text-anchor="middle" font-size="10" font-weight="600" fill="#9B2C2C" font-family="Outfit,sans-serif">Resistive · 3 sec hold</text>
</svg>`
}
],
'l3': [
{
id:'l3-1', level:'l3', title:'Tongue Lateralization',
muscles:'Styloglossus · Transverse intrinsic · Palatoglossus',
reps:10, sets:3, holdSec:3,
chips:['10 reps','3 sets','3 sec hold'],
steps:[
{n:'1', text:'With mouth slightly open, move tongue tip slowly to the <strong>right corner</strong> of the mouth. Hold 3 seconds.'},
{n:'2', text:'Return tongue to center. Hold 1 second.'},
{n:'3', text:'Move tongue tip to the <strong>left corner</strong>. Hold 3 seconds. Return to center.'},
{n:'4', text:'1 rep = right + left. Maintain tongue tip position — avoid tongue body moving instead.'},
],
tip:{type:'info', text:'Tongue lateralization is a prerequisite skill for rotary chewing and bolus management. Master lateral movement before advancing to tongue tip elevation.'},
illus: `<svg viewBox="0 0 180 160" xmlns="http://www.w3.org/2000/svg" aria-label="Tongue lateralizing to right corner of mouth">
<ellipse cx="90" cy="78" rx="62" ry="44" fill="#FDDBB4" opacity="0.7"/>
<path d="M36 76 Q90 64 144 76 Q126 70 90 69 Q54 70 36 76Z" fill="#C0605A"/>
<path d="M36 82 Q90 94 144 82" stroke="#8B3A35" stroke-width="2" fill="none"/>
<path d="M68 82 Q100 78 134 80" stroke="#E8A0A0" stroke-width="8" stroke-linecap="round" fill="none"/>
<circle cx="134" cy="80" r="8" fill="#D08080" stroke="#9B2C2C" stroke-width="2"/>
<defs><marker id="arLat" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><polygon points="0,0 6,3 0,6" fill="#9B2C2C"/></marker></defs>
<path d="M108 78 L128 78" stroke="#9B2C2C" stroke-width="2" stroke-linecap="round" marker-end="url(#arLat)"/>
<rect x="20" y="130" width="140" height="14" rx="4" fill="#FAE8E8"/>
<text x="90" y="141" text-anchor="middle" font-size="10" font-weight="600" fill="#9B2C2C" font-family="Outfit,sans-serif">Lateral sweep · hold at corner</text>
</svg>`
},
{
id:'l3-2', level:'l3', title:'Tongue Tip Elevation (Alveolar)',
muscles:'Genioglossus (anterior) · Superior longitudinal intrinsic',
reps:10, sets:3, holdSec:3,
chips:['10 reps','3 sets','3 sec hold'],
steps:[
{n:'1', text:'With mouth slightly open, <strong>elevate tongue tip</strong> to touch the alveolar ridge — the bony bump just behind the upper front teeth.'},
{n:'2', text:'Maintain firm, precise contact. Hold 3 seconds. No jaw movement.'},
{n:'3', text:'Lower tongue tip to resting position. Rest 1 second. Repeat.'},
{n:'4', text:'Progress: tap quickly 10× (for /t/, /d/ phoneme precision), then add voiced airflow for sustained /n/ sound.'},
],
tip:{type:'info', text:'Tongue tip alveolar contact is the placement for /t/, /d/, /n/, /l/. Precise placement here translates directly to consonant clarity.'},
illus: `<svg viewBox="0 0 180 160" xmlns="http://www.w3.org/2000/svg" aria-label="Side profile showing tongue tip touching alveolar ridge">
<path d="M148 14 Q162 34 164 60 Q168 90 161 118 Q150 148 132 164 Q112 178 92 180 Q68 180 52 166 Q36 152 34 130 Q30 106 36 86 Q30 66 34 48 Q42 26 58 16 Q76 6 100 6 Q126 6 148 14 Z" fill="#FDDBB4" opacity="0.6" transform="scale(0.78) translate(18,0)"/>
<path d="M102 108 Q124 103 148 110 Q154 116 150 124" stroke="#C87D50" stroke-width="3" fill="none" stroke-linecap="round" transform="scale(0.78) translate(18,0)"/>
<ellipse cx="128" cy="88" rx="8" ry="6" fill="#9B2C2C" opacity="0.85" transform="scale(0.78) translate(18,0)"/>
<text x="122" y="72" text-anchor="middle" font-size="9" fill="#9B2C2C" font-family="Outfit" transform="scale(0.78) translate(18,0)">alveolar ridge</text>
<path d="M58 144 Q80 136 108 128 Q122 122 136 115 Q142 111 146 109" fill="#E8A0A0" stroke="#C07070" stroke-width="1.5" transform="scale(0.78) translate(18,0)"/>
<circle cx="128" cy="88" r="7" fill="#D08080" stroke="#9B2C2C" stroke-width="2" transform="scale(0.78) translate(18,0)"/>
<rect x="18" y="132" width="144" height="14" rx="4" fill="#FAE8E8"/>
<text x="90" y="143" text-anchor="middle" font-size="10" font-weight="600" fill="#9B2C2C" font-family="Outfit,sans-serif">Tongue tip → alveolar ridge</text>
</svg>`
},
{
id:'l3-3', level:'l3', title:'Tongue Protrusion & Retraction',
muscles:'Genioglossus · Hyoglossus · Styloglossus',
reps:10, sets:2, holdSec:3,
chips:['10 reps','2 sets','3 sec each'],
steps:[
{n:'1', text:'<strong>Protrude:</strong> Extend tongue as far forward as possible beyond the lips. Hold 3 seconds.'},
{n:'2', text:'<strong>Retract:</strong> Pull tongue as far back as comfortable into the mouth. Hold 3 seconds.'},
{n:'3', text:'1 rep = protrude + retract. Maintain midline position — avoid lateral deviation.'},
{n:'4', text:'Use a flat tongue blade lightly placed on the tongue tip as a tactile target to touch during protrusion.'},
],
tip:{type:'info', text:'Full range protrusion-retraction builds the range of motion required for posterior vowels and velar consonants /k/ and /g/.'},
illus: `<svg viewBox="0 0 180 160" xmlns="http://www.w3.org/2000/svg" aria-label="Tongue protruding forward beyond lips">
<ellipse cx="90" cy="78" rx="60" ry="42" fill="#FDDBB4" opacity="0.6"/>
<path d="M40 76 Q90 66 140 76 Q122 70 90 69 Q58 70 40 76Z" fill="#C0605A"/>
<path d="M40 84 Q90 94 140 84" stroke="#8B3A35" stroke-width="2" fill="none"/>
<path d="M68 80 Q90 84 112 80 Q122 78 148 78" fill="#E8A0A0" stroke="#C07070" stroke-width="7" stroke-linecap="round"/>
<defs><marker id="arProt" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><polygon points="0,0 6,3 0,6" fill="#9B2C2C"/></marker></defs>
<path d="M132 78 L152 78" stroke="#9B2C2C" stroke-width="2" marker-end="url(#arProt)"/>
<rect x="18" y="130" width="144" height="14" rx="4" fill="#FAE8E8"/>
<text x="90" y="141" text-anchor="middle" font-size="10" font-weight="600" fill="#9B2C2C" font-family="Outfit,sans-serif">Protrude → hold → retract</text>
</svg>`
}
],
'l4': [
{
id:'l4-1', level:'l4', title:'Phoneme Integration Drill',
muscles:'Speech integration · Motor planning · Articulatory precision',
reps:5, sets:3, holdSec:0,
chips:['5 min daily','3 complexity tiers'],
steps:[
{n:'1', text:'<strong>Tier 1 — Phoneme:</strong> Produce the target phoneme in isolation 5× (e.g., /t/, /l/, /m/). Clinician models, client imitates immediately.'},
{n:'2', text:'<strong>Tier 2 — Word:</strong> Produce 5 words containing the phoneme in initial, medial, and final positions. Use immediate imitation, then delayed.'},
{n:'3', text:'<strong>Tier 3 — Phrase:</strong> Produce 3–5 carrier phrases embedding the target word. Move to spontaneous generation when 80% accuracy is stable.'},
{n:'4', text:'Criterion for level advancement: 80% accuracy across 2 consecutive sessions at each tier.'},
],
tip:{type:'info', text:'Phoneme integration is the bridge between oromotor exercise and functional speech. Do not advance to this level if Level 1–3 exercises are not yet mastered.'},
illus: `<svg viewBox="0 0 180 160" xmlns="http://www.w3.org/2000/svg" aria-label="Person speaking with sound waves showing levels of production">
<ellipse cx="78" cy="48" rx="46" ry="42" fill="#4A3728"/>
<ellipse cx="78" cy="88" rx="44" ry="52" fill="#F0C090"/>
<path d="M56 110 Q78 106 100 110 Q90 120 78 121 Q66 120 56 110 Z" fill="#C0605A"/>
<path d="M108 112 Q118 104 118 114 Q118 124 108 118" stroke="#3D2A8A" stroke-width="2" fill="none"/>
<path d="M122 106 Q136 96 136 114 Q136 132 122 124" stroke="#3D2A8A" stroke-width="1.5" fill="none" opacity="0.55"/>
<path d="M139 100 Q157 88 157 114 Q157 140 139 130" stroke="#3D2A8A" stroke-width="1" fill="none" opacity="0.3"/>
<text x="38" y="148" text-anchor="middle" font-size="9" fill="#3D2A8A" font-weight="600" font-family="Outfit">/t/</text>
<text x="78" y="148" text-anchor="middle" font-size="9" fill="#3D2A8A" font-weight="600" font-family="Outfit">word</text>
<text x="118" y="148" text-anchor="middle" font-size="9" fill="#3D2A8A" font-weight="600" font-family="Outfit">phrase</text>
<rect x="18" y="152" width="144" height="6" rx="3" fill="#EDEAF8"/>
<rect x="18" y="152" width="48" height="6" rx="3" fill="#3D2A8A" opacity="0.5"/>
</svg>`
},
{
id:'l4-2', level:'l4', title:'Connected Speech Carryover',
muscles:'Generalisation · Real-world transfer · Prosody',
reps:0, sets:0, holdSec:0,
chips:['5 min daily','3 contexts'],
steps:[
{n:'1', text:'<strong>Context 1 — Structured reading:</strong> Read a paragraph aloud. Client self-monitors target phoneme accuracy. Clinician marks errors.'},
{n:'2', text:'<strong>Context 2 — Structured conversation:</strong> Clinician asks open questions on a familiar topic. Client responds in 2–3 sentence turns.'},
{n:'3', text:'<strong>Context 3 — Naturalistic:</strong> Client names what they see in a picture scene or describes a route. No overt monitoring.'},
{n:'4', text:'Compare accuracy across the three contexts. Generalization is achieved when accuracy in Context 3 matches Contexts 1 and 2.'},
],
tip:{type:'info', text:'Home practice: Assign 2–3 target words for the week. Family members are briefed to prompt gently when errors occur in natural conversation.'},
illus: `<svg viewBox="0 0 180 160" xmlns="http://www.w3.org/2000/svg" aria-label="Two people conversing with speech bubble and generalization arrows">
<ellipse cx="52" cy="46" rx="30" ry="28" fill="#4A3728"/>
<ellipse cx="52" cy="78" rx="28" ry="36" fill="#F0C090"/>
<ellipse cx="130" cy="46" rx="30" ry="28" fill="#2C1810"/>
<ellipse cx="130" cy="78" rx="28" ry="36" fill="#FDDBB4"/>
<rect x="65" y="30" width="52" height="26" rx="8" fill="#EDEAF8" stroke="#B0A4E0" stroke-width="1.5"/>
<text x="91" y="44" text-anchor="middle" font-size="9" fill="#3D2A8A" font-family="Outfit" font-weight="500">sentence ...</text>
<path d="M66 43 L63 50 L72 44" fill="#EDEAF8" stroke="#B0A4E0" stroke-width="1"/>
<path d="M72 108 L90 108" stroke="#1A6B5A" stroke-width="1.5" stroke-dasharray="4 3" stroke-linecap="round"/>
<defs><marker id="arGen" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><polygon points="0,0 6,3 0,6" fill="#1A6B5A"/></marker></defs>
<path d="M96 108 L108 108" stroke="#1A6B5A" stroke-width="1.5" marker-end="url(#arGen)"/>
<rect x="18" y="132" width="144" height="14" rx="4" fill="#EDEAF8"/>
<text x="90" y="143" text-anchor="middle" font-size="10" font-weight="600" fill="#3D2A8A" font-family="Outfit,sans-serif">Structured → Spontaneous</text>
</svg>`
}
],
'veg': [
{
id:'veg-1', level:'veg', title:'Rotary Chewing & Tongue Lateralization',
muscles:'Masseter · Pterygoids · Tongue intrinsics · Buccinator',
reps:20, sets:1, holdSec:0,
chips:['20 cycles','gauze/chew tube','rotary pattern'],
steps:[
{n:'A', text:'<strong>Gauze chewing:</strong> Wet a folded gauze pad and place on the molars. Perform 20 rhythmic chewing cycles with tongue pushing food to each side alternately. Builds rotary jaw pattern safely without food.'},
{n:'B', text:'<strong>Tongue lateralization to teeth:</strong> After each chew cycle, tongue sweeps across the lower gum line to clear residue. 5 sweeps per side.'},
{n:'C', text:'Progress to graded chew tube resistance when gauze is well-tolerated for 3 consecutive sessions.'},
{n:'D', text:'Only introduce real food after instrumental swallowing assessment (MBS/FEES) confirms safety.'},
],
tip:{type:'safe', text:'⚠️ Food-based chewing must NOT be introduced until safe swallowing is confirmed. Use gauze or chew tubes exclusively as initial tools.'},
illus: `<svg viewBox="0 0 180 160" xmlns="http://www.w3.org/2000/svg" aria-label="Rotary chewing motion with tongue lateralizing food">
<ellipse cx="90" cy="78" rx="62" ry="48" fill="#FDDBB4" opacity="0.7"/>
<path d="M36 76 Q90 64 144 76 Q126 70 90 69 Q54 70 36 76Z" fill="#C0605A"/>
<path d="M40 86 Q90 100 140 86" stroke="#8B3A35" stroke-width="2" fill="none"/>
<path d="M58 86 Q80 82 108 84" stroke="#E8A0A0" stroke-width="7" stroke-linecap="round" fill="none"/>
<circle cx="108" cy="84" r="7" fill="#D08080" stroke="#C07070"/>
<path d="M140 100 Q154 88 150 74 Q144 62 134 60 Q122 58 116 64" stroke="#8B5E1A" stroke-width="2" fill="none" stroke-dasharray="5 3"/>
<defs><marker id="arRot2" markerWidth="5" markerHeight="5" refX="2.5" refY="2.5" orient="auto"><polygon points="0,0 5,2.5 0,5" fill="#8B5E1A"/></marker></defs>
<path d="M116 65 L110 70" stroke="#8B5E1A" stroke-width="2" marker-end="url(#arRot2)"/>
<text x="148" y="84" font-size="8" fill="#8B5E1A" font-family="Outfit">rotary</text>
<rect x="18" y="132" width="144" height="14" rx="4" fill="#E4EEF8"/>
<text x="90" y="143" text-anchor="middle" font-size="10" font-weight="600" fill="#1E5C8A" font-family="Outfit,sans-serif">Rotary chew + tongue lateral</text>
</svg>`
},
{
id:'veg-2', level:'veg', title:'Resistive Jaw Opening (Feeding)',
muscles:'Lateral pterygoid · Digastric · Mylohyoid',
reps:10, sets:2, holdSec:3,
chips:['10 reps','2 sets','3 sec resist'],
steps:[
{n:'1', text:'<strong>Clinician position:</strong> Place index and middle fingers firmly under the client\'s chin. Apply steady upward resistance.'},
{n:'2', text:'Client opens jaw slowly against resistance. Hold at maximum comfortable opening for 3 seconds.'},
{n:'3', text:'Release resistance. Client closes jaw slowly. Rest 2 seconds.'},
{n:'4', text:'Document maximum comfortable opening in cm. Track across sessions as a progress measure.'},
],
tip:{type:'safe', text:'⚠️ Contraindications: TMJ disorder, trismus, post-radiation fibrosis, cervical spine instability. Do not apply forceful resistance. If pain is reported, stop immediately.'},
illus: `<svg viewBox="0 0 180 160" xmlns="http://www.w3.org/2000/svg" aria-label="Chin resistance technique for jaw opening">
<ellipse cx="90" cy="66" rx="56" ry="46" fill="#FDDBB4" opacity="0.8"/>
<path d="M46 64 Q90 54 134 64" stroke="#8B3A35" stroke-width="2" fill="none"/>
<path d="M58 88 Q90 98 122 88 Q108 106 90 107 Q72 106 58 88Z" fill="#C0605A"/>
<rect x="68" y="110" width="44" height="16" rx="8" fill="#FDDBB4" stroke="#D4956A" stroke-width="1.5"/>
<circle cx="78" cy="118" r="3.5" fill="#D4956A" opacity="0.7"/>
<circle cx="88" cy="118" r="3.5" fill="#D4956A" opacity="0.7"/>
<circle cx="98" cy="118" r="3.5" fill="#D4956A" opacity="0.7"/>
<circle cx="108" cy="118" r="3.5" fill="#D4956A" opacity="0.7"/>
<defs><marker id="arChin" markerWidth="5" markerHeight="5" refX="2.5" refY="1" orient="auto"><polygon points="0,5 2.5,0 5,5" fill="#1E5C8A"/></marker></defs>
<path d="M90 126 L90 116" stroke="#1E5C8A" stroke-width="2.5" stroke-linecap="round" marker-end="url(#arChin)"/>
<rect x="18" y="140" width="144" height="14" rx="4" fill="#E4EEF8"/>
<text x="90" y="151" text-anchor="middle" font-size="10" font-weight="600" fill="#1E5C8A" font-family="Outfit,sans-serif">Resist ↑ · open ↓ · hold 3s</text>
</svg>`
},
{
id:'veg-3', level:'veg', title:'Jaw Lateralization Drills',
muscles:'Lateral pterygoid · Internal pterygoid · Masseter',
reps:8, sets:2, holdSec:3,
chips:['8 reps','2 sets','3 sec hold'],
steps:[
{n:'1', text:'Move jaw <strong>slowly to the right</strong> as far as comfortably possible. Hold 3 seconds.'},
{n:'2', text:'Return to center. Hold 1 second.'},
{n:'3', text:'Move jaw <strong>slowly to the left</strong>. Hold 3 seconds. Return to center.'},
{n:'4', text:'Graded jaw lateralization trains the medial pterygoid range of motion needed for mature rotary chewing and vowel articulation precision.'},
],
tip:{type:'warn', text:'Jaw lateralization should be pain-free. If client reports jaw joint pain, clicking, or locking, discontinue and assess for TMJ dysfunction.'},
illus: `<svg viewBox="0 0 180 160" xmlns="http://www.w3.org/2000/svg" aria-label="Jaw lateralizing to the right with arrow indicators">
<ellipse cx="90" cy="76" rx="60" ry="46" fill="#FDDBB4" opacity="0.7"/>
<path d="M42 74 Q95 64 140 72 Q125 68 96 67 Q68 68 42 74Z" fill="#C0605A"/>
<path d="M44 84 Q98 94 140 84" stroke="#8B3A35" stroke-width="2" fill="none"/>
<defs><marker id="arLJ" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><polygon points="0,0 6,3 0,6" fill="#1E5C8A"/></marker></defs>
<path d="M60 100 L100 100" stroke="#1E5C8A" stroke-width="2.5" stroke-linecap="round" marker-end="url(#arLJ)"/>
<rect x="18" y="130" width="144" height="14" rx="4" fill="#E4EEF8"/>
<text x="90" y="141" text-anchor="middle" font-size="10" font-weight="600" fill="#1E5C8A" font-family="Outfit,sans-serif">Lateral sweep · 3 sec hold</text>
</svg>`
},
{
id:'veg-4', level:'veg', title:'Buccal Tone & Cheek Inflation',
muscles:'Buccinator · Orbicularis oris · Zygomaticus',
reps:10, sets:2, holdSec:5,
chips:['10 reps','2 sets','5 sec hold'],
steps:[
{n:'A', text:'<strong>Sensory prep (massage):</strong> Massage cheek mucosa (inside cheek) with fingertip in circular motion — 30 seconds per side. This normalizes oral tactile sensitivity before the motor task.'},
{n:'B', text:'<strong>Cheek inflation:</strong> Inflate both cheeks with air. Hold lips firmly sealed. Hold 5 seconds. Release slowly.'},
{n:'C', text:'<strong>Unilateral inflation:</strong> Inflate right cheek only, transferring air back and forth 5 times. Then left. Builds differential buccinator control.'},
{n:'D', text:'Buccinator tone directly supports /b/, /p/, and sibilant production by maintaining lateral oral seal during speech.'},
],
tip:{type:'info', text:'Massage (Step A) targets sensory processing; inflation (Steps B–C) targets motor strength. These serve different clinical purposes — document them separately.'},
illus: `<svg viewBox="0 0 180 160" xmlns="http://www.w3.org/2000/svg" aria-label="Both cheeks inflated with air, sealed lips">
<ellipse cx="90" cy="78" rx="72" ry="52" fill="#FDDBB4" opacity="0.75"/>
<ellipse cx="44" cy="82" rx="22" ry="16" fill="#FDDBB4" stroke="#D4956A" stroke-width="2" opacity="0.9"/>
<ellipse cx="136" cy="82" rx="22" ry="16" fill="#FDDBB4" stroke="#D4956A" stroke-width="2" opacity="0.9"/>
<path d="M66 80 Q90 72 114 80 Q100 76 90 75 Q80 76 66 80Z" fill="#C0605A"/>
<path d="M66 84 Q90 92 114 84 Q100 88 90 89 Q80 88 66 84Z" fill="#C0605A"/>
<text x="44" y="86" text-anchor="middle" font-size="18" fill="rgba(212,149,106,0.5)">💨</text>
<text x="136" y="86" text-anchor="middle" font-size="18" fill="rgba(212,149,106,0.5)">💨</text>
<rect x="18" y="130" width="144" height="14" rx="4" fill="#E4EEF8"/>
<text x="90" y="141" text-anchor="middle" font-size="10" font-weight="600" fill="#1E5C8A" font-family="Outfit,sans-serif">Inflate · sealed · 5 sec hold</text>
</svg>`
},
{
id:'veg-5', level:'veg', title:'Saliva Swallow with Laryngeal Palpation',
muscles:'Suprahyoids · Thyrohyoid · Constrictors · Soft palate',
reps:10, sets:2, holdSec:0,
chips:['10 reps','2 sets','palpate larynx'],
steps:[
{n:'1', text:'<strong>Clinician palpation:</strong> Place two fingers lightly on the client\'s throat — index on the hyoid bone, middle finger on the thyroid notch.'},
{n:'2', text:'Instruct client: "Gather saliva, close lips, and swallow." Feel for hyoid and larynx elevating under your fingers.'},
{n:'3', text:'<strong>Effortful swallow variant:</strong> "Swallow as hard as you can, squeezing all your throat muscles." Increased hyoid elevation should be palpable.'},
{n:'4', text:'Document: (a) laryngeal elevation present/absent, (b) timing of elevation (immediate vs. delayed), (c) any wet/gurgly voice quality post-swallow.'},
],
tip:{type:'safe', text:'⚠️ If the client coughs, chokes, or voice quality changes (wet/gurgly), this may indicate aspiration risk. Refer for instrumental assessment (MBS or FEES) before continuing oral feeding.'},
illus: `<svg viewBox="0 0 180 180" xmlns="http://www.w3.org/2000/svg" aria-label="Side profile showing laryngeal elevation during swallow with palpation">
<path d="M130 16 Q145 36 148 64 Q152 94 144 122 Q132 152 112 166 Q90 178 68 172 Q44 164 34 144 Q22 120 26 94 Q20 70 26 50 Q36 28 54 18 Q72 8 98 8 Q116 8 130 16 Z" fill="#FDDBB4" opacity="0.6" transform="scale(0.75) translate(22, 0)"/>
<path d="M78 108 Q106 102 128 106" stroke="#8B5E1A" stroke-width="3" fill="none" stroke-linecap="round" transform="scale(0.75) translate(22,0)"/>
<defs>
<marker id="arHy" markerWidth="5" markerHeight="5" refX="2.5" refY="1" orient="auto"><polygon points="0,5 2.5,0 5,5" fill="#8B5E1A"/></marker>
<marker id="arLar" markerWidth="5" markerHeight="5" refX="2.5" refY="1" orient="auto"><polygon points="0,5 2.5,0 5,5" fill="#1A7A4A"/></marker>
</defs>
<path d="M82 84 L82 74" stroke="#8B5E1A" stroke-width="2" stroke-linecap="round" marker-end="url(#arHy)" transform="scale(0.75) translate(22,0)"/>
<text x="72" y="72" text-anchor="middle" font-size="8" fill="#8B5E1A" font-family="Outfit">hyoid↑</text>
<path d="M80 122 Q98 114 120 120 Q128 128 120 138 Q106 145 94 144 Q80 141 76 130 Z" fill="#B07040" stroke="#D4956A" stroke-width="1.2" transform="scale(0.75) translate(22,0)"/>
<path d="M98 114 L98 104" stroke="#1A7A4A" stroke-width="2" stroke-linecap="round" marker-end="url(#arLar)" transform="scale(0.75) translate(22,0)"/>
<text x="56" y="100" text-anchor="middle" font-size="8" fill="#1A7A4A" font-family="Outfit">larynx↑</text>
<ellipse cx="54" cy="120" rx="10" ry="12" fill="#FDDBB4" opacity="0.85" stroke="#D4956A" stroke-width="1.2"/>
<text x="36" y="138" text-anchor="middle" font-size="8" fill="var(--muted)" font-family="Outfit">palpate</text>
<rect x="18" y="152" width="144" height="14" rx="4" fill="#E4EEF8"/>
<text x="90" y="163" text-anchor="middle" font-size="10" font-weight="600" fill="#1E5C8A" font-family="Outfit,sans-serif">Palpate hyoid + larynx elevation</text>
</svg>`
}
]
};
// ─── RENDER ──────────────────────────────────────────────────────────────────
function colorClass(level) {
return {l1:'l1',l2:'l2',l3:'l3',l4:'l4',veg:'veg'}[level];
}
function renderExercise(ex) {
const cc = colorClass(ex.level);
const isVeg = ex.level === 'veg';
const btnColor = isVeg ? 'btn-veg' : 'btn-primary';
const chips = ex.chips.map(c => `<span class="meta-chip c-${cc}">${c}</span>`).join('');
const steps = ex.steps.map(s =>
`<div class="step-row">
<div class="step-n sn-${cc}">${s.n}</div>
<div class="step-text">${s.text}</div>
</div>`
).join('');
const tipHtml = ex.tip ? `
<div class="ex-tip ${ex.tip.type === 'safe' ? 'safe' : ex.tip.type === 'warn' ? 'warn' : 'info'}">
<span>${ex.tip.type === 'safe' ? '🚨' : ex.tip.type === 'warn' ? '⚠️' : '💡'}</span>
<span>${ex.tip.text}</span>
</div>` : '';
// Timer panel
const hasHold = ex.holdSec > 0;
const hasReps = ex.reps > 0;
let timerHtml = '';
if (hasReps || hasHold) {
const setDots = ex.sets > 0 ? Array.from({length:ex.sets}, (_,i) =>
`<div class="set-dot" id="dot-${ex.id}-${i}" data-set="${i}" data-ex="${ex.id}"></div>`
).join('') : '';
timerHtml = `
<div class="timer-panel" id="timer-${ex.id}">
<div class="timer-title">Exercise Tracker</div>
<div class="timer-controls">
${hasHold ? `
<div class="hold-ring-wrap" id="ring-${ex.id}">
<svg width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="34" fill="none" stroke="#EDE8DE" stroke-width="7"/>
<circle cx="40" cy="40" r="34" fill="none" stroke="var(--l${isVeg ? '1' : cc.slice(-1) || '1'})" stroke-width="7"
stroke-dasharray="213.6" stroke-dashoffset="213.6"
id="ring-circle-${ex.id}" style="transition:stroke-dashoffset 1s linear;stroke:var(--${isVeg?'veg':'l'+cc.slice(-1)})"/>
</svg>
<div class="hold-ring-center" id="ring-num-${ex.id}">${ex.holdSec}</div>
</div>
` : ''}
<div>
${hasReps ? `<div class="rep-counter">
<button class="btn-icon" onclick="adjustRep('${ex.id}',-1)">−</button>
<div>
<div class="rep-display"><span id="rep-cur-${ex.id}">0</span><span style="font-size:16px;color:var(--muted)">/${ex.reps}</span></div>
<div class="rep-label" style="text-align:center">reps</div>
</div>
<button class="btn-icon" onclick="adjustRep('${ex.id}',1)">+</button>
</div>` : ''}
<div style="margin-top:10px;display:flex;gap:8px;flex-wrap:wrap">
${hasHold ? `<button class="btn ${btnColor} btn-sm" onclick="startHold('${ex.id}',${ex.holdSec})">▶ Start Hold</button>` : ''}
<button class="btn btn-outline btn-sm" onclick="nextSet('${ex.id}')">✓ Complete Set</button>
<button class="btn btn-outline btn-sm" onclick="resetExercise('${ex.id}',${ex.sets},${ex.holdSec})">↺ Reset</button>
</div>
<span class="status-chip status-idle" id="status-${ex.id}" style="margin-top:8px;display:inline-block">Idle</span>
</div>
</div>
${ex.sets > 0 ? `<div class="sets-row">${setDots}</div>` : ''}
${hasReps ? `<div class="progress-bar" style="margin-top:8px"><div class="progress-fill ${isVeg?'veg':''}" id="prog-${ex.id}" style="width:0%"></div></div>` : ''}
</div>`;
}
return `
<div class="ex-card" id="card-${ex.id}" onclick="toggleCard('${ex.id}', event)">
<div class="ex-card-top">
<div class="ex-num-badge n-${cc}">${ex.id.toUpperCase().replace(/[A-Z]+-/,'').replace('-',' ')}</div>
<div class="ex-info">
<div class="ex-title">${ex.title}</div>
<div class="ex-muscle">${ex.muscles}</div>
<div class="ex-meta-row">${chips}</div>
</div>
<div class="expand-arrow">▾</div>
</div>
<div class="ex-illus">${ex.illus}</div>
<div class="ex-steps-wrap">
${steps}
${tipHtml}
${timerHtml}
</div>
</div>`;
}
function renderAll() {
document.getElementById('grid-l1').innerHTML = exercises.l1.map(renderExercise).join('');
document.getElementById('grid-l2').innerHTML = exercises.l2.map(renderExercise).join('');
document.getElementById('grid-l3').innerHTML = exercises.l3.map(renderExercise).join('');
document.getElementById('grid-l4').innerHTML = exercises.l4.map(renderExercise).join('');
document.getElementById('grid-veg').innerHTML = exercises.veg.map(renderExercise).join('');
}
// ─── INTERACTIONS ─────────────────────────────────────────────────────────────
function toggleCard(id, e) {
if (e && e.target.closest('.timer-panel, .btn, .btn-icon')) return;
const card = document.getElementById('card-' + id);
card.classList.toggle('expanded');
}
const pageMap = {
'dashboard': {title:'Dashboard', sub:'OroTherapy — Oromotor & Vegetative Skills Library'},
'oro-l1': {title:'Level 1 — Lip Strengthening', sub:'Orbicularis oris · Levator labii · Foundation for /p/, /b/, /m/'},
'oro-l2': {title:'Level 2 — Jaw Grading', sub:'Masseter · Pterygoids · Jaw range of motion'},
'oro-l3': {title:'Level 3 — Tongue Mobility', sub:'Genioglossus · Styloglossus · Intrinsic tongue muscles'},
'oro-l4': {title:'Level 4 — Speech Integration', sub:'Motor gains → functional speech production'},
'veg': {title:'Vegetative Skills', sub:'Chewing · Swallowing readiness · Laryngeal elevation'},
};
function showPage(id) {
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
document.getElementById('page-' + id).classList.add('active');
const nav = document.getElementById('nav-' + id);
if (nav) nav.classList.add('active');
const meta = pageMap[id] || {title: id, sub:''};
document.getElementById('page-title').textContent = meta.title;
document.getElementById('page-sub').textContent = meta.sub;
closeSidebar();
window.scrollTo(0, 0);
}
function openSidebar() {
document.getElementById('sidebar').classList.add('open');
document.getElementById('overlay').classList.add('open');
}
function closeSidebar() {
document.getElementById('sidebar').classList.remove('open');
document.getElementById('overlay').classList.remove('open');
}
// ─── TIMER / REP LOGIC ────────────────────────────────────────────────────────
const timers = {};
const repState = {};
const setStates = {};
function initState(id, maxSets) {
if (!repState[id]) repState[id] = 0;
if (!setStates[id]) setStates[id] = {current:0, max:maxSets, done:false};
}
function adjustRep(id, delta) {
const ex = findEx(id);
if (!ex) return;
initState(id, ex.sets);
repState[id] = Math.max(0, Math.min(ex.reps, repState[id] + delta));
const cur = document.getElementById('rep-cur-' + id);
if (cur) cur.textContent = repState[id];
const prog = document.getElementById('prog-' + id);
if (prog) prog.style.width = (repState[id] / ex.reps * 100) + '%';
setStatus(id, repState[id] >= ex.reps ? 'done' : 'active', ex.level === 'veg');
}
function startHold(id, secs) {
if (timers[id]) {clearInterval(timers[id]); timers[id] = null;}
const ex = findEx(id);
const isVeg = ex && ex.level === 'veg';
let remaining = secs;
const circle = document.getElementById('ring-circle-' + id);
const num = document.getElementById('ring-num-' + id);
const circumference = 213.6;
setStatus(id, 'active', isVeg);
if (circle) {
circle.style.strokeDashoffset = '0';
circle.style.transition = 'none';
}
if (num) num.textContent = remaining;
setTimeout(() => {
if (circle) {
circle.style.transition = `stroke-dashoffset ${secs}s linear`;
circle.style.strokeDashoffset = circumference + '';
}
}, 50);
timers[id] = setInterval(() => {
remaining--;
if (num) num.textContent = Math.max(0, remaining);
if (remaining <= 0) {
clearInterval(timers[id]);
timers[id] = null;
setStatus(id, 'done', isVeg);
if (num) num.textContent = '✓';
}
}, 1000);
}
function nextSet(id) {
const ex = findEx(id);
if (!ex) return;
initState(id, ex.sets);
const st = setStates[id];
if (st.current < st.max) {
const dot = document.getElementById(`dot-${id}-${st.current}`);
if (dot) dot.classList.add(ex.level === 'veg' ? 'done-veg' : 'done');
st.current++;
repState[id] = 0;
const cur = document.getElementById('rep-cur-' + id);
if (cur) cur.textContent = '0';
const prog = document.getElementById('prog-' + id);
if (prog) prog.style.width = '0%';
if (st.current >= st.max) {
setStatus(id, 'done', ex.level === 'veg');
} else {
setStatus(id, 'rest', false);
setTimeout(() => setStatus(id, 'idle', false), 3000);
}
}
}
function resetExercise(id, maxSets, holdSec) {
const ex = findEx(id);
if (timers[id]) {clearInterval(timers[id]); timers[id] = null;}
repState[id] = 0;
setStates[id] = {current:0, max:maxSets, done:false};
const cur = document.getElementById('rep-cur-' + id);
if (cur) cur.textContent = '0';
const prog = document.getElementById('prog-' + id);
if (prog) prog.style.width = '0%';
const num = document.getElementById('ring-num-' + id);
if (num) num.textContent = holdSec;
const circle = document.getElementById('ring-circle-' + id);
if (circle) {circle.style.transition='none'; circle.style.strokeDashoffset = '213.6';}
for (let i = 0; i < maxSets; i++) {
const dot = document.getElementById(`dot-${id}-${i}`);
if (dot) {dot.classList.remove('done','done-veg');}
}
setStatus(id, 'idle', false);
}
function setStatus(id, state, isVeg) {
const el = document.getElementById('status-' + id);
if (!el) return;
el.className = 'status-chip';
if (state === 'idle') { el.classList.add('status-idle'); el.textContent = 'Idle'; }
else if (state === 'active') { el.classList.add(isVeg ? 'status-active-veg' : 'status-active'); el.textContent = 'Active'; }
else if (state === 'done') { el.classList.add('status-done'); el.textContent = '✓ Done'; }
else if (state === 'rest') { el.classList.add('status-rest'); el.textContent = '⏸ Rest'; }
}
function findEx(id) {
const all = [...exercises.l1, ...exercises.l2, ...exercises.l3, ...exercises.l4, ...exercises.veg];
return all.find(e => e.id === id) || null;
}
// ─── INIT ─────────────────────────────────────────────────────────────────────
renderAll();
</script>
</body>
</html>
wc -l /home/daytona/workspace/oromotor-app/index.html && grep -c "ex-card" /home/daytona/workspace/oromotor-app/index.html && echo "File OK"
grep -n "grid-l1\|grid-l2\|grid-l3\|grid-l4\|grid-veg\|renderAll\|showPage\|startHold\|nextSet\|adjustRep" /home/daytona/workspace/oromotor-app/index.html | head -30
index
HTML File · HTML
| Feature | Detail |
|---|---|
| SVG illustration | Custom-drawn anatomical diagrams for every exercise |
| Muscle labels | Exact muscles targeted (e.g., genioglossus, buccinator) |
| Step-by-step instructions | Clinically accurate procedures |
| Dosage chips | Reps, sets, hold duration |
| Tip/warning box | Color-coded: 💡 info, ⚠️ caution, 🚨 safety/contraindication |
| Rep counter | + / − buttons with live progress bar |
| Hold timer | Circular countdown ring with animated stroke |
| Set tracker | Dot indicators per set, marks complete on each set |
| Status chip | Idle → Active → Rest → Done |
can develop the webbased app on oromotor exercises and vegetative skills stengthining exercises
index
HTML File · HTML
cant open
ls -lh /home/daytona/workspace/oromotor-app/index.html && head -5 /home/daytona/workspace/oromotor-app/index.html
cd /home/daytona/workspace/oromotor-app && python3 -m http.server 7860 &> /tmp/server.log & sleep 2 && echo "Server started" && curl -s -o /dev/null -w "%{http_code}" http://localhost:7860/index.html
index
HTML File · HTML
orotherapy.htmlHelp me download and save the HTML file to my computer
index
HTML File · HTML
Ctrl + SCmd + Sorotherapy.html (or any name you like)orotherapy.html