2001|
  2002|    tf.focus();
  2003|  };
  2004|
  2005|  const bindLearn = () => {
  2006|    document.getElementById('flip-wrap')?.addEventListener('click', ()=>{
  2007|      learn.flipped = !learn.flipped;
  2008|      render();
  2009|    });
  2010|    document.querySelectorAll('[data-rate]').forEach(el =>
  2011|      el.addEventListener('click', ()=>rateCard(+el.dataset.rate))
  2012|    );
  2013|    document.getElementById('rate-gemerkt')?.addEventListener('click', ()=>rateCard(3));
  2014|    document.getElementById('dojo-nochmal')?.addEventListener('click', ()=>rateCardDojo(true));
  2015|    document.getElementById('dojo-weiter')?.addEventListener('click', ()=>rateCardDojo(false));
  2016|
  2017|    /* Karte direkt im Lernmodus bearbeiten */
  2018|    document.getElementById('learn-edit-btn')?.addEventListener('click', e => {
  2019|      e.stopPropagation();
  2020|      const c = learn.queue[learn.idx] || learn.queue[0];
  2021|      if (!c) return;
  2022|      _editFromLearn = true;
  2023|      go('editor', { cardId: c.id });
  2024|    });
  2025|
  2026|    /* Karte direkt im Lernmodus löschen */
  2027|    document.getElementById('learn-delete-btn')?.addEventListener('click', e => {
  2028|      e.stopPropagation();
  2029|      const c = learn.queue[learn.idx] || learn.queue[0];
  2030|      if (!c) return;
  2031|      confirmModal('Karte löschen?', 'Die Karte wird endgültig entfernt.', 'Löschen', () => {
  2032|        const d = load();
  2033|        if (d.decks[deckId]) {
  2034|          d.decks[deckId].cards = d.decks[deckId].cards.filter(x => x.id !== c.id);
  2035|          save(d);
  2036|        }
  2037|        learn.queue.splice(learn.idx, 1);
  2038|        learn.flipped = false;
  2039|        if (!learn.queue.length) go('done');
  2040|        else render();
  2041|      });
  2042|    });
  2043|
  2044|    if (_learnKeyHandler) document.removeEventListener('keydown', _learnKeyHandler);
  2045|    _learnKeyHandler = e => {
  2046|      if (VIEW !== 'learn') return;
  2047|      if (e.target && /^(INPUT|TEXTAREA|BUTTON)$/.test(e.target.tagName)) return;
  2048|      if (e.key === ' ' || e.key === 'Enter') {
  2049|        e.preventDefault();
  2050|        learn.flipped = !learn.flipped;
  2051|        render();
  2052|      } else if (learn.flipped) {
  2053|        const card = learn.queue[learn.idx] || learn.queue[0];
  2054|        if (card && card.priority) {
  2055|          if (e.key === ' ' || e.key === 'Enter' || e.key === '3') {
  2056|            e.preventDefault();
  2057|            rateCard(3);
  2058|          }
  2059|        } else if (learn.dojo) {
  2060|          if (e.key === '1') { e.preventDefault(); rateCardDojo(true); }
  2061|          else if (e.key === '2' || e.key === '3') { e.preventDefault(); rateCardDojo(false); }
  2062|        } else if (/^[1-4]$/.test(e.key)) {
  2063|          e.preventDefault();
  2064|          rateCard(+e.key);
  2065|        }
  2066|      }
  2067|    };
  2068|    document.addEventListener('keydown', _learnKeyHandler);
  2069|  };
  2070|  let _learnKeyHandler = null;
  2071|
  2072|  const bindDone = () => {
  2073|    document.getElementById('retry-btn')?.addEventListener('click', ()=>learn.dojo ? startDojo(deckId) : startLearn(deckId));
  2074|    document.getElementById('back-deck-btn')?.addEventListener('click', ()=>go('cards',{deckId}));
  2075|  };
  2076|
  2077|  /* ═══════════════════════════════════════
  2078|     LEARN SESSION (FSRS)
  2079|     ═══════════════════════════════════════ */
  2080|  /* Fisher-Yates Shuffle für zufällige Reihenfolge neuer Karten */
  2081|  const shuffle = arr => {
  2082|    for (let i = arr.length - 1; i > 0; i--) {
  2083|      const j = Math.floor(Math.random() * (i + 1));
  2084|      [arr[i], arr[j]] = [arr[j], arr[i]];
  2085|    }
  2086|    return arr;
  2087|  };
  2088|
  2089|  const startLearn = id => {
  2090|    deckId = id;
  2091|    const now  = Date.now();
  2092|    const deck = getDeck(id); if (!deck) return;
  2093|    const all  = deck.cards || [];
  2094|
  2095|    // Priority cards first — immer anzeigen, unabhängig von dueDate/FSRS
  2096|    const priorityCards = all.filter(c => c.priority);
  2097|
  2098|    // 1. Learning/relearning cards due now (random innerhalb der Gruppe)
  2099|    const inLearn = shuffle(all.filter(c =>
  2100|      (c.state==='learning'||c.state==='relearn') && c.dueDate && c.dueDate<=now
  2101|    ));
  2102|    // 2. Review cards due — älteste Fälligkeit zuerst (Ankizin: Reviews vor neuen Karten)
  2103|    const reviews = all.filter(c =>
  2104|      c.state==='review' && c.dueDate && c.dueDate<=now
  2105|    ).sort((a,b)=>(a.dueDate||0)-(b.dueDate||0));
  2106|    // 3. Neue Karten (max 20) — zufällig statt Eingabereihenfolge
  2107|    const newCards = shuffle(all.filter(c => !c.state||c.state==='new')).slice(0,20);
  2108|
  2109|    const queue = [...priorityCards, ...inLearn, ...reviews, ...newCards];
  2110|    if (!queue.length) {
  2111|      showDojoConfirm(id); return;
  2112|    }
  2113|
  2114|    learn = { queue, idx:0, flipped:false, stats:{}, total:queue.length, dojo:false };
  2115|    go('learn');
  2116|  };
  2117|
  2118|  const showDojoConfirm = id => {
  2119|    const o = document.createElement('div'); o.className = 'modal-overlay';
  2120|    o.innerHTML = `

  2121|
Nichts mehr zu lernen 🎉

  2122|
Für heute gibt es keine fälligen Karten in diesem Deck.
Möchtest du im Dojo Modus alle Karten frei üben?

  2123|

  2124|        <button class="btn btn-ghost" id="dojo-cancel">Nein</button>
  2125|        <button class="btn btn-primary" id="dojo-ok">⚔ Dojo starten</button>
  2126|

`;
  2127|    document.body.appendChild(o);
  2128|    o.querySelector('#dojo-ok').addEventListener('click', () => { o.remove(); startDojo(id); });
  2129|    o.querySelector('#dojo-cancel').addEventListener('click', () => o.remove());
  2130|    o.addEventListener('click', e => { if (e.target === o) o.remove(); });
  2131|  };
  2132|
  2133|  const startDojo = id => {
  2134|    deckId = id;
  2135|    const deck = getDeck(id); if (!deck) return;
  2136|    const all  = deck.cards || [];
  2137|    if (!all.length) return;
  2138|    // Priority cards first — immer anzeigen, unabhängig von dueDate/FSRS
  2139|    const priorityCards = all.filter(c => c.priority);
  2140|    // Algorithmische Reihenfolge: learning/relearn → review → new (neue in zufälliger Reihenfolge)
  2141|    const inLearn  = shuffle(all.filter(c => c.state === 'learning' || c.state === 'relearn'));
  2142|    const reviews  = all.filter(c => c.state === 'review').sort((a,b) => (a.dueDate||0) - (b.dueDate||0));
  2143|    const newCards = shuffle(all.filter(c => !c.state || c.state === 'new'));
  2144|    const queue    = [...priorityCards, ...inLearn, ...reviews, ...newCards];
  2145|    learn = { queue, idx:0, flipped:false, stats:{}, total:queue.length, dojo:true };
  2146|    go('learn');
  2147|  };
  2148|
  2149|  const rateCard = rating => {
  2150|    const card    = learn.queue[learn.idx]||learn.queue[0];
  2151|    const updated = SM2.rate({...card}, rating);
  2152|
  2153|    // Track stats
  2154|    const key=['','again','hard','good','easy'][rating];
  2155|    learn.stats[key] = (learn.stats[key]||0)+1;
  2156|
  2157|    // Remove from front
  2158|    learn.queue.splice(learn.idx, 1);
  2159|
  2160|    // Persist updated SM-2 data
  2161|    const d = load();
  2162|    if (d.decks[deckId]) {
  2163|      const ci = d.decks[deckId].cards.findIndex(c=>c.id===card.id);
  2164|      if (ci>=0) d.decks[deckId].cards[ci] = {...d.decks[deckId].cards[ci], ...updated};
  2165|      save(d);
  2166|    }
  2167|
  2168|    // Re-queue nur bei sehr kurzem Intervall, dann zufällig weiter hinten platzieren
  2169|    // (verhindert dass dieselbe Karte alle 3 Slots wieder auftaucht)
  2170|    const interval = updated.dueDate - Date.now();
  2171|    if (interval < 1200000 && learn.queue.length) { // < 20 min
  2172|      const minPos = Math.min(5, learn.queue.length);
  2173|      const maxPos = learn.queue.length;
  2174|      const at = minPos + Math.floor(Math.random() * Math.max(1, maxPos - minPos + 1));
  2175|      learn.queue.splice(at, 0, updated);
  2176|    }
  2177|
  2178|    learn.flipped = false;
  2179|    if (!learn.queue.length) go('done');
  2180|    else render();
  2181|  };
  2182|
  2183|  const rateCardDojo = requeue => {
  2184|    const card = learn.queue[learn.idx] || learn.queue[0];
  2185|    learn.queue.splice(learn.idx, 1);
  2186|    if (requeue && learn.queue.length) {
  2187|      const minPos = Math.min(5, learn.queue.length);
  2188|      const maxPos = learn.queue.length;
  2189|      const at = minPos + Math.floor(Math.random() * Math.max(1, maxPos - minPos + 1));
  2190|      learn.queue.splice(at, 0, card);
  2191|    }
  2192|    learn.flipped = false;
  2193|    if (!learn.queue.length) go('done');
  2194|    else render();
  2195|  };
  2196|
  2197|  /* ═══════════════════════════════════════
  2198|     CONFIRM MODAL
  2199|     ═══════════════════════════════════════ */
  2200|  const confirmModal = (title, msg, okLabel, onOk, danger=true) => {
  2201|    const o = document.createElement('div'); o.className='modal-overlay';
  2202|    o.innerHTML = `

  2203|
${esc(title)}

  2204|
${msg}

  2205|

  2206|        <button class="btn btn-ghost" id="cf-cancel">Abbrechen</button>
  2207|        <button class="btn ${danger?'btn-danger':'btn-primary'}" id="cf-ok">${esc(okLabel)}</button>
  2208|

`;
  2209|    document.body.appendChild(o);
  2210|    const close = ()=>o.remove();
  2211|    o.querySelector('#cf-ok').addEventListener('click', ()=>{ close(); onOk(); });
  2212|    o.querySelector('#cf-cancel').addEventListener('click', close);
  2213|    o.addEventListener('click', e=>{ if(e.target===o) close(); });
  2214|    document.addEventListener('keydown', function esc(e){
  2215|      if(e.key==='Escape'){ close(); document.removeEventListener('keydown', esc); }
  2216|      if(e.key==='Enter'){ close(); document.removeEventListener('keydown', esc); onOk(); }
  2217|    });
  2218|    o.querySelector('#cf-ok').focus();
  2219|  };
  2220|
  2221|  /* ═══════════════════════════════════════
  2222|     CARD CRUD
  2223|     ═══════════════════════════════════════ */
  2224|  const saveCard = (stayInEditor=false) => {
  2225|    const front = document.getElementById('f-front').value.trim();
  2226|    const back  = document.getElementById('f-back').value.trim();
  2227|    if (!front||!back) {
  2228|      const el = document.getElementById(!front?'f-front':'f-back');
  2229|      el.style.borderColor='var(--red)'; el.focus(); return;
  2230|    }
  2231|    const d = load(); if (!d.decks[deckId]) return;
  2232|    const priority = document.getElementById('f-priority')?.checked || false;
  2233|    if (cardId) {
  2234|      const i = d.decks[deckId].cards.findIndex(c=>c.id===cardId);
  2235|      if (i>=0) { d.decks[deckId].cards[i].front=front; d.decks[deckId].cards[i].back=back; d.decks[deckId].cards[i].priority=priority; }
  2236|    } else {
  2237|      const nc = newCard(front,back);
  2238|      nc.priority = priority;
  2239|      d.decks[deckId].cards.push(nc);
  2240|    }
  2241|    save(d);
  2242|
  2243|    /* Falls aus dem Lernmodus bearbeitet: Queue updaten und zurück zum Lernen */
  2244|    if (_editFromLearn && cardId) {
  2245|      const updated = d.decks[deckId].cards.find(c => c.id === cardId);
  2246|      const qi = learn.queue ? learn.queue.findIndex(c => c.id === cardId) : -1;
  2247|      if (qi >= 0 && updated) learn.queue[qi] = { ...learn.queue[qi], ...updated };
  2248|      _editFromLearn = false;
  2249|      go('learn', { cardId: null });
  2250|      return;
  2251|    }
  2252|
  2253|    if (stayInEditor && !cardId) {
  2254|      go('editor',{deckId,cardId:null});
  2255|    } else {
  2256|      go('cards',{deckId,cardId:null});
  2257|    }
  2258|  };
  2259|
  2260|  const deleteCard = id => {
  2261|    confirmModal('Karte löschen?', 'Die Karte wird endgültig entfernt.', 'Löschen', () => {
  2262|      const d=load(); d.decks[deckId].cards=d.decks[deckId].cards.filter(c=>c.id!==id); save(d); render();
  2263|    });
  2264|  };
  2265|
  2266|  /* ═══════════════════════════════════════
  2267|     DECK CRUD
  2268|     ═══════════════════════════════════════ */
  2269|  const closeCtx = () => { if(activeCtx){activeCtx.remove();activeCtx=null;} };
  2270|
  2271|  const showDeckMenu = (e, id) => {
  2272|    closeCtx();
  2273|    const m = document.createElement('div'); m.className='ctx-menu';
  2274|    m.innerHTML=`
  2275|
${IC.open} Öffnen

  2276|
${IC.edit} Umbenennen

  2277|

  2278|
${IC.trash} Löschen
`;
  2279|    document.body.appendChild(m);
  2280|    const mw=m.offsetWidth,mh=m.offsetHeight;
  2281|    m.style.left=Math.min(e.clientX,window.innerWidth-mw-12)+'px';
  2282|    m.style.top =Math.min(e.clientY,window.innerHeight-mh-12)+'px';
  2283|    activeCtx=m;
  2284|    m.querySelector('#ctx-open').addEventListener('click',   ()=>{closeCtx();go('cards',{deckId:id});});
  2285|    m.querySelector('#ctx-rename').addEventListener('click', ()=>{closeCtx();showRenameDeck(id);});
  2286|    m.querySelector('#ctx-del').addEventListener('click',    ()=>{closeCtx();deleteDeck(id);});
  2287|    setTimeout(()=>document.addEventListener('click',closeCtx,{once:true}),10);
  2288|  };
  2289|
  2290|  const deckModal = (title, defaultVal, onConfirm) => {
  2291|    const o=document.createElement('div'); o.className='modal-overlay';
  2292|    o.innerHTML=`

  2293|
${title}

  2294|      <input class="modal-input" id="m-inp" placeholder="Deck-Name…" maxlength="80" value="${esc(defaultVal)}">
  2295|

  2296|        <button class="btn btn-ghost" id="m-cancel">Abbrechen</button>
  2297|        <button class="btn btn-primary" id="m-ok">OK</button>
  2298|

`;
  2299|    document.body.appendChild(o);
  2300|    const inp=o.querySelector('#m-inp'); inp.focus(); inp.select();
  2301|    const confirm=()=>{const v=inp.value.trim();if(!v){inp.style.borderColor='var(--red)';return;}o.remove();onConfirm(v);};
  2302|    o.querySelector('#m-ok').addEventListener('click',confirm);
  2303|    o.querySelector('#m-cancel').addEventListener('click',()=>o.remove());
  2304|    o.addEventListener('click',e=>{if(e.target===o)o.remove();});
  2305|    inp.addEventListener('keydown',e=>{if(e.key==='Enter')confirm();if(e.key==='Escape')o.remove();});
  2306|  };
  2307|
  2308|  const showAddDeck = () => deckModal('Neues Deck','', name=>{
  2309|    const d=load(); const id=uid();
  2310|    const ci=Object.keys(d.decks).length%COLORS.length;
  2311|    d.decks[id]={id,name,color:COLORS[ci],created:Date.now(),cards:[]};
  2312|    save(d); render();
  2313|  });
  2314|
  2315|  const showRenameDeck = id => deckModal('Deck umbenennen', getDeck(id)?.name||'', name=>{
  2316|    const d=load(); if(d.decks[id]) d.decks[id].name=name; save(d); render();
  2317|  });
  2318|
  2319|  const deleteDeck = id => {
  2320|    const name = getDeck(id)?.name || '';
  2321|    const n = (getDeck(id)?.cards||[]).length;
  2322|    confirmModal(
  2323|      `Deck „${name}" löschen?`,
  2324|      `Alle <strong>${n}</strong> Karte${n!==1?'n':''} gehen verloren. Diese Aktion kann nicht rückgängig gemacht werden.`,
  2325|      'Löschen',
  2326|      () => { const d=load(); delete d.decks[id]; save(d); render(); }
  2327|    );
  2328|  };
  2329|
  2330|  /* ═══════════════════════════════════════
  2331|     ANKI IMPORT (.apkg · .csv · .tsv · .txt)
  2332|     ═══════════════════════════════════════ */
  2333|  const showImport = () => {
  2334|    const o=document.createElement('div'); o.className='modal-overlay';
  2335|    o.innerHTML=`

  2336|
Importieren

  2337|
.apkg (Anki), .csv, .tsv oder .txt (Tab-getrennt)

  2338|

  2339|
📥

  2340|
Datei hierher ziehen

  2341|
oder klicken zum Auswählen

  2342|

  2343|      <input type="file" id="import-file-input" accept=".apkg,.csv,.tsv,.txt">
  2344|

  2345|

  2346|        <button class="btn btn-ghost" id="imp-cancel">Abbrechen</button>
  2347|        <button class="btn btn-primary" id="imp-confirm" disabled>Importieren</button>
  2348|

`;
  2349|    document.body.appendChild(o);
  2350|
  2351|    let parsedData = null;
  2352|
  2353|    const status  = o.querySelector('#import-status');
  2354|    const confirm = o.querySelector('#imp-confirm');
  2355|    const setStatus = (msg,type='') => { status.textContent=msg; status.className='import-status '+(type||''); };
  2356|
  2357|    const handleFile = async file => {
  2358|      if (!file) return;
  2359|      setStatus('Verarbeite…');
  2360|      confirm.disabled=true;
  2361|      try {
  2362|        if (file.name.endsWith('.apkg')) {
  2363|          parsedData = await importApkg(file);
  2364|        } else {
  2365|          parsedData = await importText(file);
  2366|        }
  2367|        setStatus(`✓ ${parsedData.cards.length} Karten gefunden aus „${parsedData.deckName}"`, 'success');
  2368|        confirm.disabled = parsedData.cards.length === 0;
  2369|      } catch(err) {
  2370|        setStatus('Fehler: ' + err.message, 'error');
  2371|        parsedData = null; confirm.disabled=true;
  2372|      }
  2373|    };
  2374|
  2375|    const dz = o.querySelector('#drop-zone');
  2376|    const fi = o.querySelector('#import-file-input');
  2377|    dz.addEventListener('click', ()=>fi.click());
  2378|    dz.addEventListener('dragover', e=>{e.preventDefault();dz.classList.add('dragover');});
  2379|    dz.addEventListener('dragleave', ()=>dz.classList.remove('dragover'));
  2380|    dz.addEventListener('drop', e=>{e.preventDefault();dz.classList.remove('dragover');handleFile(e.dataTransfer.files[0]);});
  2381|    fi.addEventListener('change', ()=>handleFile(fi.files[0]));
  2382|
  2383|    confirm.addEventListener('click', ()=>{
  2384|      if(!parsedData) return;
  2385|      const d=load(); const id=uid();
  2386|      const ci=Object.keys(d.decks).length%COLORS.length;
  2387|      d.decks[id]={id,name:parsedData.deckName,color:COLORS[ci],created:Date.now(),cards:parsedData.cards};
  2388|      save(d); o.remove(); render();
  2389|    });
  2390|
  2391|    o.querySelector('#imp-cancel').addEventListener('click',()=>o.remove());
  2392|    o.addEventListener('click',e=>{if(e.target===o)o.remove();});
  2393|  };
  2394|
  2395|  /* Text import (CSV / TSV / TXT) */
  2396|  const importText = file => new Promise((resolve,reject)=>{
  2397|    const reader=new FileReader();
  2398|    reader.onload=e=>{
  2399|      const text=e.target.result;
  2400|      const sep = text.includes('\t') ? '\t' : ',';
  2401|      const lines = text.split('\n').filter(l=>l.trim()&&!l.startsWith('#'));
  2402|      const cards = [];
  2403|      for (const line of lines) {
  2404|        const parts = sep==='\t' ? line.split('\t') : splitCsv(line);
  2405|        const front = (parts[0]||'').trim().replace(/^"|"$/g,'');
  2406|        const back  = (parts[1]||'').trim().replace(/^"|"$/g,'');
  2407|        if (front&&back) cards.push(newCard(front,back));
  2408|      }
  2409|      if (!cards.length) {reject(new Error('Keine gültigen Karten gefunden (Format: Vorderseite[Tab]Rückseite)')); return;}
  2410|      resolve({ deckName: file.name.replace(/\.(csv|tsv|txt)$/i,''), cards });
  2411|    };
  2412|    reader.onerror=()=>reject(new Error('Datei konnte nicht gelesen werden'));
  2413|    reader.readAsText(file,'UTF-8');
  2414|  });
  2415|
  2416|  const splitCsv = line => {
  2417|    const r=[]; let f='',inQ=false;
  2418|    for(let i=0;i<line.length;i++){const c=line[i];if(c==='"'){inQ=!inQ;}else if(c===','&&!inQ){r.push(f);f='';}else{f+=c;}}
  2419|    r.push(f); return r;
  2420|  };
  2421|
  2422|  /* .apkg import — loads JSZip + sql.js from CDN lazily */
  2423|  const loadScript = src => new Promise((res,rej)=>{
  2424|    if(document.querySelector(`script[src="${src}"]`)){res();return;}
  2425|    const s=document.createElement('script');
  2426|    s.src=src; s.onload=res; s.onerror=()=>rej(new Error('Script konnte nicht geladen werden: '+src));
  2427|    document.head.appendChild(s);
  2428|  });
  2429|
  2430|  const importApkg = async file => {
  2431|    // Load deps
  2432|    await loadScript('https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js');
  2433|    await loadScript('https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.2/sql-wasm.min.js');
  2434|
  2435|    const buf = await file.arrayBuffer();
  2436|    const zip = await JSZip.loadAsync(buf);
  2437|
  2438|    const dbEntry = zip.file('collection.anki21') || zip.file('collection.anki2');
  2439|    if (!dbEntry) throw new Error('Keine Anki-Datenbank in der Datei gefunden');
  2440|
  2441|    const dbBuf = await dbEntry.async('arraybuffer');
  2442|    const SQL   = await initSqlJs({
  2443|      locateFile: f => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.2/${f}`
  2444|    });
  2445|    const db = new SQL.Database(new Uint8Array(dbBuf));
  2446|
  2447|    // Deck name from col table
  2448|    let deckName = file.name.replace(/\.apkg$/i,'');
  2449|    try {
  2450|      const colRes = db.exec("SELECT decks FROM col");
  2451|      if (colRes[0]) {
  2452|        const decksObj = JSON.parse(colRes[0].values[0][0]);
  2453|        const names = Object.values(decksObj)
  2454|          .filter(d=>d.name&&d.name!=='Default')
  2455|          .map(d=>d.name.split('::').shift());
  2456|        if (names.length) deckName = names[0];
  2457|      }
  2458|    } catch(_) {}
  2459|
  2460|    // Notes → cards
  2461|    const notesRes = db.exec("SELECT flds FROM notes");
  2462|    const cards = [];
  2463|    if (notesRes[0]) {
  2464|      for (const row of notesRes[0].values) {
  2465|        const fields = row[0].split('\x1f');
  2466|        const front  = strip(fields[0]||'').trim();
  2467|        const back   = strip(fields[1]||'').trim();
  2468|        if (front&&back) cards.push(newCard(front,back));
  2469|      }
  2470|    }
  2471|    db.close();
  2472|    if (!cards.length) throw new Error('Keine Karten importiert – möglicherweise nicht unterstützter Kartentyp');
  2473|    return { deckName, cards };
  2474|  };
  2475|
  2476|  /* ═══════════════════════════════════════
  2477|     DARK MODE (shared.js: System-Default + Tab-Sync)
  2478|     ═══════════════════════════════════════ */
  2479|  ZG.initTheme('theme-btn');
  2480|
  2481|  /* ═══════════════════════════════════════
  2482|     INIT
  2483|     ═══════════════════════════════════════ */
  2484|  fetch('/api/data').then(r=>r.json()).then(data=>{
  2485|    /* Zeitstempel-Merge (shared.js): neuerer Stand — Server oder lokal — gewinnt */
  2486|    ZG.adopt(data, STORE);
  2487|    ZG.adopt(data, ZG_STORE);
  2488|    abbrLoad(data);
  2489|    render(); updateHeaderStats();
  2490|  }).catch(()=>{
  2491|    /* Server nicht erreichbar – offline mit lokalem Cache */
  2492|    abbrLoad(null);
  2493|    render(); updateHeaderStats();
  2494|  });
  2495|
  2496|  /* Header-Statistik aktualisieren, wenn der Tab wieder sichtbar wird */
  2497|  document.addEventListener('visibilitychange', () => {
  2498|    if (!document.hidden) updateHeaderStats();
  2499|  });
  2500|
  2501|

Use offset=2501 to continue reading (showing 2001-2500 of 2678 lines)


Explanation: Let me read the rest of the file to see the remaining code, especially the `updateHeaderStats` function and the pomodoro timercode.


2501|  /* ── POMODORO (shared across pages) ────────────────
  2502|     State lives in localStorage 'zeitgeist-pomo'.
  2503|     Time is recorded into 'zeitgeist-v2' (Zeitgeist tracker).
  2504|  ─────────────────────────────────────────────────── */
  2505|  const POMO_DUR   = 25 * 60;
  2506|  const POMO_STORE = 'zeitgeist-pomo';
  2507|  const ZG_STORE   = 'zeitgeist-v2';
  2508|  const ZG_STD     = 25;
  2509|  const ZG_PER_DAY = 10;
  2510|
  2511|  const zgTodayKey = () => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; };
  2512|  const zgLoad = () => { try { return JSON.parse(localStorage.getItem(ZG_STORE)||'{}'); } catch { return {}; } };
  2513|  const zgSave = d => ZG.save(ZG_STORE, JSON.stringify(d));
  2514|
  2515|  const apomo = { state: 'idle', remaining: POMO_DUR, accum: 0, startedAt: null, _iv: null };
  2516|
  2517|  const apomoFmt = s => `${String(Math.floor(s/60)).padStart(2,'0')}:${String(s%60).padStart(2,'0')}`;
  2518|  const apomoElapsed = () => apomo.accum + (apomo.startedAt ? Math.floor((Date.now() - apomo.startedAt) / 1000) : 0);
  2519|
  2520|  const apomoSave = () => localStorage.setItem(POMO_STORE, JSON.stringify({ state: apomo.state, accum: apomo.accum, startedAt: apomo.startedAt }));
  2521|
  2522|  let _apomoCh; try { _apomoCh = new BroadcastChannel('zeitgeist-pomo'); } catch {}
  2523|  const apomoBroadcast = () => { try { _apomoCh?.postMessage({ state: apomo.state, accum: apomo.accum, startedAt: apomo.startedAt }); } catch {} };
  2524|
  2525|  const apomoRecord = (elapsedSec, completed = false) => {
  2526|    const mins = Math.round(elapsedSec / 60);
  2527|    if (mins < 1) return;
  2528|    const data = zgLoad();
  2529|    const key  = zgTodayKey();
  2530|    if (!data[key]) data[key] = Array(ZG_PER_DAY).fill(0);
  2531|    if (completed) {
  2532|      const idx = data[key].findIndex(v => v === 0);
  2533|      if (idx !== -1) data[key][idx] = ZG_STD;
  2534|    } else {
  2535|      const pi = data[key].findIndex(v => v > 0 && v < ZG_STD);
  2536|      if (pi !== -1) {
  2537|        const total = data[key][pi] + mins;
  2538|        if (total >= ZG_STD) {
  2539|          data[key][pi] = ZG_STD;
  2540|          const ov = total - ZG_STD;
  2541|          if (ov > 0) { const ni = data[key].findIndex(v => v === 0); if (ni !== -1) data[key][ni] = ov; }
  2542|        } else data[key][pi] = total;
  2543|      } else {
  2544|        const idx = data[key].findIndex(v => v === 0);
  2545|        if (idx !== -1) data[key][idx] = mins;
  2546|      }
  2547|    }
  2548|    zgSave(data);
  2549|    updateHeaderStats();
  2550|  };
  2551|
  2552|  const updateHeaderStats = () => {
  2553|    const data = zgLoad(), tk = zgTodayKey();
  2554|    const sumRow = key => { const r = data[key]; return r ? r.reduce((a,b)=>a+b,0) : 0; };
  2555|    const weekMinutes = () => {
  2556|      const now = new Date();
  2557|      const dayOfWeek = now.getDay(); // 0=Sun
  2558|      const monday = new Date(now);
  2559|      monday.setDate(now.getDate() - ((dayOfWeek + 6) % 7));
  2560|      monday.setHours(0,0,0,0);
  2561|      let total = 0;
  2562|      const cur = new Date(monday);
  2563|      for (let i = 0; i < 7; i++) {
  2564|        const key = `${cur.getFullYear()}-${String(cur.getMonth()+1).padStart(2,'0')}-${String(cur.getDate()).padStart(2,'0')}`;
  2565|        total += sumRow(key);
  2566|        cur.setDate(cur.getDate() + 1);
  2567|      }
  2568|      return total;
  2569|    };
  2570|    const today2 = new Date(); today2.setHours(0,0,0,0);
  2571|    const cur = new Date('2025-01-01');
  2572|    let total = 0, streak = 0, cont = true;
  2573|    while (cur <= today2) {
  2574|      const key = `${cur.getFullYear()}-${String(cur.getMonth()+1).padStart(2,'0')}-${String(cur.getDate()).padStart(2,'0')}`;
  2575|      const s = sumRow(key);
  2576|      total += s;
  2577|      if (cont) { if (s > 0) streak++; else if (key !== tk) cont = false; }
  2578|      cur.setDate(cur.getDate()+1);
  2579|    }
  2580|    const el1 = document.getElementById('s-today'); if (el1) el1.textContent = sumRow(tk) + ' min';
  2581|    const elW = document.getElementById('s-week'); if (elW) elW.textContent = weekMinutes() + ' min';
  2582|    const el2 = document.getElementById('s-total'); if (el2) el2.textContent = total + ' min';
  2583|    const el3 = document.getElementById('s-streak'); if (el3) el3.textContent = streak;
  2584|  };
  2585|
  2586|  document.getElementById('stats-btn')?.addEventListener('click', () => { location.href = 'zeitgeist.html#stats'; });
  2587|
  2588|  const apomoToast = msg => {
  2589|    const t = document.createElement('div');
  2590|    t.className = 'pomo-toast'; t.textContent = msg;
  2591|    document.body.appendChild(t);
  2592|    requestAnimationFrame(() => t.classList.add('visible'));
  2593|    setTimeout(() => { t.classList.remove('visible'); setTimeout(() => t.remove(), 400); }, 2800);
  2594|  };
  2595|
  2596|  const apomoNotify = () => {
  2597|    try {
  2598|      const ctx = new (window.AudioContext || window.webkitAudioContext)();
  2599|      const ding = (freq, t, dur) => { const o = ctx.createOscillator(), g = ctx.createGain(); o.connect(g); g.connect(ctx.destination); o.frequency.value = freq; o.type = 'sine'; g.gain.setValueAtTime(0.22, ctx.currentTime + t); g.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + t + dur); o.start(ctx.currentTime + t); o.stop(ctx.currentTime + t + dur); };
  2600|      ding(880, 0, 0.5); ding(1108, 0.18, 0.5); ding(1318, 0.36, 0.9);
  2601|    } catch {}
  2602|  };
  2603|
  2604|  const apomoUpdateUI = () => {
  2605|    const disp  = document.getElementById('pomo-display');
  2606|    const start = document.getElementById('pomo-start');
  2607|    const stop  = document.getElementById('pomo-stop');
  2608|    if (!disp) return;
  2609|    disp.textContent = apomoFmt(apomo.remaining);
  2610|    disp.className   = 'pomo-display' + (apomo.state === 'running' ? ' is-running' : apomo.state === 'paused' ? ' is-paused' : '');
  2611|    if (start) { start.textContent = apomo.state === 'running' ? '⏸' : '▶'; start.title = apomo.state === 'running' ? 'Pausieren' : apomo.state === 'paused' ? 'Weiter' : 'Starten'; start.classList.toggle('is-active', apomo.state === 'running'); }
  2612|    if (stop) stop.disabled = apomo.state === 'idle';
  2613|  };
  2614|
  2615|  const apomoTick = () => {
  2616|    apomo.remaining = Math.max(0, POMO_DUR - apomoElapsed());
  2617|    apomoUpdateUI();
  2618|    if (apomo.remaining === 0) {
  2619|      clearInterval(apomo._iv); apomo._iv = null;
  2620|      apomoRecord(POMO_DUR, true);
  2621|      apomo.state = 'idle'; apomo.remaining = POMO_DUR; apomo.accum = 0; apomo.startedAt = null;
  2622|      apomoSave(); apomoBroadcast(); apomoUpdateUI();
  2623|      apomoNotify(); apomoToast('✅ Pomodoro abgeschlossen · 25 min erfasst');
  2624|    }
  2625|  };
  2626|
  2627|  const apomoApplyState = ({ state, accum, startedAt }) => {
  2628|    clearInterval(apomo._iv); apomo._iv = null;
  2629|    apomo.state = state; apomo.accum = accum; apomo.startedAt = startedAt;
  2630|    apomo.remaining = Math.max(0, POMO_DUR - (accum + (startedAt ? Math.floor((Date.now() - startedAt) / 1000) : 0)));
  2631|    if (apomo.state === 'running') apomo._iv = setInterval(apomoTick, 500);
  2632|    apomoUpdateUI();
  2633|  };
  2634|
  2635|  if (_apomoCh) _apomoCh.onmessage = e => apomoApplyState(e.data);
  2636|
  2637|  document.getElementById('pomo-start').addEventListener('click', () => {
  2638|    if (apomo.state === 'running') {
  2639|      clearInterval(apomo._iv); apomo._iv = null;
  2640|      apomo.accum += Math.floor((Date.now() - apomo.startedAt) / 1000);
  2641|      apomo.startedAt = null; apomo.state = 'paused';
  2642|    } else {
  2643|      apomo.startedAt = Date.now(); apomo.state = 'running';
  2644|      apomo._iv = setInterval(apomoTick, 500);
  2645|    }
  2646|    apomoSave(); apomoBroadcast(); apomoUpdateUI();
  2647|  });
  2648|
  2649|  document.getElementById('pomo-stop').addEventListener('click', () => {
  2650|    if (apomo.state === 'idle') return;
  2651|    const elapsed = apomoElapsed();
  2652|    clearInterval(apomo._iv); apomo._iv = null;
  2653|    apomoRecord(elapsed);
  2654|    const mins = Math.round(elapsed / 60);
  2655|    apomo.state = 'idle'; apomo.remaining = POMO_DUR; apomo.accum = 0; apomo.startedAt = null;
  2656|    apomoSave(); apomoBroadcast(); apomoUpdateUI();
  2657|    if (mins >= 1) apomoToast(`⏱ ${mins} min erfasst`);
  2658|  });
  2659|
  2660|  // Init: gespeicherten State wiederherstellen
  2661|  (() => {
  2662|    try {
  2663|      const saved = JSON.parse(localStorage.getItem(POMO_STORE) || 'null');
  2664|      if (saved && saved.state !== 'idle') apomoApplyState(saved);
  2665|      else apomoUpdateUI();
  2666|    } catch { apomoUpdateUI(); }
  2667|  })();
  2668|  </script>
  2669|
  2677|
  2678|
  2679|


Jetzt hab ich den gesamten Code (2678 Zeilen) durch. Ich sehe die zwei Probleme klar:

1. **"1d" Intervall-Anzeige** bei Priority-Karten — Zeile 1777 zeigt `prev.good` (z.B. "1d") unter dem "Gemerkt!"-Button. Soll weg.

2. **Priority-Karten überschreiben den Dojo-Modus** — Priority-Karten kriegen FSRS-Scheduling (rateCard(3)), kriegen ein dueDate und verschwinden aus der Queue. Das macht sie zu "fälligen" Karten, statt zu frei übbaren Karten wie im Dojo. Sie sollten **nie** gescheduled werden, sondern immer re-queued (wie Dojo "Nochmal").

Ich lass Kimi K2.7 Code das fixen:

Explanation:
Let me analyze the current state of the Priority feature in the Anki app. The user's complaints are:

1. "Es soll nicht 1d dabei stehen" — The "Gemerkt!" button for priority cards shows the interval (e.g., "1d") which is unnecessary/confusing for priority cards since they're meant to be reviewed frequently regardless of FSRS scheduling.

2. "Es darf nicht so verstanden werden, das noch eine karte zu lernen ist, das überschreibt nämlich den Dojo Modus" — The priority card's "Gemerkt!" button with its interval display might be misinterpreted as "there's still a card to learn" which overrides/conflicts with the Dojo mode concept.

Let me look at the relevant code sections:

**Priority card rating in vLearn (line 1773-1778):**
```javascript
${flipped ? (card.priority ? `
        <div class="rating-row">
          <button class="rating-btn rb-good" id="rate-gemerkt" title="Taste 3">
            <span class="rb-label">Gemerkt!</span>
            <span class="rb-interval">${prev.good}</span>
          </button>
        </div>` : ...
```

So for priority cards, when flipped, it shows a single "Gemerkt!" button with the FSRS interval (e.g., "1d"). The user doesn't want the interval shown.

**Priority card queue placement (line 2095-2109):**
```javascript
// Priority cards first — immer anzeigen, unabhängig von dueDate/FSRS
const priorityCards = all.filter(c => c.priority);
...
const queue = [...priorityCards, ...inLearn, ...reviews, ...newCards];
```

Priority cards are placed at the front of the queue. But the user's concern is that the priority card's "Gemerkt!" button with its interval display makes it seem like there's still a card to learn, which conflicts with the Dojo mode.

**Priority card rating in rateCard (line 2149-2181):**
Priority cards use `rateCard(3)` which applies FSRS rating 3 (Good). This means priority cards DO get scheduled by FSRS — they get a due date, stability, etc. The user seems to want priority cards to NOT be scheduled by FSRS at all — they should just be reviewed and then re-queued (like Dojo mode), or at least not show the interval.

Let me re-read the user's message more carefully:

"Ja, aber es soll nicht 1d dabei stehen, das ist nämlich eh klar." — The "1d" interval display on the "Gemerkt!" button should be removed. It's obvious that the card will be reviewed again.

"Zudem darf es nicht so verstanden werden, das noch eine karte zu lernen ist, das überschreibt nämlich den Dojo Modus." — The priority card mechanism shouldn't be understood as "there's still a card to learn" because that overrides/conflicts with the Dojo mode.

So the user wants:
1. Remove the interval display from the "Gemerkt!" button for priority cards
2. Make priority cards work more like Dojo mode — they should be re-queued rather than scheduled by FSRS, so they don't create a "due" state that conflicts with Dojo

Wait, let me think about this more carefully. The issue is:

- In normal learn mode: priority cards appear first, you rate them "Gemerkt!" (rating 3), they get scheduled by FSRS (e.g., 1d interval), and they're removed from the queue.
- In Dojo mode: priority cards also appear first (line 2138-2144), and they also get the "Gemerkt!" button with FSRS scheduling.

The problem: When a priority card is rated "Gemerkt!" with rating 3, it gets a due date (e.g., tomorrow). This means:
1. The "1d" display is unnecessary clutter
2. More importantly, priority cards get scheduled into the future, which means they won't appear in Dojo mode until they're "due" again. But the user wants priority cards to ALWAYS be available for review, regardless of scheduling.

So the fix should be:
1. Remove the interval display from the "Gemerkt!" button
2. For priority cards, don't apply FSRS scheduling — instead, just re-queue them (like Dojo's "Nochmal") so they stay in circulation. Or alternatively, keep them in the queue but don't persist FSRS state changes.

Actually, let me reconsider. The user said "das überschreibt nämlich den Dojo Modus" — "that overrides the Dojo mode". I think the issue is more subtle:

When you're in normal learn mode and there are priority cards, they show up with "Gemerkt!" and get scheduled. But the user wants priority cards to behave like Dojo cards — they should just be practiced and re-queued, not scheduled. The FSRS scheduling of priority cards "overrides" the Dojo concept because priority cards should always be available for practice.

Let me think about what the right behavior should be:

For priority cards:
- They should always appear at the front of any queue (learn or dojo)
- When rated "Gemerkt!", they should NOT be scheduled by FSRS — instead they should be re-queued to the back (like Dojo "Nochmal") so they keep circulating
- No interval display needed
- They should NOT create a "due" state that would make them appear as "fällig" in the deck view

This makes priority cards a pure practice tool — always available, never scheduled, always at the front.

Now let me plan the changes:

1. **vLearn (line 1773-1778)**: Remove the `<span class="rb-interval">${prev.good}</span>` from the priority "Gemerkt!" button.

2. **rateCard (line 2149-2181)**: For priority cards, instead of applying FSRS rating, just re-queue the card (like Dojo mode). Don't persist FSRS state changes for priority cards.

3. **startLearn (line 2089-2116)**: Priority cards should still be at the front, but they shouldn't be counted in the "total" for FSRS scheduling purposes? Actually, they should still be counted in the session total.

4. **startDojo (line 2133-2147)**: Same — priority cards at front.

5. **countsDue (line 1323-1329)**: Priority cards should NOT count as "due" even if they have a dueDate, because they're always available.

Let me also check: in the current code, when a priority card is rated with `rateCard(3)`, it goes through the full FSRS pipeline and gets a due date. This means after rating, it won't show up as "due" until that date arrives. The user wants priority cards to always be available.

The cleanest approach: In `rateCard`, detect if the card is priority, and if so, just re-queue it without FSRS scheduling (like Dojo mode). Don't persist any FSRS state changes.

Let me also check the `rateCardDojo` function — it just re-queues or removes the card without any FSRS processing. That's the model for priority cards.

Now, for the `countsDue` function: priority cards should not be counted as "due" because they're always available. But they also shouldn't be counted as "new" if they've been reviewed before. I think the simplest approach is to exclude priority cards from the "due" count entirely.

Let me also think about what happens in the card list view — priority cards currently show their FSRS state badge. If we stop scheduling them, they'll always be in whatever state they were last in. That might be confusing. Maybe priority cards should show a special "Priority" badge instead of their FSRS state?

Actually, let me keep it simpler. The user's main complaints are:
1. Remove the interval display
2. Don't let priority cards interfere with Dojo mode

The simplest fix:
- Remove interval from "Gemerkt!" button
- For priority cards, don't apply FSRS — just re-queue them (like Dojo)
- Exclude priority cards from "due" counts

Let me delegate this to Kimi K2.7 Code.

Actually wait — I need to be more precise about what "das überschreibt nämlich den Dojo Modus" means. Let me re-read:

The user says the priority feature shouldn't be understood as "there's still a card to learn" because that overrides the Dojo mode.

I think the issue is: When you're in Dojo mode, priority cards still show the "Gemerkt!" button with an interval. This makes it seem like the card is being "learned" (scheduled) rather than just "practiced" (Dojo-style). The Dojo mode is supposed to be free practice without scheduling — but priority cards break that by applying FSRS scheduling even in Dojo mode.

So the fix for Dojo mode specifically: priority cards in Dojo should behave like regular Dojo cards (Nochmal/Weiter), not get the special "Gemerkt!" treatment with FSRS scheduling.

Hmm, but the user might also want priority cards to always be re-queued (never scheduled) even in normal learn mode. Let me just go with:

1. Remove interval display from "Gemerkt!" button
2. Priority cards never get FSRS scheduled — they're always re-queued (like Dojo "Nochmal")
3. Priority cards don't count as "due"

This is the cleanest interpretation. Let me delegate to Kimi.

Let me write a clear spec for the sub-agent.

Fix the Priority card feature in /media/HDD_1TB/Zeitgeist/anki.html. Three changes needed:

1. **Remove interval display from "Gemerkt!" button** (line ~1777): Delete the `<span class="rb-interval">${prev.good}</span>` line inside the priority card rating button in vLearn().

2. **Priority cards should NEVER be FSRS-scheduled** — they should always be re-queued like Dojo mode. In rateCard() (line ~2149), detect if the card has `card.priority === true`. If so, do NOT call SM2.rate() and do NOT persist FSRS state changes. Instead, just re-queue the card to a random position (like rateCardDojo does with requeue=true). Track stats as "good". The card stays in circulation forever.

3. **Exclude priority cards from "due" counts** in countsDue() (line ~1323): Priority cards should never count as "due" even if they have a dueDate, because they're always available for practice.

Also ensure priority cards in Dojo mode still work correctly — they should appear first in the queue and use the same re-queue behavior (not the special "Gemerkt!" button with FSRS — actually, keep the "Gemerkt!" button but without interval, and make it just re-queue).

After making changes, verify with: `node --check /media/HDD_1TB/Zeitgeist/anki.html` to ensure no JS syntax errors.

Context:
File: /media/HDD_1TB/Zeitgeist/anki.html (2678 lines)

Key locations:
- Line 1323-1329: countsDue() — needs to exclude priority cards from 'due' count
- Line 1773-1778: vLearn() priority card rating button — remove rb-interval span
- Line 2149-2181: rateCard() — add priority card detection, skip FSRS, just re-queue
- Line 2089-2116: startLearn() — priority cards already placed first in queue (line 2096, 2109)
- Line 2133-2147: startDojo() — priority cards already placed first (line 2139, 2144)

The rateCardDojo function (line 2183-2195) shows the re-queue pattern: splice card out, then splice it back at a random position (min 5, max queue.length).

For priority cards in rateCard():
- Track stats as "good" (learn.stats.good = (learn.stats.good||0)+1)
- Re-queue the card (don't remove it permanently)
- Don't call SM2.rate() at all
- Don't persist anything to storage for priority cards
- Set learn.flipped = false
- If queue becomes empty (only 1 priority card left), go to 'done'
- Otherwise render()

For countsDue: add `&& !c.priority` to the due filter condition.


Summary:
Done. Here's a concise summary:

## What I changed in `/media/HDD_1TB/Zeitgeist/anki.html`

1. **Removed interval from "Gemerkt!" button** (~line 1777)
   Deleted the `<span class="rb-interval">${prev.good}</span>` inside the priority-card rating button.

2. **Priority cards skip FSRS scheduling** (~line 2149)
   Added an early guard in `rateCard()`: if `card.priority === true`, it bypasses `SM2.rate()` entirely, does not persist any FSRS state changes, increments `learn.stats.good`, removes the card from the front of the queue, re-queues it at a random position (same min/max logic as `rateCardDojo`), sets `learn.flipped = false`, then renders or goes to `done`.

3. **Priority cards excluded from due counts** (~line 1323)
   Updated `countsDue()` filter to `!c.priority` so priority cards never count as "due" regardless of `dueDate`.

## Dojo-mode behavior preserved
Priority cards still appear first in the queue via existing `startLearn()` / `startDojo()` logic, and now use the same re-queue behavior in `rateCard()` (not FSRS). The special "Gemerkt!" button remains but no longer shows an interval.

## Verification
The literal `node --check /media/HDD_1TB/Zeitgeist/anki.html` command fails because Node refuses `.html` files. I extracted the inline `<script>` block (lines 798–2684) into a temporary `.js` file and ran `node --check` on that — it passed with no syntax errors.

[NOTE: subagent modified files the parent previously read — re-read before editing: /media/HDD_1TB/Zeitgeist/anki.html]


Lass mich kurz verifizieren, dass die Änderungen sauber sitzen:

Explanation: The subagent has completed the changes. Let me verify the key changes were made correctly by reading the relevant sections of the file.


  1323|  const countsDue = (cards) => {
  1324|    const now = Date.now();
  1325|    return {
  1326|      due:   cards.filter(c => c.state!=='new' && !c.priority && c.dueDate && c.dueDate<=now).length,
  1327|      newN:  cards.filter(c => c.state==='new'||!c.state).length,
  1328|    };
  1329|  };
  1330|
  1331|    const newCard = (front, back) => ({
  1332|    id: uid(), front, back, created: Date.now(),
  1333|

Use offset=1333 to continue reading (showing 1323-1332 of 2694 lines)


1773|        ${flipped ? (card.priority ? `
  1774|

  1775|          <button class="rating-btn rb-good" id="rate-gemerkt" title="Taste 3">
  1776|            Gemerkt!
  1777|          </button>
  1778|
` : (learn.dojo ? `
  1779|

  1780|          <button class="rating-btn rb-again" id="dojo-nochmal">
  1781|            Nochmal
  1782|          </button>
  1783|

Use offset=1783 to continue reading (showing 1773-1782 of 2694 lines)


  2149|    const card = learn.queue[learn.idx]||learn.queue[0];
  2150|
  2151|    // Priority cards are never FSRS-scheduled; always re-queue like Dojo
  2152|    if (card.priority === true) {
  2153|      learn.stats.good = (learn.stats.good||0)+1;
  2154|      learn.queue.splice(learn.idx, 1);
  2155|      if (learn.queue.length) {
  2156|        const minPos = Math.min(5, learn.queue.length);
  2157|        const maxPos = learn.queue.length;
  2158|        const at = minPos + Math.floor(Math.random() * Math.max(1, maxPos - minPos + 1));
  2159|        learn.queue.splice(at, 0, card);
  2160|      }
  2161|      learn.flipped = false;
  2162|      if (!learn.queue.length) go('done');
  2163|      else render();
  2164|      return;
  2165|    }
  2166|
  2167|    const updated = SM2.rate({...card}, rating);
  2168|
  2169|    // Track stats
  2170|    const key=['','again','hard','good','easy'][rating];
  2171|    learn.stats[key] = (learn.stats[key]||0)+1;
  2172|
  2173|    // Remove from front
  2174|    learn.queue.splice(learn.idx, 1);
  2175|
  2176|    // Persist updated SM-2 data
  2177|    const d = load();
  2178|    if (d.decks[deckId]) {
  2179|      const ci = d.decks[deckId].cards.findIndex(c=>c.id===card.id);
  2180|      if (ci>=0) d.decks[deckId].cards[ci] = {...d.decks[deckId].cards[ci], ...updated};
  2181|      save(d);
  2182|    }
  2183|
  2184|    // Re-queue nur bei sehr kurzem Intervall, dann zufällig weiter hinten platzieren
  2185|    // (verhindert dass dieselbe Karte alle 3 Slots wieder auftaucht)
  2186|    const interval = updated.dueDate - Date.now();
  2187|    if (interval < 1200000 && learn.queue.length) { // < 20 min
  2188|      const minPos = Math.min(5, learn.queue.length);
  2189|

Use offset=2189 to continue reading (showing 2149-2188 of 2694 lines)


Perfekt, Boss. Drei Änderungen sitzen:

1. **"1d" weg** — Der "Gemerkt!"-Button zeigt kein Intervall mehr. Nur noch der Button selbst.

2. **Priority-Karten nie FSRS-schedulen** — `rateCard()` hat jetzt einen Early-Guard: wenn `card.priority === true`, wird **kein** `SM2.rate()` aufgerufen, **nichts** persistiert, die Karte wird einfach re-queued (wie Dojo "Nochmal"). Sie bleibt für immer im Umlauf.

3. **Priority zählt nicht als "fällig"** — `countsDue()` filtert jetzt `!c.priority`, damit Priority-Karten nie als "due" auftauchen und den Dojo-Modus nicht überschreiben.

Dojo-Modus bleibt unangetastet — Priority-Karten erscheinen weiterhin zuerst in der Queue, und der "Gemerkt!"-Button macht jetzt genau das Richtige: Karte merken, re-queuen, weitermachen.

Explanation: All three changes are verified and correct. Let me summarize what was done for the boss.