Amharic

<!doctype html>

<html lang=”am”>

<head>

<meta charset=”utf-8″ />

<meta name=”viewport” content=”width=device-width, initial-scale=1″ />

<title>የፊደል ቤተሰብ ስክራምብል + ድምፅ (33+)</title>

<style>

  :root{

    –bg:#f4f9ff; –card:#fff; –ink:#17324d; –muted:#5b7088;

    –accent:#2f80ed; –good:#27ae60; –bad:#e74c3c;

    –shadow:0 10px 25px rgba(0,0,0,.10); –radius:16px;

  }

  body{

    font-family:system-ui,-apple-system,”Noto Sans Ethiopic”,”Nyala”,”Abyssinica SIL”,Segoe UI,Roboto,Arial,sans-serif;

    background:var(–bg);

    margin:0; padding:18px;

    color:var(–ink);

    text-align:center;

  }

  .container{

    max-width:880px;

    margin:auto;

    background:var(–card);

    padding:18px;

    border-radius:var(–radius);

    box-shadow:var(–shadow);

  }

  h1{margin:4px 0 6px;}

  .sub{margin:0 0 12px; color:var(–muted); font-size:14px}

  .topbar{

    display:flex; flex-wrap:wrap; gap:10px;

    align-items:center; justify-content:space-between;

    margin:10px 0 8px;

  }

  .left, .right{display:flex; flex-wrap:wrap; gap:10px; align-items:center}

  .pill{

    padding:8px 12px; border-radius:999px;

    border:2px solid #d9e8ff; background:#fff;

    font-weight:800; color:var(–ink);

  }

  button{

    padding:10px 16px;

    border:none;

    border-radius:999px;

    font-weight:900;

    cursor:pointer;

    background:#eef5ff;

    color:var(–ink);

    user-select:none;

  }

  button.primary{ background:var(–accent); color:#fff; }

  button.good{ background:var(–good); color:#fff; }

  button:disabled{opacity:.5; cursor:not-allowed}

  label.toggle{

    display:flex; align-items:center; gap:8px;

    padding:8px 12px; border-radius:999px;

    border:2px solid #d9e8ff; background:#fff;

    font-weight:900;

  }

  label.toggle input{ transform:scale(1.15); }

  .familyTitle{

    font-size:20px; font-weight:900; margin:10px 0 6px;

  }

  .targetLine{

    color:var(–muted);

    font-size:14px;

    margin:0 0 12px;

    word-spacing:8px;

  }

  .zones{

    display:grid;

    grid-template-columns:1fr;

    gap:12px;

    margin-top:10px;

  }

  @media (min-width: 860px){

    .zones{ grid-template-columns:1fr 1fr; }

  }

  .panel{

    border-radius:var(–radius);

    border:2px dashed #cfe3ff;

    background:#fbfdff;

    padding:14px;

    text-align:left;

  }

  .panel h3{

    margin:0 0 10px;

    font-size:14px;

    color:var(–muted);

    font-weight:900;

  }

  .dropzone{

    border-radius:var(–radius);

    border:2px solid #d9e8ff;

    background:#fff;

    padding:12px;

    min-height:110px;

  }

  .dropzone.over{ outline:3px solid rgba(47,128,237,.25); outline-offset:2px; }

  .tiles{

    display:flex;

    flex-wrap:wrap;

    gap:10px;

    align-content:flex-start;

    justify-content:flex-start;

  }

  .tile{

    width:76px;

    height:76px;

    font-size:34px;

    font-weight:900;

    display:flex;

    align-items:center;

    justify-content:center;

    border-radius:18px;

    background:#fff;

    border:2px solid #cfe3ff;

    box-shadow:0 6px 14px rgba(10,50,90,.08);

    cursor:grab;

    user-select:none;

  }

  .tile:active{ cursor:grabbing; }

  .tile.dragging{ opacity:.6; transform:scale(1.03); }

  .tile.correct{ background:#e9fff0; border-color:#2ecc71; }

  .tile.wrong{ background:#fff0f0; border-color:#e74c3c; }

  .status{

    margin-top:10px;

    font-weight:900;

    font-size:14px;

  }

  .status.good{ color:var(–good); }

  .status.bad{ color:var(–bad); }

  .hint{ color:var(–muted); font-size:13px; margin-top:6px; }

</style>

</head>

<body>

<div class=”container”>

  <h1>የፊደል ቤተሰብ ስክራምብል (33+)</h1>

  <p class=”sub”>እያንዳንዱ ቤተሰብ 7 ፊደሎች ናቸው። ይዘው ያንቀሳቅሱ እና በትክክል ቅደም ተከተል ያድርጉ። ፊደል ላይ ጠቅ ሲያደርጉ ድምፅ ይሰማል።</p>

  <div class=”topbar”>

    <div class=”left”>

      <span class=”pill” id=”progressPill”>ቤተሰብ 1 / 34</span>

      <span class=”pill” id=”familyPill”>ፊደል: ሀ</span>

    </div>

    <div class=”right”>

      <button id=”prevBtn”>◀ ቀድሞ</button>

      <button class=”primary” id=”checkBtn”>አረጋግጥ</button>

      <button class=”good” id=”nextBtn”>ቀጣይ ▶</button>

      <button id=”shuffleBtn”>ደባልቅ</button>

      <button id=”playFamilyBtn”>🔊 ቤተሰብ አንብብ</button>

      <label class=”toggle” title=”ድምፅ አብራ/አጥፋ”>

        <input type=”checkbox” id=”audioToggle” checked />

        ድምፅ

      </label>

    </div>

  </div>

  <div class=”familyTitle” id=”familyTitle”>ቤተሰብ: ሀ</div>

  <div class=”targetLine” id=”targetLine”></div>

  <div class=”zones”>

    <div class=”panel”>

      <h3>ባንክ (ከዚህ ይዘው ያንቀሳቅሱ)</h3>

      <div class=”dropzone” id=”bankZone”><div class=”tiles” id=”bank”></div></div>

      <div class=”hint”>ምክር፡ ፊደል ላይ ጠቅ ብለው ወደ ሌላ ቦታ መላክ ይችላሉ።</div>

    </div>

    <div class=”panel”>

      <h3>መስመር (እዚህ ይጥሉ)</h3>

      <div class=”dropzone” id=”lineZone”><div class=”tiles” id=”line”></div></div>

      <div class=”hint”>ከዚያ “አረጋግጥ” ይጫኑ።</div>

    </div>

  </div>

  <div class=”status” id=”status”></div>

</div>

<script>

(() => {

  // — 33+ Fidel families (7 forms each) —

  // Includes vowel-carrier family አ and also all common 33 base families; total here = 34 families.

  const FAMILIES = [

    [“ሀ”,”ሁ”,”ሂ”,”ሃ”,”ሄ”,”ህ”,”ሆ”],

    [“ለ”,”ሉ”,”ሊ”,”ላ”,”ሌ”,”ል”,”ሎ”],

    [“ሐ”,”ሑ”,”ሒ”,”ሓ”,”ሔ”,”ሕ”,”ሖ”],

    [“መ”,”ሙ”,”ሚ”,”ማ”,”ሜ”,”ም”,”ሞ”],

    [“ሠ”,”ሡ”,”ሢ”,”ሣ”,”ሤ”,”ሥ”,”ሦ”],

    [“ረ”,”ሩ”,”ሪ”,”ራ”,”ሬ”,”ር”,”ሮ”],

    [“ሰ”,”ሱ”,”ሲ”,”ሳ”,”ሴ”,”ስ”,”ሶ”],

    [“ሸ”,”ሹ”,”ሺ”,”ሻ”,”ሼ”,”ሽ”,”ሾ”],

    [“ቀ”,”ቁ”,”ቂ”,”ቃ”,”ቄ”,”ቅ”,”ቆ”],

    [“በ”,”ቡ”,”ቢ”,”ባ”,”ቤ”,”ብ”,”ቦ”],

    [“ተ”,”ቱ”,”ቲ”,”ታ”,”ቴ”,”ት”,”ቶ”],

    [“ቸ”,”ቹ”,”ቺ”,”ቻ”,”ቼ”,”ች”,”ቾ”],

    [“ኀ”,”ኁ”,”ኂ”,”ኃ”,”ኄ”,”ኅ”,”ኆ”],

    [“ነ”,”ኑ”,”ኒ”,”ና”,”ኔ”,”ን”,”ኖ”],

    [“ኘ”,”ኙ”,”ኚ”,”ኛ”,”ኜ”,”ኝ”,”ኞ”],

    // vowel carrier (often taught as a “family”)

    [“አ”,”ኡ”,”ኢ”,”ኣ”,”ኤ”,”እ”,”ኦ”],

    [“ከ”,”ኩ”,”ኪ”,”ካ”,”ኬ”,”ክ”,”ኮ”],

    [“ኸ”,”ኹ”,”ኺ”,”ኻ”,”ኼ”,”ኽ”,”ኾ”],

    [“ወ”,”ዉ”,”ዊ”,”ዋ”,”ዌ”,”ው”,”ዎ”],

    [“ዐ”,”ዑ”,”ዒ”,”ዓ”,”ዔ”,”ዕ”,”ዖ”],

    [“ዘ”,”ዙ”,”ዚ”,”ዛ”,”ዜ”,”ዝ”,”ዞ”],

    [“ዠ”,”ዡ”,”ዢ”,”ዣ”,”ዤ”,”ዥ”,”ዦ”],

    [“የ”,”ዩ”,”ዪ”,”ያ”,”ዬ”,”ይ”,”ዮ”],

    [“ደ”,”ዱ”,”ዲ”,”ዳ”,”ዴ”,”ድ”,”ዶ”],

    [“ጀ”,”ጁ”,”ጂ”,”ጃ”,”ጄ”,”ጅ”,”ጆ”],

    [“ገ”,”ጉ”,”ጊ”,”ጋ”,”ጌ”,”ግ”,”ጎ”],

    [“ጠ”,”ጡ”,”ጢ”,”ጣ”,”ጤ”,”ጥ”,”ጦ”],

    [“ጨ”,”ጩ”,”ጪ”,”ጫ”,”ጬ”,”ጭ”,”ጮ”],

    [“ጰ”,”ጱ”,”ጲ”,”ጳ”,”ጴ”,”ጵ”,”ጶ”],

    [“ጸ”,”ጹ”,”ጺ”,”ጻ”,”ጼ”,”ጽ”,”ጾ”],

    [“ፀ”,”ፁ”,”ፂ”,”ፃ”,”ፄ”,”ፅ”,”ፆ”],

    [“ፈ”,”ፉ”,”ፊ”,”ፋ”,”ፌ”,”ፍ”,”ፎ”],

    [“ፐ”,”ፑ”,”ፒ”,”ፓ”,”ፔ”,”ፕ”,”ፖ”]

  ];

  // — Elements —

  const bankEl = document.getElementById(“bank”);

  const lineEl = document.getElementById(“line”);

  const bankZone = document.getElementById(“bankZone”);

  const lineZone = document.getElementById(“lineZone”);

  const progressPill = document.getElementById(“progressPill”);

  const familyPill = document.getElementById(“familyPill”);

  const familyTitle = document.getElementById(“familyTitle”);

  const targetLine = document.getElementById(“targetLine”);

  const statusEl = document.getElementById(“status”);

  const prevBtn = document.getElementById(“prevBtn”);

  const nextBtn = document.getElementById(“nextBtn”);

  const shuffleBtn = document.getElementById(“shuffleBtn”);

  const checkBtn = document.getElementById(“checkBtn”);

  const playFamilyBtn = document.getElementById(“playFamilyBtn”);

  const audioToggle = document.getElementById(“audioToggle”);

  let index = 0;       // current family index

  let draggedId = null;

  // — Shuffle helper —

  function shuffle(arr){

    const a = arr.slice();

    for(let i=a.length-1;i>0;i–){

      const j = Math.floor(Math.random()*(i+1));

      [a[i],a[j]]=[a[j],a[i]];

    }

    return a;

  }

  // — Beep sounds (success/fail) —

  const audioCtx = (() => {

    try { return new (window.AudioContext || window.webkitAudioContext)(); }

    catch { return null; }

  })();

  function beep(type=”ok”){

    if(!audioToggle.checked || !audioCtx) return;

    const ctx = audioCtx;

    const o = ctx.createOscillator();

    const g = ctx.createGain();

    o.connect(g); g.connect(ctx.destination);

    const now = ctx.currentTime;

    const freq = type === “ok” ? 740 : 220;

    o.frequency.setValueAtTime(freq, now);

    g.gain.setValueAtTime(0.0001, now);

    g.gain.exponentialRampToValueAtTime(0.22, now + 0.01);

    g.gain.exponentialRampToValueAtTime(0.0001, now + 0.18);

    o.start(now);

    o.stop(now + 0.2);

  }

  // — Amharic TTS (Web Speech API) —

  // Works best if the device/browser has an Amharic voice installed.

  function getAmharicVoice(){

    const voices = window.speechSynthesis?.getVoices?.() || [];

    // Prefer Amharic voices

    let v = voices.find(x => (x.lang || “”).toLowerCase().startsWith(“am”));

    // Some systems label Ethiopia Amharic differently—still try a sensible fallback:

    if(!v) v = voices.find(x => /ethiop/i.test(x.name) || /amhar/i.test(x.name));

    return v || null;

  }

  function speak(text, {rate=0.9, pitch=1.0} = {}){

    if(!audioToggle.checked) return;

    if(!(“speechSynthesis” in window)){

      // If TTS not supported, just beep

      beep(“ok”);

      return;

    }

    // Stop current speech

    window.speechSynthesis.cancel();

    const u = new SpeechSynthesisUtterance(text);

    u.lang = “am-ET”;

    u.rate = rate;

    u.pitch = pitch;

    const v = getAmharicVoice();

    if(v) u.voice = v;

    window.speechSynthesis.speak(u);

  }

  // Ensure voices load (some browsers load asynchronously)

  if(“speechSynthesis” in window){

    window.speechSynthesis.onvoiceschanged = () => { /* voices refreshed */ };

  }

  // — Drag/drop helpers —

  function setZoneHover(zone, on){ zone.classList.toggle(“over”, on); }

  function allowDrop(zone){

    zone.addEventListener(“dragover”, (e) => {

      e.preventDefault();

      e.dataTransfer.dropEffect = “move”;

      setZoneHover(zone, true);

    });

    zone.addEventListener(“dragleave”, () => setZoneHover(zone, false));

    zone.addEventListener(“drop”, (e) => {

      e.preventDefault();

      setZoneHover(zone, false);

      const id = e.dataTransfer.getData(“text/plain”) || draggedId;

      const tile = document.getElementById(id);

      if(tile){

        zone.querySelector(“.tiles”).appendChild(tile);

      }

    });

  }

  function clearMarks(){

    […bankEl.children, …lineEl.children].forEach(t => {

      t.classList.remove(“correct”,”wrong”);

    });

  }

  function setStatus(msg, type=””){

    statusEl.className = “status” + (type ? ” ” + type : “”);

    statusEl.textContent = msg;

  }

  // — Tile creation —

  function makeTile(letter){

    const div = document.createElement(“div”);

    div.className = “tile”;

    div.textContent = letter;

    div.draggable = true;

    div.id = “tile-” + index + “-” + letter + “-” + Math.random().toString(16).slice(2);

    div.addEventListener(“dragstart”, (e) => {

      draggedId = div.id;

      div.classList.add(“dragging”);

      e.dataTransfer.setData(“text/plain”, draggedId);

      e.dataTransfer.effectAllowed = “move”;

    });

    div.addEventListener(“dragend”, () => {

      div.classList.remove(“dragging”);

      draggedId = null;

    });

    // Click-to-move + speak letter

    div.addEventListener(“click”, () => {

      // move between zones

      if(div.parentElement === bankEl) lineEl.appendChild(div);

      else bankEl.appendChild(div);

      // speak the clicked letter

      speak(letter, {rate: 0.85});

      clearMarks();

    });

    // Right-click / long press alternative not needed—click already speaks

    return div;

  }

  // — Render family —

  function render(){

    const family = FAMILIES[index];

    // UI labels

    progressPill.textContent = `ቤተሰብ ${index+1} / ${FAMILIES.length}`;

    familyPill.textContent = `ፊደል: ${family[0]}`;

    familyTitle.textContent = `ቤተሰብ: ${family[0]}`;

    targetLine.textContent = family.join(”  “);

    prevBtn.disabled = index === 0;

    nextBtn.disabled = index === FAMILIES.length – 1;

    // Reset zones

    bankEl.innerHTML = “”;

    lineEl.innerHTML = “”;

    clearMarks();

    setStatus(“ዝግጁ! ፊደሎችን አስደራጁ።”);

    // Add scrambled tiles to bank

    shuffle(family).forEach(ch => bankEl.appendChild(makeTile(ch)));

  }

  // — Check order —

  function check(){

    const family = FAMILIES[index];

    const tiles = […lineEl.children];

    if(tiles.length !== family.length){

      setStatus(`እባክዎ ሁሉንም ፊደሎች አስቀምጡ (${family.length})`, “bad”);

      beep(“bad”);

      speak(“እባክዎ ሁሉንም ፊደሎች ያስቀምጡ”, {rate:0.95});

      return;

    }

    let correct = 0;

    tiles.forEach((t,i) => {

      t.classList.remove(“correct”,”wrong”);

      if(t.textContent === family[i]){

        t.classList.add(“correct”);

        correct++;

      }else{

        t.classList.add(“wrong”);

      }

    });

    if(correct === family.length){

      setStatus(“በጣም ጥሩ! 🎉 ትክክል ነው”, “good”);

      beep(“ok”);

      speak(“በጣም ጥሩ! ትክክል ነው”, {rate:0.95});

    }else{

      setStatus(`${correct}/${family.length} ትክክል — እንደገና ይሞክሩ`, “bad”);

      beep(“bad”);

      speak(“እንደገና ይሞክሩ”, {rate:0.95});

    }

  }

  // — Play whole family (in order) —

  async function playFamily(){

    const family = FAMILIES[index];

    // Speak each letter with small pauses

    // (We chain with timeouts because SpeechSynthesisUtterance has no await)

    if(!audioToggle.checked) return;

    // Cancel any ongoing speech

    if(“speechSynthesis” in window) window.speechSynthesis.cancel();

    let i = 0;

    const speakNext = () => {

      if(i >= family.length) return;

      const u = new SpeechSynthesisUtterance(family[i]);

      u.lang = “am-ET”;

      u.rate = 0.82;

      const v = getAmharicVoice();

      if(v) u.voice = v;

      u.onend = () => {

        i++;

        setTimeout(speakNext, 180);

      };

      window.speechSynthesis.speak(u);

    };

    // If TTS unsupported, fallback to beeps

    if(!(“speechSynthesis” in window)){

      beep(“ok”);

      return;

    }

    speakNext();

  }

  // — Wire up events —

  allowDrop(bankZone);

  allowDrop(lineZone);

  prevBtn.addEventListener(“click”, () => { index = Math.max(0, index-1); render(); });

  nextBtn.addEventListener(“click”, () => { index = Math.min(FAMILIES.length-1, index+1); render(); });

  shuffleBtn.addEventListener(“click”, () => render());

  checkBtn.addEventListener(“click”, check);

  playFamilyBtn.addEventListener(“click”, playFamily);

  // Start

  render();

})();

</script>

</body>

</html>

Contact info

contact@example.com

123-456-7890

123 Startup Lane, Floor 3 Unit C, Profit, MA