Refactor code structure for improved readability and maintainability
Browse files- app/workbook.py +1 -1
- static/index.html +329 -21
app/workbook.py
CHANGED
|
@@ -3988,7 +3988,7 @@ CATEGORIES = [{"id": f"l{lesson_id(*key)}-{tid}", "name": name, "lesson": lesson
|
|
| 3988 |
|
| 3989 |
# Games live outside the lesson path. Their phrases use the same scoring bank, but they are not topics,
|
| 3990 |
# do not affect lesson completion, and cannot be assigned as homework.
|
| 3991 |
-
GAMES = [{"id": "game-yalla-verb", "name": "פועל
|
| 3992 |
"unit": "פועל", "task": "אמרו את הפועל בערבית מדוברת: בעבר ואז בעתיד, לפני שהבועה נעלמת"}]
|
| 3993 |
|
| 3994 |
|
|
|
|
| 3988 |
|
| 3989 |
# Games live outside the lesson path. Their phrases use the same scoring bank, but they are not topics,
|
| 3990 |
# do not affect lesson completion, and cannot be assigned as homework.
|
| 3991 |
+
GAMES = [{"id": "game-yalla-verb", "name": "פועל זמני", "lesson": "1", "bubble": True, "hidden": False,
|
| 3992 |
"unit": "פועל", "task": "אמרו את הפועל בערבית מדוברת: בעבר ואז בעתיד, לפני שהבועה נעלמת"}]
|
| 3993 |
|
| 3994 |
|
static/index.html
CHANGED
|
@@ -169,6 +169,80 @@ body[data-view=verbs] #vids{max-width:none;gap:6px;margin-top:0}
|
|
| 169 |
.vt-say .tr i{font-style:normal;opacity:.5;margin-inline:3px}
|
| 170 |
@media (max-width:420px){.vt-row{grid-template-columns:minmax(0,7.2em) minmax(0,1fr)}}
|
| 171 |
.vt-empty{display:none}.vt-none .vt-empty{display:block}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
#vids button.now{padding-block:16px;background:var(--olive);border-color:var(--olive);color:var(--on-olive);box-shadow:0 3px 0 var(--olive-deep);font-size:17px}
|
| 173 |
#vids button.now i,#vids button.now small{color:inherit}
|
| 174 |
/* Refer a friend: a compact campaign page inside the home navigation, with the two rewards kept visually distinct. */
|
|
@@ -802,7 +876,7 @@ body.driving .card.storymode .prompt{display:block}
|
|
| 802 |
/* a story's quiz, once it ended: the grade is back on the card, over the mistakes made most often */
|
| 803 |
.card.storymode.quizdone .verdict{display:flex}
|
| 804 |
.tips li .sub{display:block}
|
| 805 |
-
/* "פועל
|
| 806 |
.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}
|
| 807 |
@property --bead-hole{syntax:"<color>";inherits:true;initial-value:#151a13}
|
| 808 |
@property --bead-neck{syntax:"<color>";inherits:true;initial-value:#899b79}
|
|
@@ -812,12 +886,12 @@ body.driving .card.storymode .prompt{display:block}
|
|
| 812 |
@property --bead-c{syntax:"<color>";inherits:true;initial-value:#4a5941}
|
| 813 |
@property --bead-d{syntax:"<color>";inherits:true;initial-value:#303a2b}
|
| 814 |
@property --bead-ring{syntax:"<color>";inherits:true;initial-value:#aabd98}
|
| 815 |
-
.card.bubblemode .prompt{--bead-hole:#151a13;--bead-neck:#899b79;--bead-light:#fff;--bead-a:#aabd98;--bead-b:#718363;--bead-c:#4a5941;--bead-d:#303a2b;--bead-ring:#aabd98;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(
|
| 816 |
.card.bubblemode .prompt.bead-yellow{--bead-hole:#704708;--bead-neck:#d9a82c;--bead-light:#fff6ba;--bead-a:#ffe27a;--bead-b:#d7a423;--bead-c:#aa7110;--bead-d:#714507;--bead-ring:#e2b32e;border-color:#72500d;color:#3c2800;text-shadow:0 1px 0 rgb(255 247 194/.6)}
|
| 817 |
.card.bubblemode .prompt.bead-orange{--bead-hole:#74360b;--bead-neck:#e8943c;--bead-light:#fff0d3;--bead-a:#ffbd72;--bead-b:#e78329;--bead-c:#c75f1d;--bead-d:#913c12;--bead-ring:#e78329;border-color:#924d12;color:#481d05;text-shadow:0 1px 0 rgb(255 224 173/.55)}
|
| 818 |
.card.bubblemode .prompt.bead-red{--bead-hole:#552622;--bead-neck:#d98980;--bead-light:#fff;--bead-a:#efaaa0;--bead-b:#d77970;--bead-c:#b85d55;--bead-d:#843e38;--bead-ring:#d77970;border-color:#7d3d37;color:#481713;text-shadow:0 1px 0 rgb(255 225 218/.55)}
|
| 819 |
.card.bubblemode .prompt::before{content:"";position:absolute;inset:-10px;border-radius:50%;border:5px solid var(--bead-ring);clip-path:inset(0);animation:verb-time 9s linear both}
|
| 820 |
-
.card.bubblemode .prompt::after{content:"";position:absolute;
|
| 821 |
.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)}
|
| 822 |
.card.bubblemode .bubble-grade::before{content:"★";margin-inline-end:4px;color:#fff;text-shadow:0 1px 1px rgb(92 58 0/.35)}
|
| 823 |
.card.bubblemode .bubble-grade.good{background:linear-gradient(180deg,#ffe66f,#ffc52f);color:#513600}
|
|
@@ -841,24 +915,33 @@ body.driving .card.storymode .prompt{display:block}
|
|
| 841 |
.misbaha-cord .imam{fill:url(#imamGold);stroke:#6e4508;stroke-width:1.2;filter:drop-shadow(1px 2px 1px rgb(0 0 0/.45))}
|
| 842 |
.misbaha-cord .tassel-cap{fill:#191916;stroke:#090a08;stroke-width:1}
|
| 843 |
.misbaha-cord .tassel-thread{fill:none;stroke:#171713;stroke-width:1.35;stroke-linecap:round;opacity:.95}
|
| 844 |
-
.mini-bead{position:relative;z-index:1;width:19px;height:19px;border:1px solid #72500d;border-radius:50%;background:radial-gradient(
|
| 845 |
-
.mini-bead::after{content:"";position:absolute;inset:
|
| 846 |
.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}
|
| 847 |
.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}
|
| 848 |
-
.mini-bead.good{border-color:#1b2118;background:radial-gradient(
|
| 849 |
-
.mini-bead.
|
| 850 |
-
.mini-bead.
|
| 851 |
-
.mini-bead.assisted::after{border-color:rgb(120 55 14/.62);box-shadow:0 0 0 1px rgb(255 224 173/.28)}
|
| 852 |
-
.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)}
|
| 853 |
-
.mini-bead.failed::after{border-color:rgb(108 43 38/.6);box-shadow:0 0 0 1px rgb(255 225 218/.28)}
|
| 854 |
.mini-bead.landed{animation:mini-land .3s cubic-bezier(.2,1.7,.4,1)}
|
| 855 |
-
.bead-flight{position:fixed;z-index:30;margin:0;border:3px solid #72500d;border-radius:50%;background:radial-gradient(
|
| 856 |
-
.bead-flight.good{border-color:#1b2118;background:radial-gradient(
|
| 857 |
-
.bead-flight.assisted{border-color:#924d12;background:radial-gradient(
|
| 858 |
-
.bead-flight.failed{border-color:#7d3d37;background:radial-gradient(
|
| 859 |
.card.bubblemode .prompt.collected{visibility:hidden}
|
| 860 |
.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}
|
| 861 |
.bubble-hint[hidden]{display:none}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 862 |
.bubble-hint small{display:block;color:#527254;font-size:11px}
|
| 863 |
.bubble-hint .binyan{display:block;margin-top:2px}
|
| 864 |
.bubble-hint .gizra{display:block;margin-top:3px;color:#103f13;font:800 23px/1.35 var(--f-display);text-shadow:0 1px 0 #fff}
|
|
@@ -868,7 +951,7 @@ body.driving .card.storymode .prompt{display:block}
|
|
| 868 |
.bubble-hint .past em{display:block;margin-top:2px;color:#527254;font:700 17px/1.2 sans-serif;font-style:normal;direction:rtl}
|
| 869 |
.card.bubblemode .task{font-weight:700;color:var(--olive)}
|
| 870 |
.card.bubblemode .donehint,.card.bubblemode .progress{position:relative;z-index:2}
|
| 871 |
-
.card.bubblemode>:not(.prompt):not(.bubble-score):not(.bubble-beads):not(.bubble-
|
| 872 |
body.bubble-game #wrap>header,body.bubble-game #practice>.roundbar{display:none}
|
| 873 |
#gameBack{display:none}
|
| 874 |
body.bubble-game #gameBack{display:flex;position:fixed;top:max(12px,env(safe-area-inset-top));right:12px;z-index:12;width:40px;height:40px;align-items:center;justify-content:center;border:2px solid #111;border-radius:50%;background:#63bd4d;color:#111;padding:0;cursor:pointer;box-shadow:0 3px 0 #111}
|
|
@@ -968,7 +1051,7 @@ body.quiet :is(#mic,#playMine,#driveBtn,.phone-btn),body.driving :is(#quietBtn,#
|
|
| 968 |
<main class="card" id="card" aria-live="polite">
|
| 969 |
<div class="bubble-score" id="bubbleScore" hidden><span>ניקוד<small id="bubbleCount"></small></span><b id="bubblePoints">0</b></div>
|
| 970 |
<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>
|
| 971 |
-
<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>
|
| 972 |
<div class="meta"><span id="counter"></span></div>
|
| 973 |
<div class="progress" id="progress" hidden><i id="progressBar"></i></div>
|
| 974 |
<div class="donehint" id="doneHint" hidden></div>
|
|
@@ -1424,7 +1507,10 @@ function drawNav(){ // the tabs, and the screen of videos when one is on show
|
|
| 1424 |
switcher.append(mark("","↻","i"),mark("","פועל יוצא","b"),mark("","מהבנה לשליפה מהירה של הטיית הפועל","small"));
|
| 1425 |
switcher.onclick=openVerbGame; box.append(switcher);
|
| 1426 |
}
|
| 1427 |
-
|
|
|
|
|
|
|
|
|
|
| 1428 |
for(const game of games){
|
| 1429 |
const b=mark("now","","button"), done=PHRASES.filter(p=>p.cat===game.id&&best[p.id]!=null).length;
|
| 1430 |
b.append(mark("","●","i"),mark("",game.name,"b"),mark("",done?`תורגלו ${done} פעלים`:"אמרו עבר ועתיד לפני שהבועה נעלמת","small"));
|
|
@@ -1490,7 +1576,7 @@ function drawNav(){ // the tabs, and the screen of videos when one is on show
|
|
| 1490 |
for(const v of libVideos()) row(v,libWatched,"ihki.libWatched","");
|
| 1491 |
}
|
| 1492 |
}
|
| 1493 |
-
/* The verb table. Its verbs are the "פועל
|
| 1494 |
בניין and גזרה (workbook.py, `_game_verbs`). A student sees those of the lessons up to the one they are on, a teacher all. */
|
| 1495 |
const VT_BINYANIM={ // the past and future pattern of each בניין, in the course's transliteration
|
| 1496 |
"בניין ראשון":"פַעַל / פִעֵל", "בניין שני":"פַעַّל – יְפַעֵّל", "בניין שלישי":"פַאעַל – יְפַאעֵל", "בניין רביעי":"אַפְעַל",
|
|
@@ -1548,7 +1634,9 @@ function drawVerbTable(box){
|
|
| 1548 |
const tips=mark("vt-tips","💡 הסברים","button"); tips.type="button"; // what the verbs of each גזרה share: shown, or put away to fit more
|
| 1549 |
const setTips=(on,keep)=>{ tips.setAttribute("aria-pressed",on); cards.classList.toggle("bare",!on); if(keep) store.set("ihki.vtTips",on); };
|
| 1550 |
const chosen=store.get("ihki.vtTips",null);
|
| 1551 |
-
tips.onclick=()=>setTips(tips.getAttribute("aria-pressed")!=="true",true); setTips(chosen??true);
|
|
|
|
|
|
|
| 1552 |
bar.append(top,slide); box.append(bar,mark("vt-empty","לא נמצא פועל כזה.","p"),cards);
|
| 1553 |
const filter=()=>{ // rows, then the cards left with none, are hidden
|
| 1554 |
const q=vtPlain(vtQuery.trim());
|
|
@@ -1595,6 +1683,211 @@ function drawVerbTable(box){
|
|
| 1595 |
// on a wide screen the table is meant to fit in one: unless the student chose, the explanations give way when it does not
|
| 1596 |
if(chosen==null&&innerWidth>=900) requestAnimationFrame(()=>{ if(cards.getBoundingClientRect().bottom>innerHeight-$("nav").offsetHeight) setTips(false); });
|
| 1597 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1598 |
function showView(view){ // "" is the path, which is laid out again: it was not while hidden
|
| 1599 |
if(view) document.body.dataset.view=view; else delete document.body.dataset.view;
|
| 1600 |
scrollTo(0,0); drawPath(true);
|
|
@@ -2080,7 +2373,7 @@ function armBubble(){
|
|
| 2080 |
pauseBubble();
|
| 2081 |
if(!bubbleCat()||home) return;
|
| 2082 |
if(bubbleFor!==cur.id){
|
| 2083 |
-
bubbleFor=cur.id; bubbleDeadline=Date.now()+BUBBLE_MS; bubbleResolving=false; bubbleHintShown=false; bubblePastShown=false;
|
| 2084 |
$("bubbleHint").hidden=true; $("bubblePast").hidden=true;
|
| 2085 |
}
|
| 2086 |
const left=Math.max(0,bubbleDeadline-Date.now());
|
|
@@ -2929,11 +3222,26 @@ function render(r){
|
|
| 2929 |
}
|
| 2930 |
}
|
| 2931 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2932 |
async function bubbleOutcome(r,expired=false){
|
| 2933 |
const id=cur.id, correct=!r.unrelated&&r.score>=BUBBLE_PASS;
|
| 2934 |
if(bubbleResolving) return;
|
| 2935 |
bubbleResolving=true;
|
| 2936 |
if(!expired){
|
|
|
|
| 2937 |
const old=$("prompt").querySelector(".bubble-grade"); if(old) old.remove();
|
| 2938 |
const score=Math.round(r.score), band=score>=85?"good":score>=60?"mid":"bad";
|
| 2939 |
$("prompt").append(mark(`bubble-grade ${band}`,`${score} נק׳`));
|
|
|
|
| 169 |
.vt-say .tr i{font-style:normal;opacity:.5;margin-inline:3px}
|
| 170 |
@media (max-width:420px){.vt-row{grid-template-columns:minmax(0,7.2em) minmax(0,1fr)}}
|
| 171 |
.vt-empty{display:none}.vt-none .vt-empty{display:block}
|
| 172 |
+
#vids .vt-play-mg{margin-inline-start:4px;padding:2px 9px;border:1px solid #b67d10;border-radius:999px;background:linear-gradient(180deg,#ffe27a,#d7a423);color:#3c2800;font:800 12px/1.4 var(--f-body);display:inline-block}
|
| 173 |
+
/* "הפועל זכרון": the verbs of the verb table's lesson as a memory game on a misbaha that snakes over the whole screen.
|
| 174 |
+
A bead shows gold, turning on its cord; open, a verb: the Hebrew, or the Arabic (said aloud) in transliteration. A found
|
| 175 |
+
pair turns green and stays open. The cord, the imam and the tassel are those of "פועל זמני", grown to the screen. */
|
| 176 |
+
body.memory-game{overflow:hidden}
|
| 177 |
+
.mg{position:fixed;inset:0;z-index:40;display:flex;flex-direction:column;background:#101510 url("/static/assets/arabic-calligraphy-path.webp?v=7") top center/100% auto repeat-y;color:#f7faef;user-select:none;-webkit-user-select:none;touch-action:manipulation;overflow:hidden}
|
| 178 |
+
.mg-hud{position:relative;z-index:3;display:flex;align-items:center;gap:8px;padding:max(10px,env(safe-area-inset-top)) 12px 4px}
|
| 179 |
+
.mg-hud .mg-title{flex:1;min-width:0}
|
| 180 |
+
.mg-hud h2{margin:0;font:400 clamp(20px,5.4vw,30px)/1.1 var(--f-display);color:#ffe27a;text-shadow:0 2px 0 #714507,0 4px 10px rgb(0 0 0/.5)}
|
| 181 |
+
.mg-hud .mg-title small{display:block;color:#c9d6bb;font:700 12px/1.3 var(--f-body);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
| 182 |
+
.mg-close{flex:none;display:grid;place-items:center;width:40px;height:40px;padding:0;border:2px solid #111;border-radius:50%;background:#63bd4d;color:#111;font:800 18px/1 var(--f-body);box-shadow:0 3px 0 #111;cursor:pointer}
|
| 183 |
+
.mg-close:active{transform:translateY(2px);box-shadow:0 1px 0 #111}
|
| 184 |
+
.mg-stat{flex:none;min-width:58px;padding:5px 9px;border:2px solid #205f20;border-radius:12px;background:rgb(255 255 255/.92);color:#174f18;box-shadow:0 3px 0 #205f20;text-align:center;font:800 11px/1 var(--f-body)}
|
| 185 |
+
.mg-stat b{display:block;font:800 22px/1 var(--f-display);font-variant-numeric:tabular-nums}
|
| 186 |
+
.mg-stat.tick b{animation:score-pop .38s cubic-bezier(.2,1.8,.4,1)}
|
| 187 |
+
.mg-combo{position:absolute;top:100%;left:50%;transform:translateX(-50%);padding:3px 12px;border-radius:999px;background:linear-gradient(180deg,#ffbd72,#e78329);color:#481d05;font:800 14px/1.3 var(--f-body);box-shadow:0 3px 0 #913c12;white-space:nowrap;animation:mg-combo .9s cubic-bezier(.2,1.8,.4,1) both;pointer-events:none}
|
| 188 |
+
.mg-field{position:relative;flex:1;min-height:0}
|
| 189 |
+
.mg-field>svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible;pointer-events:none}
|
| 190 |
+
.mg-cord path{fill:none;stroke-linecap:round;stroke-linejoin:round}
|
| 191 |
+
.mg-cord .c-shadow{stroke:#38280a;stroke-width:calc(var(--d) * .1);filter:drop-shadow(2px 5px 3px rgb(0 0 0/.6))}
|
| 192 |
+
.mg-cord .c-base{stroke:#b67d10;stroke-width:calc(var(--d) * .068)}
|
| 193 |
+
.mg-cord .c-a,.mg-cord .c-b{stroke-width:calc(var(--d) * .054);stroke-dasharray:calc(var(--d) * .09) calc(var(--d) * .09)}
|
| 194 |
+
.mg-cord .c-a{stroke:#f3c64c}.mg-cord .c-b{stroke:#8e5b08;stroke-dashoffset:calc(var(--d) * .09)}
|
| 195 |
+
.mg-cord .c-glint{stroke:#fff1a6;stroke-width:calc(var(--d) * .016);stroke-dasharray:calc(var(--d) * .03) calc(var(--d) * .15);opacity:.8}
|
| 196 |
+
.mg-cord .imam{fill:url(#mgGold);stroke:#6e4508;stroke-width:2;filter:drop-shadow(2px 4px 2px rgb(0 0 0/.45))}
|
| 197 |
+
.mg-cord .tassel-cap{fill:#191916;stroke:#090a08;stroke-width:1.5}
|
| 198 |
+
.mg-cord .tassel-thread{fill:none;stroke:#171713;stroke-width:2.2;stroke-linecap:round}
|
| 199 |
+
.mg-bead{position:absolute;left:0;top:0;width:var(--d);height:var(--d);padding:0;border:0;border-radius:50%;background:none;color:inherit;cursor:pointer;will-change:transform;-webkit-tap-highlight-color:transparent}
|
| 200 |
+
.mg-bead:focus-visible{outline:3px solid #ffe27a;outline-offset:3px}
|
| 201 |
+
.mg-rot{position:absolute;inset:0;transform-style:preserve-3d;transform:perspective(600px) rotateY(0);transition:transform .6s cubic-bezier(.3,1.55,.5,1);animation:mg-in .55s cubic-bezier(.2,1.7,.4,1) backwards;animation-delay:calc(var(--i) * 45ms)}
|
| 202 |
+
.mg-bead:not(.open):hover .mg-rot{transform:perspective(600px) rotateY(0) scale(1.08) rotate(-4deg)}
|
| 203 |
+
.mg-bead.open .mg-rot{transform:perspective(600px) rotateY(180deg)}
|
| 204 |
+
.mg-bead.miss .mg-rot{animation:mg-miss .5s ease-in-out}
|
| 205 |
+
.mg-bead.done .mg-rot{animation:mg-pop .6s cubic-bezier(.2,1.8,.4,1)}
|
| 206 |
+
.mg-bead.done{cursor:default}
|
| 207 |
+
.mg-side{position:absolute;inset:0;border-radius:50%;backface-visibility:hidden;-webkit-backface-visibility:hidden;overflow:hidden}
|
| 208 |
+
.mg-back{border:2px solid #72500d;background:radial-gradient(circle at 29% 23%,#fff1a0 0 7%,transparent 10%),radial-gradient(circle at 38% 34%,#ffe27a,#d7a423 44%,#8e5b08 100%);box-shadow:inset calc(var(--d) * -.07) calc(var(--d) * -.09) calc(var(--d) * .13) rgb(91 55 4/.38),0 calc(var(--d) * .08) calc(var(--d) * .12) rgb(0 0 0/.42)}
|
| 209 |
+
.mg-back i{position:absolute;inset:0;border-radius:50%;background:repeating-linear-gradient(180deg,rgb(255 248 200/.3) 0 5%,transparent 5% 14%,rgb(112 71 8/.22) 14% 18%,transparent 18% 25%);background-size:100% 200%;-webkit-mask:radial-gradient(circle,#000 30%,transparent 74%);mask:radial-gradient(circle,#000 30%,transparent 74%);animation:mg-band var(--spin,5s) linear infinite var(--way,normal)} /* the bands run over the top: the bead turning round its horizontal axis */
|
| 210 |
+
.mg-face{transform:rotateY(180deg);display:flex;flex-direction:column;align-items:center;justify-content:center;padding:10%;text-align:center;border:3px solid #c9a24a;background:radial-gradient(circle at 30% 24%,#fff 0 6%,transparent 9%),radial-gradient(circle at 38% 32%,#fffdf3,#f6e7bd 55%,#d9b86a 100%);color:#3c2800;box-shadow:inset calc(var(--d) * -.07) calc(var(--d) * -.09) calc(var(--d) * .13) rgb(120 80 10/.3),0 calc(var(--d) * .08) calc(var(--d) * .12) rgb(0 0 0/.42)}
|
| 211 |
+
.mg-face.he{font:800 calc(var(--d) * .175)/1.15 var(--f-body)}
|
| 212 |
+
.mg-face.ar{border-color:#924d12;background:radial-gradient(circle at 30% 24%,#fff 0 6%,transparent 9%),radial-gradient(circle at 38% 32%,#fff0d3,#ffbd72 55%,#e78329 100%);color:#481d05}
|
| 213 |
+
.mg-face .tr{display:block;font:800 calc(var(--d) * .15)/1.3 var(--f-tr);letter-spacing:.02em;white-space:nowrap}
|
| 214 |
+
.mg-face .ear{font-size:calc(var(--d) * .11);line-height:1;margin-top:2px;opacity:.75}
|
| 215 |
+
.mg-bead.done .mg-face{border-color:#1b2118;background:radial-gradient(circle at 30% 24%,rgb(255 255 255/.7) 0 6%,transparent 9%),radial-gradient(circle at 38% 34%,#c4d6b0,#8ea57b 45%,#4a5941 100%);color:#f7faef;text-shadow:0 1px 2px rgb(0 0 0/.55)}
|
| 216 |
+
.mg-bead.done .mg-face::after{content:"✓";position:absolute;top:6%;left:50%;transform:translateX(-50%);font:800 calc(var(--d) * .13)/1 var(--f-body);color:#e9ffd8}
|
| 217 |
+
.mg-spark{position:absolute;left:0;top:0;z-index:4;font-style:normal;font-size:calc(var(--d) * .22);pointer-events:none;animation:mg-fly .95s cubic-bezier(.15,.7,.3,1) forwards}
|
| 218 |
+
.mg-praise{position:absolute;left:0;top:0;z-index:5;padding:4px 16px;border:3px solid #fff;border-radius:999px;background:linear-gradient(180deg,#ffe66f,#ffc52f);color:#513600;box-shadow:0 4px 0 #a96800,0 10px 20px rgb(0 0 0/.35);font:400 clamp(22px,6vw,34px)/1.2 var(--f-tr);white-space:nowrap;pointer-events:none;animation:mg-praise 1.3s cubic-bezier(.2,1.6,.4,1) forwards}
|
| 219 |
+
.mg-win{position:absolute;inset:0;z-index:6;display:grid;place-items:center;padding:16px;background:rgb(10 14 9/.55);backdrop-filter:blur(2px);animation:mg-fade .3s ease-out both}
|
| 220 |
+
.mg-win>div{width:min(360px,100%);padding:22px 20px 18px;border:3px solid #205f20;border-radius:24px;background:#fffdf3;color:#174f18;text-align:center;box-shadow:0 6px 0 #205f20,0 24px 50px rgb(0 0 0/.45);animation:mg-pop-flat .7s cubic-bezier(.2,1.8,.4,1) both}
|
| 221 |
+
.mg-win .big{font-size:56px;line-height:1;animation:mg-wiggle 1.2s ease-in-out infinite}
|
| 222 |
+
.mg-win h3{margin:8px 0 2px;font:400 32px/1.15 var(--f-tr);color:#103f13}
|
| 223 |
+
.mg-win p{margin:4px 0 0;font:700 16px/1.4 var(--f-body);color:#527254}
|
| 224 |
+
.mg-win .stars{margin:8px 0 4px;font-size:38px;letter-spacing:6px;color:#f2a91d;text-shadow:0 2px 0 #a96800}
|
| 225 |
+
.mg-win .stars span{display:inline-block;animation:mg-pop-flat .5s cubic-bezier(.2,1.8,.4,1) backwards}
|
| 226 |
+
.mg-win .stars span:nth-child(2){animation-delay:.18s}.mg-win .stars span:nth-child(3){animation-delay:.36s}
|
| 227 |
+
.mg-win .stars .off{color:#d5dacb;text-shadow:none}
|
| 228 |
+
.mg-win .acts{display:flex;gap:10px;justify-content:center;margin-top:14px}
|
| 229 |
+
.mg-win button{padding:11px 18px;border:2px solid #111;border-radius:14px;background:#63bd4d;color:#111;font:800 17px/1 var(--f-body);box-shadow:0 4px 0 #111;cursor:pointer}
|
| 230 |
+
.mg-win button+button{background:#fff}
|
| 231 |
+
.mg-win button:active{transform:translateY(2px);box-shadow:0 2px 0 #111}
|
| 232 |
+
.mg-drop{position:absolute;top:-40px;z-index:5;width:var(--s);height:var(--s);border-radius:50%;pointer-events:none;background:radial-gradient(circle at 32% 26%,#fff 0 8%,transparent 11%),radial-gradient(circle at 38% 34%,var(--c1),var(--c2) 50%,var(--c3) 100%);box-shadow:0 3px 5px rgb(0 0 0/.35);animation:mg-drop var(--t) cubic-bezier(.4,.1,.7,1) var(--w) forwards}
|
| 233 |
+
.mg-none{margin:auto;padding:20px;max-width:320px;text-align:center;font:700 18px/1.5 var(--f-body)}
|
| 234 |
+
@keyframes mg-band{to{background-position:0 100%}}
|
| 235 |
+
@keyframes mg-fade{from{opacity:0}}
|
| 236 |
+
@keyframes mg-in{from{transform:perspective(600px) scale(0) rotate(-200deg)}}
|
| 237 |
+
@keyframes mg-miss{0%,100%{transform:perspective(600px) rotateY(180deg)}20%{transform:perspective(600px) rotateY(180deg) translateX(-9px) rotate(-6deg)}45%{transform:perspective(600px) rotateY(180deg) translateX(8px) rotate(5deg)}70%{transform:perspective(600px) rotateY(180deg) translateX(-5px)}}
|
| 238 |
+
@keyframes mg-pop{0%{transform:perspective(600px) rotateY(180deg) scale(1)}40%{transform:perspective(600px) rotateY(180deg) scale(1.3) rotate(8deg)}70%{transform:perspective(600px) rotateY(180deg) scale(.94) rotate(-3deg)}100%{transform:perspective(600px) rotateY(180deg) scale(1)}}
|
| 239 |
+
@keyframes mg-pop-flat{0%{transform:scale(.3) rotate(-6deg);opacity:0}65%{transform:scale(1.08) rotate(2deg);opacity:1}100%{transform:none}}
|
| 240 |
+
@keyframes mg-fly{0%{transform:translate(var(--x),var(--y)) scale(.3);opacity:1}100%{transform:translate(calc(var(--x) + var(--dx)),calc(var(--y) + var(--dy))) scale(1.1) rotate(var(--r));opacity:0}}
|
| 241 |
+
@keyframes mg-praise{0%{transform:translate(var(--x),var(--y)) translate(-50%,-50%) scale(.2) rotate(-12deg);opacity:0}25%{transform:translate(var(--x),var(--y)) translate(-50%,-80%) scale(1.15) rotate(3deg);opacity:1}75%{transform:translate(var(--x),var(--y)) translate(-50%,-110%) scale(1);opacity:1}100%{transform:translate(var(--x),var(--y)) translate(-50%,-150%) scale(.9);opacity:0}}
|
| 242 |
+
@keyframes mg-combo{0%{transform:translateX(-50%) scale(.3);opacity:0}30%{transform:translateX(-50%) scale(1.2);opacity:1}80%{opacity:1}100%{transform:translateX(-50%) translateY(10px) scale(1);opacity:0}}
|
| 243 |
+
@keyframes mg-wiggle{0%,100%{transform:rotate(-8deg)}50%{transform:rotate(8deg) scale(1.1)}}
|
| 244 |
+
@keyframes mg-drop{to{transform:translateY(calc(100vh + 80px)) rotate(var(--r))}}
|
| 245 |
+
@media (prefers-reduced-motion:reduce){.mini-bead::after,.card.bubblemode .prompt::after,.mg-back i,.mg-win .big{animation:none}.mg-rot{transition-duration:.2s}}
|
| 246 |
#vids button.now{padding-block:16px;background:var(--olive);border-color:var(--olive);color:var(--on-olive);box-shadow:0 3px 0 var(--olive-deep);font-size:17px}
|
| 247 |
#vids button.now i,#vids button.now small{color:inherit}
|
| 248 |
/* Refer a friend: a compact campaign page inside the home navigation, with the two rewards kept visually distinct. */
|
|
|
|
| 876 |
/* a story's quiz, once it ended: the grade is back on the card, over the mistakes made most often */
|
| 877 |
.card.storymode.quizdone .verdict{display:flex}
|
| 878 |
.tips li .sub{display:block}
|
| 879 |
+
/* "פועל זמני": every prompt is a bead; correct answers join the misbaha above. */
|
| 880 |
.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}
|
| 881 |
@property --bead-hole{syntax:"<color>";inherits:true;initial-value:#151a13}
|
| 882 |
@property --bead-neck{syntax:"<color>";inherits:true;initial-value:#899b79}
|
|
|
|
| 886 |
@property --bead-c{syntax:"<color>";inherits:true;initial-value:#4a5941}
|
| 887 |
@property --bead-d{syntax:"<color>";inherits:true;initial-value:#303a2b}
|
| 888 |
@property --bead-ring{syntax:"<color>";inherits:true;initial-value:#aabd98}
|
| 889 |
+
.card.bubblemode .prompt{--bead-hole:#151a13;--bead-neck:#899b79;--bead-light:#fff;--bead-a:#aabd98;--bead-b:#718363;--bead-c:#4a5941;--bead-d:#303a2b;--bead-ring:#aabd98;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(ellipse 7% 11% at 1.5% 50%,var(--bead-hole) 55%,transparent 72%),radial-gradient(ellipse 7% 11% at 98.5% 50%,var(--bead-hole) 55%,transparent 72%),radial-gradient(circle at 31% 25%,color-mix(in srgb,var(--bead-light) 72%,transparent) 0 4%,transparent 5%),radial-gradient(circle at 38% 34%,var(--bead-a) 0%,var(--bead-b) 37%,var(--bead-c) 72%,var(--bead-d) 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;transition:--bead-hole .4s ease,--bead-neck .4s ease,--bead-light .4s ease,--bead-a .4s ease,--bead-b .4s ease,--bead-c .4s ease,--bead-d .4s ease,--bead-ring .4s ease,border-color .4s ease,color .4s ease,text-shadow .4s ease}
|
| 890 |
.card.bubblemode .prompt.bead-yellow{--bead-hole:#704708;--bead-neck:#d9a82c;--bead-light:#fff6ba;--bead-a:#ffe27a;--bead-b:#d7a423;--bead-c:#aa7110;--bead-d:#714507;--bead-ring:#e2b32e;border-color:#72500d;color:#3c2800;text-shadow:0 1px 0 rgb(255 247 194/.6)}
|
| 891 |
.card.bubblemode .prompt.bead-orange{--bead-hole:#74360b;--bead-neck:#e8943c;--bead-light:#fff0d3;--bead-a:#ffbd72;--bead-b:#e78329;--bead-c:#c75f1d;--bead-d:#913c12;--bead-ring:#e78329;border-color:#924d12;color:#481d05;text-shadow:0 1px 0 rgb(255 224 173/.55)}
|
| 892 |
.card.bubblemode .prompt.bead-red{--bead-hole:#552622;--bead-neck:#d98980;--bead-light:#fff;--bead-a:#efaaa0;--bead-b:#d77970;--bead-c:#b85d55;--bead-d:#843e38;--bead-ring:#d77970;border-color:#7d3d37;color:#481713;text-shadow:0 1px 0 rgb(255 225 218/.55)}
|
| 893 |
.card.bubblemode .prompt::before{content:"";position:absolute;inset:-10px;border-radius:50%;border:5px solid var(--bead-ring);clip-path:inset(0);animation:verb-time 9s linear both}
|
| 894 |
+
.card.bubblemode .prompt::after{content:"";position:absolute;inset:0;z-index:-1;border-radius:50%;background:repeating-linear-gradient(180deg,rgb(255 255 255/.2) 0 5%,transparent 5% 14%,rgb(0 0 0/.14) 14% 18%,transparent 18% 25%) 0 0/100% 200%;-webkit-mask:radial-gradient(circle,#000 30%,transparent 74%);mask:radial-gradient(circle,#000 30%,transparent 74%);animation:mg-band 5.6s linear infinite;pointer-events:none} /* the beads of "הפועל זכרון": they turn round their horizontal axis */
|
| 895 |
.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)}
|
| 896 |
.card.bubblemode .bubble-grade::before{content:"★";margin-inline-end:4px;color:#fff;text-shadow:0 1px 1px rgb(92 58 0/.35)}
|
| 897 |
.card.bubblemode .bubble-grade.good{background:linear-gradient(180deg,#ffe66f,#ffc52f);color:#513600}
|
|
|
|
| 915 |
.misbaha-cord .imam{fill:url(#imamGold);stroke:#6e4508;stroke-width:1.2;filter:drop-shadow(1px 2px 1px rgb(0 0 0/.45))}
|
| 916 |
.misbaha-cord .tassel-cap{fill:#191916;stroke:#090a08;stroke-width:1}
|
| 917 |
.misbaha-cord .tassel-thread{fill:none;stroke:#171713;stroke-width:1.35;stroke-linecap:round;opacity:.95}
|
| 918 |
+
.mini-bead{position:relative;z-index:1;width:19px;height:19px;border:1px solid #72500d;border-radius:50%;background:radial-gradient(ellipse 9% 14% at 2% 50%,#704708 55%,transparent 75%),radial-gradient(ellipse 9% 14% at 98% 50%,#704708 55%,transparent 75%),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)}
|
| 919 |
+
.mini-bead::after{content:"";position:absolute;inset:0;border-radius:50%;background:repeating-linear-gradient(180deg,rgb(255 255 255/.24) 0 6%,transparent 6% 15%,rgb(0 0 0/.16) 15% 19%,transparent 19% 25%) 0 0/100% 200%;-webkit-mask:radial-gradient(circle,#000 30%,transparent 74%);mask:radial-gradient(circle,#000 30%,transparent 74%);animation:mg-band 5s linear infinite}
|
| 920 |
.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}
|
| 921 |
.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}
|
| 922 |
+
.mini-bead.good{border-color:#1b2118;background:radial-gradient(ellipse 9% 14% at 2% 50%,#151a13 55%,transparent 75%),radial-gradient(ellipse 9% 14% at 98% 50%,#151a13 55%,transparent 75%),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)}
|
| 923 |
+
.mini-bead.assisted{border-color:#924d12;background:radial-gradient(ellipse 9% 14% at 2% 50%,#74360b 55%,transparent 75%),radial-gradient(ellipse 9% 14% at 98% 50%,#74360b 55%,transparent 75%),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)}
|
| 924 |
+
.mini-bead.failed{border-color:#7d3d37;background:radial-gradient(ellipse 9% 14% at 2% 50%,#552622 55%,transparent 75%),radial-gradient(ellipse 9% 14% at 98% 50%,#552622 55%,transparent 75%),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)}
|
|
|
|
|
|
|
|
|
|
| 925 |
.mini-bead.landed{animation:mini-land .3s cubic-bezier(.2,1.7,.4,1)}
|
| 926 |
+
.bead-flight{position:fixed;z-index:30;margin:0;border:3px solid #72500d;border-radius:50%;background:radial-gradient(ellipse 7% 11% at 1.5% 50%,#704708 55%,transparent 72%),radial-gradient(ellipse 7% 11% at 98.5% 50%,#704708 55%,transparent 72%),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}
|
| 927 |
+
.bead-flight.good{border-color:#1b2118;background:radial-gradient(ellipse 7% 11% at 1.5% 50%,#151a13 55%,transparent 72%),radial-gradient(ellipse 7% 11% at 98.5% 50%,#151a13 55%,transparent 72%),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)}
|
| 928 |
+
.bead-flight.assisted{border-color:#924d12;background:radial-gradient(ellipse 7% 11% at 1.5% 50%,#74360b 55%,transparent 72%),radial-gradient(ellipse 7% 11% at 98.5% 50%,#74360b 55%,transparent 72%),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)}
|
| 929 |
+
.bead-flight.failed{border-color:#7d3d37;background:radial-gradient(ellipse 7% 11% at 1.5% 50%,#552622 55%,transparent 72%),radial-gradient(ellipse 7% 11% at 98.5% 50%,#552622 55%,transparent 72%),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%)}
|
| 930 |
.card.bubblemode .prompt.collected{visibility:hidden}
|
| 931 |
.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}
|
| 932 |
.bubble-hint[hidden]{display:none}
|
| 933 |
+
/* under the bead: the student's try as a WhatsApp text message (one: a new try takes its place), and the hint under it */
|
| 934 |
+
.bubble-foot{display:none}
|
| 935 |
+
.card.bubblemode .bubble-foot{position:absolute;left:50%;bottom:18px;z-index:3;display:flex;flex-direction:column;align-items:center;gap:10px;width:min(300px,calc(100% - 32px));transform:translateX(-50%)}
|
| 936 |
+
.bubble-foot .bubble-hint{position:static;left:auto;bottom:auto;animation:wa-in .25s ease-out both}
|
| 937 |
+
.wa-msg{position:relative;align-self:flex-end;display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:flex-end;gap:0 8px;max-width:min(272px,100%);padding:6px 8px 5px 9px;border-radius:0 8px 8px 8px;background:#d9fdd3;color:#111b21;box-shadow:0 1px .5px rgb(11 20 26/.13),0 6px 16px rgb(0 0 0/.25);animation:wa-in .35s cubic-bezier(.2,1.5,.4,1) both}
|
| 938 |
+
.wa-msg::before{content:"";position:absolute;top:0;right:100%;width:8px;height:13px;background:#d9fdd3;clip-path:polygon(0 0,100% 0,100% 100%)} /* the tail, toward the speaker's side */
|
| 939 |
+
.wa-text{flex:1 1 auto;font:700 19px/1.45 var(--f-tr);letter-spacing:.02em;overflow-wrap:anywhere}
|
| 940 |
+
.wa-msg.empty .wa-text{font:italic 400 14px/1.4 var(--f-body);color:#667781}
|
| 941 |
+
.wa-when{display:inline-flex;align-items:center;gap:3px;margin-bottom:1px;color:#667781;font:400 11px/1 -apple-system,"Segoe UI",Arial,sans-serif;font-variant-numeric:tabular-nums;white-space:nowrap}
|
| 942 |
+
.wa-ticks{display:inline-flex;color:#8696a0}.wa-ticks svg{width:16px;height:11px;fill:currentColor}
|
| 943 |
+
.wa-msg.read .wa-ticks{color:#53bdeb}
|
| 944 |
+
@keyframes wa-in{from{opacity:0;transform:translateY(10px) scale(.96)}}
|
| 945 |
.bubble-hint small{display:block;color:#527254;font-size:11px}
|
| 946 |
.bubble-hint .binyan{display:block;margin-top:2px}
|
| 947 |
.bubble-hint .gizra{display:block;margin-top:3px;color:#103f13;font:800 23px/1.35 var(--f-display);text-shadow:0 1px 0 #fff}
|
|
|
|
| 951 |
.bubble-hint .past em{display:block;margin-top:2px;color:#527254;font:700 17px/1.2 sans-serif;font-style:normal;direction:rtl}
|
| 952 |
.card.bubblemode .task{font-weight:700;color:var(--olive)}
|
| 953 |
.card.bubblemode .donehint,.card.bubblemode .progress{position:relative;z-index:2}
|
| 954 |
+
.card.bubblemode>:not(.prompt):not(.bubble-score):not(.bubble-beads):not(.bubble-foot){display:none!important}
|
| 955 |
body.bubble-game #wrap>header,body.bubble-game #practice>.roundbar{display:none}
|
| 956 |
#gameBack{display:none}
|
| 957 |
body.bubble-game #gameBack{display:flex;position:fixed;top:max(12px,env(safe-area-inset-top));right:12px;z-index:12;width:40px;height:40px;align-items:center;justify-content:center;border:2px solid #111;border-radius:50%;background:#63bd4d;color:#111;padding:0;cursor:pointer;box-shadow:0 3px 0 #111}
|
|
|
|
| 1051 |
<main class="card" id="card" aria-live="polite">
|
| 1052 |
<div class="bubble-score" id="bubbleScore" hidden><span>ניקוד<small id="bubbleCount"></small></span><b id="bubblePoints">0</b></div>
|
| 1053 |
<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>
|
| 1054 |
+
<div class="bubble-foot" id="bubbleFoot"><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></div>
|
| 1055 |
<div class="meta"><span id="counter"></span></div>
|
| 1056 |
<div class="progress" id="progress" hidden><i id="progressBar"></i></div>
|
| 1057 |
<div class="donehint" id="doneHint" hidden></div>
|
|
|
|
| 1507 |
switcher.append(mark("","↻","i"),mark("","פועל יוצא","b"),mark("","מהבנה לשליפה מהירה של הטיית הפועל","small"));
|
| 1508 |
switcher.onclick=openVerbGame; box.append(switcher);
|
| 1509 |
}
|
| 1510 |
+
const memo=mark("now","","button"), upto=vtLessonNow(); // the memory game, on the verb table's lesson
|
| 1511 |
+
memo.append(mark("","📿","i"),mark("","הפועל זכרון","b"),mark("",`הפכו חרוזים ומצאו זוגות של פעלים · עד ${(ALL_LESSONS.find(l=>l.id===upto)||{}).name||""}`,"small"));
|
| 1512 |
+
memo.onclick=openMemoryGame; if(upto) box.append(memo);
|
| 1513 |
+
if(!games.length&&!upto) box.append(mark("","המשחקים הראשונים יגיעו בקרוב.","p"));
|
| 1514 |
for(const game of games){
|
| 1515 |
const b=mark("now","","button"), done=PHRASES.filter(p=>p.cat===game.id&&best[p.id]!=null).length;
|
| 1516 |
b.append(mark("","●","i"),mark("",game.name,"b"),mark("",done?`תורגלו ${done} פעלים`:"אמרו עבר ועתיד לפני שהבועה נעלמת","small"));
|
|
|
|
| 1576 |
for(const v of libVideos()) row(v,libWatched,"ihki.libWatched","");
|
| 1577 |
}
|
| 1578 |
}
|
| 1579 |
+
/* The verb table. Its verbs are the "פועל זמני" game's: every verb once, in the lesson that first teaches it, with its
|
| 1580 |
בניין and גזרה (workbook.py, `_game_verbs`). A student sees those of the lessons up to the one they are on, a teacher all. */
|
| 1581 |
const VT_BINYANIM={ // the past and future pattern of each בניין, in the course's transliteration
|
| 1582 |
"בניין ראשון":"פַעַל / פִעֵל", "בניין שני":"פַעַّל – יְפַעֵّל", "בניין שלישי":"פַאעַל – יְפַאעֵל", "בניין רביעי":"אַפְעַל",
|
|
|
|
| 1634 |
const tips=mark("vt-tips","💡 הסברים","button"); tips.type="button"; // what the verbs of each גזרה share: shown, or put away to fit more
|
| 1635 |
const setTips=(on,keep)=>{ tips.setAttribute("aria-pressed",on); cards.classList.toggle("bare",!on); if(keep) store.set("ihki.vtTips",on); };
|
| 1636 |
const chosen=store.get("ihki.vtTips",null);
|
| 1637 |
+
tips.onclick=()=>setTips(tips.getAttribute("aria-pressed")!=="true",true); setTips(chosen??true);
|
| 1638 |
+
const play=mark("vt-play-mg","📿 הפועל זכרון","button"); play.type="button"; play.title="הפועל זכרון: משחק זיכרון על הפעלים עד השיעור שבסרגל"; play.onclick=openMemoryGame;
|
| 1639 |
+
label.append(tips,play);
|
| 1640 |
bar.append(top,slide); box.append(bar,mark("vt-empty","לא נמצא פועל כזה.","p"),cards);
|
| 1641 |
const filter=()=>{ // rows, then the cards left with none, are hidden
|
| 1642 |
const q=vtPlain(vtQuery.trim());
|
|
|
|
| 1683 |
// on a wide screen the table is meant to fit in one: unless the student chose, the explanations give way when it does not
|
| 1684 |
if(chosen==null&&innerWidth>=900) requestAnimationFrame(()=>{ if(cards.getBoundingClientRect().bottom>innerHeight-$("nav").offsetHeight) setTips(false); });
|
| 1685 |
}
|
| 1686 |
+
/* "הפועל זכרון": the verbs up to the verb table's lesson (the student's own until the slider moves), about half of them
|
| 1687 |
+
that lesson's, each as two beads: its Hebrew and its Arabic. The beads hang on a cord that winds over the screen in rows,
|
| 1688 |
+
like a snake, and a wave runs down it; each bead turns on the cord. A bead opened with the Arabic says it. */
|
| 1689 |
+
const MG_PRAISE=["מֻמְתַאז!","יַא סַלַאם!","שַאטֵר!","בְּרַאבוֹ!","עַאש!","זַי אִלְעַסַל!","תִסְלַם!"];
|
| 1690 |
+
const MG_SPARKS=["✨","⭐","🌟","💛","💚","🎉"];
|
| 1691 |
+
let mg=null; // the game on show: its beads, its track and its counts
|
| 1692 |
+
function vtLessonNow(){ // the verb table's lesson: the slider's, or the student's own
|
| 1693 |
+
const lessons=LESSONS.filter(l=>!l.soon), rank=reachedRank();
|
| 1694 |
+
if(lessons.some(l=>l.id===vtAt)) return vtAt;
|
| 1695 |
+
return (lessons.find(l=>ALL_LESSONS.findIndex(x=>x.id===l.id)===rank)||lessons[0]||{}).id;
|
| 1696 |
+
}
|
| 1697 |
+
function mgPool(upto){ // every verb taught up to the lesson, each Hebrew once (עשה, ענה and נכנס name two verbs)
|
| 1698 |
+
const place=id=>ALL_LESSONS.findIndex(l=>l.id===id), u=place(upto), seen=new Set();
|
| 1699 |
+
return PHRASES.filter(p=>p.binyan&&GAMES.some(g=>g.id===p.cat)&&place(p.unlock)<=u).filter(p=>{
|
| 1700 |
+
const k=vtPlain(p.he).replace(/\s*(ו|,).*$/,""); return !seen.has(k)&&seen.add(k);
|
| 1701 |
+
});
|
| 1702 |
+
}
|
| 1703 |
+
function mgTone(notes,type="triangle",vol=.07){ // the game's small sounds: [frequency, seconds] one after another
|
| 1704 |
+
const ctx=unlockPlayback(); if(!ctx) return;
|
| 1705 |
+
let t=ctx.currentTime;
|
| 1706 |
+
for(const [f,d] of notes){
|
| 1707 |
+
const o=ctx.createOscillator(), g=ctx.createGain(); o.type=type; o.frequency.setValueAtTime(f,t);
|
| 1708 |
+
g.gain.setValueAtTime(vol,t); g.gain.exponentialRampToValueAtTime(.0001,t+d); o.connect(g).connect(ctx.destination); o.start(t); o.stop(t+d+.02); t+=d*.75;
|
| 1709 |
+
}
|
| 1710 |
+
}
|
| 1711 |
+
const MG_GAP=1.1; // bead to bead along the cord, in beads: close, as on a misbaha
|
| 1712 |
+
function mgRoom(W,H){ // how many beads fill the field: the snake in every row it has room for, beads still easy to read
|
| 1713 |
+
const d=W<600?78:W<1000?86:96, m=6+d*.2, g=d*1.5, rows=Math.max(1,Math.floor((H-2*m-d)/g)+1), R=g/2, run=Math.max(0,W-2*(m+d/2+R));
|
| 1714 |
+
return Math.max(2,Math.floor((rows*run+(rows-1)*Math.PI*R-d*1.95)/(d*MG_GAP))+1);
|
| 1715 |
+
}
|
| 1716 |
+
function mgTrack(W,H,n){ // the cord: rows across the field joined by half circles; the largest beads it holds, in as few rows as it takes
|
| 1717 |
+
for(let d=Math.min(150,Math.floor(Math.min(W,H)/2.2));d>=34;d-=2){
|
| 1718 |
+
const m=6+d*.2, g=d*1.5, most=Math.max(1,Math.floor((H-2*m-d)/g)+1), R=g/2, a=m+d/2+R, b=W-m-d/2-R; // m: room for the wave
|
| 1719 |
+
if(b<=a) continue;
|
| 1720 |
+
const need=d*1.95+(n-1)*d*MG_GAP;
|
| 1721 |
+
for(let rows=1;rows<=most;rows++){
|
| 1722 |
+
const total=rows*(b-a)+(rows-1)*Math.PI*R;
|
| 1723 |
+
if(total>=need||d===34&&rows===most) return {d,g,rows,R,a,b,total,top:(H-(rows-1)*g)/2};
|
| 1724 |
+
}
|
| 1725 |
+
}
|
| 1726 |
+
}
|
| 1727 |
+
function mgSamples(t){ // points along the cord every 3px, from the top row's right end, with the normal at each
|
| 1728 |
+
const pts=[], step=3, add=(x,y)=>pts.push({x,y});
|
| 1729 |
+
for(let r=0;r<t.rows;r++){
|
| 1730 |
+
const y=t.top+r*t.g, left=r%2===1, from=left?t.a:t.b, to=left?t.b:t.a, len=Math.abs(to-from);
|
| 1731 |
+
for(let s=0;s<len;s+=step) add(from+(to>from?s:-s),y);
|
| 1732 |
+
if(r<t.rows-1){ // the turn down to the next row, round the end it reached
|
| 1733 |
+
const cx=to, cy=y+t.R, side=to===t.a?-1:1, arc=Math.PI*t.R;
|
| 1734 |
+
for(let s=0;s<arc;s+=step){ const th=-Math.PI/2+s/t.R; add(cx+side*Math.cos(th)*t.R,cy+Math.sin(th)*t.R); }
|
| 1735 |
+
}else add(to,y);
|
| 1736 |
+
}
|
| 1737 |
+
for(let j=0;j<pts.length;j++){
|
| 1738 |
+
const p=pts[Math.max(0,j-1)], q=pts[Math.min(pts.length-1,j+1)], dx=q.x-p.x, dy=q.y-p.y, l=Math.hypot(dx,dy)||1;
|
| 1739 |
+
Object.assign(pts[j],{nx:-dy/l,ny:dx/l,s:j*step});
|
| 1740 |
+
}
|
| 1741 |
+
return pts;
|
| 1742 |
+
}
|
| 1743 |
+
function openMemoryGame(){
|
| 1744 |
+
const upto=vtLessonNow(); if(!upto) return;
|
| 1745 |
+
closeMemoryGame();
|
| 1746 |
+
const name=(ALL_LESSONS.find(l=>l.id===upto)||{}).name||"";
|
| 1747 |
+
const root=mark("mg","","section"); root.setAttribute("role","dialog"); root.setAttribute("aria-label","הפועל זכרון");
|
| 1748 |
+
root.innerHTML=`<div class="mg-hud"><button class="mg-close" type="button" aria-label="יציאה מהמשחק">✕</button>
|
| 1749 |
+
<div class="mg-title"><h2>הפועל זכרון</h2><small></small></div>
|
| 1750 |
+
<div class="mg-stat mg-pairs"><b>0</b>זוגות</div><div class="mg-stat mg-moves"><b>0</b>ניסיונות</div></div>
|
| 1751 |
+
<div class="mg-field"><svg class="mg-cord" aria-hidden="true"><defs><linearGradient id="mgGold" 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>
|
| 1752 |
+
<path class="c-shadow"/><path class="c-base"/><path class="c-a"/><path class="c-b"/><path class="c-glint"/><g class="mg-tassel"></g></svg></div>`;
|
| 1753 |
+
document.body.append(root); document.body.classList.add("memory-game");
|
| 1754 |
+
const field=root.querySelector(".mg-field"), hud=root.querySelector(".mg-hud");
|
| 1755 |
+
mg={root,field,hud,upto,name,beads:[],first:null,lock:false,pairs:0,moves:0,streak:0,start:0,raf:0,party:0,audio:null,
|
| 1756 |
+
paths:[...root.querySelectorAll(".mg-cord path")],tassel:root.querySelector(".mg-tassel"),still:matchMedia("(prefers-reduced-motion: reduce)").matches};
|
| 1757 |
+
root.querySelector(".mg-close").onclick=closeMemoryGame;
|
| 1758 |
+
mg.onkey=e=>{ if(e.key==="Escape") closeMemoryGame(); }; addEventListener("keydown",mg.onkey);
|
| 1759 |
+
mg.onresize=()=>mgLayout(); addEventListener("resize",mg.onresize);
|
| 1760 |
+
mgDeal();
|
| 1761 |
+
const frame=now=>{ if(!mg) return; mgFrame(now/1000); mg.raf=requestAnimationFrame(frame); };
|
| 1762 |
+
mg.raf=requestAnimationFrame(frame);
|
| 1763 |
+
}
|
| 1764 |
+
function closeMemoryGame(){
|
| 1765 |
+
if(!mg) return;
|
| 1766 |
+
cancelAnimationFrame(mg.raf); removeEventListener("keydown",mg.onkey); removeEventListener("resize",mg.onresize);
|
| 1767 |
+
if(mg.audio) mg.audio.pause();
|
| 1768 |
+
mg.root.remove(); document.body.classList.remove("memory-game"); mg=null;
|
| 1769 |
+
}
|
| 1770 |
+
function mgDeal(){ // a new round: as many pairs as fill the screen (or as the lessons have verbs), the lesson's own verbs first
|
| 1771 |
+
const pool=mgPool(mg.upto), box=mg.field.getBoundingClientRect(), n=Math.min(Math.floor(mgRoom(box.width,box.height)/2),pool.length);
|
| 1772 |
+
mg.root.querySelector(".mg-win")?.remove(); mg.beads.forEach(b=>b.el.remove());
|
| 1773 |
+
Object.assign(mg,{beads:[],first:null,lock:false,pairs:0,moves:0,streak:0,start:0,party:0,n});
|
| 1774 |
+
mg.root.querySelector(".mg-title small").textContent=`${n} זוגות · הפעלים עד ${mg.name} · פתחו שני חרוזים: עברית וערבית`;
|
| 1775 |
+
mgCount();
|
| 1776 |
+
if(n<2){ mg.field.append(mark("mg-none","עוד אין מספיק פעלים למשחק. הם יגיעו עם השיעורים הבאים 💛","p")); return; }
|
| 1777 |
+
const fresh=shuffle(pool.filter(p=>p.unlock===mg.upto)), rest=shuffle(pool.filter(p=>p.unlock!==mg.upto)), half=Math.ceil(n/2);
|
| 1778 |
+
const verbs=[...fresh.slice(0,half),...rest,...fresh.slice(half)].slice(0,n);
|
| 1779 |
+
for(const p of verbs) answerAudio(p.id); // fetched now, so a bead speaks as it opens
|
| 1780 |
+
const sides=shuffle(verbs.flatMap(p=>[{p,side:"he"},{p,side:"ar"}]));
|
| 1781 |
+
sides.forEach((b,i)=>{
|
| 1782 |
+
const el=mark("mg-bead","","button"), rot=mark("mg-rot","","span"), back=mark("mg-side mg-back","","span"), face=mark(`mg-side mg-face ${b.side}`,"","span");
|
| 1783 |
+
el.type="button"; el.setAttribute("aria-label","חרוז סגור"); el.style.setProperty("--i",i);
|
| 1784 |
+
el.style.setProperty("--spin",`${(3.75+Math.random()*5).toFixed(2)}s`); el.style.setProperty("--way",Math.random()<.5?"normal":"reverse");
|
| 1785 |
+
back.append(document.createElement("i"));
|
| 1786 |
+
if(b.side==="he") face.textContent=b.p.he;
|
| 1787 |
+
else{ const [past,future]=b.p.tr.split(/,\s*/); face.append(mark("tr",past,"bdi"),mark("tr",future||"","bdi"),mark("ear","🔊","span")); }
|
| 1788 |
+
rot.append(back,face); el.append(rot); el.onclick=()=>mgFlip(b);
|
| 1789 |
+
b.el=el; mg.beads.push(b); mg.field.append(el);
|
| 1790 |
+
});
|
| 1791 |
+
mgLayout();
|
| 1792 |
+
}
|
| 1793 |
+
function mgLayout(){ // the track for the field's size, and the beads' places along it
|
| 1794 |
+
if(!mg||!mg.beads.length) return;
|
| 1795 |
+
const r=mg.field.getBoundingClientRect(), t=mgTrack(r.width,r.height,mg.beads.length);
|
| 1796 |
+
mg.track=t; mg.pts=mgSamples(t); mg.root.style.setProperty("--d",`${t.d}px`);
|
| 1797 |
+
const n=mg.beads.length, start=t.d*1.35, gap=t.d*MG_GAP;
|
| 1798 |
+
mg.pts=mg.pts.filter(p=>p.s<=start+(n-1)*gap+t.d*.6); // the cord ends just past its last bead
|
| 1799 |
+
mg.beads.forEach((b,i)=>b.j=Math.min(mg.pts.length-1,Math.round((start+i*gap)/3)));
|
| 1800 |
+
mg.wave=Math.max(gap*2.4,t.d*3);
|
| 1801 |
+
}
|
| 1802 |
+
function mgFrame(time){ // the wave runs down the cord; the beads ride it, each turned along the cord
|
| 1803 |
+
const t=mg.track; if(!t) return;
|
| 1804 |
+
const pts=mg.pts, amp=mg.still?0:t.d*(mg.party?.05:.03), speed=mg.party?2.6:1, k=2*Math.PI/mg.wave, w=2*Math.PI/3.4*speed;
|
| 1805 |
+
const X=new Float32Array(pts.length), Y=new Float32Array(pts.length);
|
| 1806 |
+
let d="";
|
| 1807 |
+
for(let j=0;j<pts.length;j++){
|
| 1808 |
+
const p=pts[j], ease=Math.min(1,p.s/(t.d*2)), off=amp*ease*Math.sin(k*p.s-w*time);
|
| 1809 |
+
X[j]=p.x+p.nx*off; Y[j]=p.y+p.ny*off;
|
| 1810 |
+
if(j%2===0||j===pts.length-1) d+=`${j?"L":"M"}${X[j].toFixed(1)} ${Y[j].toFixed(1)}`;
|
| 1811 |
+
}
|
| 1812 |
+
for(const path of mg.paths) path.setAttribute("d",d);
|
| 1813 |
+
const angle=j=>{ const a=Math.max(0,j-2), b=Math.min(pts.length-1,j+2); return Math.atan2(Y[b]-Y[a],X[b]-X[a])*180/Math.PI; };
|
| 1814 |
+
for(const b of mg.beads){
|
| 1815 |
+
b.el.style.transform=`translate(${(X[b.j]-t.d/2).toFixed(1)}px,${(Y[b.j]-t.d/2).toFixed(1)}px)`;
|
| 1816 |
+
}
|
| 1817 |
+
// the imam and the tassel at the cord's head, turned along it, the tassel's threads swinging
|
| 1818 |
+
const j0=Math.round(t.d*.42/3), s=t.d/40, sway=mg.still?0:Math.sin(time*2.2)*6;
|
| 1819 |
+
mg.tassel.setAttribute("transform",`translate(${X[j0].toFixed(1)} ${Y[j0].toFixed(1)}) rotate(${(angle(j0)+180).toFixed(1)}) scale(${s.toFixed(3)})`);
|
| 1820 |
+
if(!mg.tassel.firstChild){
|
| 1821 |
+
mg.tassel.innerHTML=`<rect class="imam" x="-13" y="-6" width="26" height="12" rx="6"/><rect class="tassel-cap" x="13" y="-7" width="8" height="14" rx="2"/><g class="tassel-threads"></g>`;
|
| 1822 |
+
}
|
| 1823 |
+
const th=mg.tassel.lastChild, q=i=>(i-3.5)*3.2;
|
| 1824 |
+
th.innerHTML=[0,1,2,3,4,5,6,7].map(i=>`<path class="tassel-thread" d="M20 0Q${32+i} ${q(i)+sway*.6} ${44+(i%3)*4} ${q(i)*1.9+sway}"/>`).join("");
|
| 1825 |
+
}
|
| 1826 |
+
function mgCount(){
|
| 1827 |
+
const set=(cls,v)=>{ const box=mg.root.querySelector(cls), b=box.querySelector("b"); if(b.textContent!==String(v)){ b.textContent=v; box.classList.remove("tick"); void box.offsetWidth; box.classList.add("tick"); } };
|
| 1828 |
+
set(".mg-pairs",`${mg.pairs}/${mg.n||0}`); set(".mg-moves",mg.moves);
|
| 1829 |
+
}
|
| 1830 |
+
async function mgSay(p){ // the Arabic, in the lessons' voice, or else the device's
|
| 1831 |
+
if(mg.audio) mg.audio.pause();
|
| 1832 |
+
const a=await answerAudio(p.id); if(!mg) return;
|
| 1833 |
+
if(!a){ speakLocal(p.ar,"ar"); return; }
|
| 1834 |
+
const au=answerPlayer(a.url); mg.audio=au; au.play().catch(()=>speakLocal(p.ar,"ar"));
|
| 1835 |
+
}
|
| 1836 |
+
function mgAt(b){ const r=b.el.getBoundingClientRect(), f=mg.field.getBoundingClientRect(); return {x:r.left-f.left+r.width/2,y:r.top-f.top+r.height/2}; }
|
| 1837 |
+
function mgBurst(x,y,many=12){
|
| 1838 |
+
for(let i=0;i<many;i++){
|
| 1839 |
+
const s=mark("mg-spark",MG_SPARKS[i%MG_SPARKS.length],"i"), th=Math.random()*Math.PI*2, far=mg.track.d*(.7+Math.random()*.9);
|
| 1840 |
+
Object.entries({"--x":`${x-12}px`,"--y":`${y-12}px`,"--dx":`${Math.cos(th)*far}px`,"--dy":`${Math.sin(th)*far}px`,"--r":`${Math.random()*360-180}deg`}).forEach(([k,v])=>s.style.setProperty(k,v));
|
| 1841 |
+
s.onanimationend=()=>s.remove(); mg.field.append(s);
|
| 1842 |
+
}
|
| 1843 |
+
}
|
| 1844 |
+
function mgFlip(b){
|
| 1845 |
+
if(!mg||mg.lock||b.open||b.done) return;
|
| 1846 |
+
if(!mg.start) mg.start=Date.now();
|
| 1847 |
+
b.open=true; b.el.classList.add("open"); b.el.setAttribute("aria-label",b.side==="he"?b.p.he:b.p.tr);
|
| 1848 |
+
mgTone([[620,.06],[930,.07]]);
|
| 1849 |
+
if(b.side==="ar") mgSay(b.p);
|
| 1850 |
+
if(!mg.first){ mg.first=b; return; }
|
| 1851 |
+
const a=mg.first; mg.first=null; mg.moves++;
|
| 1852 |
+
if(a.p===b.p){ // a pair: both stay open, green, and cannot be pressed again
|
| 1853 |
+
mg.pairs++; mg.streak++;
|
| 1854 |
+
for(const x of [a,b]){ x.done=true; x.el.classList.add("done"); x.el.disabled=true; }
|
| 1855 |
+
const pa=mgAt(a), pb=mgAt(b);
|
| 1856 |
+
setTimeout(()=>{ if(!mg) return; mgBurst(pa.x,pa.y); mgBurst(pb.x,pb.y); mgTone([[660,.11],[880,.11],[1320,.22]],"sine",.08); },220);
|
| 1857 |
+
const praise=mark("mg-praise",MG_PRAISE[Math.random()*MG_PRAISE.length|0],"span");
|
| 1858 |
+
praise.style.setProperty("--x",`${(pa.x+pb.x)/2}px`); praise.style.setProperty("--y",`${Math.min(pa.y,pb.y)}px`);
|
| 1859 |
+
praise.onanimationend=()=>praise.remove(); mg.field.append(praise);
|
| 1860 |
+
if(mg.streak>1){ const c=mark("mg-combo",`🔥 ${mg.streak} ברצף!`,"span"); c.onanimationend=()=>c.remove(); mg.hud.append(c); }
|
| 1861 |
+
mgCount();
|
| 1862 |
+
if(mg.pairs===mg.n) setTimeout(mgWin,900);
|
| 1863 |
+
}else{ // not a pair: they shake, and close again
|
| 1864 |
+
mg.streak=0; mg.lock=true; mgCount();
|
| 1865 |
+
setTimeout(()=>{ if(!mg) return; a.el.classList.add("miss"); b.el.classList.add("miss"); mgTone([[240,.14],[180,.2]],"sawtooth",.035); },650);
|
| 1866 |
+
setTimeout(()=>{ if(!mg) return;
|
| 1867 |
+
for(const x of [a,b]){ x.open=false; x.el.classList.remove("open","miss"); x.el.setAttribute("aria-label","חרוז סגור"); }
|
| 1868 |
+
mg.lock=false;
|
| 1869 |
+
},1350);
|
| 1870 |
+
}
|
| 1871 |
+
}
|
| 1872 |
+
function mgWin(){ // every pair found: the misbaha dances, beads rain, and the stars
|
| 1873 |
+
if(!mg) return;
|
| 1874 |
+
mg.party=1; mgTone([[523,.14],[659,.14],[784,.14],[1047,.32],[784,.12],[1047,.4]],"sine",.09);
|
| 1875 |
+
const secs=Math.round((Date.now()-mg.start)/1000), stars=mg.moves<=mg.n*1.4?3:mg.moves<=mg.n*2?2:1;
|
| 1876 |
+
const tones=[["#e2ead8","#aabd98","#4a5941"],["#fff1a0","#ffe27a","#8e5b08"],["#ffe0ad","#ffbd72","#a94716"],["#ffe1da","#efaaa0","#9b4f48"]];
|
| 1877 |
+
for(let i=0;i<46;i++){
|
| 1878 |
+
const drop=mark("mg-drop","","i"), [c1,c2,c3]=tones[i%tones.length];
|
| 1879 |
+
Object.entries({"--c1":c1,"--c2":c2,"--c3":c3,"--s":`${14+Math.random()*22}px`,"--t":`${1.8+Math.random()*1.8}s`,"--w":`${Math.random()*1.4}s`,"--r":`${Math.random()*720-360}deg`}).forEach(([k,v])=>drop.style.setProperty(k,v));
|
| 1880 |
+
drop.style.left=`${Math.random()*100}%`; drop.onanimationend=()=>drop.remove(); mg.root.append(drop);
|
| 1881 |
+
}
|
| 1882 |
+
const win=mark("mg-win","","div"), card=document.createElement("div");
|
| 1883 |
+
card.innerHTML=`<div class="big">📿</div><h3>${MG_PRAISE[Math.random()*3|0]}</h3>
|
| 1884 |
+
<div class="stars">${[0,1,2].map(i=>`<span class="${i<stars?"":"off"}">★</span>`).join("")}</div>
|
| 1885 |
+
<p>${mg.n} זוגות ב־${mg.moves} ניסיונות · ${Math.floor(secs/60)}:${String(secs%60).padStart(2,"0")}</p>
|
| 1886 |
+
<div class="acts"><button type="button" class="again">עוד סיבוב 🔄</button><button type="button" class="out">סיום</button></div>`;
|
| 1887 |
+
win.append(card); mg.root.append(win);
|
| 1888 |
+
card.querySelector(".again").onclick=()=>mgDeal(); card.querySelector(".out").onclick=closeMemoryGame;
|
| 1889 |
+
card.querySelector(".again").focus({preventScroll:true});
|
| 1890 |
+
}
|
| 1891 |
function showView(view){ // "" is the path, which is laid out again: it was not while hidden
|
| 1892 |
if(view) document.body.dataset.view=view; else delete document.body.dataset.view;
|
| 1893 |
scrollTo(0,0); drawPath(true);
|
|
|
|
| 2373 |
pauseBubble();
|
| 2374 |
if(!bubbleCat()||home) return;
|
| 2375 |
if(bubbleFor!==cur.id){
|
| 2376 |
+
bubbleFor=cur.id; bubbleDeadline=Date.now()+BUBBLE_MS; bubbleResolving=false; bubbleHintShown=false; bubblePastShown=false; waClear();
|
| 2377 |
$("bubbleHint").hidden=true; $("bubblePast").hidden=true;
|
| 2378 |
}
|
| 2379 |
const left=Math.max(0,bubbleDeadline-Date.now());
|
|
|
|
| 3222 |
}
|
| 3223 |
}
|
| 3224 |
|
| 3225 |
+
/* the student's try in "פועל זמני", as a WhatsApp text message: what was heard, in transliteration, with the time it was
|
| 3226 |
+
sent and its ticks, blue when it passed. One message: a new try takes the last one's place. */
|
| 3227 |
+
let waMsg=null;
|
| 3228 |
+
const WA_TICKS='<svg viewBox="0 0 16 11"><path d="M11.07.65 10.4.1a.5.5 0 0 0-.7.08L4.06 7.16 1.94 5.2a.5.5 0 0 0-.7.02l-.6.64a.5.5 0 0 0 .02.7l3.06 2.86a.5.5 0 0 0 .73-.05l6.7-8.1a.5.5 0 0 0-.08-.6zM15.07.65 14.4.1a.5.5 0 0 0-.7.08L8.06 7.16l-.8-.75-.97 1.17 1.3 1.22a.5.5 0 0 0 .73-.05l6.7-8.1a.5.5 0 0 0-.08-.6z"/></svg>';
|
| 3229 |
+
function waClear(){ if(waMsg){ waMsg.remove(); waMsg=null; } }
|
| 3230 |
+
function waText(r,passed){
|
| 3231 |
+
waClear();
|
| 3232 |
+
const heard=(r.words||[]).map(w=>w.heard_tr).filter(Boolean).join(" "), now=new Date();
|
| 3233 |
+
const el=mark(`wa-msg${passed?" read":""}`,"","div"), text=mark("wa-text","","bdi"), when=mark("wa-when",`${String(now.getHours()).padStart(2,"0")}:${String(now.getMinutes()).padStart(2,"0")}`,"span");
|
| 3234 |
+
if(heard) setTr(text,heard); else{ text.textContent="לא זוהה דיבור"; el.classList.add("empty"); }
|
| 3235 |
+
const ticks=mark("wa-ticks","","span"); ticks.innerHTML=WA_TICKS; when.append(ticks);
|
| 3236 |
+
el.append(text,when); el.setAttribute("aria-label",`מה שאמרתם: ${heard||"לא זוהה"}`);
|
| 3237 |
+
waMsg=el; $("bubbleFoot").prepend(el);
|
| 3238 |
+
}
|
| 3239 |
async function bubbleOutcome(r,expired=false){
|
| 3240 |
const id=cur.id, correct=!r.unrelated&&r.score>=BUBBLE_PASS;
|
| 3241 |
if(bubbleResolving) return;
|
| 3242 |
bubbleResolving=true;
|
| 3243 |
if(!expired){
|
| 3244 |
+
waText(r,correct);
|
| 3245 |
const old=$("prompt").querySelector(".bubble-grade"); if(old) old.remove();
|
| 3246 |
const score=Math.round(r.score), band=score>=85?"good":score>=60?"mid":"bad";
|
| 3247 |
$("prompt").append(mark(`bubble-grade ${band}`,`${score} נק׳`));
|