/* Mastery map · Japanese mode — frequency-band layout Same surface, different domain. Frequency bands replace prerequisite ranks as the primary organizing axis. Entity types extend: kanji, vocab, grammar. */ const MasteryMapJapanese = () => { const [hovered, setHovered] = React.useState(null); const [layout, setLayout] = React.useState("frequency"); const [filter, setFilter] = React.useState("all"); // Bands by corpus frequency rank. Each node carries kind, freq rank, FSRS state. // Mix of kanji / vocab / grammar to show the typed entity legend extended. const nodes = [ // Top 100 { id:"k_日", label:"日", kind:"kanji", band:0, x:0.10, y:0.12, mastery:"mastered", retention:0.95, due:18 }, { id:"v_する", label:"する", kind:"vocab", band:0, x:0.22, y:0.10, mastery:"mastered", retention:0.97, due:22 }, { id:"v_いる", label:"いる", kind:"vocab", band:0, x:0.34, y:0.14, mastery:"mastered", retention:0.93, due:14 }, { id:"g_は", label:"は · topic", kind:"grammar",band:0, x:0.46, y:0.10, mastery:"mastered", retention:0.96, due:30 }, { id:"g_を", label:"を · obj", kind:"grammar",band:0, x:0.58, y:0.14, mastery:"mastered", retention:0.92, due:12 }, { id:"v_食べる",label:"食べる", kind:"vocab", band:0, x:0.70, y:0.10, mastery:"mastered", retention:0.94, due:35 }, { id:"k_人", label:"人", kind:"kanji", band:0, x:0.82, y:0.14, mastery:"mastered", retention:0.88, due: 5 }, { id:"v_見る", label:"見る", kind:"vocab", band:0, x:0.92, y:0.10, mastery:"mastered", retention:0.62, due:-3 }, // 100-500 { id:"k_動", label:"動", kind:"kanji", band:1, x:0.08, y:0.30, mastery:"learning", retention:0.52, due:-1 }, { id:"v_行く", label:"行く", kind:"vocab", band:1, x:0.20, y:0.28, mastery:"mastered", retention:0.81, due: 6 }, { id:"k_時", label:"時", kind:"kanji", band:1, x:0.32, y:0.32, mastery:"mastered", retention:0.74, due: 2 }, { id:"g_て", label:"て · conj", kind:"grammar",band:1, x:0.44, y:0.28, mastery:"learning", retention:0.45, due: 0 }, { id:"v_友達", label:"友達", kind:"vocab", band:1, x:0.56, y:0.32, mastery:"mastered", retention:0.79, due: 9 }, { id:"k_車", label:"車", kind:"kanji", band:1, x:0.68, y:0.28, mastery:"mastered", retention:0.86, due:11 }, { id:"g_たい", label:"たい · desid",kind:"grammar",band:1,x:0.80, y:0.32, mastery:"mastered", retention:0.55, due:-4 }, { id:"v_来る", label:"来る", kind:"vocab", band:1, x:0.92, y:0.28, mastery:"mastered", retention:0.83, due:13 }, // 500-1000 { id:"v_美味しい",label:"美味しい",kind:"vocab", band:2, x:0.10, y:0.46, mastery:"mastered", retention:0.78, due: 3 }, { id:"k_運", label:"運", kind:"kanji", band:2, x:0.24, y:0.50, mastery:"learning", retention:0.46, due:-2 }, { id:"v_運動", label:"運動", kind:"vocab", band:2, x:0.36, y:0.46, mastery:"mastered", retention:0.71, due: 1 }, { id:"g_ば", label:"ば · cond", kind:"grammar",band:2, x:0.50, y:0.50, mastery:"frontier", retention:null, due:null }, { id:"v_使う", label:"使う", kind:"vocab", band:2, x:0.62, y:0.46, mastery:"frontier", retention:null, due:null }, { id:"k_新", label:"新", kind:"kanji", band:2, x:0.74, y:0.50, mastery:"frontier", retention:null, due:null, focus:true }, { id:"v_新しい",label:"新しい", kind:"vocab", band:2, x:0.86, y:0.46, mastery:"frontier", retention:null, due:null }, // 1000-5000 { id:"k_識", label:"識", kind:"kanji", band:3, x:0.12, y:0.66, mastery:"locked", retention:null, due:null }, { id:"v_意識", label:"意識", kind:"vocab", band:3, x:0.26, y:0.70, mastery:"locked", retention:null, due:null }, { id:"g_させる",label:"させる · caus",kind:"grammar",band:3,x:0.40, y:0.66, mastery:"locked", retention:null, due:null }, { id:"v_似る", label:"似る", kind:"vocab", band:3, x:0.54, y:0.70, mastery:"locked", retention:null, due:null }, { id:"k_鏡", label:"鏡", kind:"kanji", band:3, x:0.68, y:0.66, mastery:"locked", retention:null, due:null }, { id:"v_鏡", label:"鏡 · mirror",kind:"vocab", band:3, x:0.82, y:0.70, mastery:"locked", retention:null, due:null }, // 5000+ { id:"k_鬱", label:"鬱", kind:"kanji", band:4, x:0.18, y:0.86, mastery:"locked", retention:null, due:null }, { id:"v_憂鬱", label:"憂鬱", kind:"vocab", band:4, x:0.36, y:0.90, mastery:"locked", retention:null, due:null }, { id:"g_らしい",label:"らしい·hear",kind:"grammar",band:4,x:0.54, y:0.86, mastery:"locked", retention:null, due:null }, { id:"k_薔", label:"薔", kind:"kanji", band:4, x:0.72, y:0.90, mastery:"locked", retention:null, due:null }, { id:"v_薔薇", label:"薔薇", kind:"vocab", band:4, x:0.88, y:0.86, mastery:"locked", retention:null, due:null }, ]; const bands = [ { id:0, label:"TOP 100", range:"freq #1–100", count: nodes.filter(n=>n.band===0).length, y:0.12 }, { id:1, label:"100 – 500", range:"core daily", count: nodes.filter(n=>n.band===1).length, y:0.30 }, { id:2, label:"500 – 1000", range:"common — frontier",count: nodes.filter(n=>n.band===2).length, y:0.48 }, { id:3, label:"1K – 5K", range:"academic", count: nodes.filter(n=>n.band===3).length, y:0.68 }, { id:4, label:"5K+", range:"literary · rare", count: nodes.filter(n=>n.band===4).length, y:0.88 }, ]; const stateOf = (n) => { if (n.mastery === "frontier") return "frontier"; if (n.mastery === "locked") return "locked"; if (n.mastery === "learning") return "learning"; if (n.due !== null && n.due < 0) return "review"; return "mastered"; }; const counts = { mastered: nodes.filter(n => stateOf(n)==="mastered").length, review: nodes.filter(n => stateOf(n)==="review").length, learning: nodes.filter(n => stateOf(n)==="learning").length, frontier: nodes.filter(n => stateOf(n)==="frontier").length, locked: nodes.filter(n => stateOf(n)==="locked").length, }; const matchFilter = (n) => filter === "all" || stateOf(n) === filter; const W = 1180, H = 720, PAD_X = 70, PAD_Y = 30; const xToPx = (x) => PAD_X + x*(W-2*PAD_X); const yToPx = (y) => PAD_Y + y*(H-2*PAD_Y); const reviewDue = nodes.filter(n => n.due !== null && n.due < 0).sort((a,b) => a.due - b.due); const frontier = nodes.filter(n => n.mastery === "frontier"); const nodeById = Object.fromEntries(nodes.map(n => [n.id, n])); // Shape per kind: kanji=square, vocab=circle, grammar=diamond const drawShape = (n, cx, cy) => { const st = stateOf(n); const fill = st==="mastered" ? "var(--ink)" : st==="frontier" ? "var(--accent)" : "var(--paper)"; const stroke = st==="locked" ? "var(--rule-2)" : st==="frontier" ? "var(--accent)" : "var(--ink)"; const sw = n.focus ? 2 : 1.25; if (n.kind === "kanji") { return ; } if (n.kind === "grammar") { return ; } return ; }; return (
§ MAP / Mastery map · 日本語 · frequency-organized · 32 nodes shown / 14,082 total
Domain Layout
{[["frequency","Frequency"],["jlpt","JLPT"],["radical","Radical"]].map(([k,l]) => ( ))}

{/* Frequency bands as horizontal bars */} {bands.map((b, i) => ( {b.label} · {b.range} · {b.count} {/* density indicator on right */} {b.id === 0 ? "essentials" : b.id === 4 ? "long tail" : ""} ))} {/* Frontier glow */} {nodes.filter(n => stateOf(n) === "frontier").map(n => ( ))} {/* Lateral connections — kanji↔vocab containment */} {/* Nodes */} {nodes.map(n => { const cx = xToPx(n.x), cy = yToPx(n.y); const dim = filter !== "all" && !matchFilter(n); const st = stateOf(n); return ( setHovered(n.id)} onMouseLeave={()=>setHovered(null)} style={{cursor:"pointer"}} opacity={dim ? 0.18 : 1}> {drawShape(n, cx, cy)} {st === "review" && } {n.focus && } {n.retention !== null && st !== "frontier" && ( )} {n.label} ); })}

); }; window.MasteryMapJapanese = MasteryMapJapanese;