Add classroom demo HTML for live lesson interaction with students
Browse files- static/classroom-demo.html +117 -0
- static/index.html +33 -13
static/classroom-demo.html
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="he" dir="rtl">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
| 6 |
+
<title>احكي · שיעור חי — הדגמת תפעול</title>
|
| 7 |
+
<style>
|
| 8 |
+
@import url('https://fonts.googleapis.com/css2?family=Assistant:wght@400;600;700&family=Frank+Ruhl+Libre:wght@500;700&display=swap');
|
| 9 |
+
:root{--ink:#182018;--muted:#687065;--paper:#f4f1e9;--surface:#fffdf7;--olive:#596b3d;--olive-dark:#354329;--soft:#e7ecdc;--line:#d9d8cb;--sumac:#9f3f32;--amber:#d68b22;--ok:#287451;--shadow:0 14px 34px rgba(40,48,35,.1)}
|
| 10 |
+
*{box-sizing:border-box} body{margin:0;background:var(--paper);color:var(--ink);font-family:Assistant,Arial,sans-serif;min-height:100vh}
|
| 11 |
+
button,input,select{font:inherit} button{cursor:pointer}.shell{max-width:1280px;margin:auto;padding:20px}
|
| 12 |
+
header{display:flex;align-items:center;gap:14px;margin-bottom:18px}.brand{font:700 27px/1 'Frank Ruhl Libre',serif}.brand small{display:block;color:var(--muted);font:600 12px/1.4 Assistant,sans-serif}.live{margin-inline-start:auto;display:flex;align-items:center;gap:8px;padding:8px 12px;border-radius:99px;background:#e3f1e8;color:var(--ok);font-weight:700}.live i{width:9px;height:9px;border-radius:50%;background:currentColor;box-shadow:0 0 0 5px rgba(40,116,81,.12)}.view-toggle{display:flex;gap:3px;padding:3px;border-radius:11px;background:#e5e5dc}.view-toggle button{border:0;border-radius:8px;padding:7px 11px;background:transparent;color:var(--muted);font-weight:700}.view-toggle button.active{background:#fff;color:var(--ink);box-shadow:0 1px 4px rgba(0,0,0,.12)}
|
| 13 |
+
.top{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px;align-items:center;background:var(--olive-dark);color:#fff;padding:18px 20px;border-radius:18px;box-shadow:var(--shadow)}
|
| 14 |
+
.top h1{font:500 30px/1.1 'Frank Ruhl Libre',serif;margin:0}.top p{margin:6px 0 0;color:#d9e2cf}.session-access{display:grid;grid-template-columns:auto 1fr;gap:2px 10px;align-items:center;padding:10px 13px;border-radius:12px;background:rgba(255,255,255,.1)}.session-access .check{grid-row:1/3;display:grid;place-items:center;width:34px;height:34px;border-radius:50%;background:#dfead5;color:var(--olive-dark);font-weight:700}.session-access small{color:#d9e2cf}.session-access b{font-size:16px}
|
| 15 |
+
.layout{display:grid;grid-template-columns:330px minmax(0,1fr);gap:16px;margin-top:16px}.card{background:var(--surface);border:1px solid var(--line);border-radius:18px;box-shadow:0 6px 20px rgba(40,48,35,.05)}
|
| 16 |
+
.roster{padding:18px;align-self:start;position:sticky;top:16px}.section-head{display:flex;align-items:start;gap:10px;margin-bottom:14px}.section-head h2{margin:0;font:500 22px/1.2 'Frank Ruhl Libre',serif}.section-head p{margin:3px 0 0;color:var(--muted);font-size:13px}.count{margin-inline-start:auto;font-weight:700;background:var(--soft);color:var(--olive-dark);padding:5px 9px;border-radius:99px}
|
| 17 |
+
.students{display:flex;flex-direction:column;gap:7px}.student{display:grid;grid-template-columns:38px 1fr auto;gap:9px;align-items:center;padding:8px;border-radius:12px;transition:.25s}.student:hover{background:#f2f3ec}.avatar{display:grid;place-items:center;width:38px;height:38px;border-radius:50%;background:#e8e4d9;font-weight:700;color:var(--muted)}.student.on .avatar{background:var(--soft);color:var(--olive-dark)}.student b{display:block}.student small{color:var(--muted)}.status{width:9px;height:9px;border-radius:50%;background:#c5c4bc}.student.on .status{background:var(--ok);box-shadow:0 0 0 4px #e3f1e8}.student.answering .status{background:var(--amber);box-shadow:0 0 0 4px #f9ecd7}
|
| 18 |
+
.main{display:flex;flex-direction:column;gap:16px}.composer{padding:20px}.steps{display:flex;gap:8px;margin-bottom:18px}.step{display:flex;align-items:center;gap:7px;color:var(--muted);font-size:13px}.step b{display:grid;place-items:center;width:24px;height:24px;border-radius:50%;background:#ecebe4}.step.active{color:var(--olive-dark);font-weight:700}.step.active b{background:var(--olive);color:#fff}.step+.step:before{content:"";width:24px;border-top:1px solid var(--line);margin-inline-end:2px}
|
| 19 |
+
.question-modes{display:grid;grid-template-columns:1fr 1fr;gap:10px}.mode{border:1px solid var(--line);border-radius:14px;padding:13px;text-align:right;background:#fff;color:var(--ink)}.mode strong{display:block}.mode small{color:var(--muted)}.mode.active{border-color:var(--olive);background:var(--soft);box-shadow:inset 0 0 0 1px var(--olive)}
|
| 20 |
+
.field{margin-top:13px}.field label{display:block;margin-bottom:6px;font-weight:700;font-size:14px}.select,input{width:100%;border:1px solid var(--line);background:#fff;color:var(--ink);border-radius:11px;padding:11px 12px}.speech{display:none}.speech.visible{display:block}.speechbox{display:flex;gap:8px}.speechbox input{flex:1}.mic{border:0;border-radius:11px;background:var(--sumac);color:#fff;min-width:48px;font-size:20px}.mic.listening{animation:pulse 1s infinite}@keyframes pulse{50%{transform:scale(.94);box-shadow:0 0 0 7px rgba(159,63,50,.13)}}
|
| 21 |
+
.preview{margin-top:14px;padding:14px;border:1px dashed #b5b8a9;border-radius:13px;background:#faf9f3}.preview small{color:var(--muted)}.preview h3{margin:5px 0 1px;font-size:21px}.preview .arabic{font-size:19px;direction:rtl;color:var(--olive-dark)}.preview .pending{color:var(--amber);font-size:13px}.actions{display:flex;gap:8px;margin-top:16px}.btn{border:1px solid var(--line);border-radius:11px;padding:10px 15px;background:#fff;color:var(--ink);font-weight:700}.btn.primary{background:var(--olive);border-color:var(--olive);color:#fff}.btn:disabled{opacity:.45;cursor:not-allowed}
|
| 22 |
+
.results{padding:20px}.empty{padding:34px;text-align:center;color:var(--muted)}.empty span{display:block;font-size:38px;margin-bottom:7px}.question-live{display:flex;gap:12px;align-items:center;padding:14px;border-radius:14px;background:var(--olive-dark);color:#fff;margin-bottom:14px}.question-live .q{flex:1}.question-live small{display:block;color:#d9e2cf}.question-live strong{font-size:19px}.timer{font-weight:700;font-variant-numeric:tabular-nums;background:rgba(255,255,255,.12);padding:8px 11px;border-radius:10px}
|
| 23 |
+
table{width:100%;border-collapse:collapse}th{text-align:right;color:var(--muted);font-size:12px;padding:7px 9px}td{padding:10px 9px;border-top:1px solid var(--line);vertical-align:top}.answer{min-width:260px}.said-line{display:grid;grid-template-columns:3.7em 1fr;gap:8px;align-items:baseline;line-height:1.65}.said-line+.said-line{margin-top:2px}.said-line small{color:var(--muted);font-size:12px;font-weight:600}.translit{font-size:17px;font-weight:700;letter-spacing:.02em}.translit.expected{color:var(--olive-dark)}.translit.heard{color:var(--ink)}.expected-diff,.letter-diff{padding:1px 2px;border-radius:4px;text-decoration:underline;text-decoration-thickness:2px;text-underline-offset:3px}.expected-diff{background:#e4f1e8;color:var(--ok)}.letter-diff{background:#f8dcd5;color:var(--sumac)}.missing{color:var(--sumac);font-weight:700}.score{display:inline-grid;place-items:center;min-width:42px;height:30px;border-radius:9px;font-weight:700;background:#e4f1e8;color:var(--ok)}.score.mid{background:#faecd7;color:#955a0b}.score.low{background:#f6e4df;color:var(--sumac)}.wait{color:var(--muted)}.note{font-size:12px;color:var(--muted);margin:12px 0 0}.toast{position:fixed;bottom:20px;left:50%;transform:translate(-50%,20px);opacity:0;background:var(--ink);color:#fff;padding:10px 16px;border-radius:99px;transition:.25s;pointer-events:none}.toast.show{opacity:1;transform:translate(-50%,0)}
|
| 24 |
+
#studentView{display:none;max-width:620px;margin:42px auto}.student-view #studentView{display:block}.student-view .teacher-only{display:none}.student-shell{padding:28px;text-align:center}.student-shell .class-name{color:var(--muted);font-weight:700}.student-shell h1{font:500 31px/1.25 'Frank Ruhl Libre',serif;margin:22px 0 8px}.student-shell .instruction{color:var(--muted);margin:0 0 24px}.student-mic{display:grid;place-items:center;width:92px;height:92px;margin:20px auto;border:0;border-radius:50%;background:var(--sumac);color:#fff;font-size:35px;box-shadow:0 8px 0 #713128}.student-mic:active{transform:translateY(4px);box-shadow:0 4px 0 #713128}.waiting-ring{display:grid;place-items:center;width:76px;height:76px;margin:26px auto;border:7px solid var(--soft);border-top-color:var(--olive);border-radius:50%;animation:turn 1.5s linear infinite}@keyframes turn{to{transform:rotate(360deg)}}.student-score{font:700 48px/1 'Frank Ruhl Libre',serif;color:var(--ok);margin:12px 0 20px}.student-compare{max-width:460px;margin:0 auto;text-align:right;padding:14px;border-radius:13px;background:#f6f5ef}.student-actions{display:flex;justify-content:center;gap:8px;margin-top:18px}
|
| 25 |
+
@media(max-width:800px){.layout{grid-template-columns:1fr}.roster{position:static}.students{display:grid;grid-template-columns:repeat(2,1fr)}.question-modes{grid-template-columns:1fr}.top{grid-template-columns:1fr}.session-access{justify-self:start}}
|
| 26 |
+
@media(max-width:520px){.shell{padding:10px}header{flex-wrap:wrap}.live{margin-inline-start:0}.view-toggle{order:3;width:100%}.view-toggle button{flex:1}.students{grid-template-columns:1fr}.steps{overflow:auto}.step+.step:before{width:8px}.question-live{align-items:start}th:nth-child(3),td:nth-child(3){display:none}}
|
| 27 |
+
</style>
|
| 28 |
+
</head>
|
| 29 |
+
<body>
|
| 30 |
+
<div class="shell">
|
| 31 |
+
<header><div class="brand">احكي · ihki<small id="viewName">הדגמת שיעור חי · מסך המדריך</small></div><div class="view-toggle"><button class="active" data-view="teacher">מסך המדריך</button><button data-view="student">מסך התלמיד</button></div><div class="live"><i></i> המפגש פתוח</div></header>
|
| 32 |
+
<section class="top teacher-only"><div><h1>שיעור 6 · קבוצת יום רביעי</h1><p>המדריך מכין שאלה, שולח אותה לתלמידים ורואה את התשובות בזמן אמת.</p></div><div class="session-access"><span class="check">✓</span><b>המפגש פתוח לתלמידי הקבוצה</b><small>5 מתוך 8 הצטרפו אוטומטית</small></div></section>
|
| 33 |
+
<div class="layout teacher-only">
|
| 34 |
+
<aside class="card roster">
|
| 35 |
+
<div class="section-head"><div><h2>התלמידים</h2><p>מי כבר נמצא בשיעור</p></div><span class="count" id="onlineCount">5/8</span></div>
|
| 36 |
+
<div class="students" id="students"></div>
|
| 37 |
+
</aside>
|
| 38 |
+
<main class="main">
|
| 39 |
+
<section class="card composer">
|
| 40 |
+
<div class="steps"><span class="step active"><b>1</b>מכינים שאלה</span><span class="step"><b>2</b>שולחים לכיתה</span><span class="step"><b>3</b>רואים תשובות</span></div>
|
| 41 |
+
<div class="section-head"><div><h2>מה אומרים עכשיו?</h2><p>אפשר לבחור מהשיעור או לומר משפט בעברית.</p></div></div>
|
| 42 |
+
<div class="question-modes">
|
| 43 |
+
<button class="mode active" data-mode="bank"><strong>בחירה מהשיעור</strong><small>משפט מוכן עם תשובה בערבית</small></button>
|
| 44 |
+
<button class="mode" data-mode="speech"><strong>אומרים משפט בעברית</strong><small>המערכת מחפשת משפט מתאים במאגר</small></button>
|
| 45 |
+
</div>
|
| 46 |
+
<div class="field bank" id="bankField"><label for="sentence">משפט מתוך השיעור</label><select class="select" id="sentence"><option value="איפה היית אתמול?|وين كنت امبارح؟|וֵין כֻּנְת אִמְבַּארֶח?">איפה היית אתמול?</option><option value="מתי הגעתם לבית הספר?|ايمتى وصلتوا عالمدرسة؟|אֵימְתַא וִצֶלְתוּ עַלְמַדְרַסֶה?">מתי הגעתם לבית הספר?</option><option value="מה אתם רוצים לעשות מחר?|شو بدّكم تعملوا بكرا؟|שוּ בִּדְّכֹּם תִעְמַלוּ בֻּכְּרַא?">מה אתם רוצים לעשות מחר?</option></select></div>
|
| 47 |
+
<div class="field speech" id="speechField"><label for="spoken">המשפט שאמרת</label><div class="speechbox"><input id="spoken" value="מה עשיתם בסוף השבוע?" aria-label="המשפט בעברית"><button class="mic" id="micBtn" title="הדמיית הקלטה">●</button></div></div>
|
| 48 |
+
<div class="preview"><small id="previewLabel">התלמידים יראו</small><h3 id="questionText">איפה היית אתמול?</h3><div class="arabic" id="arabicText">وين كنت امبارح؟</div><div class="pending" id="pending" hidden></div></div>
|
| 49 |
+
<div class="actions"><button class="btn primary" id="sendBtn">שליחה לכולם</button></div>
|
| 50 |
+
</section>
|
| 51 |
+
<section class="card results" id="results"><div class="empty"><span>◌</span><b>עוד לא נשלחה שאלה</b><div>התשובות והציונים יופיעו כאן.</div></div></section>
|
| 52 |
+
</main>
|
| 53 |
+
</div>
|
| 54 |
+
<section id="studentView" class="card" aria-live="polite"></section>
|
| 55 |
+
</div>
|
| 56 |
+
<script>
|
| 57 |
+
const $=id=>document.getElementById(id);
|
| 58 |
+
const pupils=[['נועה','נכנסה עכשיו',1],['אמיר','מחובר',1],['מאיה','מחוברת',1],['יוסף','מחובר',1],['ליאן','מחוברת',1],['תום','טרם נכנס',0],['רים','טרם נכנסה',0],['אדם','טרם נכנס',0]];
|
| 59 |
+
const speechBank=[
|
| 60 |
+
{he:'מה עשיתם בסוף השבוע?',ar:'شو عملتوا بنهاية الأسبوع؟',tr:'שוּ עְמֵלְתוּ בִּנְהַאיֵת אִלְאֻסְבּוּע?'},
|
| 61 |
+
{he:'איפה היית אתמול?',ar:'وين كنت امبارح؟',tr:'וֵין כֻּנְת אִמְבַּארֶח?'},
|
| 62 |
+
{he:'מתי הגעתם לבית הספר?',ar:'ايمتى وصلتوا عالمدرسة؟',tr:'אֵימְתַא וִצֶלְתוּ עַלְמַדְרַסֶה?'}
|
| 63 |
+
];
|
| 64 |
+
let mode='bank', sent=false, currentMatch=null;
|
| 65 |
+
function drawStudents(answering=false){$('students').innerHTML=pupils.map((p,i)=>`<div class="student ${p[2]?'on':''} ${answering&&i===3?'answering':''}"><span class="avatar">${p[0][0]}</span><span><b>${p[0]}</b><small>${answering&&i===3?'עונה עכשיו':p[1]}</small></span><i class="status"></i></div>`).join('')}
|
| 66 |
+
function studentWaiting(){
|
| 67 |
+
$('studentView').innerHTML='<div class="student-shell"><div class="class-name">שיעור 6 · קבוצת יום רביעי</div><div class="waiting-ring"></div><h1>הצטרפת לשיעור החי</h1><p class="instruction">זיהינו את הכיתה שלך וחיברנו אותך למפגש. השאלה תופיע כאן כשהמדריך ישלח אותה.</p></div>';
|
| 68 |
+
}
|
| 69 |
+
function studentQuestion(){
|
| 70 |
+
if(!sent||!currentMatch)return studentWaiting();
|
| 71 |
+
$('studentView').innerHTML=`<div class="student-shell"><div class="class-name">מחוברים לשיעור 6 · קבוצת יום רביעי</div><h1>${currentMatch.he}</h1><p class="instruction">ענו בערבית. התשובה והתעתיק אינם מוצגים לפני ההקלטה.</p><button class="student-mic" id="studentMic" aria-label="הקלטת תשובה">●</button><b>לחצו והתחילו לדבר</b></div>`;
|
| 72 |
+
}
|
| 73 |
+
function studentResult(){
|
| 74 |
+
const rows=answerRows(currentMatch.tr), mine=rows[2];
|
| 75 |
+
$('studentView').innerHTML=`<div class="student-shell"><div class="class-name">התשובה נבדקה</div><h1>המשוב האישי שלך</h1><div class="student-score">72</div><div class="student-compare"><div class="said-line"><small>צריך</small><span class="translit expected">${mine[1]}</span></div><div class="said-line"><small>נאמר</small><span class="translit heard">${mine[2]}</span></div></div><p class="instruction">האותיות המסומנות נשמעו שונות. רק את והמדריך רואים את הציון הזה.</p><div class="student-actions"><button class="btn primary" data-student-action="retry">ניסיון נוסף</button><button class="btn" data-student-action="done">סיום והמתנה</button></div></div>`;
|
| 76 |
+
}
|
| 77 |
+
const clean=s=>s.replace(/[?!.,]/g,'').replace(/\s+/g,' ').trim();
|
| 78 |
+
function sync(){
|
| 79 |
+
let he,ar,tr;
|
| 80 |
+
if(mode==='bank'){
|
| 81 |
+
[he,ar,tr]=$('sentence').value.split('|'); currentMatch={he,ar,tr};
|
| 82 |
+
$('previewLabel').textContent='התלמידים יראו'; $('pending').hidden=true;
|
| 83 |
+
}else{
|
| 84 |
+
he=$('spoken').value.trim(); currentMatch=speechBank.find(x=>clean(x.he)===clean(he))||null;
|
| 85 |
+
$('previewLabel').textContent=currentMatch?'נמצאה התאמה במאגר':'תוצאת החיפוש במאגר';
|
| 86 |
+
$('pending').hidden=false;
|
| 87 |
+
$('pending').textContent=currentMatch?'זהו משפט קיים ומוכן לבדיקה פונמית. לא נוצר תרגום חדש.':'לא נמצא משפט מוכן. אפשר לבחור משפט מהשיעור או לנסות ניסוח אחר.';
|
| 88 |
+
ar=currentMatch?currentMatch.ar:'אין משפט מתאים במאגר';
|
| 89 |
+
}
|
| 90 |
+
$('questionText').textContent=he||'אמרו משפט בעברית'; $('arabicText').textContent=ar; $('sendBtn').disabled=!currentMatch;
|
| 91 |
+
}
|
| 92 |
+
function expectedTr(){return currentMatch.tr}
|
| 93 |
+
function compared(expected,from,to){return [expected.replace(from,`<span class="expected-diff">${from}</span>`),expected.replace(from,`<span class="letter-diff">${to}</span>`)]}
|
| 94 |
+
function answerRows(expected){
|
| 95 |
+
let change=['כֻּנְת','כּוּנְת'];
|
| 96 |
+
if(expected.includes('וִצֶלְתוּ'))change=['וִצֶלְתוּ','וִסֶלְתוּ'];
|
| 97 |
+
else if(expected.includes('תִעְמַלוּ'))change=['תִעְמַלוּ','תַעְמַלוּ'];
|
| 98 |
+
else if(expected.includes('עְמֵלְתוּ'))change=['עְמֵלְתוּ','עַמַלְתוּ'];
|
| 99 |
+
const different=compared(expected,change[0],change[1]);
|
| 100 |
+
return [['נועה',expected,expected,94,'הגייה ברורה'],['אמיר',expected,expected,87,'המשפט זוהה במלואו'],['מאיה',different[0],different[1],72,'האותיות המסומנות נשמעו שונות'],['יוסף','','',null,'מקליט עכשיו…'],['ליאן',expected,expected,91,'הגייה ברורה']]
|
| 101 |
+
}
|
| 102 |
+
document.querySelectorAll('.mode').forEach(b=>b.onclick=()=>{mode=b.dataset.mode;document.querySelectorAll('.mode').forEach(x=>x.classList.toggle('active',x===b));$('bankField').style.display=mode==='bank'?'block':'none';$('speechField').classList.toggle('visible',mode==='speech');sync()});
|
| 103 |
+
document.querySelectorAll('[data-view]').forEach(b=>b.onclick=()=>{const student=b.dataset.view==='student';document.body.classList.toggle('student-view',student);document.querySelectorAll('[data-view]').forEach(x=>x.classList.toggle('active',x===b));$('viewName').textContent=`הדגמת שיעור חי · מסך ${student?'התלמיד':'המדריך'}`;if(student)studentQuestion()});
|
| 104 |
+
$('studentView').onclick=e=>{
|
| 105 |
+
if(e.target.id==='studentMic'){
|
| 106 |
+
e.target.disabled=true;e.target.classList.add('listening');e.target.closest('.student-shell').querySelector('b').textContent='מקליט…';
|
| 107 |
+
setTimeout(()=>{if($('studentView'))$('studentView').innerHTML='<div class="student-shell"><div class="waiting-ring"></div><h1>בודקים את ההגייה…</h1><p class="instruction">התשובה נשלחה למדריך.</p></div>';setTimeout(studentResult,900)},900);
|
| 108 |
+
}else if(e.target.dataset.studentAction==='retry')studentQuestion();
|
| 109 |
+
else if(e.target.dataset.studentAction==='done')studentWaiting();
|
| 110 |
+
};
|
| 111 |
+
$('sentence').onchange=sync;$('spoken').oninput=sync;
|
| 112 |
+
$('micBtn').onclick=()=>{const b=$('micBtn');b.classList.add('listening');$('spoken').value='מקשיב…';sync();setTimeout(()=>{b.classList.remove('listening');$('spoken').value='מה עשיתם בסוף השבוע?';sync()},1400)};
|
| 113 |
+
$('sendBtn').onclick=()=>{sent=true;drawStudents(true);studentQuestion();document.querySelectorAll('.step').forEach((s,i)=>s.classList.toggle('active',i<3));const he=$('questionText').textContent,expected=expectedTr();$('results').innerHTML=`<div class="question-live"><div class="q"><small>השאלה פתוחה עכשיו</small><strong>${he}</strong></div><span class="timer" id="timer">00:30</span><button class="btn" id="closeQ">סיום</button></div><table><thead><tr><th>תלמיד/ה</th><th>מה היה צריך לומר / מה נאמר</th><th>משוב</th><th>ציון</th></tr></thead><tbody id="answerRows"><tr><td colspan="4" class="wait">ממתינים לתשובות…</td></tr></tbody></table><p class="note">התעתיק העברי מציג את פענוח ההקלטה. ירוק מסמן את הרצף הצפוי ואדום את מה שנשמע במקומו. זהו אב־טיפוס והנתונים בו מדומים.</p>`;let n=30;const timer=setInterval(()=>{if(!$('timer'))return clearInterval(timer);$('timer').textContent=`00:${String(--n).padStart(2,'0')}`;if(n<=0)clearInterval(timer)},1000);setTimeout(()=>{if(!sent)return;$('answerRows').innerHTML=answerRows(expected).map(a=>`<tr><td><b>${a[0]}</b></td><td class="answer">${a[3]==null?'<span class="wait">ממתינים לסיום ההקלטה…</span>':`<div class="said-line"><small>צריך</small><span class="translit expected">${a[1]}</span></div><div class="said-line"><small>נאמר</small><span class="translit heard">${a[2]}</span></div>`}</td><td>${a[4]}</td><td>${a[3]==null?'<span class="wait">…</span>':`<span class="score ${a[3]<80?'mid':''}">${a[3]}</span>`}</td></tr>`).join('');$('closeQ').onclick=()=>{sent=false;drawStudents();studentWaiting();$('closeQ').disabled=true;$('closeQ').textContent='השאלה הסתיימה';clearInterval(timer)}},850)};
|
| 114 |
+
drawStudents();sync();studentWaiting();
|
| 115 |
+
</script>
|
| 116 |
+
</body>
|
| 117 |
+
</html>
|
static/index.html
CHANGED
|
@@ -607,8 +607,8 @@ body.driving .card.storymode .prompt{display:block}
|
|
| 607 |
.tips li .sub{display:block}
|
| 608 |
/* "פועל נגד הזמן": every prompt is a bead; correct answers join the misbaha above. */
|
| 609 |
.card.bubblemode{min-height:min(610px,72vh);overflow:hidden;position:relative;background:#101510 url("/static/assets/arabic-calligraphy-path.webp?v=7") top center/100% auto repeat-y}
|
| 610 |
-
.card.bubblemode .prompt{position:relative;z-index:1;display:grid;place-items:center;width:clamp(160px,44vw,220px);aspect-ratio:1;margin:clamp(72px,13vh,105px) auto 18px;padding:40px 28px 28px;box-sizing:border-box;border:3px solid #1b2118;border-radius:50%;background:radial-gradient(circle at 50% 15%,#151a13 0 7px,#899b79 8px 11px,transparent 12px),radial-gradient(circle at 31% 25%,rgb(255 255 255/.72) 0 4%,transparent 5%),radial-gradient(circle at 38% 34%,#aabd98 0%,#718363 37%,#4a5941 72%,#303a2b 100%);color:#f7faef;text-shadow:0 2px 1px rgb(0 0 0/.45);box-shadow:inset 13px 16px 24px rgb(255 255 255/.14),inset -18px -22px 30px rgb(10 17 9/.38),0 18px 28px rgb(0 0 0/.38);animation:bead-rise
|
| 611 |
-
.card.bubblemode .prompt::before{content:"";position:absolute;inset:-10px;border-radius:50%;border:5px solid #aabd98;clip-path:inset(0);animation:verb-time
|
| 612 |
.card.bubblemode .prompt::after{content:"";position:absolute;left:50%;top:13px;width:15px;height:15px;border-radius:50%;background:#111710;box-shadow:inset 0 3px 4px #050705,0 1px 0 rgb(255 255 255/.24);transform:translateX(-50%)}
|
| 613 |
.card.bubblemode .bubble-grade{position:absolute;left:50%;bottom:28px;min-width:64px;padding:6px 11px;border:2px solid #fff;border-radius:999px;background:linear-gradient(180deg,#ffd95e,#f2a91d);color:#4c3100;box-shadow:0 3px 0 #a96800,0 6px 12px rgb(66 50 0/.22);font:800 20px/1 var(--f-display);font-variant-numeric:tabular-nums;white-space:nowrap;transform:translateX(-50%);animation:grade-pop .38s cubic-bezier(.2,1.8,.4,1)}
|
| 614 |
.card.bubblemode .bubble-grade::before{content:"★";margin-inline-end:4px;color:#fff;text-shadow:0 1px 1px rgb(92 58 0/.35)}
|
|
@@ -635,15 +635,18 @@ body.driving .card.storymode .prompt{display:block}
|
|
| 635 |
.misbaha-cord .tassel-thread{fill:none;stroke:#171713;stroke-width:1.35;stroke-linecap:round;opacity:.95}
|
| 636 |
.mini-bead{position:relative;z-index:1;width:19px;height:19px;border:1px solid #72500d;border-radius:50%;background:radial-gradient(circle at 50% 50%,#704708 0 2px,transparent 3px),radial-gradient(circle at 29% 23%,#fff1a0 0 9%,transparent 11%),radial-gradient(circle at 38% 34%,#ffe27a,#d7a423 44%,#8e5b08 100%);box-shadow:inset -3px -4px 5px rgb(91 55 4/.32),0 2px 3px rgb(0 0 0/.38)}
|
| 637 |
.mini-bead::after{content:"";position:absolute;inset:3px;border:1px dotted rgb(112 71 8/.65);border-radius:50%;box-shadow:0 0 0 1px rgb(255 231 135/.28)}
|
| 638 |
-
.mini-bead:nth-of-type(1){grid-area:
|
| 639 |
-
.mini-bead:nth-of-type(6){grid-area:
|
| 640 |
.mini-bead.good{border-color:#1b2118;background:radial-gradient(circle at 50% 50%,#151a13 0 2px,transparent 3px),radial-gradient(circle at 31% 25%,#e2ead8 0 8%,transparent 10%),radial-gradient(circle at 38% 34%,#aabd98,#718363 44%,#35402f 100%);box-shadow:inset -3px -4px 5px rgb(10 17 9/.3),0 2px 3px rgb(0 0 0/.35)}
|
| 641 |
.mini-bead.good::after{border-color:rgb(40 55 34/.65);box-shadow:0 0 0 1px rgb(226 234 216/.26)}
|
|
|
|
|
|
|
| 642 |
.mini-bead.failed{border-color:#7d3d37;background:radial-gradient(circle at 50% 50%,#552622 0 2px,transparent 3px),radial-gradient(circle at 31% 25%,#ffe1da 0 8%,transparent 10%),radial-gradient(circle at 38% 34%,#efaaa0,#d77970 44%,#9b4f48 100%);box-shadow:inset -3px -4px 5px rgb(91 35 30/.25),0 2px 3px rgb(0 0 0/.35)}
|
| 643 |
.mini-bead.failed::after{border-color:rgb(108 43 38/.6);box-shadow:0 0 0 1px rgb(255 225 218/.28)}
|
| 644 |
.mini-bead.landed{animation:mini-land .3s cubic-bezier(.2,1.7,.4,1)}
|
| 645 |
.bead-flight{position:fixed;z-index:30;margin:0;border:3px solid #72500d;border-radius:50%;background:radial-gradient(circle at 50% 15%,#704708 0 7px,#d9a82c 8px 11px,transparent 12px),radial-gradient(circle at 31% 25%,rgb(255 246 186/.82) 0 4%,transparent 5%),radial-gradient(circle at 38% 34%,#ffe27a,#d7a423 42%,#8e5b08 100%);box-shadow:inset -14px -18px 30px rgb(91 55 4/.32),0 14px 24px rgb(0 0 0/.3);pointer-events:none}
|
| 646 |
.bead-flight.good{border-color:#1b2118;background:radial-gradient(circle at 50% 15%,#151a13 0 7px,#899b79 8px 11px,transparent 12px),radial-gradient(circle at 31% 25%,rgb(255 255 255/.72) 0 4%,transparent 5%),radial-gradient(circle at 38% 34%,#aabd98,#718363 42%,#303a2b 100%);box-shadow:inset -14px -18px 30px rgb(10 17 9/.35),0 14px 24px rgb(0 0 0/.3)}
|
|
|
|
| 647 |
.bead-flight.failed{border-color:#7d3d37;background:radial-gradient(circle at 50% 15%,#552622 0 7px,#d98980 8px 11px,transparent 12px),radial-gradient(circle at 31% 25%,rgb(255 255 255/.7) 0 4%,transparent 5%),radial-gradient(circle at 38% 34%,#efaaa0,#d77970 42%,#9b4f48 100%)}
|
| 648 |
.card.bubblemode .prompt.collected{visibility:hidden}
|
| 649 |
.bubble-hint{position:absolute;left:50%;bottom:18px;z-index:3;width:max-content;min-width:170px;max-width:min(230px,calc(100% - 32px));box-sizing:border-box;padding:10px 13px;border-radius:12px;background:rgb(255 255 255/.94);color:#174f18;box-shadow:0 4px 14px rgb(30 91 36/.18);font:700 14px/1.35 var(--f-body);animation:hint-in .25s ease-out both}
|
|
@@ -664,12 +667,20 @@ body.bubble-game #gameBack{display:flex;position:fixed;top:max(12px,env(safe-are
|
|
| 664 |
body.bubble-game #gameBack .ic{width:20px;height:20px}
|
| 665 |
body.bubble-game #gameBack:active{transform:translateY(2px);box-shadow:0 1px 0 #111}
|
| 666 |
@keyframes bead-rise{0%{transform:translateY(54px) rotate(-3deg) scale(.9)}50%{transform:translateY(2px) rotate(2deg) scale(.97)}100%{transform:translateY(-58px) rotate(-2deg) scale(1.02)}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 667 |
@keyframes verb-time{from{clip-path:inset(0)}to{clip-path:inset(100% 0 0 0)}}
|
| 668 |
@keyframes score-pop{0%{transform:scale(1)}45%{transform:scale(1.55);color:#2d9b31}100%{transform:scale(1)}}
|
| 669 |
@keyframes grade-pop{0%{transform:translateX(-50%) scale(.25) rotate(-8deg);opacity:0}65%{transform:translateX(-50%) scale(1.18) rotate(3deg);opacity:1}100%{transform:translateX(-50%) scale(1) rotate(0)}}
|
| 670 |
@keyframes hint-in{from{transform:translate(-50%,6px);opacity:0}to{transform:translate(-50%,0);opacity:1}}
|
| 671 |
@keyframes mini-land{0%{transform:scale(.4)}65%{transform:scale(1.35)}100%{transform:scale(1)}}
|
| 672 |
-
@media (prefers-reduced-motion:reduce){.card.bubblemode .prompt{animation:
|
| 673 |
/* Without a microphone ("תרגול שקט ללא ציון"): a Hebrew sentence is answered by hand instead of said, built from its
|
| 674 |
Arabic words, tapped in order out of a bank that holds a few words too many. */
|
| 675 |
body.quiet :is(#mic,#playMine,#driveBtn,.phone-btn),body.driving :is(#quietBtn,#heBtn,.miclab,.phone-btn){display:none}
|
|
@@ -756,7 +767,7 @@ body.quiet :is(#mic,#playMine,#driveBtn,.phone-btn),body.driving :is(#quietBtn,#
|
|
| 756 |
|
| 757 |
<main class="card" id="card" aria-live="polite">
|
| 758 |
<div class="bubble-score" id="bubbleScore" hidden><span>ניקוד<small id="bubbleCount"></small></span><b id="bubblePoints">0</b></div>
|
| 759 |
-
<div class="bubble-beads" id="bubbleBeads" hidden aria-label="תוצאות במסבחה: ירוק
|
| 760 |
<div class="bubble-hint" id="bubbleHint" hidden><small>רמז</small><span class="binyan" id="bubbleBinyan"></span><strong class="gizra" id="bubbleGizra"></strong><span class="past" id="bubblePast" hidden><small>עבר · הוא</small><b id="bubblePastText"></b><em id="bubblePastAr" lang="ar"></em></span></div>
|
| 761 |
<div class="meta"><span id="counter"></span></div>
|
| 762 |
<div class="progress" id="progress" hidden><i id="progressBar"></i></div>
|
|
@@ -1519,7 +1530,7 @@ const EXCELLENT=85; // the score the verdict calls "מצוין"
|
|
| 1519 |
// left, it is the whole topic, and a practised sentence shows its best score (see show).
|
| 1520 |
// `here` is the topic's sentence on the card: not always the round's, because going back passes through the practised ones.
|
| 1521 |
let ordered=false, topic=[], here=null, showDone=store.get("ihki.showDone",false);
|
| 1522 |
-
const BUBBLE_MS=
|
| 1523 |
let bubbleTimer=null, bubbleHintTimer=null, bubblePastTimer=null, bubbleDeadline=0, bubbleFor=null, bubbleResolving=false, bubbleHintShown=false, bubblePastShown=false;
|
| 1524 |
let bubbleGame={answered:0,correct:0,total:0};
|
| 1525 |
const bubbleCat=()=>!daily&&(categoryOf(cat)||{}).bubble;
|
|
@@ -1534,12 +1545,21 @@ function countBubble(correct){
|
|
| 1534 |
drawBubbleScore(correct);
|
| 1535 |
}
|
| 1536 |
async function collectBead(result){
|
| 1537 |
-
const source=$("prompt"), shelf=$("bubbleBeads"), from=source.getBoundingClientRect(), tone=result==="clean"?" good":result==="failed"?" failed":"", mini=mark(`mini-bead${tone}`,"");
|
| 1538 |
shelf.append(mini); const to=mini.getBoundingClientRect(); mini.style.visibility="hidden"; source.classList.add("collected");
|
| 1539 |
const ghost=mark(`bead-flight${tone}`,""); Object.assign(ghost.style,{left:from.left+"px",top:from.top+"px",width:from.width+"px",height:from.height+"px"}); document.body.append(ghost);
|
| 1540 |
if(!matchMedia("(prefers-reduced-motion:reduce)").matches){
|
| 1541 |
-
const
|
| 1542 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1543 |
}
|
| 1544 |
ghost.remove(); mini.style.visibility=""; mini.classList.add("landed");
|
| 1545 |
}
|
|
@@ -2282,7 +2302,7 @@ function render(r){
|
|
| 2282 |
}
|
| 2283 |
|
| 2284 |
async function bubbleOutcome(r,expired=false){
|
| 2285 |
-
const id=cur.id, correct=!r.unrelated&&r.score>=
|
| 2286 |
if(bubbleResolving) return;
|
| 2287 |
bubbleResolving=true;
|
| 2288 |
if(!expired){
|
|
@@ -2291,10 +2311,10 @@ async function bubbleOutcome(r,expired=false){
|
|
| 2291 |
$("prompt").append(mark(`bubble-grade ${band}`,`${score} נק׳`));
|
| 2292 |
}
|
| 2293 |
if(correct){
|
| 2294 |
-
const
|
| 2295 |
countBubble(true);
|
| 2296 |
stopBubble();
|
| 2297 |
-
await collectBead(
|
| 2298 |
}else{
|
| 2299 |
if(!expired&&Date.now()<bubbleDeadline){
|
| 2300 |
bubbleResolving=false;
|
|
|
|
| 607 |
.tips li .sub{display:block}
|
| 608 |
/* "פועל נגד הזמן": every prompt is a bead; correct answers join the misbaha above. */
|
| 609 |
.card.bubblemode{min-height:min(610px,72vh);overflow:hidden;position:relative;background:#101510 url("/static/assets/arabic-calligraphy-path.webp?v=7") top center/100% auto repeat-y}
|
| 610 |
+
.card.bubblemode .prompt{position:relative;z-index:1;display:grid;place-items:center;width:clamp(160px,44vw,220px);aspect-ratio:1;margin:clamp(72px,13vh,105px) auto 18px;padding:40px 28px 28px;box-sizing:border-box;border:3px solid #1b2118;border-radius:50%;background:radial-gradient(circle at 50% 15%,#151a13 0 7px,#899b79 8px 11px,transparent 12px),radial-gradient(circle at 31% 25%,rgb(255 255 255/.72) 0 4%,transparent 5%),radial-gradient(circle at 38% 34%,#aabd98 0%,#718363 37%,#4a5941 72%,#303a2b 100%);color:#f7faef;text-shadow:0 2px 1px rgb(0 0 0/.45);box-shadow:inset 13px 16px 24px rgb(255 255 255/.14),inset -18px -22px 30px rgb(10 17 9/.38),0 18px 28px rgb(0 0 0/.38);animation:bead-rise 9s linear both,bead-color 9s linear both}
|
| 611 |
+
.card.bubblemode .prompt::before{content:"";position:absolute;inset:-10px;border-radius:50%;border:5px solid #aabd98;clip-path:inset(0);animation:verb-time 9s linear both,bead-ring 9s linear both}
|
| 612 |
.card.bubblemode .prompt::after{content:"";position:absolute;left:50%;top:13px;width:15px;height:15px;border-radius:50%;background:#111710;box-shadow:inset 0 3px 4px #050705,0 1px 0 rgb(255 255 255/.24);transform:translateX(-50%)}
|
| 613 |
.card.bubblemode .bubble-grade{position:absolute;left:50%;bottom:28px;min-width:64px;padding:6px 11px;border:2px solid #fff;border-radius:999px;background:linear-gradient(180deg,#ffd95e,#f2a91d);color:#4c3100;box-shadow:0 3px 0 #a96800,0 6px 12px rgb(66 50 0/.22);font:800 20px/1 var(--f-display);font-variant-numeric:tabular-nums;white-space:nowrap;transform:translateX(-50%);animation:grade-pop .38s cubic-bezier(.2,1.8,.4,1)}
|
| 614 |
.card.bubblemode .bubble-grade::before{content:"★";margin-inline-end:4px;color:#fff;text-shadow:0 1px 1px rgb(92 58 0/.35)}
|
|
|
|
| 635 |
.misbaha-cord .tassel-thread{fill:none;stroke:#171713;stroke-width:1.35;stroke-linecap:round;opacity:.95}
|
| 636 |
.mini-bead{position:relative;z-index:1;width:19px;height:19px;border:1px solid #72500d;border-radius:50%;background:radial-gradient(circle at 50% 50%,#704708 0 2px,transparent 3px),radial-gradient(circle at 29% 23%,#fff1a0 0 9%,transparent 11%),radial-gradient(circle at 38% 34%,#ffe27a,#d7a423 44%,#8e5b08 100%);box-shadow:inset -3px -4px 5px rgb(91 55 4/.32),0 2px 3px rgb(0 0 0/.38)}
|
| 637 |
.mini-bead::after{content:"";position:absolute;inset:3px;border:1px dotted rgb(112 71 8/.65);border-radius:50%;box-shadow:0 0 0 1px rgb(255 231 135/.28)}
|
| 638 |
+
.mini-bead:nth-of-type(1){grid-area:2/1}.mini-bead:nth-of-type(2){grid-area:2/2}.mini-bead:nth-of-type(3){grid-area:2/3}.mini-bead:nth-of-type(4){grid-area:2/4}.mini-bead:nth-of-type(5){grid-area:2/5}
|
| 639 |
+
.mini-bead:nth-of-type(6){grid-area:1/5}.mini-bead:nth-of-type(7){grid-area:1/4}.mini-bead:nth-of-type(8){grid-area:1/3}.mini-bead:nth-of-type(9){grid-area:1/2}.mini-bead:nth-of-type(10){grid-area:1/1}
|
| 640 |
.mini-bead.good{border-color:#1b2118;background:radial-gradient(circle at 50% 50%,#151a13 0 2px,transparent 3px),radial-gradient(circle at 31% 25%,#e2ead8 0 8%,transparent 10%),radial-gradient(circle at 38% 34%,#aabd98,#718363 44%,#35402f 100%);box-shadow:inset -3px -4px 5px rgb(10 17 9/.3),0 2px 3px rgb(0 0 0/.35)}
|
| 641 |
.mini-bead.good::after{border-color:rgb(40 55 34/.65);box-shadow:0 0 0 1px rgb(226 234 216/.26)}
|
| 642 |
+
.mini-bead.assisted{border-color:#924d12;background:radial-gradient(circle at 50% 50%,#74360b 0 2px,transparent 3px),radial-gradient(circle at 31% 25%,#ffe0ad 0 8%,transparent 10%),radial-gradient(circle at 38% 34%,#ffbd72,#e78329 44%,#a94716 100%);box-shadow:inset -3px -4px 5px rgb(104 45 9/.28),0 2px 3px rgb(0 0 0/.35)}
|
| 643 |
+
.mini-bead.assisted::after{border-color:rgb(120 55 14/.62);box-shadow:0 0 0 1px rgb(255 224 173/.28)}
|
| 644 |
.mini-bead.failed{border-color:#7d3d37;background:radial-gradient(circle at 50% 50%,#552622 0 2px,transparent 3px),radial-gradient(circle at 31% 25%,#ffe1da 0 8%,transparent 10%),radial-gradient(circle at 38% 34%,#efaaa0,#d77970 44%,#9b4f48 100%);box-shadow:inset -3px -4px 5px rgb(91 35 30/.25),0 2px 3px rgb(0 0 0/.35)}
|
| 645 |
.mini-bead.failed::after{border-color:rgb(108 43 38/.6);box-shadow:0 0 0 1px rgb(255 225 218/.28)}
|
| 646 |
.mini-bead.landed{animation:mini-land .3s cubic-bezier(.2,1.7,.4,1)}
|
| 647 |
.bead-flight{position:fixed;z-index:30;margin:0;border:3px solid #72500d;border-radius:50%;background:radial-gradient(circle at 50% 15%,#704708 0 7px,#d9a82c 8px 11px,transparent 12px),radial-gradient(circle at 31% 25%,rgb(255 246 186/.82) 0 4%,transparent 5%),radial-gradient(circle at 38% 34%,#ffe27a,#d7a423 42%,#8e5b08 100%);box-shadow:inset -14px -18px 30px rgb(91 55 4/.32),0 14px 24px rgb(0 0 0/.3);pointer-events:none}
|
| 648 |
.bead-flight.good{border-color:#1b2118;background:radial-gradient(circle at 50% 15%,#151a13 0 7px,#899b79 8px 11px,transparent 12px),radial-gradient(circle at 31% 25%,rgb(255 255 255/.72) 0 4%,transparent 5%),radial-gradient(circle at 38% 34%,#aabd98,#718363 42%,#303a2b 100%);box-shadow:inset -14px -18px 30px rgb(10 17 9/.35),0 14px 24px rgb(0 0 0/.3)}
|
| 649 |
+
.bead-flight.assisted{border-color:#924d12;background:radial-gradient(circle at 50% 15%,#74360b 0 7px,#e8943c 8px 11px,transparent 12px),radial-gradient(circle at 31% 25%,rgb(255 240 211/.78) 0 4%,transparent 5%),radial-gradient(circle at 38% 34%,#ffbd72,#e78329 42%,#a94716 100%);box-shadow:inset -14px -18px 30px rgb(104 45 9/.3),0 14px 24px rgb(0 0 0/.3)}
|
| 650 |
.bead-flight.failed{border-color:#7d3d37;background:radial-gradient(circle at 50% 15%,#552622 0 7px,#d98980 8px 11px,transparent 12px),radial-gradient(circle at 31% 25%,rgb(255 255 255/.7) 0 4%,transparent 5%),radial-gradient(circle at 38% 34%,#efaaa0,#d77970 42%,#9b4f48 100%)}
|
| 651 |
.card.bubblemode .prompt.collected{visibility:hidden}
|
| 652 |
.bubble-hint{position:absolute;left:50%;bottom:18px;z-index:3;width:max-content;min-width:170px;max-width:min(230px,calc(100% - 32px));box-sizing:border-box;padding:10px 13px;border-radius:12px;background:rgb(255 255 255/.94);color:#174f18;box-shadow:0 4px 14px rgb(30 91 36/.18);font:700 14px/1.35 var(--f-body);animation:hint-in .25s ease-out both}
|
|
|
|
| 667 |
body.bubble-game #gameBack .ic{width:20px;height:20px}
|
| 668 |
body.bubble-game #gameBack:active{transform:translateY(2px);box-shadow:0 1px 0 #111}
|
| 669 |
@keyframes bead-rise{0%{transform:translateY(54px) rotate(-3deg) scale(.9)}50%{transform:translateY(2px) rotate(2deg) scale(.97)}100%{transform:translateY(-58px) rotate(-2deg) scale(1.02)}}
|
| 670 |
+
/* Matches the hint clock: green 0–3s, yellow 3–6s, orange 6–9s; only expiry is red. */
|
| 671 |
+
@keyframes bead-color{
|
| 672 |
+
0%,24.99%{border-color:#1b2118;background:radial-gradient(circle at 50% 15%,#151a13 0 7px,#899b79 8px 11px,transparent 12px),radial-gradient(circle at 31% 25%,rgb(255 255 255/.72) 0 4%,transparent 5%),radial-gradient(circle at 38% 34%,#aabd98 0%,#718363 37%,#4a5941 72%,#303a2b 100%);color:#f7faef;text-shadow:0 2px 1px rgb(0 0 0/.45)}
|
| 673 |
+
25%,49.99%{border-color:#72500d;background:radial-gradient(circle at 50% 15%,#704708 0 7px,#d9a82c 8px 11px,transparent 12px),radial-gradient(circle at 31% 25%,rgb(255 246 186/.82) 0 4%,transparent 5%),radial-gradient(circle at 38% 34%,#ffe27a 0%,#d7a423 40%,#aa7110 73%,#714507 100%);color:#3c2800;text-shadow:0 1px 0 rgb(255 247 194/.6)}
|
| 674 |
+
50%,99.99%{border-color:#924d12;background:radial-gradient(circle at 50% 15%,#74360b 0 7px,#e8943c 8px 11px,transparent 12px),radial-gradient(circle at 31% 25%,rgb(255 240 211/.78) 0 4%,transparent 5%),radial-gradient(circle at 38% 34%,#ffbd72 0%,#e78329 40%,#c75f1d 73%,#913c12 100%);color:#481d05;text-shadow:0 1px 0 rgb(255 224 173/.55)}
|
| 675 |
+
100%{border-color:#7d3d37;background:radial-gradient(circle at 50% 15%,#552622 0 7px,#d98980 8px 11px,transparent 12px),radial-gradient(circle at 31% 25%,rgb(255 255 255/.7) 0 4%,transparent 5%),radial-gradient(circle at 38% 34%,#efaaa0 0%,#d77970 40%,#b85d55 73%,#843e38 100%);color:#481713;text-shadow:0 1px 0 rgb(255 225 218/.55)}
|
| 676 |
+
}
|
| 677 |
+
@keyframes bead-ring{0%,24.99%{border-color:#aabd98}25%,49.99%{border-color:#e2b32e}50%,99.99%{border-color:#e78329}100%{border-color:#d77970}}
|
| 678 |
@keyframes verb-time{from{clip-path:inset(0)}to{clip-path:inset(100% 0 0 0)}}
|
| 679 |
@keyframes score-pop{0%{transform:scale(1)}45%{transform:scale(1.55);color:#2d9b31}100%{transform:scale(1)}}
|
| 680 |
@keyframes grade-pop{0%{transform:translateX(-50%) scale(.25) rotate(-8deg);opacity:0}65%{transform:translateX(-50%) scale(1.18) rotate(3deg);opacity:1}100%{transform:translateX(-50%) scale(1) rotate(0)}}
|
| 681 |
@keyframes hint-in{from{transform:translate(-50%,6px);opacity:0}to{transform:translate(-50%,0);opacity:1}}
|
| 682 |
@keyframes mini-land{0%{transform:scale(.4)}65%{transform:scale(1.35)}100%{transform:scale(1)}}
|
| 683 |
+
@media (prefers-reduced-motion:reduce){.card.bubblemode .prompt{animation:bead-color 9s linear both}}
|
| 684 |
/* Without a microphone ("תרגול שקט ללא ציון"): a Hebrew sentence is answered by hand instead of said, built from its
|
| 685 |
Arabic words, tapped in order out of a bank that holds a few words too many. */
|
| 686 |
body.quiet :is(#mic,#playMine,#driveBtn,.phone-btn),body.driving :is(#quietBtn,#heBtn,.miclab,.phone-btn){display:none}
|
|
|
|
| 767 |
|
| 768 |
<main class="card" id="card" aria-live="polite">
|
| 769 |
<div class="bubble-score" id="bubbleScore" hidden><span>ניקוד<small id="bubbleCount"></small></span><b id="bubblePoints">0</b></div>
|
| 770 |
+
<div class="bubble-beads" id="bubbleBeads" hidden aria-label="תוצאות במסבחה: ירוק ללא רמז, צהוב אחרי רמז ראשון, כתום אחרי רמז שני ואדום בכישלון"><svg class="misbaha-cord" viewBox="0 0 150 88" aria-hidden="true"><defs><linearGradient id="imamGold" x1="0" x2="1"><stop stop-color="#845308"/><stop offset=".28" stop-color="#f5ce57"/><stop offset=".58" stop-color="#b8790d"/><stop offset=".82" stop-color="#ffe68a"/><stop offset="1" stop-color="#744607"/></linearGradient></defs><path class="cord-shadow" d="M36 14H120Q134 14 134 25T120 36H36"/><path class="cord-base" d="M36 14H120Q134 14 134 25T120 36H36"/><path class="cord-weave-a" d="M36 14H120Q134 14 134 25T120 36H36"/><path class="cord-weave-b" d="M36 14H120Q134 14 134 25T120 36H36"/><path class="cord-glint" d="M36 14H120Q134 14 134 25T120 36H36"/><path class="cord-base" d="M36 36H27"/><rect class="imam" x="9" y="31" width="21" height="10" rx="5"/><rect class="tassel-cap" x="4" y="30" width="7" height="12" rx="2"/><path class="tassel-thread" d="M7 42Q8 55 2 75M7 42Q12 57 8 80M7 42Q16 58 15 82M7 42Q20 56 22 78M7 42Q24 53 29 73M7 42Q18 48 35 67M7 42Q13 62 3 84M7 42Q21 61 18 85"/></svg></div>
|
| 771 |
<div class="bubble-hint" id="bubbleHint" hidden><small>רמז</small><span class="binyan" id="bubbleBinyan"></span><strong class="gizra" id="bubbleGizra"></strong><span class="past" id="bubblePast" hidden><small>עבר · הוא</small><b id="bubblePastText"></b><em id="bubblePastAr" lang="ar"></em></span></div>
|
| 772 |
<div class="meta"><span id="counter"></span></div>
|
| 773 |
<div class="progress" id="progress" hidden><i id="progressBar"></i></div>
|
|
|
|
| 1530 |
// left, it is the whole topic, and a practised sentence shows its best score (see show).
|
| 1531 |
// `here` is the topic's sentence on the card: not always the round's, because going back passes through the practised ones.
|
| 1532 |
let ordered=false, topic=[], here=null, showDone=store.get("ihki.showDone",false);
|
| 1533 |
+
const BUBBLE_MS=9000, BUBBLE_HINT_MS=3000, BUBBLE_PAST_MS=6000, BUBBLE_PASS=70, BUBBLE_ROUND=10;
|
| 1534 |
let bubbleTimer=null, bubbleHintTimer=null, bubblePastTimer=null, bubbleDeadline=0, bubbleFor=null, bubbleResolving=false, bubbleHintShown=false, bubblePastShown=false;
|
| 1535 |
let bubbleGame={answered:0,correct:0,total:0};
|
| 1536 |
const bubbleCat=()=>!daily&&(categoryOf(cat)||{}).bubble;
|
|
|
|
| 1545 |
drawBubbleScore(correct);
|
| 1546 |
}
|
| 1547 |
async function collectBead(result){
|
| 1548 |
+
const source=$("prompt"), shelf=$("bubbleBeads"), from=source.getBoundingClientRect(), tone=result==="clean"?" good":result==="assisted"?" assisted":result==="failed"?" failed":"", mini=mark(`mini-bead${tone}`,"");
|
| 1549 |
shelf.append(mini); const to=mini.getBoundingClientRect(); mini.style.visibility="hidden"; source.classList.add("collected");
|
| 1550 |
const ghost=mark(`bead-flight${tone}`,""); Object.assign(ghost.style,{left:from.left+"px",top:from.top+"px",width:from.width+"px",height:from.height+"px"}); document.body.append(ghost);
|
| 1551 |
if(!matchMedia("(prefers-reduced-motion:reduce)").matches){
|
| 1552 |
+
const chain=shelf.getBoundingClientRect(), origin={x:from.left+from.width/2,y:from.top+from.height/2}, scale=to.width/from.width;
|
| 1553 |
+
const point=(x,y,s=scale)=>({transform:`translate(${x-origin.x}px,${y-origin.y}px) scale(${s})`});
|
| 1554 |
+
const entry={x:chain.left+chain.width*36/150,y:chain.top+chain.height*14/88};
|
| 1555 |
+
const bendTop={x:chain.left+chain.width*120/150,y:entry.y};
|
| 1556 |
+
const bendMid={x:chain.left+chain.width*134/150,y:chain.top+chain.height*25/88};
|
| 1557 |
+
const bendBottom={x:bendTop.x,y:chain.top+chain.height*36/88};
|
| 1558 |
+
const target={x:to.left+to.width/2,y:to.top+to.height/2}, n=shelf.querySelectorAll(".mini-bead").length;
|
| 1559 |
+
const frames=[{transform:"translate(0,0) scale(1)"},point(entry.x,entry.y)];
|
| 1560 |
+
if(n<=5) frames.push(point(bendTop.x,bendTop.y),point(bendMid.x,bendMid.y),point(bendBottom.x,bendBottom.y));
|
| 1561 |
+
frames.push(point(target.x,target.y));
|
| 1562 |
+
try{ await ghost.animate(frames,{duration:n<=5?900:520,easing:"ease-in-out",fill:"forwards"}).finished; }catch{}
|
| 1563 |
}
|
| 1564 |
ghost.remove(); mini.style.visibility=""; mini.classList.add("landed");
|
| 1565 |
}
|
|
|
|
| 2302 |
}
|
| 2303 |
|
| 2304 |
async function bubbleOutcome(r,expired=false){
|
| 2305 |
+
const id=cur.id, correct=!r.unrelated&&r.score>=BUBBLE_PASS;
|
| 2306 |
if(bubbleResolving) return;
|
| 2307 |
bubbleResolving=true;
|
| 2308 |
if(!expired){
|
|
|
|
| 2311 |
$("prompt").append(mark(`bubble-grade ${band}`,`${score} נק׳`));
|
| 2312 |
}
|
| 2313 |
if(correct){
|
| 2314 |
+
const result=bubblePastShown?"assisted":bubbleHintShown?"hinted":"clean";
|
| 2315 |
countBubble(true);
|
| 2316 |
stopBubble();
|
| 2317 |
+
await collectBead(result);
|
| 2318 |
}else{
|
| 2319 |
if(!expired&&Date.now()<bubbleDeadline){
|
| 2320 |
bubbleResolving=false;
|