"use strict";var Y=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var nr=Y((Tu,tr)=>{"use strict";tr.exports=`
.lh-quadrant-stats { display:flex; gap:10px; padding:14px 16px; }
.lh-quadrant-stat { flex:1; display:flex; flex-direction:column; align-items:center; gap:4px; padding:12px 8px; border-radius:12px; background:var(--background-secondary); border:1px solid var(--background-modifier-border); }
.lh-quadrant-stat-num { font-size:22px; font-weight:800; color:var(--text-normal); }
.lh-quadrant-stat-label { font-size:11px; color:var(--text-muted); }
.lh-quadrant-empty { padding:32px 16px; display:flex; flex-direction:column; }
.lp-wrap { font-family: system-ui, sans-serif; border-radius: 16px; overflow: hidden; box-shadow: 0 4px 24px rgba(0,0,0,0.12); }
.lp-clouds { position: relative; height: 0; }
.lp-cloud { position: absolute; background: rgba(255,255,255,0.85); border-radius: 50px; width: 100px; height: 35px; filter: blur(1px); z-index: 1; }
.lp-cloud::before { content:''; position:absolute; background:rgba(255,255,255,0.9); border-radius:50%; width:50px; height:50px; top:-25px; left:12px; }
.lp-cloud::after  { content:''; position:absolute; background:rgba(255,255,255,0.9); border-radius:50%; width:35px; height:35px; top:-15px; left:52px; }
.lp-map { position: relative; background: var(--lp-bg); overflow: auto; }
.lp-node { position: absolute; display: flex; flex-direction: column; align-items: center; cursor: pointer; transition: transform 0.2s; user-select: none; width: 110px; }
.lp-node:hover { transform: scale(1.1) translateY(-4px); }
.lp-island { width: 80px; height: 80px; border-radius: 50%; display: flex; align-items: center; justify-content: center; position: relative; box-shadow: 0 6px 18px rgba(0,0,0,0.2), inset 0 -3px 6px rgba(0,0,0,0.1); border: 2px solid rgba(255,255,255,0.4); }
.lp-emoji { font-size: 32px; }
.lp-badge { position:absolute; top:-4px; right:-4px; width:24px; height:24px; border-radius:50%; background:white; display:flex; align-items:center; justify-content:center; font-size:13px; box-shadow:0 2px 5px rgba(0,0,0,0.25); }
.lp-label { margin-top: 6px; font-size: 12px; font-weight: 600; text-align: center; text-shadow: 0 1px 3px rgba(255,255,255,0.7); line-height: 1.2; }
.lp-mini-bar { width: 70px; height: 5px; background: rgba(0,0,0,0.15); border-radius: 3px; margin-top: 4px; overflow: hidden; }
.lp-mini-bar > div { height: 100%; border-radius: 3px; }
@keyframes lp-pulse { 0%,100%{ filter:drop-shadow(0 0 6px rgba(255,200,50,0.5)); } 50%{ filter:drop-shadow(0 0 18px rgba(255,200,50,1)); } }
.lp-pulse { animation: lp-pulse 2s ease-in-out infinite; }
.lp-panel { background: white; padding: 20px 24px 24px; }
.lp-panel-title { font-size: 18px; font-weight: 700; color: #1a1a2e; margin-bottom: 6px; }
.lp-panel-desc { font-size: 13px; color: #555; margin-bottom: 16px; line-height: 1.6; }
.lp-btn { background: linear-gradient(135deg, #38bdf8, #0284c7); color: white; border: none; border-radius: 50px; padding: 10px 32px; font-size: 14px; font-weight: 600; cursor: pointer; display: block; width: 100%; max-width: 240px; margin-bottom: 12px; transition: all 0.2s; }
.lp-btn:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 4px 14px rgba(102,126,234,0.5); }
.lp-btn:disabled { background: #ccc; cursor: not-allowed; }
.lp-progress-row { display: flex; align-items: center; gap: 10px; }
.lp-bar-bg { flex: 1; max-width: 180px; height: 7px; background: #e0e0e0; border-radius: 4px; overflow: hidden; }
.lp-bar-fill { height: 100%; border-radius: 4px; transition: width 0.4s; }
.lp-progress-text { font-size: 12px; color: #777; }
/* Quest Map animations */
.qm-map-outer { position: relative; } .qm-scroll-wrapper { width: 100%; overflow-x: auto; overflow-y: hidden; border-radius: 12px; border: 1px solid var(--background-modifier-border); box-shadow: 0 8px 24px rgba(0,0,0,0.08); scrollbar-width: thin; }
.qm-hybrid-container { position: relative; width: 100%; min-width: 900px; height: 650px; background-size: cover; background-position: center; overflow: hidden; background-color: #0d1b3e; }
.qm-svg-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; }
.qm-island-group { position: absolute; z-index: 2; transform: translate(-50%, -50%); display: flex; flex-direction: column; align-items: center; cursor: pointer; transition: filter 0.3s ease; animation: floatIsland 4s ease-in-out infinite; width: 152px; background: transparent !important; border: none !important; isolation: isolate; }
@keyframes floatIsland { 0%, 100% { transform: translate(-50%, -50%); } 50% { transform: translate(-50%, calc(-50% - 12px)); } }
.qm-island-group:hover, .qm-active { filter: brightness(1.1) drop-shadow(0 20px 20px rgba(0,0,0,0.3)); z-index: 10; }
.qm-platform { width: 120px; height: auto; object-fit: contain; filter: drop-shadow(0 12px 16px rgba(0, 30, 80, 0.5)); transition: all 0.3s; }
.qm-island-img { width: 120px; display: block; mix-blend-mode: lighten; filter: drop-shadow(0 10px 18px rgba(0,80,200,0.6)); }
.qm-active .qm-platform { width: 138px; filter: drop-shadow(0 16px 22px rgba(0, 100, 200, 0.5)) brightness(1.15); }
.qm-diff-boss .qm-platform { width: 150px; filter: drop-shadow(0 20px 25px rgba(150, 0, 150, 0.5)) hue-rotate(240deg) brightness(0.85) contrast(1.2); }
.qm-style-forest .qm-platform { filter: drop-shadow(0 15px 15px rgba(0, 80, 30, 0.4)) hue-rotate(-110deg) saturate(1.2); }
.qm-style-galaxy .qm-platform { filter: drop-shadow(0 15px 15px rgba(60, 0, 80, 0.4)) hue-rotate(80deg) saturate(1.5); }
.qm-visited .qm-platform { filter: brightness(0.65) sepia(0.3); }
.qm-label-wrap { margin-top: 8px; background: linear-gradient(135deg,rgba(15,20,60,0.92),rgba(20,30,80,0.88)); padding: 7px 18px; border-radius: 22px; font-size: 13px; font-weight: 700; color: #bae6fd; box-shadow: 0 4px 16px rgba(0,0,0,0.5), 0 0 12px rgba(80,120,255,0.2); position: relative; z-index: 10; max-width: 180px; text-align: center; line-height: 1.3; border: 1px solid rgba(100,140,255,0.3); letter-spacing: 0.02em; }
.qm-active .qm-label-wrap { border: 1.5px solid rgba(100,200,255,0.6); box-shadow: 0 4px 20px rgba(59,130,246,0.5), 0 0 20px rgba(80,160,255,0.3); color: #ffffff; }
.qm-emoji { position: absolute; top: -55px; left: 50%; transform: translateX(-50%); font-size: 32px; width: 60px; height: 60px; background: linear-gradient(135deg, rgba(255,255,255,0.4), rgba(255,255,255,0.1)); border: 2px solid rgba(255,255,255,0.6); backdrop-filter: blur(4px); border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 8px 16px rgba(0,0,0,0.2); z-index: 5; transition: transform 0.2s; }
.qm-island-group:hover .qm-emoji { transform: translateX(-50%) scale(1.15); }
.qm-icon-prop { position: absolute; top: -55px; left: 50%; transform: translateX(-50%); width: 65px; height: 65px; object-fit: contain; filter: drop-shadow(0 8px 10px rgba(0,0,0,0.3)); z-index: 5; animation: floatProp 3s ease-in-out infinite; transition: transform 0.2s ease; }
.qm-island-group:hover .qm-icon-prop { transform: translateX(-50%) scale(1.15); }
.qm-icon-boss { top: -85px; width: 100px; height: 100px; filter: drop-shadow(0 15px 25px rgba(255, 200, 0, 0.4)); }
@keyframes floatProp { 0%, 100% { transform: translateX(-50%) translateY(0); } 50% { transform: translateX(-50%) translateY(-8px); } }
.qm-current-badge { position: absolute; top: -80px; left: 50%; transform: translateX(-50%); background: #0f172a; color: white; font-size: 10px; font-weight: 700; padding: 4px 10px; border-radius: 12px; border: 1px solid #4ade80; display: flex; align-items: center; gap: 4px; z-index: 6; box-shadow: 0 4px 10px rgba(74, 222, 128, 0.3); }
.qm-current-badge::before { content: ""; display: inline-block; width: 6px; height: 6px; background: #4ade80; border-radius: 50%; box-shadow: 0 0 5px #4ade80; animation: qmpulseDot 1.5s infinite; }
@keyframes qmpulseDot { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
@keyframes dashFlow { to { stroke-dashoffset: -26; } }
.qm-crown { position: absolute; top: -52px; left: 50%; transform: translateX(-50%); font-size: 26px; z-index: 6; filter: drop-shadow(0 2px 6px rgba(0,0,0,0.5)); animation: crownFloat 3s ease-in-out infinite; }@keyframes crownFloat { 0%,100%{transform:translateX(-50%) translateY(0)} 50%{transform:translateX(-50%) translateY(-5px)} }
@keyframes qm-bounce { 0%{transform:translate(-50%,-50%) scale(1)} 50%{transform:translate(-50%,calc(-50% - 15px)) scale(1.05)} 100%{transform:translate(-50%,-50%) scale(1)} }
.qm-bounce { animation: qm-bounce 0.4s ease-out; }\\n@keyframes qm-unlock { 0%{transform:translate(-50%,-50%) scale(0.8);opacity:0.3} 60%{transform:translate(-50%,calc(-50% - 20px)) scale(1.15)} 100%{transform:translate(-50%,-50%) scale(1);opacity:1} }\\n.qm-unlock { animation: qm-unlock 0.55s cubic-bezier(0.34,1.56,0.64,1); }\\n.qm-completed .qm-platform { filter: drop-shadow(0 0 18px rgba(255,200,50,0.9)) brightness(1.1) saturate(1.2) !important; }\\n.qm-completed .qm-label-wrap { border: 2px solid #fbbf24 !important; box-shadow: 0 0 16px rgba(251,191,36,0.5) !important; color: #fff !important; }.qm-completed { outline: 3px solid #fbbf24; outline-offset: 4px; border-radius: 50%; }
@keyframes qm-modal-in { from{opacity:0;transform:scale(0.94) translateY(12px)} to{opacity:1;transform:scale(1) translateY(0)} }
@keyframes qm-celebrate-in  { from{opacity:0;transform:scale(0.8)} to{opacity:1;transform:scale(1)} }
@keyframes qm-celebrate-out { 0%{opacity:1} 100%{opacity:0} }
@keyframes qm-shake { 0%,100%{transform:translateX(0)} 20%{transform:translateX(-7px)} 40%{transform:translateX(7px)} 60%{transform:translateX(-4px)} 80%{transform:translateX(4px)} }
.qm-shake { animation: qm-shake 0.4s ease-in-out; }
.qm-ch-btn { border-radius:16px;padding:16px 20px;font-size:15px;font-weight:600;cursor:pointer;border:2px solid var(--background-modifier-border);background:var(--background-primary);color:var(--text-normal);transition:all 0.15s;text-align:left;width:100%;white-space:normal;word-break:break-word;min-width:0;display:flex;align-items:center;gap:12px;box-shadow:0 2px 0 var(--background-modifier-border); }
.qm-ch-btn:hover:not(:disabled) { border-color:var(--interactive-accent);background:var(--background-secondary);transform:translateY(-1px);box-shadow:0 4px 0 var(--interactive-accent); }
.qm-ch-btn:active:not(:disabled) { transform:translateY(1px);box-shadow:0 1px 0 var(--background-modifier-border); }
.qm-ch-btn.qm-selected { border-color:var(--interactive-accent);background:rgba(56,189,248,0.08);color:var(--interactive-accent);font-weight:700;box-shadow:0 2px 0 var(--interactive-accent); }
.qm-ch-btn-label { width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:800;flex-shrink:0;background:rgba(0,0,0,0.06);color:var(--text-muted); }
.qm-ch-btn-text { flex:1;line-height:1.4; }
.qm-question-text { font-size:18px;font-weight:700;color:var(--text-normal);margin-bottom:20px;line-height:1.5;padding:0 2px; }
.qm-options-col { display:flex;flex-direction:column;gap:10px; }
.qm-rules-banner { padding:14px 18px;border-radius:14px;margin-bottom:18px;border:2px solid transparent; }
.qm-rules-banner-title { display:flex;align-items:center;gap:8px;margin-bottom:5px;font-weight:800;font-size:14px;color:var(--text-normal); }
.qm-rules-banner-body { font-size:13px;color:var(--text-muted);line-height:1.6; }
.qm-continue-btn { width:100%;padding:16px;border-radius:16px;background:#1a1a2e;color:#ffffff;border:none;cursor:pointer;font-size:16px;font-weight:800;margin-top:16px;transition:all 0.15s;box-shadow:0 4px 0 rgba(0,0,0,0.3);letter-spacing:0.02em; }
.qm-continue-btn:hover { background:#2d2d4e;transform:translateY(-1px);box-shadow:0 6px 0 rgba(0,0,0,0.3); }
.qm-continue-btn:active { transform:translateY(2px);box-shadow:0 2px 0 rgba(0,0,0,0.3); }
.qm-ch-input { flex:1;padding:10px 14px;border-radius:8px;border:1.5px solid var(--background-modifier-border);background:var(--background-primary);color:var(--text-normal);font-size:13px;outline:none; }
.qm-ch-input:focus { border-color:var(--interactive-accent); }
.qm-ch-chip { border-radius:20px;padding:7px 16px;font-size:13px;cursor:pointer;border:1.5px solid var(--background-modifier-border);background:var(--background-primary);color:var(--text-normal);transition:all 0.15s; }
.qm-ch-chip:hover:not(:disabled) { border-color:var(--interactive-accent); }
.qm-ch-chip:disabled { opacity:0.35;cursor:not-allowed; }
/* Quest Map visual upgrade \u2014 SVG badges, locked state, bottom panel */
.qm-badge { position:absolute;z-index:5; }
.qm-badge-check { top:-2px;right:15px;width:28px;height:28px; }
.qm-badge-lock { top:5px;left:50%;transform:translateX(-50%);width:32px;height:40px; }
.qm-island-locked .qm-platform,.qm-island-locked .qm-island-img { filter:grayscale(70%) brightness(0.55) saturate(0.3) !important; }
.qm-island-locked { pointer-events:none;animation:none !important; }
@keyframes qmShimmer { to { left:100%; } }
.qm-bottom-panel { position:absolute;bottom:20px;left:50%;transform:translateX(-50%);width:min(480px,calc(100% - 40px));border-radius:16px;z-index:10;padding:14px 20px 16px; }
.qm-panel-dark { background:rgba(8,10,28,0.92);border:1px solid rgba(200,180,80,0.3);box-shadow:0 0 30px rgba(0,0,0,0.7),inset 0 1px 0 rgba(255,220,100,0.1); }
.qm-panel-light { background:rgba(255,255,255,0.95);border:1px solid rgba(0,0,0,0.06);box-shadow:0 4px 24px rgba(0,0,0,0.12); }
.qm-bp-header { display:flex;justify-content:space-between;align-items:center;margin-bottom:6px; }
.qm-bp-title { font-size:15px;font-weight:700; }
.qm-panel-dark .qm-bp-title { color:#e0f2fe; }
.qm-panel-light .qm-bp-title { color:#1a202c; }
.qm-bp-stars { display:flex;gap:2px;align-items:center; }
.qm-bp-desc { font-size:12px;margin-bottom:10px;line-height:1.5;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical; }
.qm-panel-dark .qm-bp-desc { color:rgba(200,208,255,0.55); }
.qm-panel-light .qm-bp-desc { color:#718096; }
.qm-bp-prog-wrap { display:flex;align-items:center;gap:10px; }
.qm-bp-prog-bar { flex:1;height:12px;border-radius:6px;overflow:hidden; }
.qm-panel-dark .qm-bp-prog-bar { background:rgba(255,255,255,0.07);border:1px solid rgba(255,210,80,0.15); }
.qm-panel-light .qm-bp-prog-bar { background:#e2e8f0; }
.qm-bp-prog-fill { height:100%;border-radius:6px;position:relative;overflow:hidden; }
.qm-pfill-dark { background:linear-gradient(90deg,#c8930e,#f5c842);box-shadow:0 0 8px rgba(245,200,66,0.3); }
.qm-pfill-light { background:linear-gradient(90deg,#38bdf8,#7dd3fc); }
.qm-bp-prog-fill::after { content:'';position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,0.25),transparent);animation:qmShimmer 2.5s ease-in-out infinite; }
.qm-bp-prog-label { font-size:12px;font-weight:600;min-width:40px;text-align:right; }
.qm-panel-dark .qm-bp-prog-label { color:#fbbf24; }
.qm-panel-light .qm-bp-prog-label { color:#38bdf8; }
/* Review Deck */
.rd-wrap { font-family:system-ui,sans-serif; }
.rd-stats { display:flex;flex-wrap:wrap;gap:12px;padding:10px 14px;background:var(--background-secondary);border-radius:10px;margin-bottom:12px;font-size:13px;color:var(--text-muted); }
.rd-achievement { font-weight:700;display:flex;align-items:center;gap:4px; }
.rd-title { font-size:16px;font-weight:700;color:var(--text-normal);margin-bottom:12px; }
.rd-grid { display:grid;gap:10px;margin-top:4px; }
.rd-card { aspect-ratio:2/3;border-radius:12px;cursor:pointer;position:relative;transition:transform .18s,box-shadow .18s;overflow:hidden; }
.rd-card:hover { transform:translateY(-4px) scale(1.03);box-shadow:0 8px 24px rgba(0,0,0,0.18); }
.rd-card-face { position:absolute;inset:0;border-radius:inherit;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px; }
.rd-card-emoji { font-size:28px;line-height:1; }
.rd-card-state { position:absolute;top:6px;right:6px;font-size:14px; }
@keyframes rd-glow { 0%,100%{box-shadow:0 0 8px rgba(245,158,11,0.4)} 50%{box-shadow:0 0 20px rgba(245,158,11,0.8)} }
.rd-due-glow { animation:rd-glow 1.8s ease-in-out infinite; }
.rd-modal-hint { border-radius:10px;padding:14px 16px;margin-bottom:8px; }
.rd-rating-row { display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-top:4px; }
.rd-rating-btn { border-radius:10px;padding:12px 6px;font-size:13px;font-weight:700;cursor:pointer;border:none;color:white;display:flex;flex-direction:column;align-items:center;gap:4px;transition:filter .15s; }
.rd-rating-btn:hover { filter:brightness(1.12); }
.rd-rating-btn small { font-size:11px;font-weight:400;opacity:0.88; }
.rd-action-btn { border-radius:8px;padding:10px 20px;font-size:14px;font-weight:600;cursor:pointer;transition:all .15s;width:100%; }
/* Isolate plugin controls from theme CSS so Obsidian or snippet styles do not override component behavior. */
/* :where() keeps specificity at zero, which lets component-specific classes still win when needed. */
:where(.lh-hub) button,:where(.lh-hub) input,:where(.lh-hub) select,:where(.lh-hub) textarea { all:unset;box-sizing:border-box;cursor:pointer; }
:where(.lh-hub) input,:where(.lh-hub) textarea { cursor:text; }
/* Override Obsidian global user-select:none on modal so text in the hub is selectable. */
.lh-hub { user-select:text; }
.qm-modal { user-select:text; }
/* Shared animations and primitives reused across hub, decks, and maps. */
@keyframes lh-fadeslide { from{opacity:0;transform:translateY(-6px)} to{opacity:1;transform:translateY(0)} }
@keyframes lh-glow-due { 0%,100%{box-shadow:0 0 0 rgba(56,189,248,0)} 50%{box-shadow:0 0 18px rgba(56,189,248,0.45)} }
/* Hub navigation bar */
.lh-nav { display:flex;align-items:center;gap:8px;padding:10px 16px;flex-shrink:0;border-bottom:2px solid #e5e7eb;background:#ffffff !important;color:#1f2937 !important;position:relative; }
.lh-logo { font-size:12px;font-weight:800;letter-spacing:.08em;color:#9ca3af !important;text-transform:uppercase;margin-right:8px;white-space:nowrap; }
.lh-tabs { display:flex;gap:4px;flex:1; }
.lh-tab { display:flex;align-items:center;gap:6px;padding:8px 16px;border-radius:99px;border:1.5px solid #e5e7eb !important;background:#ffffff !important;color:#6b7280 !important;font-size:13px;font-weight:600;cursor:pointer;transition:all .18s;white-space:nowrap;position:relative; }
.lh-tab:hover:not(.active) { border-color:#7dd3fc !important;color:#38bdf8 !important;transform:translateY(-1px); }
.lh-tab:active { transform:scale(0.95) !important; }
.lh-tab.active { background:#ffffff !important;border-color:#38bdf8 !important;color:#38bdf8 !important;font-weight:700; }
.lh-tab.active::after { content:'';position:absolute;bottom:-12px;left:20%;width:60%;height:3px;background:linear-gradient(90deg,#38bdf8,#7dd3fc);border-radius:99px; }
.lh-subtabs { display:flex;gap:8px;padding:10px 20px 14px; }
.lh-subtab { flex:1;max-width:220px;text-align:center;padding:7px 14px;border-radius:99px;border:1px solid #e5e7eb !important;background:transparent !important;color:#6b7280 !important;font-size:12.5px;font-weight:600;cursor:pointer;transition:all .15s;white-space:nowrap; }
.lh-subtab:hover:not(.active) { border-color:#7dd3fc !important;color:#38bdf8 !important; }
.lh-subtab.active { background:#38bdf8 !important;border-color:#38bdf8 !important;color:#ffffff !important; }
.lh-dark .lh-subtab { border-color:var(--background-modifier-border) !important;color:var(--text-muted) !important; }
.lh-dark .lh-subtab:hover:not(.active) { border-color:#7dd3fc !important;color:#7dd3fc !important; }
.lh-dark .lh-subtab.active { background:#38bdf8 !important;border-color:#38bdf8 !important;color:#ffffff !important; }
.lh-close { margin-left:auto;width:32px;height:32px;border-radius:50%;border:1.5px solid #e5e7eb !important;background:#ffffff !important;color:#9ca3af !important;font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .15s;flex-shrink:0; }
.lh-close:hover { background:#fee2e2 !important;border-color:#fca5a5 !important;color:#ef4444 !important; }
.lh-close:active { transform:scale(0.9); }
/* Stats header */
.lh-stats-header { position:relative;height:220px;flex-shrink:0;background-size:cover;background-position:center 20%;overflow:hidden; }
.lh-stat-grp { position:absolute;display:flex;flex-direction:column;align-items:center;gap:2px;transform:translate(-50%,-50%); }
.lh-stat-label { font-size:11px;font-weight:700;color:#fff !important;text-shadow:0 1px 6px rgba(0,0,0,0.9),0 0 12px rgba(0,0,0,0.6);letter-spacing:.06em;text-transform:uppercase; }
.lh-stat-num { font-weight:900;line-height:1;text-shadow:0 2px 20px rgba(0,0,0,0.7),0 0 40px rgba(0,0,0,0.4),-1px -1px 0 rgba(0,0,0,0.2),1px 1px 0 rgba(0,0,0,0.2); }
.lh-stat-num.ready { font-size:58px;color:#86efac !important; }
.lh-stat-num.due   { font-size:52px;color:#86efac !important; }
.lh-stat-num.new   { font-size:52px;color:#93c5fd !important; }
.lh-stat-num.total { font-size:52px;color:#fde68a !important; }
.lh-stats-helper { position:absolute;left:50%;bottom:18px;transform:translateX(-50%);padding:6px 12px;border-radius:999px;background:rgba(255,255,255,0.16) !important;color:#ffffff !important;font-size:11px;font-weight:600;backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);box-shadow:0 6px 16px rgba(0,0,0,0.14);white-space:nowrap; }
/* White content card */
.lh-card { background:#f0f9ff !important;border-radius:24px 24px 0 0;margin-top:-100px;flex:1;overflow-y:auto;padding:0;color:#1f2937 !important;box-shadow:0 -10px 30px rgba(0,0,0,0.18);position:relative;z-index:2; }
.lh-card-header { display:flex;align-items:center;justify-content:space-between;padding:20px 24px 12px; }
.lh-card-title { font-size:14px;font-weight:700;color:#374151 !important; }
.lh-study-all { display:flex;align-items:center;gap:5px;padding:8px 18px;border-radius:99px;border:1.5px solid #7dd3fc !important;background:#f0f9ff !important;color:#0284c7 !important;font-size:13px;font-weight:700;cursor:pointer;transition:all .15s; }
.lh-study-all:hover { background:#e0f2fe !important;border-color:#38bdf8 !important;transform:translateY(-1px); }
.lh-study-all:active { transform:scale(0.96); }
/* Toolbar */
.lh-toolbar { display:flex;align-items:center;gap:8px;padding:0 16px 12px; }
.lh-search { flex:1;display:flex;align-items:center;gap:6px;background:#ffffff !important;border:1.5px solid #e5e7eb !important;border-radius:10px;padding:8px 12px;transition:border-color .15s; }
.lh-search:focus-within { border-color:#7dd3fc !important; }
.lh-search-icon { color:#9ca3af !important;font-size:13px;flex-shrink:0; }
.lh-search input { border:none !important;background:transparent !important;outline:none;font-size:13px;color:#374151 !important;width:100%;display:block; }
.lh-search input::placeholder { color:#9ca3af !important; }
.lh-filter { padding:8px 12px;border-radius:10px;border:1.5px solid #e5e7eb !important;background:#ffffff !important;color:#374151 !important;font-size:12px;font-weight:600;cursor:pointer;outline:none; }
.lh-view-toggle { display:flex;gap:2px;background:#f3f4f6 !important;border-radius:8px;padding:2px; }
.lh-view-btn { width:34px;height:34px;border-radius:7px;background:transparent !important;color:#9ca3af !important;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:15px;transition:all .15s; }
.lh-view-btn.active { background:#ffffff !important;color:#38bdf8 !important;box-shadow:0 1px 4px rgba(0,0,0,0.12); }
.lh-view-btn:active { transform:scale(0.88); }
/* Deck list */
.lh-deck-list { display:flex;flex-direction:column; }
.lh-deck-row { display:flex;align-items:center;gap:14px;padding:14px 24px;border-top:1px solid #e9ecf0 !important;cursor:pointer;transition:background .12s;background:transparent !important; }
.lh-deck-row:hover { background:#eff6ff !important; }
.lh-deck-row:active { background:#dbeafe !important; }
.lh-deck-row.is-emptyready { opacity:.72; }
.lh-deck-row.is-emptyready:hover { background:#f8fafc !important; }
.lh-deck-row.is-emptyready:active { background:#e0f2fe !important; }
.lh-deck-chevron { color:#9ca3af !important;font-size:12px;margin-right:14px;transition:all .15s; }
.lh-deck-row:hover .lh-deck-chevron { color:#38bdf8 !important;transform:translateX(3px); }
.lh-deck-main { flex:1;min-width:0;display:flex;flex-direction:column;gap:3px; }
.lh-deck-row-name { font-size:14px;font-weight:700;color:#1f2937 !important; }
.lh-deck-row-sub { font-size:11px;color:#6b7280 !important; }
.lh-deck-metrics { display:flex;align-items:stretch;gap:8px;flex-shrink:0; }
.lh-deck-ready { min-width:88px;border-radius:14px;padding:8px 10px;background:linear-gradient(135deg,#ecfccb,#dcfce7) !important;border:1.5px solid #86efac !important;display:flex;flex-direction:column;align-items:center;justify-content:center; }
.lh-deck-ready.is-zero { background:linear-gradient(135deg,#f8fafc,#e0f2fe) !important;border-color:#cbd5e1 !important; }
.lh-deck-ready-label { font-size:10px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:#15803d !important; }
.lh-deck-ready.is-zero .lh-deck-ready-label { color:#64748b !important; }
.lh-deck-ready-num { font-size:28px;font-weight:900;line-height:1;color:#166534 !important; }
.lh-deck-ready.is-zero .lh-deck-ready-num { color:#475569 !important; }
.lh-deck-breakdown { display:grid;grid-template-columns:repeat(3,minmax(54px,1fr));gap:6px; }
.lh-deck-metric { border-radius:12px;padding:7px 8px;background:#ffffff !important;border:1.5px solid #e5e7eb !important;display:flex;flex-direction:column;align-items:center;justify-content:center;min-width:56px; }
.lh-deck-metric-label { font-size:10px;font-weight:700;color:#94a3b8 !important;text-transform:uppercase;letter-spacing:.04em; }
.lh-deck-metric-num { font-size:16px;font-weight:800;color:#334155 !important;line-height:1.1; }
.lh-deck-metric.due .lh-deck-metric-num { color:#16a34a !important; }
.lh-deck-metric.new .lh-deck-metric-num { color:#2563eb !important; }
.lh-deck-metric.total .lh-deck-metric-num { color:#d97706 !important; }
/* Deck grid */
.lh-deck-grid { display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:12px;padding:12px 20px; }
.lh-deck-gc { background:#fff !important;border-radius:16px;padding:16px 14px;cursor:pointer;border:1.5px solid #e5e7eb !important;transition:all .2s;display:flex;flex-direction:column;gap:8px;box-shadow:0 2px 8px rgba(0,0,0,0.06);color:#1f2937 !important; }
.lh-deck-gc:hover { transform:translateY(-3px);border-color:#7dd3fc !important;box-shadow:0 8px 20px rgba(56,189,248,0.15); }
.lh-deck-gc:active { transform:translateY(-1px); }
.lh-deck-gc.has-due { animation:lh-glow-due 2s ease-in-out infinite; }
.lh-deck-gc.is-emptyready { opacity:.78; }
.lh-deck-gc.is-emptyready:hover { transform:none;border-color:#e5e7eb !important;box-shadow:0 2px 8px rgba(0,0,0,0.06); }
.lh-deck-gc.is-emptyready:active { transform:none; }
.lh-deck-gc-emoji { font-size:28px; }
.lh-deck-gc-name { font-size:13px;font-weight:700;color:#1f2937 !important; }
.lh-deck-gc-bar-bg { background:#e5e7eb !important;border-radius:99px;height:4px;overflow:hidden; }
.lh-deck-gc-bar { height:100%;border-radius:99px;background:linear-gradient(90deg,#7dd3fc,#38bdf8) !important;transition:width .4s; }
.lh-deck-gc-sub { font-size:10px;color:#9ca3af !important; }
.lh-deck-gc-status { font-size:11px;font-weight:700;color:#475569 !important; }
.lh-deck-gc-ready { display:flex;align-items:baseline;gap:6px; }
.lh-deck-gc-ready-num { font-size:32px;font-weight:900;line-height:1;color:#166534 !important; }
.lh-deck-gc.is-emptyready .lh-deck-gc-ready-num { color:#475569 !important; }
.lh-deck-gc-ready-label { font-size:11px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:#15803d !important; }
.lh-deck-gc.is-emptyready .lh-deck-gc-ready-label { color:#64748b !important; }
.lh-deck-gc-metrics { display:grid;grid-template-columns:repeat(3,1fr);gap:6px; }
.lh-deck-gc-metric { border-radius:10px;padding:6px 4px;background:#f8fafc !important;border:1px solid #e5e7eb !important;display:flex;flex-direction:column;align-items:center;gap:2px; }
.lh-deck-gc-metric-label { font-size:9px;font-weight:700;color:#94a3b8 !important;text-transform:uppercase; }
.lh-deck-gc-metric-num { font-size:13px;font-weight:800;color:#334155 !important; }
.lh-deck-gc-metric.due .lh-deck-gc-metric-num { color:#16a34a !important; }
.lh-deck-gc-metric.new .lh-deck-gc-metric-num { color:#2563eb !important; }
.lh-deck-gc-metric.total .lh-deck-gc-metric-num { color:#d97706 !important; }
/* Empty state and shared tab cards */
.lh-empty { padding:48px 24px;text-align:center;color:#9ca3af !important;font-size:14px;line-height:1.9; }
.lh-card-count { font-size:12px;font-weight:600;color:#9ca3af !important;background:#f3f4f6 !important;border-radius:99px;padding:3px 10px; }
.lh-tab-list { display:flex;flex-direction:column;padding:0 0 16px; }
.lh-tab-list-item { display:flex;align-items:center;gap:12px;padding:13px 20px;border-bottom:1px solid #f1f5f9 !important;cursor:pointer;transition:background .12s; }
.lh-tab-list-item:hover { background:#eff6ff !important; }
.lh-tab-list-item:active { background:#dbeafe !important; }
.lh-tab-list-item.is-cleared { opacity:0.55; }
.lh-tab-list-item.is-cleared:hover { opacity:0.75; }
.lh-mm-card.is-cleared { opacity:0.55; }
.lh-mm-card.is-cleared:hover { opacity:0.75; }
.lh-tab-list-emoji { font-size:20px; }
.lh-tab-list-title { font-size:14px;font-weight:600;color:#1f2937 !important; }
.lh-tab-list-sub { font-size:11px;color:#9ca3af !important;margin-top:1px; }
.lh-tab-list-badge { border-radius:6px;padding:3px 10px;font-size:11px;font-weight:700;color:#fff !important;background:#38bdf8 !important; }
/* Memory Map grid */
.lh-mm-grid { display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:12px; }
.lh-mm-card { background:#fff !important;border-radius:16px;padding:16px 14px;cursor:pointer;border:1.5px solid #e5e7eb !important;transition:all .2s;display:flex;flex-direction:column;gap:6px;box-shadow:0 2px 8px rgba(0,0,0,0.05); }
.lh-mm-card:hover { transform:translateY(-3px);border-color:#7dd3fc !important;box-shadow:0 8px 20px rgba(56,189,248,0.15); }
.lh-mm-card:active { transform:translateY(-1px); }
.lh-mm-emoji { font-size:28px; }
.lh-mm-name { font-size:13px;font-weight:700;color:#1f2937 !important;line-height:1.3; }
.lh-mm-path { font-size:10px;color:#9ca3af !important; }
.lh-mm-open { font-size:11px;font-weight:600;color:#38bdf8 !important;margin-top:4px; }
/* Review modal */
.lh-review-nav { display:flex;align-items:center;gap:8px;padding:11px 16px;background:rgba(255,255,255,0.82) !important;backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border-bottom:1px solid rgba(255,255,255,0.5);flex-shrink:0; }
.lh-review-logo { font-size:11px;font-weight:800;color:#0ea5e9 !important;letter-spacing:.05em;text-transform:uppercase;white-space:nowrap;margin-right:6px; }
.lh-review-tabs { display:flex;gap:5px; }
.lh-review-tab { display:flex;align-items:center;gap:5px;padding:6px 14px;border-radius:99px;border:1.5px solid #e5e7eb !important;background:#ffffff !important;color:#6b7280 !important;font-size:12px;font-weight:600;cursor:pointer;transition:all .18s;white-space:nowrap; }
.lh-review-tab:hover:not(.active) { border-color:#7dd3fc !important;color:#38bdf8 !important; }
.lh-review-tab.active { background:#0ea5e9 !important;border-color:#0ea5e9 !important;color:#ffffff !important;font-weight:700;box-shadow:0 3px 10px rgba(14,165,233,0.35); }
.lh-review-tab:active { transform:scale(0.95); }
.lh-review-body { padding:14px 14px 8px;flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden; }
.lh-review-card { background:rgba(255,255,255,0.97) !important;border-radius:22px;padding:22px 24px;box-shadow:0 8px 40px rgba(0,0,0,0.14);flex:1;min-height:0;overflow-y:auto;color:#1f2937 !important; }
.lh-rc-top { display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:14px;gap:12px; }
.lh-rc-badge { background:linear-gradient(135deg,#e0f2fe,#bae6fd) !important;color:#38bdf8 !important;border-radius:99px;padding:5px 16px;font-size:12px;font-weight:700;flex-shrink:0; }
.lh-rc-edit-btn { margin-left:auto;padding:4px 10px;border-radius:99px;border:1.5px solid #e2e8f0 !important;background:#f0f9ff !important;color:#38bdf8 !important;font-size:11px;font-weight:700;cursor:pointer;white-space:nowrap;transition:all .15s;flex-shrink:0; }
.lh-rc-edit-btn:hover { background:#e0f2fe !important;border-color:#7dd3fc !important; }
.lh-rc-emoji { font-size:38px;line-height:1;flex-shrink:0; }
.lh-rc-token { display:inline-block;align-self:flex-start;margin-bottom:8px;padding:2px 9px;border-radius:999px;background:rgba(99,102,241,0.12);color:#4f46e5 !important;font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:lowercase;flex:0 0 auto; }
body.theme-dark .lh-rc-token { background:rgba(129,140,248,0.18);color:#a5b4fc !important; }
.lh-rc-question { font-size:22px;font-weight:800;color:#1e293b !important;line-height:1.5;flex:1; }
.lh-rc-question p:first-child,.lh-answer-text p:first-child,.lh-hint-text p:first-child { margin-top:0; }
.lh-rc-question p:last-child,.lh-answer-text p:last-child,.lh-hint-text p:last-child { margin-bottom:0; }
.lh-rc-question pre,.lh-answer-text pre,.lh-hint-text pre { margin:8px 0;border-radius:6px;font-size:13px; }
.lh-hint { border-radius:12px;padding:13px 16px;margin-top:10px;animation:lh-fadeslide .28s ease; }
.lh-hint-label { font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.08em;margin-bottom:5px; }
.lh-hint-text { font-size:14px;line-height:1.65; }
.lh-hint-l1 { background:#eff6ff !important;border-left:3px solid #3b82f6; }
.lh-hint-l1 .lh-hint-label { color:#3b82f6 !important; }
.lh-hint-l1 .lh-hint-text { color:#1e40af !important; }
.lh-hint-l2 { background:#f0fdf4 !important;border-left:3px solid #22c55e; }
.lh-hint-l2 .lh-hint-label { color:#22c55e !important; }
.lh-hint-l2 .lh-hint-text { color:#15803d !important; }
.lh-hint-l3 { background:#fffbeb !important;border-left:3px solid #f59e0b; }
.lh-hint-l3 .lh-hint-label { color:#f59e0b !important; }
.lh-hint-l3 .lh-hint-text { color:#92400e !important; }
.lh-answer-block { --lh-ans-px:18px;--lh-ans-py:14px;background:#f0f9ff !important;border-left:3px solid #0ea5e9;border-radius:12px;padding:var(--lh-ans-py) var(--lh-ans-px);margin-top:10px;animation:lh-fadeslide .28s ease; }
.lh-answer-label { font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.08em;color:#0ea5e9 !important;margin-bottom:6px; }
.lh-answer-text { font-size:15px;color:#0c4a6e !important;line-height:1.7; }
.lh-answer-text mark { background:#fde68a;color:#92400e;border-radius:3px;padding:0 2px; }
.lh-answer-text blockquote,.lh-hint-text blockquote { border-left:3px solid #38bdf8;margin:6px 0;padding:4px 12px;background:rgba(56,189,248,0.06);border-radius:0 4px 4px 0;color:inherit; }
/* Three pill buttons before the answer is shown */
.lh-review-footer { display:flex;flex-direction:column;gap:8px;padding:10px 14px 0; }
.lh-pill-row { display:grid;grid-template-columns:repeat(3,1fr);gap:10px; }
.lh-pill-btn { display:flex;align-items:center;justify-content:center;gap:7px;padding:0 12px;border-radius:99px;font-size:15px;font-weight:700;cursor:pointer;transition:all .18s;white-space:nowrap;min-height:54px; }
.lh-pill-btn:hover { transform:translateY(-2px);filter:brightness(1.07); }
.lh-pill-btn:active { transform:scale(0.95) translateY(0) !important;filter:brightness(0.93) !important; }
.lh-pill-show { background:#f1f5f9 !important;color:#475569 !important;border:2px solid #e2e8f0 !important; }
.lh-pill-show:hover { background:#e8eef5 !important; }
.lh-pill-recall { background:linear-gradient(135deg,#0ea5e9,#7dd3fc) !important;color:#ffffff !important;box-shadow:0 4px 16px rgba(14,165,233,0.45); }
.lh-pill-memory { background:linear-gradient(135deg,#f59e0b,#fbbf24) !important;color:#ffffff !important;box-shadow:0 4px 16px rgba(245,158,11,0.4); }
.lh-pill-memory:disabled { opacity:.38;cursor:not-allowed;transform:none !important;filter:none !important;box-shadow:none !important; }
/* Edit form */
.lh-edit-form { flex:1;overflow-y:auto;padding:16px 14px;display:flex;flex-direction:column;gap:12px; }
.lh-edit-field { display:flex;flex-direction:column;gap:5px; }
.lh-edit-label { font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#6b7280 !important; }
.lh-edit-textarea { width:100%;border-radius:10px;border:1.5px solid #e5e7eb !important;background:#ffffff !important;color:#1f2937 !important;font-size:14px;line-height:1.6;padding:10px 12px;resize:vertical;min-height:60px;font-family:inherit;transition:border-color .15s; }
.lh-edit-textarea:focus { border-color:#38bdf8 !important;outline:none; }
.lh-edit-hints-section { display:flex;flex-direction:column;gap:6px; }
.lh-edit-hints-label { font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#6b7280 !important; }
.lh-edit-hint-ta { min-height:44px; }
.lh-edit-btn-row { display:flex;gap:8px;margin-top:4px;justify-content:flex-end; }
.lh-edit-save-btn { padding:8px 20px;border-radius:99px;font-size:13px;font-weight:700;cursor:pointer;border:none !important;background:linear-gradient(135deg,#0ea5e9,#7dd3fc) !important;color:#fff !important;box-shadow:0 2px 8px rgba(14,165,233,0.35);transition:all .18s; }
.lh-edit-save-btn:hover { transform:translateY(-1px);box-shadow:0 4px 12px rgba(14,165,233,0.45); }
.lh-edit-save-btn:disabled { opacity:.5;cursor:not-allowed;transform:none !important; }
.lh-edit-cancel-btn { padding:8px 16px;border-radius:99px;font-size:13px;font-weight:600;cursor:pointer;border:1.5px solid #e2e8f0 !important;background:#f0f9ff !important;color:#475569 !important;transition:all .18s; }
.lh-edit-cancel-btn:hover { background:#f1f5f9 !important;border-color:#cbd5e1 !important; }
.lh-footer-meta { display:flex;align-items:center;justify-content:center;gap:14px; }
.lh-pill-reset { display:flex;align-items:center;justify-content:center;width:36px;height:36px;background:rgba(255,255,255,0.7) !important;color:#64748b !important;border:1.5px solid rgba(203,213,225,0.9) !important;border-radius:50%;font-size:16px;cursor:pointer;transition:all .18s; }
.lh-pill-reset:hover { background:rgba(255,255,255,0.95) !important;transform:rotate(45deg); }
.lh-dots { display:flex;gap:7px;align-items:center; }
.lh-dot { width:9px;height:9px;border-radius:50%;background:rgba(255,255,255,0.45) !important;border:1.5px solid rgba(200,215,235,0.8);transition:all .25s; }
.lh-dot.lit { background:#0ea5e9 !important;border-color:#0ea5e9 !important;box-shadow:0 0 8px rgba(14,165,233,0.6); }
/* Progress bar area */
.lh-review-progress { padding:8px 14px 14px;display:flex;flex-direction:column;align-items:center;gap:6px;flex-shrink:0; }
.lh-review-prog-wrap { width:100%;background:rgba(255,255,255,0.35) !important;height:8px;border-radius:99px;overflow:hidden; }
.lh-review-prog-bar { height:100%;border-radius:99px;background:linear-gradient(90deg,#0ea5e9,#38bdf8) !important;transition:width .4s; }
.lh-review-badge { background:rgba(255,255,255,0.92) !important;border-radius:99px;padding:5px 16px;font-size:12px;font-weight:700;color:#0ea5e9 !important;box-shadow:0 2px 8px rgba(0,0,0,0.1); }
.lh-browse-note { margin-top:10px;padding:10px 12px;border-radius:14px;background:#eff6ff !important;border:1px solid #bfdbfe !important;color:#1d4ed8 !important;font-size:12px;font-weight:600; }
.lh-browse-row { display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;padding:10px 14px 14px; }
.lh-browse-btn { border-radius:12px;padding:12px 10px;background:#ffffff !important;border:1.5px solid #dbeafe !important;color:#1e3a8a !important;font-size:13px;font-weight:700;text-align:center; }
.lh-browse-btn:hover:not(:disabled) { background:#eff6ff !important;border-color:#93c5fd !important; }
.lh-browse-btn:disabled { opacity:.45;cursor:not-allowed; }
/* Review back button */
.lh-review-back { margin-left:auto;display:flex;align-items:center;gap:6px;padding:8px 18px;border-radius:99px;border:2px solid #38bdf8 !important;background:#ffffff !important;color:#38bdf8 !important;font-size:13px;font-weight:700;cursor:pointer;transition:all .18s;flex-shrink:0; }
.lh-review-back:hover { background:#e0f2fe !important;border-color:#0ea5e9 !important;color:#0ea5e9 !important;transform:translateX(-2px);box-shadow:0 2px 10px rgba(56,189,248,0.25); }
.lh-review-back:active { transform:scale(0.95); }
/* Rating buttons after the answer is revealed */
.lh-rating-row { display:grid;grid-template-columns:repeat(4,1fr);gap:10px;padding:10px 14px 14px; }
.lh-rating-btn { display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;border-radius:20px;padding:14px 8px 12px;cursor:pointer;color:#ffffff !important;transition:all .2s;min-height:72px;position:relative;overflow:hidden; }
.lh-rating-btn::before { content:'';position:absolute;inset:0;background:linear-gradient(180deg,rgba(255,255,255,0.22) 0%,rgba(255,255,255,0) 60%);pointer-events:none; }
.lh-rating-btn:hover { transform:translateY(-3px);box-shadow:0 8px 24px rgba(0,0,0,0.28) !important; }
.lh-rating-btn:active { filter:brightness(0.88) !important;transform:scale(0.95) translateY(0) !important; }
.lh-rb-top { display:flex;align-items:center;gap:5px;position:relative; }
.lh-rb-icon { font-size:14px;line-height:1; }
.lh-rb-label { font-size:15px;font-weight:800; }
.lh-rb-days { font-size:11px;font-weight:600;opacity:.88;position:relative; }
.lh-rb-again { background:linear-gradient(145deg,#f87171,#dc2626) !important;box-shadow:0 4px 16px rgba(220,38,38,0.4); }
.lh-rb-hard  { background:linear-gradient(145deg,#fbbf24,#d97706) !important;box-shadow:0 4px 16px rgba(217,119,6,0.4); }
.lh-rb-good  { background:linear-gradient(145deg,#60a5fa,#2563eb) !important;box-shadow:0 4px 16px rgba(37,99,235,0.4); }
.lh-rb-easy  { background:linear-gradient(145deg,#4ade80,#16a34a) !important;box-shadow:0 4px 16px rgba(22,163,74,0.4); }

/* =====================================================
   MOBILE PHONE OVERRIDES  (body.is-phone only)
   ===================================================== */
body.is-phone .modal-container { overflow:visible !important; }
body.is-phone .lh-nav { padding:8px 10px; gap:4px; overflow-x:auto; overflow-y:hidden; -webkit-overflow-scrolling:touch; scrollbar-width:none; }
body.is-phone .lh-nav::-webkit-scrollbar { display:none; }
body.is-phone .lh-logo { display:none; }
body.is-phone .lh-tabs { gap:3px; flex:1; min-width:0; }
body.is-phone .lh-tab { padding:6px 9px; font-size:12px; gap:3px; }
body.is-phone .lh-tab.active::after { bottom:-10px; }
body.is-phone .lh-help-btn { display:none !important; }
body.is-phone .lh-close { width:30px; height:30px; font-size:15px; flex-shrink:0; }
body.is-phone .lh-stats-header { height:150px; }
body.is-phone .lh-stat-num.ready { font-size:40px; }
body.is-phone .lh-stat-num.due,body.is-phone .lh-stat-num.new,body.is-phone .lh-stat-num.total { font-size:36px; }
body.is-phone .lh-stats-helper { display:none !important; }
body.is-phone .lh-stat-grp { background:rgba(0,0,0,0.28);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);border-radius:10px;padding:4px 10px; z-index:10; }
body.is-phone .lh-card { margin-top:-100px; }
body.is-phone .lh-card-header { padding:12px 14px 8px; }
body.is-phone .lh-study-all { padding:7px 14px; font-size:12px; }
body.is-phone .lh-toolbar { padding:0 10px 10px; gap:6px; flex-wrap:wrap; }
body.is-phone .lh-search { padding:7px 10px; min-width:0; flex:1 1 100%; }
body.is-phone .lh-filter { font-size:11px; padding:8px 10px; flex:1 1 calc(50% - 8px); min-width:0; max-width:calc(50% - 3px); }
body.is-phone .lh-view-toggle { flex-shrink:0; }
body.is-phone .lh-deck-row { padding:12px 14px; gap:6px; flex-wrap:wrap; }
body.is-phone .lh-deck-main { flex:1 1 100%; min-width:0; }
body.is-phone .lh-deck-metrics { flex-shrink:1; width:100%; justify-content:flex-start; gap:6px; }
body.is-phone .lh-deck-ready { min-width:68px; padding:6px 8px; }
body.is-phone .lh-deck-ready-num { font-size:22px; }
body.is-phone .lh-deck-breakdown { grid-template-columns:repeat(3,minmax(0,1fr)); gap:4px; }
body.is-phone .lh-deck-metric { min-width:0; padding:5px 6px; }
body.is-phone .lh-deck-metric-num { font-size:14px; }
body.is-phone .lh-deck-chevron { margin-right:4px; }
body.is-phone .lh-deck-grid { grid-template-columns:repeat(2,1fr); padding:8px 8px; gap:8px; }
body.is-phone .lh-deck-gc { padding:10px 10px; gap:5px; border-radius:12px; }
body.is-phone .lh-deck-gc-emoji { font-size:20px; }
body.is-phone .lh-deck-gc-name { font-size:12px; }
body.is-phone .lh-deck-gc-bar-bg { height:3px; }
body.is-phone .lh-deck-gc-ready { gap:4px; }
body.is-phone .lh-deck-gc-ready-num { font-size:22px; }
body.is-phone .lh-deck-gc-ready-label { font-size:9px; }
body.is-phone .lh-deck-gc-metrics { gap:4px; }
body.is-phone .lh-deck-gc-metric { padding:3px 2px; border-radius:6px; }
body.is-phone .lh-deck-gc-metric-label { font-size:8px; }
body.is-phone .lh-deck-gc-metric-num { font-size:11px; }
body.is-phone .lh-mm-grid { grid-template-columns:1fr; }
body.is-phone .lh-review-nav { padding:10px 12px; gap:6px; overflow:hidden; }
body.is-phone .lh-review-back { order:-1; margin-left:0 !important; margin-right:6px; padding:7px 14px; font-size:12px; flex-shrink:0; }
body.is-phone .lh-review-logo { display:none; }
body.is-phone .lh-review-tabs { gap:3px; flex:1; min-width:0; }
body.is-phone .lh-review-tab { padding:6px 9px; font-size:12px; }
body.is-phone .lh-review-back { padding:7px 12px; font-size:12px; flex-shrink:0; }
body.is-phone .lh-review-body { padding:10px 10px 6px; }
body.is-phone .lh-review-card { padding:18px 16px; border-radius:18px; }
body.is-phone .lh-rc-question { font-size:18px; line-height:1.55; }
body.is-phone .lh-rc-emoji { font-size:30px; }
body.is-phone .lh-hint { padding:11px 14px; }
body.is-phone .lh-hint-text { font-size:13px; }
body.is-phone .lh-answer-block { --lh-ans-px:14px;--lh-ans-py:12px; }
body.is-phone .lh-answer-text { font-size:14px; }
body.is-phone .lh-review-footer { padding:10px 12px 0; gap:10px; }
body.is-phone .lh-pill-row { gap:6px; }
body.is-phone .lh-pill-btn { min-height:48px; font-size:13px; padding:0 8px; }
body.is-phone .lh-rc-edit-btn { font-size:10px; padding:3px 8px; }
body.is-phone .lh-footer-meta { gap:10px; margin-top:2px; }
body.is-phone .lh-pill-reset { width:40px; height:40px; font-size:15px; }
body.is-phone .lh-rating-row { grid-template-columns:repeat(4,1fr); gap:7px; padding:8px 10px 12px; }
body.is-phone .lh-rating-btn { min-height:64px; border-radius:16px; padding:12px 4px 10px; gap:4px; }
body.is-phone .lh-rb-label { font-size:13px; }
body.is-phone .lh-rb-days { font-size:10px; }
body.is-phone .lh-review-progress { padding:6px 12px 12px; gap:5px; }
body.is-phone .lh-review-badge { font-size:11px; padding:4px 14px; }
body.is-phone .lh-browse-row { gap:6px; padding:8px 10px 12px; }
body.is-phone .lh-browse-btn { padding:12px 8px; font-size:12px; border-radius:10px; }
body.is-phone .lh-complete-screen { padding:24px 20px; }
body.is-phone .lh-complete-title { font-size:22px; }
body.is-phone .lh-complete-btn { min-height:52px; font-size:14px; }
.lh-ach-card { position:relative;border-radius:14px;cursor:default;display:flex;flex-direction:column;overflow:hidden;transition:transform 0.2s,box-shadow 0.2s; }
.lh-ach-card.unlocked:hover { transform:translateY(-4px) scale(1.03); }
.lh-delete-btn { background:transparent; border:none; cursor:pointer; color:#9ca3af; padding:4px 6px; border-radius:6px; display:flex; align-items:center; justify-content:center; opacity:0; transition:all 0.15s; flex-shrink:0; }
.lh-tab-list-item:hover .lh-delete-btn, .lh-deck-row:hover .lh-delete-btn, .lh-mm-card:hover .lh-delete-btn, .lh-deck-gc:hover .lh-delete-btn { opacity:1; }
.lh-delete-btn:hover { color:#ef4444; background:#fee2e2; }@media (pointer: coarse) { .lh-delete-btn { opacity:1 !important; } .lh-deck-row { padding:12px 16px; } }
body.is-phone .lh-ach-stats-row { padding:0 12px 12px; gap:6px; }
body.is-phone .lh-ach-stat-val { font-size:16px !important; }
body.is-phone .lh-ach-familiarity { padding:0 12px 14px; gap:10px; flex-direction:column; align-items:center; }
body.is-phone .lh-ach-activity { padding:10px 12px 6px; }
body.is-phone .lh-ach-chart-box { padding:8px 8px 4px; }
body.is-phone .lh-ach-grid { grid-template-columns:repeat(auto-fill,minmax(90px,1fr)); gap:8px; padding:0 12px 20px; }
/* Achievement card rarity animations */
@keyframes lh-leg-shimmer { 0%{background-position:0% 50%} 50%{background-position:100% 50%} 100%{background-position:0% 50%} }
@keyframes lh-r-pulse { 0%,100%{box-shadow:0 0 12px rgba(96,165,250,0.5),0 6px 16px rgba(0,0,0,0.5)} 50%{box-shadow:0 0 28px rgba(96,165,250,0.9),0 6px 20px rgba(0,0,0,0.6)} }
@keyframes lh-leg-pulse { 0%,100%{box-shadow:0 0 16px rgba(245,158,11,0.6),0 6px 16px rgba(0,0,0,0.5)} 50%{box-shadow:0 0 36px rgba(245,158,11,1.0),0 8px 24px rgba(0,0,0,0.6)} }
.lh-ach-card { position:relative;border-radius:14px;cursor:default;display:flex;flex-direction:column;overflow:hidden;transition:transform 0.25s,box-shadow 0.25s; }
.lh-ach-card.unlocked:hover { transform:translateY(-4px) scale(1.03); }
.lh-ach-r { animation:lh-r-pulse 2.4s ease-in-out infinite; }
.lh-ach-leg { animation:lh-leg-pulse 2s ease-in-out infinite; }

/* =====================================================
   DARK MODE OVERRIDES  (body.theme-dark)
   ===================================================== */
.lh-dark .lh-review-logo { color:#7dd3fc !important; }
.lh-dark .lh-pill-recall { background:linear-gradient(135deg,#38bdf8,#7dd3fc) !important; box-shadow:0 4px 16px rgba(56,189,248,0.4); }
.lh-dark .lh-review-prog-bar { background:linear-gradient(90deg,#38bdf8,#7dd3fc) !important; }
.lh-dark .lh-nav { background:var(--background-primary) !important; color:var(--text-normal) !important; border-bottom-color:var(--background-modifier-border); }
.lh-dark .lh-logo { color:var(--text-faint) !important; }
.lh-dark .lh-tab { border-color:var(--background-modifier-border) !important; background:var(--background-secondary) !important; color:var(--text-muted) !important; }
.lh-dark .lh-tab:hover:not(.active) { border-color:#7dd3fc !important; color:#7dd3fc !important; }
.lh-dark .lh-tab.active { background:var(--background-secondary) !important; border-color:#7dd3fc !important; color:#7dd3fc !important; }
.lh-dark .lh-close { border-color:var(--background-modifier-border) !important; background:var(--background-secondary) !important; color:var(--text-muted) !important; }
.lh-dark .lh-close:hover { background:rgba(239,68,68,0.15) !important; border-color:rgba(239,68,68,0.4) !important; color:#f87171 !important; }
.lh-dark .lh-card { background:var(--background-primary) !important; color:var(--text-normal) !important; }
.lh-dark .lh-card-title { color:var(--text-normal) !important; }
.lh-dark .lh-study-all { background:rgba(56,189,248,0.12) !important; border-color:rgba(56,189,248,0.35) !important; color:#7dd3fc !important; }
.lh-dark .lh-study-all:hover { background:rgba(56,189,248,0.22) !important; }
.lh-dark .lh-search { background:var(--background-secondary) !important; border-color:var(--background-modifier-border) !important; }
.lh-dark .lh-search input { color:var(--text-normal) !important; }
.lh-dark .lh-filter { background:var(--background-secondary) !important; border-color:var(--background-modifier-border) !important; color:var(--text-normal) !important; }
.lh-dark .lh-view-toggle { background:var(--background-secondary) !important; }
.lh-dark .lh-view-btn.active { background:var(--background-primary) !important; }
.lh-dark .lh-deck-row { border-top-color:var(--background-modifier-border) !important; }
.lh-dark .lh-deck-row:hover { background:rgba(56,189,248,0.08) !important; }
.lh-dark .lh-deck-row:active { background:rgba(56,189,248,0.15) !important; }
.lh-dark .lh-deck-row.is-emptyready:hover { background:rgba(255,255,255,0.03) !important; }
.lh-dark .lh-deck-row-name { color:var(--text-normal) !important; }
.lh-dark .lh-deck-row-sub { color:var(--text-muted) !important; }
.lh-dark .lh-deck-metric { background:var(--background-secondary) !important; border-color:var(--background-modifier-border) !important; }
.lh-dark .lh-deck-metric-num { color:#e2e8f0 !important; }
.lh-dark .lh-deck-gc { background:var(--background-secondary) !important; border-color:var(--background-modifier-border) !important; color:var(--text-normal) !important; box-shadow:0 2px 8px rgba(0,0,0,0.3); }
.lh-dark .lh-deck-gc:hover { border-color:#7dd3fc !important; }
.lh-dark .lh-deck-gc-name { color:var(--text-normal) !important; }
.lh-dark .lh-deck-gc-bar-bg { background:var(--background-modifier-border) !important; }
.lh-dark .lh-deck-gc-metric { background:var(--background-primary) !important; border-color:var(--background-modifier-border) !important; }
.lh-dark .lh-deck-gc-metric-num { color:#e2e8f0 !important; }
.lh-dark .lh-deck-gc-status { color:var(--text-muted) !important; }
.lh-dark .lh-card-count { background:var(--background-secondary) !important; color:var(--text-muted) !important; }
.lh-dark .lh-tab-list-item { border-bottom-color:var(--background-modifier-border) !important; }
.lh-dark .lh-tab-list-item:hover { background:rgba(56,189,248,0.08) !important; }
.lh-dark .lh-tab-list-item:active { background:rgba(56,189,248,0.15) !important; }
.lh-dark .lh-tab-list-title { color:var(--text-normal) !important; }
.lh-dark .lh-mm-card { background:var(--background-secondary) !important; border-color:var(--background-modifier-border) !important; box-shadow:0 2px 8px rgba(0,0,0,0.25); }
.lh-dark .lh-mm-card:hover { border-color:#7dd3fc !important; }
.lh-dark .lh-mm-name { color:var(--text-normal) !important; }
.lh-dark .lh-review-nav { background:rgba(15,15,25,0.88) !important; border-bottom-color:rgba(255,255,255,0.07); }
.lh-dark .lh-review-tab { border-color:var(--background-modifier-border) !important; background:var(--background-secondary) !important; color:var(--text-muted) !important; }
.lh-dark .lh-review-tab:hover:not(.active) { border-color:#7dd3fc !important; color:#7dd3fc !important; }
.lh-dark .lh-review-card { background:rgba(25,25,40,0.97) !important; color:var(--text-normal) !important; box-shadow:0 8px 40px rgba(0,0,0,0.5); }
.lh-dark .lh-rc-question { color:#e2e8f0 !important; }
.lh-dark .lh-rc-badge { background:rgba(56,189,248,0.2) !important; color:#bae6fd !important; }
.lh-dark .lh-rc-edit-btn { background:rgba(25,25,40,0.8) !important; border-color:rgba(125,211,252,0.5) !important; color:#bae6fd !important; }
.lh-dark .lh-hint-l1 { background:rgba(59,130,246,0.12) !important; }
.lh-dark .lh-hint-l1 .lh-hint-text { color:#93c5fd !important; }
.lh-dark .lh-hint-l2 { background:rgba(34,197,94,0.12) !important; }
.lh-dark .lh-hint-l2 .lh-hint-text { color:#86efac !important; }
.lh-dark .lh-hint-l3 { background:rgba(245,158,11,0.12) !important; }
.lh-dark .lh-hint-l3 .lh-hint-text { color:#fcd34d !important; }
.lh-dark .lh-answer-block { background:rgba(251,191,36,0.12) !important; border-left-color:#fbbf24 !important; }
.lh-dark .lh-answer-label { color:#fbbf24 !important; }
.lh-dark .lh-answer-text { color:#f8fafc !important; line-height:1.75; }
.lh-dark .lh-answer-text mark { background:rgba(253,230,138,0.2);color:#fcd34d; }
.lh-dark .lh-answer-text blockquote,.lh-dark .lh-hint-text blockquote { border-left:3px solid #7dd3fc;background:rgba(125,211,252,0.10); }
.lh-dark .lh-pill-show { background:#2a2a3e !important; color:#cbd5e1 !important; border-color:#4a4a6a !important; }
.lh-dark .lh-pill-show:hover { background:#343450 !important; }
.lh-dark .lh-edit-textarea { background:var(--background-secondary) !important; border-color:var(--background-modifier-border) !important; color:var(--text-normal) !important; }
.lh-dark .lh-edit-label { color:var(--text-muted) !important; }
.lh-dark .lh-edit-hints-label { color:var(--text-muted) !important; }
.lh-dark .lh-edit-cancel-btn { background:var(--background-secondary) !important; border-color:var(--background-modifier-border) !important; color:var(--text-muted) !important; }
.lh-dark .lh-browse-note { background:rgba(59,130,246,0.1) !important; border-color:rgba(59,130,246,0.25) !important; color:#93c5fd !important; }
.lh-dark .lh-browse-btn { background:var(--background-secondary) !important; border-color:rgba(56,189,248,0.25) !important; color:#7dd3fc !important; }
.lh-dark .lh-browse-btn:hover:not(:disabled) { background:rgba(56,189,248,0.15) !important; border-color:#7dd3fc !important; }
.lh-dark .lh-review-back { background:rgba(20,20,35,0.8) !important; border-color:rgba(125,211,252,0.5) !important; color:#bae6fd !important; }
.lh-dark .lh-review-back:hover { background:rgba(56,189,248,0.15) !important; }
.lh-dark .lh-review-prog-wrap { background:rgba(255,255,255,0.1) !important; }
.lh-dark .lh-review-badge { background:rgba(20,20,35,0.92) !important; color:#bae6fd !important; }
.lh-dark .lh-pill-reset { background:rgba(255,255,255,0.05) !important; border-color:rgba(255,255,255,0.1) !important; color:#94a3b8 !important; }
.lh-dark .lh-pill-reset:hover { background:rgba(255,255,255,0.1) !important; }
.lh-dark .lh-delete-btn:hover { background:rgba(239,68,68,0.15); }
.lh-dark .lh-ach-chart-box { background:var(--background-secondary) !important; border-color:var(--background-modifier-border) !important; }
.lh-dark .lh-ach-activity { border-color:var(--background-modifier-border) !important; }
.lh-dark .lh-empty { color:var(--text-muted) !important; }
.lh-dark .lh-deck-gc-sub { color:var(--text-faint) !important; }
.lh-dark .lh-deck-gc-ready-num { color:#7dd3fc !important; }
.lh-dark .lh-deck-gc.is-emptyready .lh-deck-gc-ready-num { color:var(--text-muted) !important; }
.lh-dark .lh-deck-gc-ready-label { color:#7dd3fc !important; }
.lh-dark .lh-deck-gc.is-emptyready .lh-deck-gc-ready-label { color:var(--text-muted) !important; }
.lh-dark .lh-deck-ready { background:linear-gradient(135deg,rgba(56,189,248,0.18),rgba(56,189,248,0.14)) !important; border-color:rgba(125,211,252,0.45) !important; }
.lh-dark .lh-deck-ready.is-zero { background:rgba(255,255,255,0.04) !important; border-color:rgba(255,255,255,0.1) !important; }
.lh-dark .lh-deck-ready-label { color:#7dd3fc !important; }
.lh-dark .lh-deck-ready.is-zero .lh-deck-ready-label { color:var(--text-muted) !important; }
.lh-dark .lh-deck-ready-num { color:#7dd3fc !important; }
.lh-dark .lh-deck-ready.is-zero .lh-deck-ready-num { color:var(--text-muted) !important; }
.lh-dark .lh-stat-num.ready { color:#7dd3fc !important; }
.lh-dark .lh-stat-num.due { color:#7dd3fc !important; }
.lh-dark .lh-deck-metric.due .lh-deck-metric-num { color:#4ade80 !important; }
.lh-dark .lh-deck-metric.new .lh-deck-metric-num { color:#93c5fd !important; }
.lh-dark .lh-deck-metric.total .lh-deck-metric-num { color:#fbbf24 !important; }
.lh-dark .lh-deck-gc-metric.due .lh-deck-gc-metric-num { color:#4ade80 !important; }
.lh-dark .lh-deck-gc-metric.new .lh-deck-gc-metric-num { color:#93c5fd !important; }
.lh-dark .lh-deck-gc-metric.total .lh-deck-gc-metric-num { color:#fbbf24 !important; }
/* Tree View */
.lh-tree-container { padding: 12px 16px; display: flex; flex-direction: column; gap: 4px; }
.lh-tree-details { margin-bottom: 4px; }
.lh-tree-summary { list-style: none; cursor: pointer; user-select: none; padding: 8px 12px; border-radius: 10px; transition: background 0.15s; outline: none; }
.lh-tree-summary::-webkit-details-marker { display: none; }
.lh-tree-summary:hover { background: rgba(99, 102, 241, 0.06); }
.lh-dark .lh-tree-summary:hover { background: rgba(99, 102, 241, 0.12); }
.lh-tree-summary-inner { display: flex; align-items: center; gap: 8px; }
.lh-tree-folder-icon { font-size: 16px; transition: transform 0.2s; }
.lh-tree-details[open] > .lh-tree-summary .lh-tree-folder-icon { transform: rotate(90deg); }
.lh-tree-folder-name { font-size: 14px; font-weight: 700; color: #1f2937 !important; }
.lh-dark .lh-tree-folder-name { color: var(--text-normal) !important; }
.lh-tree-content { margin-left: 12px; padding-left: 12px; border-left: 2px solid rgba(156, 163, 175, 0.2); margin-top: 4px; display: flex; flex-direction: column; gap: 4px; }
.lh-tree-items-container { display: flex; flex-direction: column; gap: 4px; margin-top: 4px; }
.lh-tree-item-row { border-top: none !important; border-bottom: none !important; background: #fff !important; border-radius: 10px; border: 1.5px solid #e5e7eb !important; margin-bottom: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.03); }
.lh-dark .lh-tree-item-row { background: var(--background-secondary) !important; border-color: var(--background-modifier-border) !important; }
.lh-tree-item-row:hover { border-color: #7dd3fc !important; box-shadow: 0 2px 8px rgba(56,189,248,0.12); }
/* Quest Map dark mode */
.lh-dark .qm-ch-btn { border-color:var(--background-modifier-border);background:var(--background-secondary);color:var(--text-normal);box-shadow:0 2px 0 rgba(0,0,0,0.4); }
.lh-dark .qm-ch-btn:hover:not(:disabled) { background:var(--background-primary);box-shadow:0 4px 0 var(--interactive-accent); }
.lh-dark .qm-ch-btn-label { background:rgba(255,255,255,0.08);color:var(--text-muted); }
.lh-dark .qm-continue-btn { background:#e5e7eb;color:#1a1a2e;box-shadow:0 4px 0 rgba(0,0,0,0.5); }
.lh-dark .qm-continue-btn:hover { background:#f3f4f6; }
/* Image zoom lightbox */
.eq-lightbox { position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,0.92);display:flex;align-items:center;justify-content:center;cursor:zoom-out;animation:eq-lb-in 0.18s ease; }
@keyframes eq-lb-in { from{opacity:0} to{opacity:1} }
.eq-lightbox img { max-width:95vw;max-height:92vh;object-fit:contain;border-radius:6px;box-shadow:0 8px 40px rgba(0,0,0,0.6);touch-action:pinch-zoom; }
.eq-lightbox-close { position:absolute;top:16px;right:20px;color:#fff;font-size:28px;line-height:1;cursor:pointer;opacity:0.8;background:none;border:none;padding:4px 8px; }
.eq-lightbox-close:hover { opacity:1; }
/* /* Quest choice button: ensure multi-line text doesn't clip on mobile */
body.is-phone .qm-ch-btn { font-size:14px;padding:12px 14px;align-items:flex-start; }
body.is-phone .qm-ch-btn-label { margin-top:1px; }
body.is-phone .qm-question-text { font-size:16px; }
.eq-zoomable { cursor:zoom-in !important; }
/* Answer header with format buttons */
.lh-answer-header { display:flex;align-items:center;justify-content:space-between;margin:calc(var(--lh-ans-py)*-1) calc(var(--lh-ans-px)*-1) 6px;padding:12px var(--lh-ans-px) 8px;position:sticky;top:0;z-index:5;background:#f0f9ff;border-top-left-radius:9px;border-top-right-radius:9px;box-shadow:0 1px 0 rgba(56,189,248,0.18); }
.lh-answer-header .lh-answer-label { margin-bottom:0 !important; }
body.is-phone .lh-answer-header { padding-top:10px; }
.lh-dark .lh-answer-header { background:#2a2418;box-shadow:0 1px 0 rgba(251,191,36,0.22); }
.lh-answer-fmt-btns { display:flex;gap:4px;flex-shrink:0; }
.lh-fmt-btn { font-size:11px !important;padding:3px 8px !important;opacity:0.8; }
.lh-fmt-btn:hover { opacity:1; }
/* Edit form format toolbar */
.lh-edit-field-header { display:flex;align-items:center;justify-content:space-between;margin-bottom:5px; }
.lh-edit-field-header .lh-edit-label { margin-bottom:0; }
.lh-edit-fmt-bar { display:flex;gap:5px; }
/* Dark mode: format buttons */
.lh-dark .lh-answer-fmt-btns .lh-fmt-btn { background:rgba(25,25,40,0.8) !important;border-color:rgba(125,211,252,0.5) !important;color:#bae6fd !important; }

/* Synapse ??Pro feature CSS (cyan accent, distinct from Memory Map gold) */
/* 4-col on desktop keeps the answer area uncramped; mobile falls back to 2x2 via existing rule */
.lh-pill-row.lh-pill-row-4 { grid-template-columns:repeat(4,1fr); }
.lh-pill-row.lh-pill-row-4 .lh-pill-btn { min-height:44px;font-size:13px;padding:0 8px;gap:5px; }
body.is-phone .lh-pill-row.lh-pill-row-4 { grid-template-columns:repeat(2,1fr); }
body.is-phone .lh-pill-row.lh-pill-row-4 .lh-pill-btn { min-height:48px;font-size:13px; }
.lh-pill-synapse { background:linear-gradient(135deg,#06b6d4,#0891b2) !important;color:#ffffff !important;box-shadow:0 4px 16px rgba(6,182,212,0.40); }
.lh-pill-synapse:hover { filter:brightness(1.08); }
.lh-pill-synapse:disabled { background:#e5e7eb !important;color:#9ca3af !important;box-shadow:none !important; }
.lh-dark .lh-pill-synapse:disabled { background:rgba(60,60,80,0.6) !important;color:#94a3b8 !important; }
.lh-synapse-panel { border-radius:12px;padding:12px 14px;margin-top:10px;background:rgba(6,182,212,0.06);border-left:3px solid #06b6d4;animation:lh-fadeslide .28s ease; }
.lh-dark .lh-synapse-panel { background:rgba(6,182,212,0.10); }
.lh-synapse-title { font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.08em;color:#0891b2;margin-bottom:4px; }
.lh-dark .lh-synapse-title { color:#22d3ee; }
.lh-synapse-hint { font-size:12px;color:var(--text-muted,#6b7280);margin-bottom:8px; }
.lh-synapse-empty { font-size:13px;color:var(--text-muted,#9ca3af);font-style:italic; }
.lh-synapse-card { display:flex;flex-direction:column;gap:3px;padding:8px 10px;border-radius:8px;background:var(--background-primary,#ffffff);border:1px solid rgba(6,182,212,0.18);margin-bottom:5px;cursor:pointer;transition:background .15s,border-color .15s; }
.lh-synapse-card:hover { background:rgba(6,182,212,0.06);border-color:rgba(6,182,212,0.40); }
.lh-dark .lh-synapse-card { background:rgba(35,35,55,0.5);border-color:rgba(6,182,212,0.22); }
.lh-dark .lh-synapse-card:hover { background:rgba(6,182,212,0.10); }
.lh-synapse-card-head { display:flex;align-items:center;gap:8px; }
.lh-synapse-card-badge { font-size:15px;flex-shrink:0; }
.lh-synapse-card-front { flex:1;font-size:13px;font-weight:600;color:var(--text-normal,#1f2937);line-height:1.4; }
.lh-dark .lh-synapse-card-front { color:#e5e7eb; }
.lh-synapse-card-reason { font-size:11px;color:var(--text-muted,#6b7280);margin-left:23px;font-style:italic; }
.lh-synapse-card-back { font-size:12px;color:var(--text-normal,#1f2937);margin-top:5px;padding:8px 10px;background:var(--background-secondary,#f3f4f6);border-radius:6px;line-height:1.5; }
.lh-dark .lh-synapse-card-back { background:rgba(20,20,35,0.6);color:#cbd5e1; }
.lh-synapse-banner { display:flex;align-items:center;gap:10px;padding:12px 14px;border-radius:10px;background:linear-gradient(135deg,rgba(6,182,212,0.10),rgba(6,182,212,0.04));border:1px solid rgba(6,182,212,0.30);margin:0 0 14px 0;font-size:13px;color:var(--text-normal,#1f2937); }
.lh-dark .lh-synapse-banner { color:#e5e7eb; }
.lh-synapse-banner-text { flex:1;min-width:0; }
.lh-synapse-banner-actions { display:flex;gap:8px;margin-left:auto;flex-shrink:0; }
.lh-synapse-banner-btn { padding:6px 12px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;border:1px solid rgba(6,182,212,0.40);background:rgba(6,182,212,0.10);color:#0e7490;transition:background .15s; }
.lh-synapse-banner-btn:hover { background:rgba(6,182,212,0.20); }
.lh-dark .lh-synapse-banner-btn { color:#67e8f9;background:rgba(6,182,212,0.15); }
body.is-phone .lh-synapse-banner { flex-wrap:wrap;gap:8px; }
body.is-phone .lh-synapse-banner-actions { width:100%;justify-content:flex-end; }
/* Time-boxed Review buttons (Pro) */
.lh-timebox-row { display:flex;gap:6px;align-items:center; }
.lh-timebox-btn { padding:5px 10px;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer;border:1px solid rgba(56,189,248,0.30);background:linear-gradient(135deg,rgba(56,189,248,0.10),rgba(125,211,252,0.05));color:var(--text-normal,#1f2937);transition:transform .12s,box-shadow .12s; }
.lh-timebox-clock { font-size:13px;font-weight:700;font-variant-numeric:tabular-nums;letter-spacing:.03em;padding:2px 8px;border-radius:99px;background:rgba(34,211,238,.12);margin-left:6px;flex-shrink:0;transition:color 0.4s; }
.lh-timebox-btn:hover { transform:translateY(-1px);box-shadow:0 2px 8px rgba(56,189,248,0.18); }
.lh-dark .lh-timebox-btn { color:#e5e7eb;background:linear-gradient(135deg,rgba(56,189,248,0.20),rgba(125,211,252,0.10)); }


/* Quest Practice Mission Hub */
.lh-quest-shell { background:var(--background-primary,#fff) !important; }
.lh-quest-hero { display:flex;align-items:stretch;justify-content:space-between;gap:18px;padding:18px 24px 16px;background:linear-gradient(135deg,rgba(16,185,129,.12),rgba(59,130,246,.10));border-bottom:1px solid rgba(56,189,248,.16); }
.lh-quest-hero-main { min-width:0;display:flex;flex-direction:column;gap:5px; }
.lh-quest-title { color:var(--text-normal,#111827) !important; }
.lh-quest-desc { font-size:13px;line-height:1.45;color:var(--text-muted,#6b7280); }
.lh-quest-stats { display:grid;grid-template-columns:repeat(4,minmax(58px,1fr));gap:8px;align-items:stretch; }
.lh-quest-stat { min-width:62px;padding:8px 10px;border-radius:10px;background:rgba(255,255,255,.68);border:1px solid rgba(56,189,248,.16);text-align:center; }
.lh-quest-stat b { display:block;font-size:18px;line-height:1.1;color:#0ea5e9; }
.lh-quest-stat span { display:block;margin-top:3px;font-size:10px;font-weight:700;color:var(--text-muted,#6b7280);white-space:nowrap; }
.lh-quest-toolbar { border-bottom:1px solid rgba(148,163,184,.16); }
.lh-quest-content { flex:1;overflow-y:auto;padding:14px 18px 20px; }
.lh-quest-list { display:flex;flex-direction:column;gap:10px; }
.lh-quest-grid { display:grid;grid-template-columns:repeat(auto-fill,minmax(238px,1fr));gap:14px; }
.lh-quest-row,.lh-quest-card { position:relative;border:1px solid rgba(148,163,184,.18);background:var(--background-primary,#fff);box-shadow:0 2px 8px rgba(15,23,42,.04),0 1px 2px rgba(15,23,42,.03);transition:border-color .15s,transform .15s,box-shadow .15s;cursor:pointer; }
.lh-quest-row:hover,.lh-quest-card:hover { border-color:rgba(56,189,248,.40);transform:translateY(-2px);box-shadow:0 8px 24px rgba(14,165,233,.10),0 2px 6px rgba(15,23,42,.05); }
.lh-quest-row { display:grid;grid-template-columns:78px minmax(0,1fr) auto;gap:14px;align-items:stretch;padding:12px;border-radius:14px; }
.lh-quest-row-cover { border-radius:12px;display:flex;align-items:center;justify-content:center;color:#fff;font-size:28px;font-weight:900;min-height:76px;box-shadow:inset 0 0 0 1px rgba(255,255,255,.24); }
.lh-quest-row-body { min-width:0;display:flex;flex-direction:column;gap:7px; }
.lh-quest-row-head { display:flex;align-items:center;gap:8px;min-width:0; }
.lh-quest-card-name { font-size:15px;font-weight:800;color:var(--text-normal,#111827);line-height:1.25;min-width:0;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical; }
.lh-quest-status { flex-shrink:0;border-radius:999px;padding:3px 8px;font-size:10px;font-weight:800;background:rgba(56,189,248,.10);color:#0ea5e9; }
.lh-quest-scenario { color:var(--text-muted,#64748b);font-size:12px;line-height:1.45;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden; }
.lh-quest-meta { display:flex;align-items:center;gap:6px;flex-wrap:wrap; }
.lh-quest-meta span { border-radius:999px;padding:3px 8px;font-size:10px;font-weight:700;background:rgba(56,189,248,.07);color:var(--text-muted,#64748b);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border:1px solid rgba(56,189,248,.08); }
.lh-quest-progress { display:flex;flex-direction:column;gap:5px;margin-top:auto; }
.lh-quest-progress-top { display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:10px;font-weight:750;color:var(--text-muted,#64748b); }
.lh-quest-progress-bar { height:5px;border-radius:999px;background:rgba(148,163,184,.14);overflow:hidden; }
.lh-quest-progress-bar div { height:100%;border-radius:inherit;background:linear-gradient(90deg,#7dd3fc,#38bdf8); }
.lh-quest-row-side { display:flex;flex-direction:column;align-items:flex-end;justify-content:center;gap:8px;min-width:128px; }
.lh-quest-open { border-radius:10px;font-size:12px;font-weight:750;padding:9px 16px;cursor:pointer;transition:filter 0.15s,transform 0.12s,box-shadow 0.15s,background 0.15s,border-color 0.15s;letter-spacing:.02em; }
.lh-quest-open:hover { filter:brightness(1.08);transform:translateY(-1px); }
.lh-quest-open-primary { border:0;background:linear-gradient(135deg,#38bdf8,#7dd3fc);color:#fff;box-shadow:0 4px 12px rgba(56,189,248,.22); }
.lh-quest-open-cleared { border:1px solid rgba(20,184,166,.55);background:linear-gradient(135deg,rgba(34,197,94,.14),rgba(20,184,166,.10));color:#047857;box-shadow:0 2px 8px rgba(20,184,166,.10); }
.lh-quest-open-cleared:hover { border-color:rgba(20,184,166,.80);background:linear-gradient(135deg,rgba(34,197,94,.20),rgba(20,184,166,.16)); }
.lh-quest-open-boss { border:0;background:linear-gradient(135deg,#f97316,#e11d48);color:#fff;box-shadow:0 5px 16px rgba(244,63,94,.28); }
.lh-quest-card { display:flex;flex-direction:column;min-height:260px;border-radius:16px;overflow:hidden;position:relative; }
.lh-quest-card-cover { position:relative;min-height:56px;padding:0;display:flex;color:#fff;overflow:hidden; } .lh-quest-card-cover::before { content:'';position:absolute;inset:0;background:radial-gradient(circle at 85% 25%,rgba(255,255,255,.15) 0%,transparent 50%),radial-gradient(circle at 15% 75%,rgba(255,255,255,.10) 0%,transparent 40%),radial-gradient(circle at 90% 85%,rgba(255,255,255,.08) 0%,transparent 35%);pointer-events:none; } 
.lh-quest-card-cover b { display:none; }
.lh-quest-cover-icon { display:none; }
.lh-quest-card-cover .lh-delete-btn { display:none; } .lh-quest-card-del { position:absolute;top:8px;right:8px;z-index:5;opacity:0;transition:opacity .15s; } .lh-quest-card:hover .lh-quest-card-del { opacity:1; }
.lh-quest-card-body { flex:1;padding:14px 16px 16px;display:flex;flex-direction:column;gap:6px; }
.lh-quest-card-body .lh-quest-open { width:100%;margin-top:auto;text-align:center; }
 .lh-quest-stars { display:flex;gap:2px;align-items:center; } .lh-quest-card.is-boss-ready,.lh-quest-row.is-boss-ready { border-color:rgba(244,63,94,.45);box-shadow:0 8px 26px rgba(244,63,94,.12); }
.lh-quest-card.is-boss-ready .lh-quest-status,.lh-quest-row.is-boss-ready .lh-quest-status { background:rgba(244,63,94,.12);color:#e11d48; }
.lh-quest-card.is-cleared,.lh-quest-row.is-cleared { border-color:rgba(34,197,94,.38); }
.lh-quest-card.is-cleared .lh-quest-progress-bar div,.lh-quest-row.is-cleared .lh-quest-progress-bar div { background:linear-gradient(90deg,#22c55e,#14b8a6); }
.lh-quest-tree .lh-quest-row { margin-bottom:6px; }
.lh-dark .lh-quest-shell { background:var(--background-primary,#1e1e2e) !important; }
.lh-dark .lh-quest-hero { background:linear-gradient(135deg,rgba(16,185,129,.16),rgba(56,189,248,.15));border-bottom-color:rgba(255,255,255,.08); }
.lh-dark .lh-quest-stat { background:rgba(255,255,255,.05);border-color:rgba(255,255,255,.09); }
.lh-dark .lh-quest-stat b { color:#7dd3fc; }
.lh-dark .lh-quest-row,.lh-dark .lh-quest-card { background:var(--background-secondary,#252538);border-color:rgba(255,255,255,.12);box-shadow:0 3px 14px rgba(0,0,0,.18); }
.lh-dark .lh-quest-row:hover,.lh-dark .lh-quest-card:hover { border-color:rgba(125,211,252,.5);box-shadow:0 10px 28px rgba(0,0,0,.35),0 0 0 1px rgba(125,211,252,.15); }
.lh-dark .lh-quest-status { background:rgba(125,211,252,.18);color:#bae6fd; }
.lh-dark .lh-quest-meta span { background:rgba(255,255,255,.07); }
.lh-dark .lh-quest-progress-bar { background:rgba(255,255,255,.10); } .lh-dark .lh-quest-open-primary { background:linear-gradient(135deg,#38bdf8,#7dd3fc); } .lh-dark .lh-quest-open-cleared { background:linear-gradient(135deg,rgba(34,197,94,.20),rgba(20,184,166,.14));border-color:rgba(74,222,128,.42);color:#86efac;box-shadow:0 2px 10px rgba(20,184,166,.14); } .lh-dark .lh-quest-open-boss { background:linear-gradient(135deg,#fb923c,#f43f5e);box-shadow:0 5px 18px rgba(244,63,94,.30); } .lh-dark .lh-quest-stars { opacity:.85; } .lh-dark .lh-quest-card-del { color:var(--text-muted,#9ca3af); }
body.is-phone .lh-quest-hero { flex-direction:column;padding:14px 14px 12px;gap:12px; }
body.is-phone .lh-quest-stats { grid-template-columns:repeat(2,1fr); }
body.is-phone .lh-quest-content { padding:10px 12px 16px; }
body.is-phone .lh-quest-grid { grid-template-columns:1fr; }
body.is-phone .lh-quest-row { grid-template-columns:52px minmax(0,1fr);gap:10px; }
body.is-phone .lh-quest-row-cover { min-height:52px;font-size:22px; }
body.is-phone .lh-quest-row-side { grid-column:1 / -1;flex-direction:row;justify-content:space-between;align-items:center;min-width:0; }
body.is-phone .lh-quest-open { flex:1; }

/* ===================== GALAXY RESKIN (personal fork) ===================== */
.lh-dark .lh-nav { background:linear-gradient(180deg,#0b1224,#070b16) !important; border-bottom:1px solid rgba(56,189,248,.16) !important; }
.lh-dark .lh-tab.active, .lh-dark .lh-subtab.active { box-shadow:0 0 0 1px rgba(56,189,248,.45), 0 0 20px rgba(56,189,248,.18) !important; }
.lh-dark .lh-review-logo, .lh-dark .lh-logo { text-shadow:0 0 14px rgba(56,189,248,.35); }
/* 3 ch\u1EC9 s\u1ED1 ph\xE1t s\xE1ng theo m\xE0u ng\u1EEF ngh\u0129a */
.lh-dark .lh-stat-num.ready { color:#7dd3fc !important; text-shadow:0 0 26px rgba(125,211,252,.55),0 2px 12px rgba(0,0,0,.6) !important; }
.lh-dark .lh-stat-num.due   { color:#7dd3fc !important; text-shadow:0 0 26px rgba(125,211,252,.55),0 2px 12px rgba(0,0,0,.6) !important; }
.lh-dark .lh-stat-num.new   { color:#93c5fd !important; text-shadow:0 0 26px rgba(147,197,253,.5),0 2px 12px rgba(0,0,0,.6) !important; }
.lh-dark .lh-stat-num.total { color:#ffd166 !important; text-shadow:0 0 26px rgba(255,209,102,.5),0 2px 12px rgba(0,0,0,.6) !important; }
/* n\xFAt recall/CTA: gradient cyan + glow */
.lh-dark .lh-pill-recall { background:linear-gradient(135deg,#0ea5e9,#7dd3fc) !important; box-shadow:0 4px 18px rgba(56,189,248,.4) !important; }
.lh-dark .lh-pill-btn:hover { border-color:rgba(56,189,248,.5) !important; }
/* th\u1EBB deck trong danh s\xE1ch: vi\u1EC1n cyan m\u1EDD khi hover */
.lh-dark .lh-tab-list-item:hover { border-color:rgba(56,189,248,.35) !important; box-shadow:0 0 16px rgba(56,189,248,.12) !important; }
/* galaxy line-icon inline (thay emoji) */
.gqi{display:inline-flex;align-items:center;justify-content:center;vertical-align:-0.15em;width:1.15em;height:1.15em;line-height:0}
.gqi svg{width:100%;height:100%;display:block}
.gqi.lg{width:1.5em;height:1.5em}
.gqi.xl{width:2.2em;height:2.2em}
/* quest-map style: galaxy \u2014 h\xE0nh tinh + deep-space thay v\u01B0\u01A1ng mi\u1EC7n/l\xE2u \u0111\xE0i */
.qm-galaxy{background:radial-gradient(130% 90% at 50% 0%,#0d1b3e 0%,#080f22 55%,#04070f 100%)!important;position:relative;}
.qm-galaxy::before{content:"";position:absolute;inset:0;pointer-events:none;opacity:.85;background-image:radial-gradient(1px 1px at 18% 28%,rgba(255,255,255,.75),transparent),radial-gradient(1px 1px at 68% 62%,rgba(255,255,255,.5),transparent),radial-gradient(1px 1px at 42% 82%,rgba(180,220,255,.6),transparent),radial-gradient(1.6px 1.6px at 85% 22%,rgba(255,255,255,.65),transparent),radial-gradient(1px 1px at 55% 12%,rgba(200,225,255,.5),transparent);}
.qm-galaxy .qm-platform,.qm-galaxy .qm-node-number-wrap{display:none!important;}
.qm-gx{position:relative;z-index:4;display:flex;align-items:center;justify-content:center;filter:drop-shadow(0 10px 18px rgba(0,0,0,.4));}
.qm-gx svg{width:104px;height:auto;overflow:visible;display:block;}
.qm-gx-boss svg{width:142px;}
.qm-gx-boss{animation:qmSuperPulse 2.4s ease-in-out infinite;}
@keyframes qmSuperPulse{0%,100%{transform:scale(1);filter:drop-shadow(0 0 22px rgba(255,170,40,.45));}50%{transform:scale(1.06);filter:drop-shadow(0 0 42px rgba(255,190,70,.72));}}
.qm-galaxy .qm-active .qm-gx{animation:qmPlanetPulse 2s ease-in-out infinite;}
@keyframes qmPlanetPulse{0%,100%{filter:drop-shadow(0 0 10px rgba(56,189,248,.5));}50%{filter:drop-shadow(0 0 28px rgba(56,189,248,.9));}}
.qm-galaxy .qm-current-badge{background:rgba(56,189,248,.92)!important;color:#04121f!important;border-color:rgba(125,211,252,.6)!important;}
.qm-galaxy .qm-badge-crown,.qm-galaxy .qm-crown-img{display:none!important;}
.qm-galaxy .qm-completed{outline-color:rgba(74,222,128,.75)!important;}
.qm-galaxy .qm-completed .qm-label-wrap{border-color:rgba(74,222,128,.55)!important;box-shadow:0 0 14px rgba(74,222,128,.28)!important;}
`});var ge=Y((Cu,ir)=>{"use strict";var mt={en:{LANGUAGE:"Language",LANGUAGE_DESC:"Choose the plugin UI language.",LANGUAGE_ENGLISH:"English",LANGUAGE_ZH_TW:"Traditional Chinese",LANGUAGE_SYSTEM:"Follow Obsidian",SETTINGS_TITLE:"EngramQuest",SETTINGS_APPEARANCE:"Appearance",SETTINGS_THEME_NAME:"Visual theme",SETTINGS_THEME_DESC:"Choose the EngramQuest visual style. Reopen the window to fully apply it.",SETTINGS_THEME_BRIGHT:"Bright Sky Island (Default)",SETTINGS_THEME_DARK:"Dark Mode",SETTINGS_THEME_MINIMAL:"Minimal",SETTINGS_REVIEW_DECK:"Review Deck",SETTINGS_FLASHCARD_TAGS_NAME:"Flashcard tag prefixes",SETTINGS_FLASHCARD_TAGS_DESC:"Choose which tag prefixes count as Review Deck notes. Separate multiple prefixes with spaces or new lines. Example: flashcards card azure-card. Deck name = the first segment after the prefix (flashcards/azure -> deck azure).",SETTINGS_SR_SCAN_NAME:"Include legacy `::` notes",SETTINGS_SR_SCAN_DESC:"Migration mode for existing flashcard-note users. When enabled, notes without matching flashcard tags can also become decks based on their folder name. Default: off.",SETTINGS_MAX_INTERVAL_NAME:"Maximum review interval (days)",SETTINGS_MAX_INTERVAL_DESC:"Upper bound for review intervals. After this limit, intervals stop growing. Default: 36525 (~100 years).",SETTINGS_RETENTION_NAME:"Target retention rate",SETTINGS_RETENTION_DESC:"FSRS target recall probability (0.70-0.99). Higher = more frequent reviews. Default: 0.90 (90%).",AGAIN:"Again",HARD:"Hard",GOOD:"Good",EASY:"Easy",RECALL:"Recall",SHOW_ANSWER:"Show Answer",TRY_AGAIN:"Try Again",VIEW_SOURCE_IMAGE:"View Source Image",OPEN_SOURCE_NOTE:"Open Source Note",HINT_NEXT:"Next Hint",NO_HINT:"No hint available",DAYS:"d",TOTAL:"Total",DUE:"Due Today",MASTERED:"Mastered",RATE:"Progress",DECK_TIER_NOVICE:"Novice",DECK_TIER_RISING:"Rising",DECK_TIER_EXPERT:"Expert",DECK_TIER_MASTER:"Master",DECK_TIER_LEGEND:"Legend",ANSWER:"Answer",MEMORY_MAP:"Memory Map",RESET:"Reset",SYNAPSE:"Synapse",SYNAPSE_HINT:"Connect this to what you already know",SYNAPSE_EMPTY:"No anchors yet \u2014 run engram-synapse to build connections.",SYNAPSE_LOCKED:"Synapse is a Pro feature.",SYNAPSE_REASON_PREFIX:"\u21B3 ",SYNAPSE_NO_BACK:"Anchor back not loaded in this session.",SYNAPSE_ONBOARDING_TITLE:"Welcome to Pro! Run engram-synapse to enable \u26A1 anchors.",SYNAPSE_REFRESH_BANNER:"Synapse looks out of date. Re-run engram-synapse to refresh anchors.",SYNAPSE_REFRESH_BUTTON:"Refresh Synapse",SYNAPSE_POOL_TOO_SMALL:"Synapse needs at least {need} mastered cards (FSRS stability \u2265 7). You have {have}. Keep reviewing \u2014 the feature unlocks automatically.",TIMEBOX_MIN_SUFFIX:"min",TIMEBOX_NO_CARDS:"No cards available for time-boxed review.",TIMEBOX_TIMES_UP:"Time's up!",BACK:"Back",REVIEW_COMPLETE:"Review complete!",BACK_TO_HUB:"Back to Hub",CARDS_REVIEWED:"cards reviewed",RIBBON_TOOLTIP:"EngramQuest",HUB_TITLE:"ENGRAM-QUEST",TAB_REVIEW:"Review Deck",TAB_MEMORY:"Memory Map",TAB_QUEST:"Quest Map",STUDY_ALL:"Review Ready",HELP_TITLE:"EngramQuest Guide",HELP_OPEN:"Open help",HUB_CLOSE:"Close",SEARCH_REVIEW:"Search decks...",SEARCH_MEMORY:"Search memory maps...",SEARCH_QUEST:"Search quests...",SEARCH_QUADRANT:"Search quadrant cards...",FILTER_ALL:"All",FILTER_DUE:"Due",FILTER_NEW:"New",FILTER_DONE:"Done",READY:"Ready",READY_HELP:"Ready = due + new. Total = every card stored in the deck.",READY_NOW:"Ready now",NOTHING_READY:"Nothing ready",ALL_SCHEDULED:"All {total} cards are scheduled for later.",BROWSE_ALL:"Browse all",BROWSE_ONLY:"Browse only",BROWSE_NOTE:"Browse mode does not change scheduling.",PREVIOUS:"Previous",NEXT:"Next",BACK_TO_QUESTION:"Back to question",FILTER_ALL_TAGS:"All tags",FILTER_ALL_DIFFICULTIES:"All difficulties",FILTER_INCOMPLETE:"Incomplete",SORT_NEWEST:"Newest first",SORT_OLDEST:"Oldest first",SORT_NAME:"Name A-Z",QUEST_COMPLETE:"Quest Complete!",QUEST_PRACTICE_TITLE:"Practice Missions",QUEST_PRACTICE_DESC:"Apply notes through scenarios, decisions, and boss battles.",QUEST_IN_PROGRESS:"In progress",QUEST_BOSS_READY:"Boss ready",QUEST_BOSS_READY_SHORT:"Boss ready",QUEST_NOT_STARTED:"Not started",QUEST_CONTINUE_MISSION:"Continue mission",QUEST_CHALLENGE_BOSS:"Challenge Boss",QUEST_REVIEW_CLEAR:"Review clear",QUEST_PROGRESS_COUNT:"{done}/{total} stages",QUEST_NO_PROGRESS:"No stages",QUEST_BEST_SCORE:"Best {score}%",QUEST_MISSION_COUNT:"{count} missions",QUEST_BOSS_LABEL:"Boss: {boss}",QUEST_SCENARIO_FALLBACK:"Open the mission map and apply the note in a playable case.",SEARCH_TAG:"Search tags...",DIFF_EASY:"Easy",DIFF_MEDIUM:"Medium",DIFF_HARD:"Hard",EMPTY_DECKS:`No flashcard decks found.
Ask AI to generate a Review Deck, or add matching flashcard tags in Settings.`,EMPTY_FILTERED_DECKS:"No decks match the current filters.",EMPTY_MEMORIES:`No memory maps found.
Ask AI to generate a -memory.canvas file first.`,EMPTY_FILTERED_MEMORIES:"No memory maps match the current filters.",EMPTY_QUESTS:`No quest maps found.
Ask AI to generate a -quest.md file first.`,EMPTY_FILTERED_QUESTS:"No quests match the current filters.",OPEN_MAP:"Open Map",OPEN_QUEST:"Open Quest",CATEGORY:"Style",COUNT_UNIT:"items",CARD_COUNT_UNIT:"cards",MASTERY_RATE:"{percent}% mastered",NO_CARDS_TO_REVIEW:"No cards are ready to review.",NO_READY_IN_DECK:'"{deck}" has {total} cards, but none are due or new right now.',HELP_REVIEW_TAG:"Long-term memory",HELP_QUEST_TAG:"Applied practice",HELP_MEMORY_TAG:"Deep understanding",HELP_SCIENCE_TAG:"Why it works",HELP_SETTINGS_TAG:"Customize",HELP_AI_TAG:"AI shortcuts",HELP_FAQ_TAG:"FAQ",HELP_AI_SETUP_TAG:"AI setup",SETTINGS_AI_SKILLS:"AI Skills",SETTINGS_AI_SKILLS_DESC:"Install project-local skills or rules for Claude Code, Codex, Gemini CLI, and Cursor. Files stay inside this vault and never write to user-scope folders.",SETTINGS_AI_SKILLS_BUTTON:"Install skills",SKILLS_INSTALL_TITLE:"Install EngramQuest skills",SKILLS_INSTALL_DESC:"Choose an AI tool. The plugin installs project-local files for quest-map, review-deck, and memory-map.",SKILLS_INSTALL_PROJECT_LOCAL:"These files only work when you open the AI tool from this vault.",SKILLS_STATUS_NOT_INSTALLED:"Not installed",SKILLS_STATUS_INSTALLED:"Installed",SKILLS_STATUS_UPDATE:"Update available",SKILLS_PREVIEW_BUTTON:"Review install",SKILLS_PREVIEW_TITLE:"Review file changes",SKILLS_PREVIEW_DESC:"Only EngramQuest managed files will be created or replaced.",SKILLS_ACTION_CREATE:"create",SKILLS_ACTION_REPLACE:"replace",SKILLS_ACTION_UNCHANGED:"unchanged",SKILLS_CONFIRM_INSTALL:"Install now",SKILLS_INSTALL_DONE:"EngramQuest skills installed for {tool}.",SKILLS_NEXT_STEP:"Next step: open {tool} from this vault folder.",SKILLS_TOOL_CLAUDE:"Claude Code",SKILLS_TOOL_CODEX:"Codex / Antigravity",SKILLS_TOOL_GEMINI:"Gemini CLI",SKILLS_TOOL_CURSOR:"Cursor",SKILLS_TARGET_PATH:"Target path",SKILLS_MODULES:"Modules",SKILLS_MODAL_LOADING:"Checking installed status...",SKILLS_AI_SETUP_TITLE:"AI setup",SKILLS_AI_SETUP_COPY:"Use Settings -> Install skills to add project-local instructions for Claude Code, Codex, Gemini CLI, or Cursor. Run the tool from this vault so it can see the installed files.",SETTINGS_MEMORY_MAP:"Memory Map",SETTINGS_MEMORY_MAP_FOLDER_NAME:"Memory Map folder",SETTINGS_MEMORY_MAP_FOLDER_DESC:"Folder where -memory.canvas files are saved. Leave empty to save next to the source note. Example: Maps/MemoryMaps",SETTINGS_AUTO_UPDATE_SKILLS_NAME:"Auto update installed skills",SETTINGS_AUTO_UPDATE_SKILLS_DESC:"If EngramQuest already installed managed skills or rules in this vault, update them automatically when the plugin loads.",SETTINGS_PRO:"Pro features",SETTINGS_PRO_LICENSE_NAME:"I have a license key",SETTINGS_PRO_LICENSE_DESC:"Toggle to unlock Synapse and Time-boxed Review during testing. License key validation will be added later.",DELETE:"Delete",DELETE_CONFIRM_TITLE:'Delete "{name}"?',DELETE_CONFIRM_FILE:"The file will be moved to system trash.",DELETE_CONFIRM_DECK:"This will trash {count} source note(s). Flashcard data in those files will be removed.",DELETE_CONFIRM_BTN:"Delete",DELETE_CANCEL_BTN:"Cancel",EDIT_CARD:"Edit",EDIT_SAVE:"Save",EDIT_CANCEL:"Cancel",EDIT_FRONT:"Question",EDIT_BACK:"Answer",EDIT_HINTS:"Hints",FORMAT_HIGHLIGHT:"== Highlight",FORMAT_BLOCKQUOTE:"> Blockquote",FORMAT_SELECT_TEXT:"Please select text first",FORMAT_SELECT_PARA:"Select text in the answer first",FORMAT_SAVED:"Format applied",CREATE_CARD:"Add Card",CREATE_CARD_TITLE:'Add Card to "{deck}"',CREATE_CARD_FRONT:"Question",CREATE_CARD_BACK:"Answer",CREATE_CARD_FRONT_PLACEHOLDER:"Enter question...",CREATE_CARD_BACK_PLACEHOLDER:"Enter answer...",CREATE_CARD_SAVE:"Add Card",CREATE_CARD_CANCEL:"Cancel",CREATE_CARD_SUCCESS:'Card added to "{deck}"',CREATE_CARD_REQUIRED:"Question and answer are required.",CREATE_CARD_SAVE_FAILED:"Failed to add card.",TAB_ACHIEVEMENT:"Achievements",TAB_QUADRANT:"Quadrant Cards",QUADRANT_EMPTY_TITLE:"No quadrant cards yet",QUADRANT_EMPTY_BODY:'Use the Quadrant Card skill in your AI tool: say "upgrade" to convert flashcards you marked, or ask it to build a new A4 four-quadrant memory card. Cards appear here with their own review schedule.',QUADRANT_DUE_NOW:"Due now",QUADRANT_NEXT:"Next review: {date}",QUADRANT_REVIEW_BTN:"Review",QUADRANT_PREVIEW_BTN:"Preview",QUADRANT_UPGRADE_BTN:"Upgrade to A4 card",QUADRANT_MARKED:'Marked for upgrade. Run the Quadrant Card skill and say "upgrade".',QUADRANT_ALREADY:"Already queued for upgrade.",QUADRANT_DELETE_DECK_BODY:"This will move {count} quadrant card(s) and their schedules to trash.",QUADRANT_DELETE_CARD_BODY:"This quadrant card and its review schedule will be moved to trash.",QUADRANT_DELETE_DECK_DONE:"Deleted {count} quadrant card(s).",QUADRANT_COPY:"Copy",QUADRANT_COPIED:"Copied",QUADRANT_COPY_FAILED:"Copy failed",QUADRANT_SOURCE_OPENED:"Opened",QUADRANT_SOURCE_NOT_FOUND:"Source note not found: {name}",QUADRANT_EDIT:"Edit",QUADRANT_EDIT_TITLE:"Edit card",QUADRANT_EDIT_TITLE_LABEL:"Title",QUADRANT_EDIT_Q1:"Q1 \xB7 Question",QUADRANT_EDIT_Q2:"Q2 \xB7 Answer",QUADRANT_EDIT_Q3:"Q3 \xB7 Mnemonic",QUADRANT_EDIT_Q4:"Q4 \xB7 Image",QUADRANT_EDIT_HINT:`Title saves instantly. Content edits to Q1\u2013Q4 apply to the card's visuals after you re-run the Quadrant Card skill (say "upgrade").`,QUADRANT_SAVED:"Saved.",QUADRANT_REGEN_QUEUED:'Saved. Run the Quadrant Card skill and say "upgrade" to rebuild the card.',QUADRANT_CARD_NOT_FOUND:"This card no longer exists.",QUADRANT_SAVE_FAILED:"Failed to save card.",CAT_MEMORY:"Memory",CAT_LEARN:"Learn",CAT_ACHIEVE:"Achieve",TAB_LESSON:"Lessons",LESSON_ACADEMY_TITLE:"Lesson Academy",LESSON_ACADEMY_SUBTITLE:"Build your own knowledge journey, one lesson at a time.",LESSON_NO_COURSES_TITLE:"No courses yet",LESSON_NO_COURSES_BODY:"Use the Lesson Academy skill in your AI tool: tell it what you want to learn and it builds a course of interactive HTML lessons here. You can also import HTML lessons made elsewhere.",LESSON_NEW_COURSE:"New course",LESSON_NEW_COURSE_HINT:"Run the Lesson Academy skill in your AI tool and tell it what you want to learn.",LESSON_CREATE_TITLE:"Create course",LESSON_CREATE_NAME_LABEL:"Course name",LESSON_CREATE_DESC_LABEL:"Description (optional)",LESSON_CREATE_OUTLINE_LABEL:"Outline \u2014 one lesson title per line (optional)",LESSON_CREATE_OUTLINE_PH:`e.g.
HTTP basics
REST API design
Authentication`,LESSON_CREATE_SAVE:"Create",LESSON_CREATE_NAME_REQUIRED:"Course name is required.",LESSON_CREATED:"Course created. Ask the Lesson Academy skill to generate lesson content.",LESSON_CREATE_FAILED:"Failed to create course.",LESSON_STATUS_PLANNED:"Planned",LESSON_PLANNED_HINT:"No content yet \u2014 run the Lesson Academy skill and ask it to generate this course's planned lessons.",LESSON_ADD_OUTLINE:"Add outline item",LESSON_ADD_OUTLINE_PH:"Lesson title",LESSON_COURSE_DONE:"{completed}/{total} done",LESSON_CONTENT_OF:"{course} lessons",LESSON_FILTER_ALL:"All",LESSON_FILTER_TODO:"To do",LESSON_FILTER_DONE:"Done",LESSON_IMPORT:"Import HTML",LESSON_IMPORTED:"Imported: {title}",LESSON_IMPORT_FAILED:"Import failed.",LESSON_ARCHIVE:"Archive",LESSON_UNARCHIVE:"Unarchive",LESSON_ARCHIVE_HINT:"Hide this course from the active learning list without deleting it.",LESSON_UNARCHIVE_HINT:"Restore this course to the active learning list.",LESSON_ARCHIVED_NOTICE:"Archived: {course}",LESSON_UNARCHIVED_NOTICE:"Unarchived: {course}",LESSON_ARCHIVE_FAILED:"Archive update failed.",LESSON_ARCHIVED_BADGE:"Archived",LESSON_ARCHIVE_EMPTY:"No archived courses.",LESSON_STATUS_DONE:"Done",LESSON_STATUS_VIEWED:"In progress",LESSON_STATUS_NEW:"Not started",LESSON_MARK_DONE:"Mark done",LESSON_MARKED_DONE:"Done",LESSON_MARK_STAR:"Star",LESSON_DELETE_BODY:"This lesson's HTML file will be moved to trash.",LESSON_DELETE_COURSE_BODY:"This will move all {count} lesson file(s) of this course to trash.",LESSON_DELETED:"Lesson deleted",LESSON_COURSE_DELETED:"Course deleted",LESSON_NOT_FOUND:"Lesson file not found: {file}",LESSON_PROGRESS_TITLE:"Learning progress",LESSON_PROGRESS_OVERALL:"Overall",LESSON_STAT_IN_PROGRESS:"In progress",LESSON_STAT_COMPLETED:"Completed",LESSON_STAT_HOURS:"Est. hours",LESSON_RECENT_TITLE:"Recent",LESSON_RECENT_EMPTY:"Open a lesson to see it here.",LESSON_SOURCE_IMPORT:"Imported",LESSON_SEARCH_PH:"Search courses\u2026",LESSON_EXPAND_ALL:"Show all",LESSON_COLLAPSE:"Collapse",LESSON_CFILTER_TAG_ALL:"All tags",LESSON_CFILTER_STARRED:"Starred",LESSON_CFILTER_PROG_ALL:"All progress",LESSON_CFILTER_PROG_NEW:"Not started",LESSON_CFILTER_PROG_DOING:"In progress",LESSON_CFILTER_PROG_DONE:"Completed",LESSON_SCOPE_ACTIVE:"Active",LESSON_SCOPE_ARCHIVED:"Archived",LESSON_CSORT_RECENT:"Sort: recent",LESSON_CSORT_CREATED:"Sort: newest",LESSON_CSORT_PROGRESS:"Sort: progress",ACH_MASTERED:"Mastered",ACH_LEARNING:"Learning",ACH_DUE:"Due",ACH_UNSEEN:"Unseen",ACH_TOTAL_REVIEWED:"Total Reviewed",ACH_STREAK:"Day Streak",ACH_COLLECTION:"Achievement Collection",ACH_UNLOCKED:"Unlocked",ACH_FIRST_CARD_NAME:"First Step",ACH_FIRST_CARD_DESC:"Complete 1 review",ACH_TEN_CARDS_NAME:"Learner",ACH_TEN_CARDS_DESC:"Complete 10 reviews",ACH_FIFTY_CARDS_NAME:"Memory Trainee",ACH_FIFTY_CARDS_DESC:"Complete 50 reviews",ACH_CENTURY_NAME:"Century Scholar",ACH_CENTURY_DESC:"Complete 100 reviews",ACH_FIVE_HUNDRED_NAME:"Card Master",ACH_FIVE_HUNDRED_DESC:"Complete 500 reviews",ACH_ONE_THOUSAND_NAME:"Diamond Scholar",ACH_ONE_THOUSAND_DESC:"Complete 1000 reviews",ACH_TWO_THOUSAND_NAME:"Phoenix Legend",ACH_TWO_THOUSAND_DESC:"Complete 2000 reviews",ACH_STREAK_3_NAME:"On A Roll",ACH_STREAK_3_DESC:"3-day streak",ACH_STREAK_7_NAME:"Week Warrior",ACH_STREAK_7_DESC:"7-day streak",ACH_STREAK_30_NAME:"Monthly Devotee",ACH_STREAK_30_DESC:"30-day streak",ACH_DAILY_20_NAME:"Focused Start",ACH_DAILY_20_DESC:"Review 20 cards in one day",ACH_DAILY_50_NAME:"Memory Surge",ACH_DAILY_50_DESC:"Review 50 cards in one day",ACH_DAILY_80_NAME:"Sprint Breakthrough",ACH_DAILY_80_DESC:"Review 80 cards in one day",ACH_DAILY_100_NAME:"Hundred Fever",ACH_DAILY_100_DESC:"Review 100 cards in one day",ACH_DAILY_150_NAME:"State of Flow",ACH_DAILY_150_DESC:"Review 150 cards in one day",ACH_DAILY_200_NAME:"Memory Domain",ACH_DAILY_200_DESC:"Review 200 cards in one day",ACH_MASTER_50_NAME:"Memory Sage",ACH_MASTER_50_DESC:"50 mastered cards",ACH_ACTIVITY:"Review Activity",ACH_PERIOD_REVIEWS:"Period Reviews",ACH_STUDY_DAYS:"Study Days",ACH_DAILY_AVG:"Daily Avg",ACH_PER_DAY:"/day",ACH_PERIOD_7:"7D",ACH_PERIOD_30:"30D",ACH_PERIOD_90:"90D",ACH_FOUR_THOUSAND_NAME:"Scholar of All Things",ACH_FOUR_THOUSAND_DESC:"Complete 4000 reviews",ACH_TEN_THOUSAND_NAME:"Lord of Knowledge",ACH_TEN_THOUSAND_DESC:"Complete 10000 reviews",ACH_STREAK_100_NAME:"Hundred-Day Foundation",ACH_STREAK_100_DESC:"100-day streak",ACH_STREAK_365_NAME:"Chronicler of Time",ACH_STREAK_365_DESC:"365-day streak",ACH_MASTER_200_NAME:"Grandmaster Realm",ACH_MASTER_200_DESC:"200 mastered cards",ACH_SYNAPSE_AWAKEN_NAME:"Synapse Awakening",ACH_SYNAPSE_AWAKEN_DESC:"10 mastered cards",ACH_SYNAPSE_NETWORK_NAME:"Omniscient Web",ACH_SYNAPSE_NETWORK_DESC:"300 mastered cards",ACH_FIRST_MEMORY_MAP_NAME:"Cartographer",ACH_FIRST_MEMORY_MAP_DESC:"Generate 1 Memory Map",ACH_FIRST_QUEST_MAP_NAME:"Adventure Begins",ACH_FIRST_QUEST_MAP_DESC:"Generate 1 Quest Map",ACH_QUEST_CLEARED_NAME:"Triumphant Return",ACH_QUEST_CLEARED_DESC:"Clear 1 Quest Map",ACH_MULTIPLE_DECKS_NAME:"Polymath",ACH_MULTIPLE_DECKS_DESC:"Have 3 different Review Decks",ACH_PERFECT_SESSION_NAME:"Photographic Memory",ACH_PERFECT_SESSION_DESC:"20 correct cards in a row without Again",ACH_HUNDRED_AGAIN_NAME:"Rising to the Challenge",ACH_HUNDRED_AGAIN_DESC:"Tap Again 100 times total",ACH_FIRST_LESSON_NAME:"First Lesson",ACH_FIRST_LESSON_DESC:"Complete 1 lesson",ACH_COURSE_FINISHER_NAME:"Course Finisher",ACH_COURSE_FINISHER_DESC:"Complete 1 course",ACH_LESSON_MARATHON_NAME:"Lesson Marathon",ACH_LESSON_MARATHON_DESC:"Complete 10 lessons",ACH_FIRST_QUEST_STEP_NAME:"First Quest Step",ACH_FIRST_QUEST_STEP_DESC:"Complete 1 quest step",ACH_QUEST_VETERAN_NAME:"Quest Veteran",ACH_QUEST_VETERAN_DESC:"Complete 10 quest steps",ACH_FIRST_CHARTED_NAME:"First Contact",ACH_FIRST_CHARTED_DESC:"Chart 1 note in Pie Galaxy",ACH_NAVIGATOR_REACHED_NAME:"Navigator",ACH_NAVIGATOR_REACHED_DESC:"Chart 5 notes in Pie Galaxy",ACH_PIONEER_REACHED_NAME:"Pioneer",ACH_PIONEER_REACHED_DESC:"Chart 30 notes in Pie Galaxy",ACH_STARFARER_REACHED_NAME:"Starfarer",ACH_STARFARER_REACHED_DESC:"Chart 120 notes in Pie Galaxy",ACH_FIRST_COLONIZED_NAME:"Colonizer",ACH_FIRST_COLONIZED_DESC:"Colonize 1 note in Pie Galaxy",ACH_COLONY_TEN_NAME:"Colony Builder",ACH_COLONY_TEN_DESC:"Colonize 10 notes in Pie Galaxy",ACH_CONSTELLATION_NAME:"Constellation Complete",ACH_CONSTELLATION_DESC:"Master a full learning path in Pie Galaxy",ACH_DEEP_SPACE_NAME:"Deep Space",ACH_DEEP_SPACE_DESC:"Chart a note deep in the tree (\u22654 levels)",ACH_CARTOGRAPHER_NAME:"The Cartographer",ACH_CARTOGRAPHER_DESC:"Chart 10 notes within one domain",ACH_SESSION_UNLOCKED:"Achievement Unlocked!"},"zh-tw":{LANGUAGE:"\u8A9E\u8A00",LANGUAGE_DESC:"\u9078\u64C7\u63D2\u4EF6\u4ECB\u9762\u8A9E\u8A00\u3002",LANGUAGE_ENGLISH:"English",LANGUAGE_ZH_TW:"\u7E41\u9AD4\u4E2D\u6587",LANGUAGE_SYSTEM:"\u8DDF\u96A8 Obsidian",SETTINGS_TITLE:"EngramQuest",SETTINGS_APPEARANCE:"\u5916\u89C0",SETTINGS_THEME_NAME:"\u8996\u89BA\u4E3B\u984C",SETTINGS_THEME_DESC:"\u9078\u64C7 EngramQuest \u7684\u8996\u89BA\u98A8\u683C\u3002\u82E5\u8981\u5B8C\u6574\u5957\u7528\uFF0C\u8ACB\u91CD\u65B0\u958B\u555F\u8996\u7A97\u3002",SETTINGS_THEME_BRIGHT:"Bright Sky Island\uFF08\u9810\u8A2D\uFF09",SETTINGS_THEME_DARK:"\u6DF1\u8272\u6A21\u5F0F",SETTINGS_THEME_MINIMAL:"\u6975\u7C21",SETTINGS_REVIEW_DECK:"Review Deck",SETTINGS_FLASHCARD_TAGS_NAME:"Flashcard tag \u524D\u7DB4",SETTINGS_FLASHCARD_TAGS_DESC:"\u8A2D\u5B9A\u54EA\u4E9B tag \u524D\u7DB4\u6703\u88AB\u8996\u70BA Review Deck \u7B46\u8A18\u3002\u53EF\u7528\u7A7A\u767D\u6216\u63DB\u884C\u5206\u9694\u591A\u500B\u524D\u7DB4\uFF0C\u4F8B\u5982 flashcards card azure-card\u3002Deck \u540D\u7A31\u6703\u53D6\u524D\u7DB4\u5F8C\u7684\u7B2C\u4E00\u6BB5\uFF0C\u4F8B\u5982 flashcards/azure \u6703\u6B78\u5230 deck azure\u3002",SETTINGS_SR_SCAN_NAME:"\u7D0D\u5165\u820A\u7248 `::` \u7B46\u8A18",SETTINGS_SR_SCAN_DESC:"\u63D0\u4F9B\u7D66\u65E2\u6709 flashcard \u7B46\u8A18\u4F7F\u7528\u8005\u7684\u76F8\u5BB9\u6A21\u5F0F\u3002\u958B\u555F\u5F8C\uFF0C\u5373\u4F7F\u6C92\u6709\u7B26\u5408 tag \u7684\u7B46\u8A18\uFF0C\u4E5F\u80FD\u4F9D\u8CC7\u6599\u593E\u540D\u7A31\u7D0D\u5165 deck\u3002\u9810\u8A2D\u95DC\u9589\u3002",SETTINGS_MAX_INTERVAL_NAME:"\u6700\u5927\u8907\u7FD2\u9593\u9694\uFF08\u5929\uFF09",SETTINGS_MAX_INTERVAL_DESC:"\u8907\u7FD2\u9593\u9694\u7684\u4E0A\u9650\u3002\u8D85\u904E\u9019\u500B\u503C\u5F8C\uFF0C\u9593\u9694\u4E0D\u518D\u7E7C\u7E8C\u6210\u9577\u3002\u9810\u8A2D 36525\uFF08\u7D04 100 \u5E74\uFF09\u3002",SETTINGS_RETENTION_NAME:"\u76EE\u6A19\u4FDD\u7559\u7387",SETTINGS_RETENTION_DESC:"FSRS \u76EE\u6A19\u56DE\u60F3\u7387\uFF080.70-0.99\uFF09\u3002\u6578\u503C\u8D8A\u9AD8\uFF0C\u8907\u7FD2\u8D8A\u983B\u7E41\u3002\u9810\u8A2D 0.90\uFF0890%\uFF09\u3002",AGAIN:"\u91CD\u4F86",HARD:"\u56F0\u96E3",GOOD:"\u666E\u901A",EASY:"\u7C21\u55AE",RECALL:"\u56DE\u60F3",SHOW_ANSWER:"\u986F\u793A\u7B54\u6848",TRY_AGAIN:"\u518D\u8A66\u4E00\u6B21",VIEW_SOURCE_IMAGE:"\u67E5\u770B\u4F86\u6E90\u5716\u7247",OPEN_SOURCE_NOTE:"\u958B\u555F\u4F86\u6E90\u7B46\u8A18",HINT_NEXT:"\u4E0B\u4E00\u500B\u63D0\u793A",NO_HINT:"\u6C92\u6709\u53EF\u7528\u63D0\u793A",DAYS:"\u5929",TOTAL:"\u7E3D\u6578",DUE:"\u4ECA\u65E5\u5230\u671F",MASTERED:"\u5DF2\u638C\u63E1",RATE:"\u9032\u5EA6",DECK_TIER_NOVICE:"\u521D\u5B78\u8005",DECK_TIER_RISING:"\u9032\u968E\u8005",DECK_TIER_EXPERT:"\u5C08\u5BB6",DECK_TIER_MASTER:"\u5927\u5E2B",DECK_TIER_LEGEND:"\u50B3\u5947",ANSWER:"\u7B54\u6848",MEMORY_MAP:"Memory Map",RESET:"\u91CD\u8A2D",SYNAPSE:"Synapse",SYNAPSE_HINT:"\u8A66\u8457\u628A\u9019\u5F35\u5361\u548C\u4F60\u5DF2\u638C\u63E1\u7684\u77E5\u8B58\u9023\u8D77\u4F86",SYNAPSE_EMPTY:"\u5C1A\u672A\u5EFA\u7ACB\u9023\u7D50 \u2014 \u57F7\u884C engram-synapse \u81EA\u52D5\u5EFA\u7ACB\u3002",SYNAPSE_LOCKED:"Synapse \u662F Pro \u529F\u80FD\u3002",SYNAPSE_REASON_PREFIX:"\u21B3 ",SYNAPSE_NO_BACK:"\u6B64 session \u672A\u8F09\u5165\u6B64\u9328\u9EDE\u7684\u7B54\u6848\u3002",SYNAPSE_ONBOARDING_TITLE:"\u6B61\u8FCE\u5347\u7D1A Pro\uFF01\u57F7\u884C engram-synapse \u4EE5\u555F\u7528 \u26A1 \u9023\u7D50\u3002",SYNAPSE_REFRESH_BANNER:"Synapse \u8CC7\u6599\u53EF\u80FD\u904E\u671F\uFF0C\u8ACB\u91CD\u65B0\u57F7\u884C engram-synapse \u66F4\u65B0\u9023\u7D50\u3002",SYNAPSE_REFRESH_BUTTON:"\u66F4\u65B0 Synapse",SYNAPSE_POOL_TOO_SMALL:"Synapse \u9700\u8981\u81F3\u5C11 {need} \u5F35\u5DF2\u638C\u63E1\u7684\u5361\uFF08FSRS stability \u2265 7\uFF09\uFF0C\u4F60\u76EE\u524D\u6709 {have} \u5F35\u3002\u7E7C\u7E8C\u8907\u7FD2\uFF0C\u9054\u5230\u5F8C\u81EA\u52D5\u555F\u7528\u3002",TIMEBOX_MIN_SUFFIX:"\u5206\u9418",TIMEBOX_NO_CARDS:"\u76EE\u524D\u6C92\u6709\u53EF\u8907\u7FD2\u7684\u5361\u7247\u3002",TIMEBOX_TIMES_UP:"\u6642\u9593\u5230\uFF01",BACK:"\u8FD4\u56DE",REVIEW_COMPLETE:"\u8907\u7FD2\u5B8C\u6210\uFF01",BACK_TO_HUB:"\u56DE\u5230\u4E3B\u9801",CARDS_REVIEWED:"\u5F35\u5361\u7247",RIBBON_TOOLTIP:"EngramQuest",HUB_TITLE:"ENGRAM-QUEST",TAB_REVIEW:"Review Deck",TAB_MEMORY:"Memory Map",TAB_QUEST:"Quest Map",STUDY_ALL:"\u8907\u7FD2\u5DF2\u5C31\u7DD2\u5361\u7247",HELP_TITLE:"EngramQuest \u6307\u5357",HELP_OPEN:"\u958B\u555F\u8AAA\u660E",HUB_CLOSE:"\u95DC\u9589",SEARCH_REVIEW:"\u641C\u5C0B\u8907\u7FD2\u724C\u7D44...",SEARCH_MEMORY:"\u641C\u5C0B\u8A18\u61B6\u5730\u5716...",SEARCH_QUEST:"\u641C\u5C0B\u4EFB\u52D9\u5730\u5716...",SEARCH_QUADRANT:"\u641C\u5C0B\u56DB\u8C61\u9650\u5361...",FILTER_ALL:"\u5168\u90E8",FILTER_DUE:"\u5230\u671F",FILTER_NEW:"\u65B0\u5361",FILTER_DONE:"\u5DF2\u5B8C\u6210",READY:"\u53EF\u8907\u7FD2",READY_HELP:"\u53EF\u8907\u7FD2 = \u5230\u671F + \u65B0\u5361\u3002\u7E3D\u6578 = deck \u5167\u6240\u6709\u5361\u7247\u3002",READY_NOW:"\u73FE\u5728\u53EF\u8907\u7FD2",NOTHING_READY:"\u76EE\u524D\u6C92\u6709\u53EF\u8907\u7FD2\u5361\u7247",ALL_SCHEDULED:"\u5168\u90E8 {total} \u5F35\u5361\u7247\u90FD\u6392\u5728\u4E4B\u5F8C\u3002",BROWSE_ALL:"\u700F\u89BD\u5168\u90E8",BROWSE_ONLY:"\u50C5\u700F\u89BD",BROWSE_NOTE:"\u700F\u89BD\u6A21\u5F0F\u4E0D\u6703\u6539\u8B8A\u6392\u7A0B\u3002",PREVIOUS:"\u4E0A\u4E00\u5F35",NEXT:"\u4E0B\u4E00\u5F35",BACK_TO_QUESTION:"\u56DE\u5230\u984C\u76EE",FILTER_ALL_TAGS:"\u5168\u90E8\u6A19\u7C64",FILTER_ALL_DIFFICULTIES:"\u5168\u90E8\u96E3\u5EA6",FILTER_INCOMPLETE:"\u672A\u5B8C\u6210",SORT_NEWEST:"\u6700\u65B0\u512A\u5148",SORT_OLDEST:"\u6700\u820A\u512A\u5148",SORT_NAME:"\u540D\u7A31 A-Z",QUEST_COMPLETE:"Quest \u901A\u95DC\uFF01",QUEST_PRACTICE_TITLE:"\u61C9\u7528\u4EFB\u52D9",QUEST_PRACTICE_DESC:"\u628A\u7B46\u8A18\u653E\u9032\u60C5\u5883\u3001\u6C7A\u7B56\u8207 Boss \u6230\u4E2D\u7DF4\u7FD2\u61C9\u7528\u3002",QUEST_IN_PROGRESS:"\u9032\u884C\u4E2D",QUEST_BOSS_READY:"Boss \u5DF2\u89E3\u9396",QUEST_BOSS_READY_SHORT:"Boss \u89E3\u9396",QUEST_NOT_STARTED:"\u5C1A\u672A\u958B\u59CB",QUEST_CONTINUE_MISSION:"\u7E7C\u7E8C\u4EFB\u52D9",QUEST_CHALLENGE_BOSS:"\u6311\u6230 Boss",QUEST_REVIEW_CLEAR:"\u56DE\u9867\u901A\u95DC",QUEST_PROGRESS_COUNT:"{done}/{total} \u95DC\u5361",QUEST_NO_PROGRESS:"\u7121\u95DC\u5361",QUEST_BEST_SCORE:"\u6700\u4F73 {score}%",QUEST_MISSION_COUNT:"{count} \u500B\u4EFB\u52D9",QUEST_BOSS_LABEL:"Boss\uFF1A{boss}",QUEST_SCENARIO_FALLBACK:"\u958B\u555F\u4EFB\u52D9\u5730\u5716\uFF0C\u628A\u7B46\u8A18\u653E\u9032\u53EF\u904A\u73A9\u7684\u6848\u4F8B\u4E2D\u7DF4\u7FD2\u3002",QUEST_CLEARED:"\u5DF2\u7834\u95DC",SEARCH_TAG:"\u641C\u5C0B\u6A19\u7C64...",DIFF_EASY:"\u7C21\u55AE",DIFF_MEDIUM:"\u666E\u901A",DIFF_HARD:"\u56F0\u96E3",EMPTY_DECKS:`\u627E\u4E0D\u5230 flashcard decks\u3002
\u8ACB\u5148\u8B93 AI \u7522\u751F Review Deck\uFF0C\u6216\u5728\u8A2D\u5B9A\u4E2D\u586B\u5165\u7B26\u5408\u7684 flashcard tags\u3002`,EMPTY_FILTERED_DECKS:"\u76EE\u524D\u7BE9\u9078\u689D\u4EF6\u4E0B\u6C92\u6709 decks\u3002",EMPTY_MEMORIES:`\u627E\u4E0D\u5230 memory maps\u3002
\u8ACB\u5148\u8B93 AI \u7522\u751F -memory.canvas \u6A94\u6848\u3002`,EMPTY_FILTERED_MEMORIES:"\u76EE\u524D\u7BE9\u9078\u689D\u4EF6\u4E0B\u6C92\u6709 memory maps\u3002",EMPTY_QUESTS:`\u627E\u4E0D\u5230 Quest Maps\u3002
\u8ACB\u5148\u8B93 AI \u7522\u751F -quest.md \u6A94\u6848\u3002`,EMPTY_FILTERED_QUESTS:"\u76EE\u524D\u7BE9\u9078\u689D\u4EF6\u4E0B\u6C92\u6709 quests\u3002",OPEN_MAP:"\u958B\u555F\u5730\u5716",OPEN_QUEST:"\u958B\u555F\u4EFB\u52D9",CATEGORY:"\u98A8\u683C",COUNT_UNIT:"\u500B",CARD_COUNT_UNIT:"\u5F35\u5361",MASTERY_RATE:"\u5DF2\u638C\u63E1 {percent}%",NO_CARDS_TO_REVIEW:"\u76EE\u524D\u6C92\u6709\u53EF\u8907\u7FD2\u5361\u7247\u3002",NO_READY_IN_DECK:"\u300C{deck}\u300D\u5171\u6709 {total} \u5F35\u5361\uFF0C\u4F46\u76EE\u524D\u6C92\u6709\u5230\u671F\u6216\u65B0\u5361\u3002",HELP_REVIEW_TAG:"\u9577\u671F\u8A18\u61B6",HELP_QUEST_TAG:"\u60C5\u5883\u61C9\u7528",HELP_MEMORY_TAG:"\u6DF1\u5EA6\u7406\u89E3",HELP_SCIENCE_TAG:"\u539F\u7406",HELP_SETTINGS_TAG:"\u81EA\u8A02",HELP_AI_TAG:"AI \u5FEB\u6377\u65B9\u5F0F",HELP_FAQ_TAG:"FAQ",HELP_AI_SETUP_TAG:"AI \u8A2D\u5B9A",SETTINGS_AI_SKILLS:"AI Skills",SETTINGS_AI_SKILLS_DESC:"\u5B89\u88DD\u7D66 Claude Code\u3001Codex\u3001Gemini CLI\u3001Cursor \u7528\u7684\u5C08\u6848\u5167\u6280\u80FD\u6216\u898F\u5247\u3002\u9019\u4E9B\u6A94\u6848\u53EA\u6703\u7559\u5728\u76EE\u524D vault\uFF0C\u4E0D\u6703\u5BEB\u5230\u4F7F\u7528\u8005\u5168\u57DF\u8CC7\u6599\u593E\u3002",SETTINGS_AI_SKILLS_BUTTON:"\u5B89\u88DD skills",SKILLS_INSTALL_TITLE:"\u5B89\u88DD EngramQuest skills",SKILLS_INSTALL_DESC:"\u9078\u64C7\u4E00\u500B AI \u5DE5\u5177\u3002\u63D2\u4EF6\u6703\u5B89\u88DD quest-map\u3001review-deck\u3001memory-map \u7684\u5C08\u6848\u5167\u6A94\u6848\u3002",SKILLS_INSTALL_PROJECT_LOCAL:"\u9019\u4E9B\u6A94\u6848\u53EA\u5728\u4F60\u5F9E\u9019\u500B vault \u958B\u555F AI \u5DE5\u5177\u6642\u751F\u6548\u3002",SKILLS_STATUS_NOT_INSTALLED:"\u672A\u5B89\u88DD",SKILLS_STATUS_INSTALLED:"\u5DF2\u5B89\u88DD",SKILLS_STATUS_UPDATE:"\u53EF\u66F4\u65B0",SKILLS_PREVIEW_BUTTON:"\u9810\u89BD\u5B89\u88DD\u5167\u5BB9",SKILLS_PREVIEW_TITLE:"\u9810\u89BD\u6A94\u6848\u8B8A\u66F4",SKILLS_PREVIEW_DESC:"\u53EA\u6703\u5EFA\u7ACB\u6216\u53D6\u4EE3 EngramQuest \u7BA1\u7406\u7684\u6A94\u6848\u3002",SKILLS_ACTION_CREATE:"\u5EFA\u7ACB",SKILLS_ACTION_REPLACE:"\u53D6\u4EE3",SKILLS_ACTION_UNCHANGED:"\u4E0D\u8B8A",SKILLS_CONFIRM_INSTALL:"\u7ACB\u5373\u5B89\u88DD",SKILLS_INSTALL_DONE:"\u5DF2\u70BA {tool} \u5B89\u88DD EngramQuest skills\u3002",SKILLS_NEXT_STEP:"\u4E0B\u4E00\u6B65\uFF1A\u8ACB\u5F9E\u9019\u500B vault \u8CC7\u6599\u593E\u555F\u52D5 {tool}\u3002",SKILLS_TOOL_CLAUDE:"Claude Code",SKILLS_TOOL_CODEX:"Codex / Antigravity",SKILLS_TOOL_GEMINI:"Gemini CLI",SKILLS_TOOL_CURSOR:"Cursor",SKILLS_TARGET_PATH:"\u76EE\u6A19\u8DEF\u5F91",SKILLS_MODULES:"\u6A21\u7D44",SKILLS_MODAL_LOADING:"\u6B63\u5728\u6AA2\u67E5\u5B89\u88DD\u72C0\u614B...",SKILLS_AI_SETUP_TITLE:"AI \u8A2D\u5B9A",SKILLS_AI_SETUP_COPY:"\u8ACB\u5230\u8A2D\u5B9A\u4E2D\u7684 Install skills\uFF0C\u70BA Claude Code\u3001Codex\u3001Gemini CLI \u6216 Cursor \u52A0\u5165\u5C08\u6848\u5167\u6307\u4EE4\u3002\u4E4B\u5F8C\u8981\u5F9E\u9019\u500B vault \u555F\u52D5\u5DE5\u5177\uFF0C\u624D\u80FD\u8B80\u5230\u9019\u4E9B\u6A94\u6848\u3002",SETTINGS_MEMORY_MAP:"Memory Map",SETTINGS_MEMORY_MAP_FOLDER_NAME:"Memory Map \u5132\u5B58\u8CC7\u6599\u593E",SETTINGS_MEMORY_MAP_FOLDER_DESC:"\u8A2D\u5B9A -memory.canvas \u7684\u5132\u5B58\u8CC7\u6599\u593E\u3002\u7559\u7A7A\u5247\u5B58\u5728\u4F86\u6E90\u7B46\u8A18\u65C1\u3002\u7BC4\u4F8B\uFF1AMaps/MemoryMaps",SETTINGS_AUTO_UPDATE_SKILLS_NAME:"\u81EA\u52D5\u66F4\u65B0\u5DF2\u5B89\u88DD\u7684 skills",SETTINGS_AUTO_UPDATE_SKILLS_DESC:"\u5982\u679C\u9019\u500B vault \u5DF2\u5B89\u88DD\u7531 EngramQuest \u7BA1\u7406\u7684 skills \u6216 rules\uFF0C\u63D2\u4EF6\u8F09\u5165\u6642\u6703\u81EA\u52D5\u66F4\u65B0\u3002",SETTINGS_PRO:"Pro \u529F\u80FD",SETTINGS_PRO_LICENSE_NAME:"\u6211\u5DF2\u6709 license key",SETTINGS_PRO_LICENSE_DESC:"\u52FE\u9078\u4EE5\u89E3\u9396 Synapse \u8207 Time-boxed Review\u3002License key \u9A57\u8B49\u6A5F\u5236\u5C07\u65BC\u672A\u4F86\u7248\u672C\u52A0\u5165\u3002",DELETE:"\u522A\u9664",DELETE_CONFIRM_TITLE:"\u522A\u9664\u300C{name}\u300D\uFF1F",DELETE_CONFIRM_FILE:"\u6A94\u6848\u5C07\u88AB\u79FB\u81F3\u7CFB\u7D71\u5783\u573E\u6876\u3002",DELETE_CONFIRM_DECK:"\u9019\u5C07\u79FB\u9664 {count} \u500B\u4F86\u6E90\u7B46\u8A18\uFF0C\u5176\u4E2D\u7684 flashcard \u8CC7\u6599\u4E5F\u6703\u4E00\u4F75\u522A\u9664\u3002",DELETE_CONFIRM_BTN:"\u522A\u9664",DELETE_CANCEL_BTN:"\u53D6\u6D88",EDIT_CARD:"\u7DE8\u8F2F",EDIT_SAVE:"\u5132\u5B58",EDIT_CANCEL:"\u53D6\u6D88",EDIT_FRONT:"\u984C\u76EE",EDIT_BACK:"\u7B54\u6848",EDIT_HINTS:"\u63D0\u793A",FORMAT_HIGHLIGHT:"== \u9AD8\u4EAE",FORMAT_BLOCKQUOTE:"> \u5F15\u8FF0",FORMAT_SELECT_TEXT:"\u8ACB\u5148\u9078\u53D6\u6587\u5B57",FORMAT_SELECT_PARA:"\u8ACB\u5148\u5708\u9078\u7B54\u6848\u6BB5\u843D",FORMAT_SAVED:"\u683C\u5F0F\u5DF2\u5957\u7528",CREATE_CARD:"\u65B0\u589E\u5361\u7247",CREATE_CARD_TITLE:"\u65B0\u589E\u5361\u7247\u5230\u300C{deck}\u300D",CREATE_CARD_FRONT:"\u984C\u76EE",CREATE_CARD_BACK:"\u7B54\u6848",CREATE_CARD_FRONT_PLACEHOLDER:"\u8F38\u5165\u984C\u76EE...",CREATE_CARD_BACK_PLACEHOLDER:"\u8F38\u5165\u7B54\u6848...",CREATE_CARD_SAVE:"\u65B0\u589E\u5361\u7247",CREATE_CARD_CANCEL:"\u53D6\u6D88",CREATE_CARD_SUCCESS:"\u5361\u7247\u5DF2\u65B0\u589E\u5230\u300C{deck}\u300D",CREATE_CARD_REQUIRED:"\u8ACB\u540C\u6642\u8F38\u5165\u984C\u76EE\u548C\u7B54\u6848\u3002",CREATE_CARD_SAVE_FAILED:"\u65B0\u589E\u5361\u7247\u5931\u6557\u3002",TAB_ACHIEVEMENT:"\u6210\u5C31\u7CFB\u7D71",TAB_QUADRANT:"\u56DB\u8C61\u9650\u5361",QUADRANT_EMPTY_TITLE:"\u9084\u6C92\u6709\u56DB\u8C61\u9650\u5361",QUADRANT_EMPTY_BODY:"\u5728\u4F60\u7684 AI \u5DE5\u5177\u88E1\u4F7F\u7528\u300C\u56DB\u8C61\u9650\u5361\u300Dskill\uFF1A\u8AAA\u300C\u5347\u7D1A\u300D\u628A\u4F60\u6A19\u8A18\u7684 flashcard \u8F49\u6210\u56DB\u8C61\u9650\u5361\uFF0C\u6216\u8ACB\u5B83\u76F4\u63A5\u5EFA\u7ACB\u4E00\u5F35\u65B0\u7684 A4 \u56DB\u8C61\u9650\u8A18\u61B6\u5361\u3002\u5361\u7247\u6703\u51FA\u73FE\u5728\u9019\u88E1\uFF0C\u4E26\u64C1\u6709\u81EA\u5DF1\u7684\u8907\u7FD2\u6392\u7A0B\u3002",QUADRANT_DUE_NOW:"\u5F85\u8907\u7FD2",QUADRANT_NEXT:"\u4E0B\u6B21\u8907\u7FD2\uFF1A{date}",QUADRANT_REVIEW_BTN:"\u8907\u7FD2",QUADRANT_PREVIEW_BTN:"\u9810\u7FD2",QUADRANT_UPGRADE_BTN:"\u5347\u7D1A\u6210 A4 \u5361",QUADRANT_MARKED:"\u5DF2\u6A19\u8A18\u5347\u7D1A\u3002\u57F7\u884C\u300C\u56DB\u8C61\u9650\u5361\u300Dskill \u4E26\u8AAA\u300C\u5347\u7D1A\u300D\u5373\u53EF\u6279\u6B21\u8655\u7406\u3002",QUADRANT_ALREADY:"\u5DF2\u5728\u5347\u7D1A\u4F47\u5217\u4E2D\u3002",QUADRANT_DELETE_DECK_BODY:"\u9019\u5C07\u628A {count} \u5F35\u56DB\u8C61\u9650\u5361\u8207\u5176\u8907\u7FD2\u6392\u7A0B\u79FB\u81F3\u5783\u573E\u6876\u3002",QUADRANT_DELETE_CARD_BODY:"\u6B64\u56DB\u8C61\u9650\u5361\u8207\u5176\u8907\u7FD2\u6392\u7A0B\u5C07\u88AB\u79FB\u81F3\u5783\u573E\u6876\u3002",QUADRANT_DELETE_DECK_DONE:"\u5DF2\u522A\u9664 {count} \u5F35\u56DB\u8C61\u9650\u5361\u3002",QUADRANT_COPY:"\u8907\u88FD",QUADRANT_COPIED:"\u5DF2\u8907\u88FD",QUADRANT_COPY_FAILED:"\u8907\u88FD\u5931\u6557",QUADRANT_SOURCE_OPENED:"\u5DF2\u958B\u555F",QUADRANT_SOURCE_NOT_FOUND:"\u627E\u4E0D\u5230\u4F86\u6E90\u7B46\u8A18\uFF1A{name}",QUADRANT_EDIT:"\u7DE8\u8F2F",QUADRANT_EDIT_TITLE:"\u7DE8\u8F2F\u5361\u7247",QUADRANT_EDIT_TITLE_LABEL:"\u6A19\u984C",QUADRANT_EDIT_Q1:"Q1 \xB7 \u554F\u984C",QUADRANT_EDIT_Q2:"Q2 \xB7 \u7B54\u6848",QUADRANT_EDIT_Q3:"Q3 \xB7 \u53E3\u8A23",QUADRANT_EDIT_Q4:"Q4 \xB7 \u5716\u50CF",QUADRANT_EDIT_HINT:"\u6A19\u984C\u6703\u7ACB\u5373\u5132\u5B58\u3002Q1\u2013Q4 \u5167\u5BB9\u7684\u4FEE\u6539\u8981\u5728\u4F60\u91CD\u65B0\u57F7\u884C\u300C\u56DB\u8C61\u9650\u5361\u300Dskill\uFF08\u8AAA\u300C\u5347\u7D1A\u300D\uFF09\u5F8C\u624D\u6703\u5957\u7528\u5230\u5361\u7247\u8996\u89BA\u3002",QUADRANT_SAVED:"\u5DF2\u5132\u5B58\u3002",QUADRANT_REGEN_QUEUED:"\u5DF2\u5132\u5B58\u3002\u57F7\u884C\u300C\u56DB\u8C61\u9650\u5361\u300Dskill \u4E26\u8AAA\u300C\u5347\u7D1A\u300D\u5373\u53EF\u91CD\u65B0\u751F\u6210\u5361\u7247\u3002",QUADRANT_CARD_NOT_FOUND:"\u6B64\u5361\u7247\u5DF2\u4E0D\u5B58\u5728\u3002",QUADRANT_SAVE_FAILED:"\u5132\u5B58\u5361\u7247\u5931\u6557\u3002",CAT_MEMORY:"\u8A18\u61B6",CAT_LEARN:"\u5B78\u7FD2",CAT_ACHIEVE:"\u6210\u5C31",TAB_LESSON:"\u6559\u6750",LESSON_ACADEMY_TITLE:"\u8AB2\u7A0B\u5B78\u9662",LESSON_ACADEMY_SUBTITLE:"\u6253\u9020\u5C6C\u65BC\u4F60\u7684\u77E5\u8B58\u65C5\u7A0B\uFF0C\u5FAA\u5E8F\u6F38\u9032\uFF0C\u6210\u5C31\u5C08\u696D\u3002",LESSON_NO_COURSES_TITLE:"\u9084\u6C92\u6709\u8AB2\u7A0B",LESSON_NO_COURSES_BODY:"\u5728\u4F60\u7684 AI \u5DE5\u5177\u88E1\u4F7F\u7528\u300CLesson Academy\u300Dskill\uFF1A\u544A\u8A34\u5B83\u4F60\u60F3\u5B78\u4EC0\u9EBC\uFF0C\u5B83\u6703\u5728\u9019\u88E1\u5EFA\u7ACB\u4E00\u5957\u4E92\u52D5 HTML \u8AB2\u7A0B\u3002\u4E5F\u53EF\u4EE5\u532F\u5165\u5728\u5176\u4ED6\u5730\u65B9\u751F\u6210\u7684 HTML \u6559\u6750\u3002",LESSON_NEW_COURSE:"\u65B0\u589E\u8AB2\u7A0B",LESSON_NEW_COURSE_HINT:"\u5728 AI \u5DE5\u5177\u88E1\u57F7\u884C Lesson Academy skill\uFF0C\u544A\u8A34\u5B83\u4F60\u60F3\u5B78\u4EC0\u9EBC\u3002",LESSON_CREATE_TITLE:"\u5EFA\u7ACB\u65B0\u8AB2\u7A0B",LESSON_CREATE_NAME_LABEL:"\u8AB2\u7A0B\u540D\u7A31",LESSON_CREATE_DESC_LABEL:"\u8AB2\u7A0B\u63CF\u8FF0\uFF08\u9078\u586B\uFF09",LESSON_CREATE_OUTLINE_LABEL:"\u8AB2\u7DB1 \u2014 \u4E00\u884C\u4E00\u500B\u8AB2\u6642\u6A19\u984C\uFF08\u9078\u586B\uFF09",LESSON_CREATE_OUTLINE_PH:`\u4F8B\u5982\uFF1A
HTTP \u57FA\u790E
REST API \u8A2D\u8A08
\u8EAB\u5206\u9A57\u8B49`,LESSON_CREATE_SAVE:"\u5EFA\u7ACB",LESSON_CREATE_NAME_REQUIRED:"\u8AB2\u7A0B\u540D\u7A31\u5FC5\u586B\u3002",LESSON_CREATED:"\u8AB2\u7A0B\u5DF2\u5EFA\u7ACB\u3002\u8ACB\u57F7\u884C Lesson Academy skill \u751F\u6210\u8AB2\u6642\u5167\u5BB9\u3002",LESSON_CREATE_FAILED:"\u5EFA\u7ACB\u8AB2\u7A0B\u5931\u6557\u3002",LESSON_STATUS_PLANNED:"\u5F85\u751F\u6210",LESSON_PLANNED_HINT:"\u9084\u6C92\u6709\u5167\u5BB9 \u2014 \u57F7\u884C Lesson Academy skill \u8ACB\u5B83\u751F\u6210\u6B64\u8AB2\u7A0B\u7684\u5F85\u751F\u6210\u8AB2\u6642\u3002",LESSON_ADD_OUTLINE:"\u65B0\u589E\u8AB2\u7DB1",LESSON_ADD_OUTLINE_PH:"\u8AB2\u6642\u6A19\u984C",LESSON_COURSE_DONE:"{completed}/{total} \u5DF2\u5B8C\u6210",LESSON_CONTENT_OF:"{course}\u8AB2\u7A0B\u5167\u5BB9",LESSON_FILTER_ALL:"\u5168\u90E8",LESSON_FILTER_TODO:"\u672A\u5B8C\u6210",LESSON_FILTER_DONE:"\u5DF2\u5B8C\u6210",LESSON_IMPORT:"\u532F\u5165 HTML",LESSON_IMPORTED:"\u5DF2\u532F\u5165\uFF1A{title}",LESSON_IMPORT_FAILED:"\u532F\u5165\u5931\u6557\u3002",LESSON_ARCHIVE:"\u5C01\u5B58",LESSON_UNARCHIVE:"\u53D6\u6D88\u5C01\u5B58",LESSON_ARCHIVE_HINT:"\u5F9E\u76EE\u524D\u5B78\u7FD2\u6E05\u55AE\u96B1\u85CF\u6B64\u8AB2\u7A0B\uFF0C\u4F46\u4E0D\u522A\u9664\u8CC7\u6599\u3002",LESSON_UNARCHIVE_HINT:"\u5C07\u6B64\u8AB2\u7A0B\u6062\u5FA9\u5230\u76EE\u524D\u5B78\u7FD2\u6E05\u55AE\u3002",LESSON_ARCHIVED_NOTICE:"\u5DF2\u5C01\u5B58\uFF1A{course}",LESSON_UNARCHIVED_NOTICE:"\u5DF2\u53D6\u6D88\u5C01\u5B58\uFF1A{course}",LESSON_ARCHIVE_FAILED:"\u5C01\u5B58\u72C0\u614B\u66F4\u65B0\u5931\u6557\u3002",LESSON_ARCHIVED_BADGE:"\u5DF2\u5C01\u5B58",LESSON_ARCHIVE_EMPTY:"\u76EE\u524D\u6C92\u6709\u5C01\u5B58\u8AB2\u7A0B\u3002",LESSON_STATUS_DONE:"\u5DF2\u5B8C\u6210",LESSON_STATUS_VIEWED:"\u5B78\u7FD2\u4E2D",LESSON_STATUS_NEW:"\u672A\u958B\u59CB",LESSON_MARK_DONE:"\u6A19\u8A18\u5B8C\u6210",LESSON_MARKED_DONE:"\u5DF2\u5B8C\u6210",LESSON_MARK_STAR:"\u52A0\u661F",LESSON_DELETE_BODY:"\u6B64\u8AB2\u6642\u7684 HTML \u6A94\u6848\u5C07\u88AB\u79FB\u81F3\u5783\u573E\u6876\u3002",LESSON_DELETE_COURSE_BODY:"\u9019\u5C07\u628A\u6B64\u8AB2\u7A0B\u7684 {count} \u500B\u8AB2\u6642\u6A94\u6848\u5168\u90E8\u79FB\u81F3\u5783\u573E\u6876\u3002",LESSON_DELETED:"\u8AB2\u6642\u5DF2\u522A\u9664",LESSON_COURSE_DELETED:"\u8AB2\u7A0B\u5DF2\u522A\u9664",LESSON_NOT_FOUND:"\u627E\u4E0D\u5230\u8AB2\u6642\u6A94\u6848\uFF1A{file}",LESSON_PROGRESS_TITLE:"\u5B78\u7FD2\u9032\u5EA6",LESSON_PROGRESS_OVERALL:"\u6574\u9AD4\u9032\u5EA6",LESSON_STAT_IN_PROGRESS:"\u8AB2\u7A0B\u9032\u884C\u4E2D",LESSON_STAT_COMPLETED:"\u5DF2\u5B8C\u6210\u8AB2\u7A0B",LESSON_STAT_HOURS:"\u5B78\u7FD2\u7E3D\u6642\u6578",LESSON_RECENT_TITLE:"\u8FD1\u671F\u5B78\u7FD2",LESSON_RECENT_EMPTY:"\u6253\u958B\u4EFB\u4E00\u8AB2\u6642\u5F8C\u6703\u986F\u793A\u5728\u9019\u88E1\u3002",LESSON_SOURCE_IMPORT:"\u532F\u5165",LESSON_SEARCH_PH:"\u641C\u5C0B\u8AB2\u7A0B\u2026",LESSON_EXPAND_ALL:"\u5C55\u958B\u5168\u90E8",LESSON_COLLAPSE:"\u6536\u5408",LESSON_CFILTER_TAG_ALL:"\u5168\u90E8\u985E\u5225",LESSON_CFILTER_STARRED:"\u661F\u6A19",LESSON_CFILTER_PROG_ALL:"\u5168\u90E8\u9032\u5EA6",LESSON_CFILTER_PROG_NEW:"\u672A\u958B\u59CB",LESSON_CFILTER_PROG_DOING:"\u9032\u884C\u4E2D",LESSON_CFILTER_PROG_DONE:"\u5DF2\u5B8C\u6210",LESSON_SCOPE_ACTIVE:"\u76EE\u524D\u8AB2\u7A0B",LESSON_SCOPE_ARCHIVED:"\u5C01\u5B58\u5340",LESSON_CSORT_RECENT:"\u6392\u5E8F\uFF1A\u6700\u8FD1\u5B78\u7FD2",LESSON_CSORT_CREATED:"\u6392\u5E8F\uFF1A\u65B0\u5230\u820A",LESSON_CSORT_PROGRESS:"\u6392\u5E8F\uFF1A\u9032\u5EA6",ACH_MASTERED:"\u5DF2\u719F\u7DF4",ACH_LEARNING:"\u5B78\u7FD2\u4E2D",ACH_DUE:"\u5F85\u8907\u7FD2",ACH_UNSEEN:"\u672A\u898B\u904E",ACH_TOTAL_REVIEWED:"\u7D2F\u7A4D\u8907\u7FD2",ACH_STREAK:"\u9023\u7E8C\u5929\u6578",ACH_COLLECTION:"\u6210\u5C31\u6536\u85CF",ACH_UNLOCKED:"\u5DF2\u89E3\u9396",ACH_FIRST_CARD_NAME:"\u8E0F\u51FA\u7B2C\u4E00\u6B65",ACH_FIRST_CARD_DESC:"\u5B8C\u6210 1 \u6B21\u8907\u7FD2",ACH_TEN_CARDS_NAME:"\u5B78\u7FD2\u8005",ACH_TEN_CARDS_DESC:"\u5B8C\u6210 10 \u6B21\u8907\u7FD2",ACH_FIFTY_CARDS_NAME:"\u8A18\u61B6\u65B0\u5175",ACH_FIFTY_CARDS_DESC:"\u5B8C\u6210 50 \u6B21\u8907\u7FD2",ACH_CENTURY_NAME:"\u767E\u5361\u5B78\u8005",ACH_CENTURY_DESC:"\u5B8C\u6210 100 \u6B21\u8907\u7FD2",ACH_FIVE_HUNDRED_NAME:"\u5361\u724C\u5927\u5E2B",ACH_FIVE_HUNDRED_DESC:"\u5B8C\u6210 500 \u6B21\u8907\u7FD2",ACH_ONE_THOUSAND_NAME:"\u947D\u77F3\u5B78\u8005",ACH_ONE_THOUSAND_DESC:"\u5B8C\u6210 1000 \u6B21\u8907\u7FD2",ACH_TWO_THOUSAND_NAME:"\u9CF3\u51F0\u50B3\u8AAA",ACH_TWO_THOUSAND_DESC:"\u5B8C\u6210 2000 \u6B21\u8907\u7FD2",ACH_STREAK_3_NAME:"\u52E2\u5982\u7834\u7AF9",ACH_STREAK_3_DESC:"\u9023\u7E8C 3 \u5929",ACH_STREAK_7_NAME:"\u9031\u5EA6\u6230\u58EB",ACH_STREAK_7_DESC:"\u9023\u7E8C 7 \u5929",ACH_STREAK_30_NAME:"\u6708\u5EA6\u5B78\u8005",ACH_STREAK_30_DESC:"\u9023\u7E8C 30 \u5929",ACH_DAILY_20_NAME:"\u5C08\u6CE8\u8D77\u6B65",ACH_DAILY_20_DESC:"\u55AE\u65E5\u5B8C\u6210 20 \u5F35",ACH_DAILY_50_NAME:"\u8A18\u61B6\u6E67\u73FE",ACH_DAILY_50_DESC:"\u55AE\u65E5\u5B8C\u6210 50 \u5F35",ACH_DAILY_80_NAME:"\u885D\u523A\u7A81\u7834",ACH_DAILY_80_DESC:"\u55AE\u65E5\u5B8C\u6210 80 \u5F35",ACH_DAILY_100_NAME:"\u767E\u5361\u72C2\u71B1",ACH_DAILY_100_DESC:"\u55AE\u65E5\u5B8C\u6210 100 \u5F35",ACH_DAILY_150_NAME:"\u5FD8\u6211\u5FC3\u6D41",ACH_DAILY_150_DESC:"\u55AE\u65E5\u5B8C\u6210 150 \u5F35",ACH_DAILY_200_NAME:"\u8A18\u61B6\u795E\u57DF",ACH_DAILY_200_DESC:"\u55AE\u65E5\u5B8C\u6210 200 \u5F35",ACH_MASTER_50_NAME:"\u8A18\u61B6\u8CE2\u8005",ACH_MASTER_50_DESC:"\u719F\u7DF4\u5361\u7247\u9054 50 \u5F35",ACH_ACTIVITY:"\u8907\u7FD2\u6D3B\u52D5",ACH_PERIOD_REVIEWS:"\u672C\u671F\u8907\u7FD2",ACH_STUDY_DAYS:"\u5B78\u7FD2\u5929\u6578",ACH_DAILY_AVG:"\u65E5\u5747",ACH_PER_DAY:"\u6B21/\u5929",ACH_PERIOD_7:"7\u5929",ACH_PERIOD_30:"30\u5929",ACH_PERIOD_90:"90\u5929",ACH_FOUR_THOUSAND_NAME:"\u842C\u8C61\u5B78\u58EB",ACH_FOUR_THOUSAND_DESC:"\u5B8C\u6210 4000 \u6B21\u8907\u7FD2",ACH_TEN_THOUSAND_NAME:"\u77E5\u8B58\u9818\u4E3B",ACH_TEN_THOUSAND_DESC:"\u5B8C\u6210 10000 \u6B21\u8907\u7FD2",ACH_STREAK_100_NAME:"\u767E\u65E5\u7BC9\u57FA",ACH_STREAK_100_DESC:"\u9023\u7E8C 100 \u5929",ACH_STREAK_365_NAME:"\u6B72\u6708\u53F2\u5B98",ACH_STREAK_365_DESC:"\u9023\u7E8C 365 \u5929",ACH_MASTER_200_NAME:"\u5B97\u5E2B\u4E4B\u5883",ACH_MASTER_200_DESC:"\u719F\u7DF4\u5361\u7247\u9054 200 \u5F35",ACH_SYNAPSE_AWAKEN_NAME:"\u795E\u7D93\u89BA\u9192",ACH_SYNAPSE_AWAKEN_DESC:"\u719F\u7DF4\u5361\u7247\u9054 10 \u5F35",ACH_SYNAPSE_NETWORK_NAME:"\u5168\u77E5\u667A\u7DB2",ACH_SYNAPSE_NETWORK_DESC:"\u719F\u7DF4\u5361\u7247\u9054 300 \u5F35",ACH_FIRST_MEMORY_MAP_NAME:"\u5730\u5716\u6E2C\u7E6A\u5E2B",ACH_FIRST_MEMORY_MAP_DESC:"\u751F\u6210\u7B2C\u4E00\u5F35 Memory Map",ACH_FIRST_QUEST_MAP_NAME:"\u5192\u96AA\u555F\u7A0B",ACH_FIRST_QUEST_MAP_DESC:"\u751F\u6210\u7B2C\u4E00\u5F35 Quest Map",ACH_QUEST_CLEARED_NAME:"\u51F1\u65CB\u800C\u6B78",ACH_QUEST_CLEARED_DESC:"\u9996\u6B21\u7834\u95DC Quest Map",ACH_MULTIPLE_DECKS_NAME:"\u591A\u89D2\u8DE8\u754C",ACH_MULTIPLE_DECKS_DESC:"\u64C1\u6709\u81F3\u5C11 3 \u500B\u4E0D\u540C\u7684 Review Deck",ACH_PERFECT_SESSION_NAME:"\u904E\u76EE\u4E0D\u5FD8",ACH_PERFECT_SESSION_DESC:"\u55AE\u6B21\u8907\u7FD2\u9023\u7E8C 20 \u5F35\u672A\u9078\u300C\u91CD\u4F86\u300D",ACH_HUNDRED_AGAIN_NAME:"\u6D74\u706B\u91CD\u751F",ACH_HUNDRED_AGAIN_DESC:"\u7D2F\u8A08\u6309\u4E0B\u300C\u91CD\u4F86\u300D\u9054 100 \u6B21",ACH_FIRST_LESSON_NAME:"\u7B2C\u4E00\u5802\u8AB2",ACH_FIRST_LESSON_DESC:"\u5B8C\u6210 1 \u5802\u8AB2",ACH_COURSE_FINISHER_NAME:"\u8AB2\u7A0B\u5B8C\u8D70\u8005",ACH_COURSE_FINISHER_DESC:"\u5B8C\u6210 1 \u9580\u8AB2\u7A0B",ACH_LESSON_MARATHON_NAME:"\u5341\u8AB2\u4FEE\u884C",ACH_LESSON_MARATHON_DESC:"\u5B8C\u6210 10 \u5802\u8AB2",ACH_FIRST_QUEST_STEP_NAME:"\u4EFB\u52D9\u8D77\u6B65",ACH_FIRST_QUEST_STEP_DESC:"\u5B8C\u6210 1 \u500B Quest \u7BC0\u9EDE",ACH_QUEST_VETERAN_NAME:"\u4EFB\u52D9\u8001\u624B",ACH_QUEST_VETERAN_DESC:"\u5B8C\u6210 10 \u500B Quest \u7BC0\u9EDE",ACH_FIRST_CHARTED_NAME:"\u9996\u6B21\u63A5\u89F8",ACH_FIRST_CHARTED_DESC:"\u5728 Pie Galaxy \u6A19\u5B9A 1 \u7BC7\u7B46\u8A18",ACH_NAVIGATOR_REACHED_NAME:"\u9818\u822A\u54E1",ACH_NAVIGATOR_REACHED_DESC:"\u5728 Pie Galaxy \u6A19\u5B9A 5 \u7BC7\u7B46\u8A18",ACH_PIONEER_REACHED_NAME:"\u5148\u92D2",ACH_PIONEER_REACHED_DESC:"\u5728 Pie Galaxy \u6A19\u5B9A 30 \u7BC7\u7B46\u8A18",ACH_STARFARER_REACHED_NAME:"\u661F\u969B\u65C5\u4EBA",ACH_STARFARER_REACHED_DESC:"\u5728 Pie Galaxy \u6A19\u5B9A 120 \u7BC7\u7B46\u8A18",ACH_FIRST_COLONIZED_NAME:"\u6B96\u6C11\u8005",ACH_FIRST_COLONIZED_DESC:"\u5728 Pie Galaxy \u6B96\u6C11 1 \u7BC7\u7B46\u8A18",ACH_COLONY_TEN_NAME:"\u6B96\u6C11\u5EFA\u8A2D\u8005",ACH_COLONY_TEN_DESC:"\u5728 Pie Galaxy \u6B96\u6C11 10 \u7BC7\u7B46\u8A18",ACH_CONSTELLATION_NAME:"\u661F\u5EA7\u5B8C\u6210",ACH_CONSTELLATION_DESC:"\u5728 Pie Galaxy \u7CBE\u901A\u4E00\u689D\u5B8C\u6574\u5B78\u7FD2\u8DEF\u5F91",ACH_DEEP_SPACE_NAME:"\u6DF1\u7A7A",ACH_DEEP_SPACE_DESC:"\u6A19\u5B9A\u4E00\u7BC7\u4F4D\u65BC\u6DF1\u5C64\uFF08\u22654 \u5C64\uFF09\u7684\u7B46\u8A18",ACH_CARTOGRAPHER_NAME:"\u88FD\u5716\u5E2B",ACH_CARTOGRAPHER_DESC:"\u5728\u55AE\u4E00\u9818\u57DF\u6A19\u5B9A 10 \u7BC7\u7B46\u8A18",ACH_SESSION_UNLOCKED:"\u6210\u5C31\u89E3\u9396\uFF01"}};function Sn(t){let e=String(t||"").toLowerCase();return e==="system"?"system":e.startsWith("zh")?"zh-tw":mt[e]?e:"en"}function ar(t,e){let n=Sn(t&&t.language||"en");return n!=="system"?n:Sn(e||"en")}function rr(t,e){return e?String(t).replace(/\{(\w+)\}/g,(n,a)=>{let s=e[a];return s!=null?s:`{${a}}`}):t}function sr(t,e,n,a){var r,o;let s=ar(t,a),i=(o=(r=(mt[s]||mt.en)[e])!=null?r:mt.en[e])!=null?o:e;return rr(i,n)}function Ao(t,e,n,a){return sr(e,t,n,a)}ir.exports={DICT:mt,normalizeLocale:Sn,getLocale:ar,interpolate:rr,t:sr,tAlt:Ao}});var $t=Y((Lu,dr)=>{"use strict";function or(t=new Date){return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`}var fe=[.4072,1.1829,3.1262,15.4722,7.2102,.5316,1.0651,.0589,1.533,.1544,1.004,1.9813,.0953,.2975,2.2042,.2407,2.9466],Rn=-.5,In=19/81;function An(t,e){return Math.pow(1+In*t/e,Rn)}function Ht(t,e){let n=t/In*(Math.pow(e,1/Rn)-1);return Math.max(1,Math.round(n))}function Tn(t){return Math.max(.1,fe[t-1])}function Mn(t){return Math.min(10,Math.max(1,fe[4]-Math.exp(fe[5]*(t-1))+1))}function qn(t){return Math.min(10,Math.max(1,t))}function Cn(t,e){let n=Mn(4),a=t-fe[6]*(e-3);return qn(fe[7]*n+(1-fe[7])*a)}function lr(t,e,n,a){let s=a===2?fe[15]:1,i=a===4?fe[16]:1;return e*(Math.exp(fe[8])*(11-t)*Math.pow(e,-fe[9])*(Math.exp(fe[10]*(1-n))-1)*s*i+1)}function Ln(t,e,n){return fe[11]*Math.pow(t,-fe[12])*(Math.pow(e+1,fe[13])-1)*Math.exp(fe[14]*(1-n))}function cr(t){return t?t._migratedFromSM2?{state:t.state,stability:t.stability,difficulty:t.difficulty,interval:t.interval,repetitions:t.repetitions,elapsedDays:t.interval}:{...t,elapsedDays:t.interval}:{state:0,stability:1,difficulty:5,interval:0,repetitions:0,elapsedDays:0}}function To(t,e,n){var h,u;let a=(h=n==null?void 0:n.requestedRetention)!=null?h:.9,s=(u=n==null?void 0:n.maxInterval)!=null?u:36500,i=cr(e),r=(i.repetitions||0)+1,o,l,c,p;if(i.state===0)l=Tn(t),c=Mn(t),o=2,p=t===4?Math.min(4,s):1;else if(i.state===1)l=Tn(t),c=i.difficulty,o=2,p=Math.min(Ht(l,a),s);else if(i.state===2){let x=An(i.elapsedDays||i.interval||1,i.stability);t===1?(o=3,l=Math.max(.1,Ln(i.difficulty,i.stability,x)),c=Cn(i.difficulty,t),p=1):(o=2,l=Math.max(.1,lr(i.difficulty,i.stability,x,t)),c=Cn(i.difficulty,t),p=Math.min(Ht(l,a),s))}else{let x=An(i.elapsedDays||1,i.stability);l=Math.max(.1,Ln(i.difficulty,i.stability,x)),c=i.difficulty,o=2,p=Math.min(Ht(l,a),s)}l=Math.round(Math.max(.1,l)*1e3)/1e3,c=Math.round(qn(c)*1e3)/1e3,p=Math.max(1,Math.round(p));let d=new Date;return d.setDate(d.getDate()+p),{due:or(d),interval:p,stability:l,difficulty:c,state:o,repetitions:r}}dr.exports={W:fe,DECAY:Rn,FACTOR:In,getLocalDateStr:or,retrievability:An,intervalFromRetention:Ht,initStability:Tn,initDifficulty:Mn,clampDifficulty:qn,nextDifficulty:Cn,nextStabilityReview:lr,nextStabilityForgot:Ln,normalizeFsrsCard:cr,computeFsrs:To}});var Se=Y((Ru,xr)=>{"use strict";function pr(t=new Date){return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`}var hr=/<!--SR:!(\d{4}-\d{2}-\d{2}),(\d+),([\d.]+),([\d.]+),(\d)-->/,ur=/<!--SR:!(\d{4}-\d{2}-\d{2}),(\d+),(\d+)-->/,Co=/<!--SR:![\d\-.,]+-->/;function Lo(t){let e=t.match(hr);if(e)return{due:e[1],interval:parseInt(e[2]),stability:parseFloat(e[3]),difficulty:parseFloat(e[4]),state:parseInt(e[5]),repetitions:parseInt(e[2])<=1?1:2};let n=t.match(ur);if(!n)return null;let a=parseInt(n[2]),s=parseInt(n[3]),i=Math.max(.5,Math.round(a*.9*1e3)/1e3),r=Math.round(Math.min(10,Math.max(1,13-s/30))*1e3)/1e3,o=a>=6?2:1;return{due:n[1],interval:a,stability:i,difficulty:r,state:o,repetitions:a<=1?1:a<=6?2:3,_migratedFromSM2:!0}}function Ro(t){var n;if(!t||t.state===0)return"unseen";let e=pr();return t.due<=e?"due":t.state===1||t.state===3?"learning":((n=t.stability)!=null?n:t.interval)>=21?"mastered":"learning"}var gr=["definition","essence","problem","origin-context","mechanism","root-cause","rationale","scope-boundary","tradeoffs","consequences","application","measurement","create","connection","discrimination","metacognition","teach-back"],Io=new Set(gr);function mr(t){let e=String(t).match(/^\s*token\s*:\s*(.+?)\s*$/i);if(!e)return"";let n=e[1].toLowerCase();return Io.has(n)?n:""}function Mo(t){return mr(t)!==""}function Dn(t){for(let e of t){let n=mr(e);if(n)return n}return""}function ft(t,e,n=""){return{front:t,back:e,token:n,emoji:"",hint_l1:"",hint_l2:"",hint_l3:"",srMeta:null,srComment:"",notePath:null}}function qo(t){let e=t.split(`
`),n=[],a=0,s=[];for(;a<e.length;){let i=e[a].match(/^\s*Q:\s*(.+)/i);if(!i){s.push(e[a]),a++;continue}let r=Dn(s);s=[];let o=[i[1]];for(a++;a<e.length&&!/^\s*A:\s*/i.test(e[a]);)o.push(e[a]),a++;for(;o.length>0&&o[o.length-1].trim()==="";)o.pop();let l=a<e.length?e[a].match(/^\s*A:\s*(.*)/i):null;if(!l)continue;let c=[l[1]];for(a++;a<e.length&&!/^\s*Q:\s*/i.test(e[a]);)c.push(e[a]),a++;for(;c.length>0&&c[c.length-1].trim()==="";)c.pop();let p=o.join(`
`).trim(),d=c.join(`
`).trim();p&&d&&n.push(ft(p,d,r))}return n}function fr(t){let e=t.split(`
`),n=-1,a=-1;for(let d=0;d<e.length;d++){if(n===-1&&/^\s*Q:\s*/i.test(e[d])){n=d;continue}if(n!==-1&&/^\s*A:\s*/i.test(e[d])){a=d;break}}if(n===-1||a===-1||a<=n)return null;let s=e[n].match(/^\s*Q:\s*(.*)/i),i=e[a].match(/^\s*A:\s*(.*)/i),r=Dn(e.slice(0,n)),o=[s?s[1]:"",...e.slice(n+1,a)],l=[i?i[1]:"",...e.slice(a+1)];for(;o.length>0&&o[o.length-1].trim()==="";)o.pop();for(;l.length>0&&l[l.length-1].trim()==="";)l.pop();let c=o.join(`
`).trim(),p=l.join(`
`).trim();return!c||!p?null:ft(c,p,r)}var Nn=/^\s*%%\s*card\s*%%\s*$/i;function br(t,e){let n=e+1;for(;n<t.length&&(t[n].trim()===""||Mo(t[n]));)n++;return n<t.length&&/^\s*Q:\s*/i.test(t[n])}var No="%%card%%";function Do(t){t=t.replace(/\r\n/g,`
`);let e=t.split(`
`),n=[],a=!1;for(let s=0;s<e.length;s++){let i=e[s];if(Nn.test(i)){let g=[],b=s+1;for(;b<e.length&&!Nn.test(e[b]);)g.push(e[b]),b++;if(b>=e.length)break;let f=fr(g.join(`
`));f&&n.push(f),s=b;continue}if(/^---\s*$/.test(i)){if(!br(e,s))continue;let g=[],b=s+1;for(;b<e.length&&!/^---\s*$/.test(e[b]);)g.push(e[b]),b++;s=b;let f=g.join(`
`),v=qo(f);n.push(...v);continue}let r=/^[ \t]*(`{3,}|~{3,})/.exec(i);if(r){let g=r[1];for(s++;s<e.length&&!e[s].trimStart().startsWith(g);)s++;continue}if(a){i.includes("-->")&&(a=!1);continue}if(i.trimStart().startsWith("<!--")&&!i.trimStart().startsWith("<!--SR:")){i.includes("-->")||(a=!0);continue}let o=i.match(/^\s*Q:\s*(.+)/i);if(o){let g=[o[1]],b=s+1,f=0;for(;b<e.length&&!/^\s*A:\s*/i.test(e[b]);)if(e[b].trim()===""){if(f++,f>=2)break;b++}else f=0,g.push(e[b]),b++;for(;g.length>0&&g[g.length-1].trim()==="";)g.pop();let v=b<e.length?e[b].match(/^\s*A:\s*(.*)/i):null;if(v){let y=[v[1]],w=b+1,k=0,E=!1,S="",_=0;for(;w<e.length;){let L=e[w].match(/^[ \t]*(`{3,}|~{3,})/);if(L){let A=L[1][0],T=L[1].length;E?A===S&&T>=_&&(E=!1):(E=!0,S=A,_=T),y.push(e[w]),k=0,w++;continue}if(E){y.push(e[w]),w++;continue}if(/^\s*Q:\s*/i.test(e[w])||/^---\s*$/.test(e[w])||/\{\{c\d+::/.test(e[w]))break;if(e[w].trim()===""){if(k++,k>=2)break;y.push(e[w]),w++}else k=0,y.push(e[w]),w++}for(;y.length>0&&y[y.length-1].trim()==="";)y.pop();let C=y.join(`
`).trim();if(C){n.push(ft(g.join(`
`).trim(),C)),s=w-1;continue}}}if(/\{\{c\d+::/.test(i)){let g=/\{\{c(\d+)::([^}:]*?)(?:::([^}]*?))?\}\}/g,b=[...i.matchAll(g)],f=[...new Set(b.map(v=>v[1]))];for(let v of f){let y=i.replace(g,(k,E,S,_)=>E===v?_?`[${_}]`:"[...]":S),w=i.replace(g,(k,E,S)=>S);y.trim()&&w.trim()&&n.push(ft(y.trim(),w.trim()))}continue}let l=i.indexOf("::");if(l<1)continue;let c=i.slice(0,l);if((c.match(/`/g)||[]).length%2!==0||/\{\{c\d+::/.test(c))continue;let d=g=>g.replace(/^[*_=]+|[*_=]+$/g,"").trim(),h=c.trim(),u=i.slice(l+2).trim(),x=d(h),m=d(u);!x||!m||n.push({...ft(x,m),rawFront:h,rawBack:u})}return n}function On(t){return t.replace(/\//g,"__").replace(/\.md$/i,"")}async function Oo(t,e){let n=`engram-review/sr/${On(e)}.json`,a=null;try{a=await t.read(n)}catch(i){}if(a!=null)try{return JSON.parse(a)}catch(i){return{}}let s=`engram-review/sr/${e.split("/").pop().replace(/\.md$/i,"")}.json`;try{return JSON.parse(await t.read(s))}catch(i){}return{}}async function zo(t,e,n){let a=new Array(t.length),s=0,i=Array.from({length:Math.min(e,t.length)},async()=>{for(;s<t.length;){let r=s++;a[r]=await n(t[r],r)}});return await Promise.all(i),a}async function Po(t,e,n){let a=`engram-review/sr/${On(e)}.json`;await t.mkdir("engram-review/sr").catch(()=>{}),await t.write(a,JSON.stringify(n,null,2))}function Ho(t,e){t.forEach(n=>{let a=e[n.front];a&&(n.srMeta=a)})}function $o(t){let e={tag:null,source:null,cards:null,style:"ocean",title:"",columns:4},n=t.split(`
`),a=!1,s=null;for(let i of n){let r=i.trim();if(!(!r||r.startsWith("#"))){if(r.startsWith("tag:")){e.tag=r.slice(4).trim(),a=!1;continue}if(r.startsWith("source:")){e.source=r.slice(7).trim(),a=!1;continue}if(r.startsWith("style:")){e.style=r.slice(6).trim(),a=!1;continue}if(r.startsWith("title:")){e.title=r.slice(6).trim(),a=!1;continue}if(r.startsWith("columns:")){e.columns=parseInt(r.slice(8))||4,a=!1;continue}if(r==="cards:"){e.cards=[],a=!0,s=null;continue}if(a){if(r.startsWith("- front:")){s={front:r.slice(8).trim(),back:"",emoji:"",hint_l1:"",hint_l2:"",hint_l3:"",srMeta:null,srComment:"",notePath:null},e.cards.push(s);continue}s&&(r.startsWith("back:")&&(s.back=r.slice(5).trim()),r.startsWith("emoji:")&&(s.emoji=r.slice(6).trim()),r.startsWith("hint_l1:")&&(s.hint_l1=r.slice(8).trim()),r.startsWith("hint_l2:")&&(s.hint_l2=r.slice(8).trim()),r.startsWith("hint_l3:")&&(s.hint_l3=r.slice(8).trim()))}}}return e}function Fo(t,e){if(!(e!=null&&e.cards))return;let n=e.cards;Array.isArray(e.cards)&&(n={},e.cards.forEach(a=>{a.front&&(n[a.front]=a)})),t.forEach(a=>{let s=n[a.front];s&&(a.hint_l1=s.l1||"",a.hint_l2=s.l2||"",a.hint_l3=s.l3||"")})}function Qo(t,e){let n=(e||"").split(/[\s,\n]+/).map(a=>a.replace(/^#/,"").trim().toLowerCase()).filter(Boolean);if(n.length===0)return null;for(let a of t){let s=a.replace(/^#/,""),i=s.toLowerCase();for(let r of n)if(i===r||i.startsWith(r+"/"))return s}return null}xr.exports={advancedSrPattern:hr,legacySrPattern:ur,anySrPattern:Co,parseSrComment:Lo,getLocalDateStr:pr,getReviewStatus:Ro,parseFlashcards:Do,RUBRIC_TOKENS:gr,extractCardToken:Dn,parseCommentCardBlock:fr,cardFencePattern:Nn,CARD_FENCE:No,isFencedQaOpener:br,parseReviewDeckBlock:$o,mergeReviewHints:Fo,matchFlashcardTagPrefix:Qo,srFileName:On,loadSrData:Oo,saveSrData:Po,mergeSrIntoCards:Ho,mapLimit:zo}});var Hn=Y((Iu,kr)=>{"use strict";var{srFileName:Bo,mapLimit:Uo}=Se(),Pn="engram-review/synapse",zn=`${Pn}/_status.json`,yr=15,vr=28,wr=7;function Ft(){return{enabled:!1,reason:"not-generated",masteredPoolSize:0,generatedAt:null}}async function jo(t){try{if(!await t.exists(zn))return Ft();let e=await t.read(zn),n=JSON.parse(e);return{...Ft(),...n}}catch(e){return Ft()}}async function Er(t,e){if(!e)return null;let n=`${Pn}/${Bo(e)}.json`;try{return await t.exists(n)?JSON.parse(await t.read(n)):null}catch(a){return null}}async function Go(t,e){let n=new Map,a=[...new Set((e||[]).filter(Boolean))];return await Promise.all(a.map(async s=>{n.set(s,await Er(t,s))})),n}function Wo(t,e){t.forEach(n=>{if(!n||!n.notePath){n&&(n.synapseAnchors=[]);return}let a=e.get(n.notePath);if(!a||!a.cards){n.synapseAnchors=[];return}let s=a.cards[n.front];if(!s){n.synapseAnchors=[];return}let i=new Set(s.userRejected||[]),r=(s.anchors||[]).filter(o=>{if(!o||!o.front)return!1;let l=o.notePath?`${o.notePath}|${o.front}`:o.front;return!i.has(l)&&!i.has(o.front)});n.synapseAnchors=r})}async function Yo(t){let e="engram-review/sr";if(!await t.exists(e))return 0;let n;try{n=await t.list(e)}catch(i){return 0}let a=(n.files||[]).filter(i=>i.endsWith(".json"));return(await Uo(a,8,async i=>{try{let r=JSON.parse(await t.read(i)),o=0;for(let l of Object.keys(r)){let c=r[l];c&&typeof c.stability=="number"&&c.stability>=wr&&o++}return o}catch(r){return 0}})).reduce((i,r)=>i+r,0)}function Ko({status:t,currentMastered:e,now:n=Date.now(),dismissedAt:a=0}){if(!t||t.enabled!==!0)return{show:!1,reason:"not-enabled"};if(a&&n-a<7200*60*1e3)return{show:!1,reason:"dismissed-recently"};let s=t.masteredPoolSize||0,i=Math.abs((e||0)-s);if(i>=yr)return{show:!0,reason:"pool-changed",delta:i};let r=t.generatedAt?Date.parse(t.generatedAt):NaN;if(Number.isFinite(r)){let o=(n-r)/864e5;if(o>=vr)return{show:!0,reason:"stale",ageDays:o}}return{show:!1,reason:"fresh"}}function Vo(t){return!!(t&&t.enabled===!0)}kr.exports={SYNAPSE_DIR:Pn,STATUS_FILE:zn,POOL_DELTA_THRESHOLD:yr,POOL_AGE_DAYS_THRESHOLD:vr,MASTERED_STABILITY_THRESHOLD:wr,defaultStatus:Ft,loadSynapseStatus:jo,loadSynapseForNote:Er,loadSynapseBatch:Go,attachSynapseToCards:Wo,countMasteredFromSr:Yo,shouldShowRefreshBanner:Ko,isSynapseEnabled:Vo}});var be=Y((Mu,_r)=>{"use strict";var Jo='stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"',Q=t=>`<svg viewBox="0 0 24 24" ${Jo} xmlns="http://www.w3.org/2000/svg">${t}</svg>`,bt={cards:Q('<rect x="3" y="7" width="13" height="14" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-2"/>'),card:Q('<rect x="4" y="4" width="16" height="16" rx="2"/><path d="M8 9h8M8 13h5"/>'),stack:Q('<path d="M12 3 3 8l9 5 9-5-9-5Z"/><path d="M3 13l9 5 9-5M3 16.5l9 5 9-5" opacity=".55"/>'),gauge:Q('<path d="M4 15a8 8 0 0 1 16 0"/><path d="M12 15l4-4"/><circle cx="12" cy="15" r="1.4" fill="currentColor" stroke="none"/>'),clock:Q('<circle cx="12" cy="12" r="8.5"/><path d="M12 7.5V12l3 2"/>'),check:Q('<path d="M4 12.5 9.5 18 20 6"/>'),x:Q('<path d="M6 6l12 12M18 6 6 18"/>'),star:Q('<path d="M12 3.5l2.6 5.4 5.9.8-4.3 4.1 1 5.9-5.2-2.8-5.2 2.8 1-5.9-4.3-4.1 5.9-.8Z"/>'),trophy:Q('<path d="M7 4h10v4a5 5 0 0 1-10 0V4Z"/><path d="M7 6H4v1a3 3 0 0 0 3 3M17 6h3v1a3 3 0 0 1-3 3M9 15h6M8 20h8M12 15v5"/>'),lock:Q('<rect x="5" y="10.5" width="14" height="9" rx="2"/><path d="M8 10.5V8a4 4 0 0 1 8 0v2.5"/>'),flag:Q('<path d="M6 21V4M6 4h11l-2 3 2 3H6"/>'),target:Q('<circle cx="12" cy="12" r="8.5"/><circle cx="12" cy="12" r="4"/><circle cx="12" cy="12" r="1" fill="currentColor" stroke="none"/>'),cap:Q('<path d="M2.5 9 12 5l9.5 4-9.5 4-9.5-4Z"/><path d="M6.5 11v4c0 1.4 2.5 2.5 5.5 2.5s5.5-1.1 5.5-2.5v-4M21.5 9v5"/>'),book:Q('<path d="M4 5.5A2.5 2.5 0 0 1 6.5 3H20v15H6.5A2.5 2.5 0 0 0 4 20.5V5.5Z"/><path d="M4 20.5A2.5 2.5 0 0 1 6.5 18H20"/>'),brain:Q('<path d="M9.5 4a2.5 2.5 0 0 0-2.5 2.5A2.5 2.5 0 0 0 5 9a2.5 2.5 0 0 0 .8 4.8A2.5 2.5 0 0 0 9.5 20 2.5 2.5 0 0 0 12 17.5V6.5A2.5 2.5 0 0 0 9.5 4Z"/><path d="M14.5 4A2.5 2.5 0 0 1 17 6.5 2.5 2.5 0 0 1 19 9a2.5 2.5 0 0 1-.8 4.8A2.5 2.5 0 0 1 14.5 20 2.5 2.5 0 0 1 12 17.5"/>'),bulb:Q('<path d="M9 17h6M10 20h4"/><path d="M8 12a4 4 0 1 1 8 0c0 1.6-1 2.4-1.5 3.2-.3.5-.5 1-.5 1.8h-4c0-.8-.2-1.3-.5-1.8C9 14.4 8 13.6 8 12Z"/>'),bolt:Q('<path d="M13 3 5 13h6l-1 8 8-10h-6l1-8Z"/>'),map:Q('<path d="M9 5 3.5 7v12L9 17l6 2 5.5-2V5L15 7 9 5Z"/><path d="M9 5v12M15 7v12"/>'),folder:Q('<path d="M3 7a2 2 0 0 1 2-2h4l2 2.5h8a2 2 0 0 1 2 2V17a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7Z"/>'),file:Q('<path d="M7 3h7l4 4v13a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1Z"/><path d="M14 3v4h4"/>'),link:Q('<path d="M10 14a3.5 3.5 0 0 0 5 0l3-3a3.5 3.5 0 0 0-5-5l-1.5 1.5"/><path d="M14 10a3.5 3.5 0 0 0-5 0l-3 3a3.5 3.5 0 0 0 5 5l1.5-1.5"/>'),clipboard:Q('<rect x="6" y="4" width="12" height="17" rx="2"/><path d="M9 4V3h6v1M9 4a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2"/>'),copy:Q('<rect x="8" y="8" width="12" height="12" rx="2"/><path d="M4 16V5a1 1 0 0 1 1-1h11" opacity=".7"/>'),trash:Q('<path d="M5 7h14M10 7V5h4v2M6.5 7l.8 12.5a1.5 1.5 0 0 0 1.5 1.4h6.4a1.5 1.5 0 0 0 1.5-1.4L18.5 7"/>'),home:Q('<path d="M4 11 12 4l8 7"/><path d="M6 10v9h12v-9"/>'),search:Q('<circle cx="11" cy="11" r="6.5"/><path d="m16 16 4 4"/>'),edit:Q('<path d="M14 5 19 10M4 20l1-4L16 5a1.5 1.5 0 0 1 2.5 0L20 6.5a1.5 1.5 0 0 1 0 2.5L9 20l-5 0Z"/>'),chat:Q('<path d="M20 12a7 7 0 0 1-9.5 6.5L4 20l1.5-6A7 7 0 1 1 20 12Z"/>'),tools:Q('<path d="M14 7a3.5 3.5 0 0 1 4.8 4.2l-9 9-4-4 9-9Z"/><path d="m8 12-4 4"/>'),robot:Q('<rect x="5" y="8" width="14" height="11" rx="2.5"/><path d="M12 5v3M9 13v1M15 13v1M3 12v3M21 12v3"/>'),alert:Q('<path d="M12 4.5 21 19H3L12 4.5Z"/><path d="M12 10v4M12 16.5v.5" stroke-width="2.2"/>'),sparkle:Q('<path d="M12 3v4M12 17v4M3 12h4M17 12h4M6 6l2.5 2.5M15.5 15.5 18 18M18 6l-2.5 2.5M8.5 15.5 6 18"/>'),rocket:Q('<path d="M12 3c3.5 2 5 5.5 5 9l-2.5 2.5h-5L7 12c0-3.5 1.5-7 5-9Z"/><circle cx="12" cy="10" r="1.5"/><path d="M9.5 16 7 20M14.5 16 17 20"/>'),pointer:Q('<path d="M6 4v10l3-2 2 5 2.5-1-2-4.5h4L6 4Z"/>'),wave:Q('<path d="M3 9c2-2 4-2 6 0s4 2 6 0 4-2 6 0M3 14c2-2 4-2 6 0s4 2 6 0 4-2 6 0"/>'),tree:Q('<path d="M12 3 6 12h4l-3 5h10l-3-5h4L12 3ZM12 17v4"/>'),galaxy:Q('<circle cx="12" cy="12" r="1.6" fill="currentColor" stroke="none"/><path d="M12 5c4 0 7 3 7 5s-4 2-7 2-7 0-7-2 3-5 7-5ZM12 19c-4 0-7-3-7-5s4-2 7-2 7 0 7 2-3 5-7 5Z"/>'),cloud:Q('<path d="M7 17h10a3.5 3.5 0 0 0 .3-7A5 5 0 0 0 7.6 11 3 3 0 0 0 7 17Z"/>'),coin:Q('<ellipse cx="12" cy="12" rx="8" ry="8"/><path d="M9.5 9.5h3a2 2 0 0 1 0 4h-3M11 8v8"/>'),camera:Q('<rect x="3" y="7" width="18" height="13" rx="2"/><circle cx="12" cy="13.5" r="3.5"/><path d="M8 7l1.5-2h5L16 7"/>'),crown:Q('<path d="M4 8l3.5 3L12 5l4.5 6L20 8l-1.5 10H5.5L4 8Z"/><path d="M5.5 18h13" opacity=".6"/>'),flame:Q('<path d="M12 3c1 3 4 4.5 4 8a4 4 0 0 1-8 0c0-1.5.7-2.5 1.5-3 .2 1 1 1.5 1.5 1.5C10 8 12 6 12 3Z"/>'),sprout:Q('<path d="M12 21v-7M12 14c0-3-2-5-5-5 0 3 2 5 5 5ZM12 12c0-2.5 2-4.5 5-4.5 0 2.5-2 4.5-5 4.5Z"/>'),eye:Q('<path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12Z"/><circle cx="12" cy="12" r="3"/>'),back:Q('<path d="M20 12H5M11 6l-6 6 6 6"/>'),arrow:Q('<path d="M4 12h15M13 6l6 6-6 6"/>'),grid:Q('<rect x="4" y="4" width="7" height="7" rx="1.5"/><rect x="13" y="4" width="7" height="7" rx="1.5"/><rect x="4" y="13" width="7" height="7" rx="1.5"/><rect x="13" y="13" width="7" height="7" rx="1.5"/>'),list:Q('<path d="M8 6h12M8 12h12M8 18h12M4 6h.01M4 12h.01M4 18h.01" stroke-width="2.2"/>'),castle:Q('<path d="M4 21V9l3 1.5V7l3 1.5V7l4 0v1.5L17 7v3.5L20 9v12H4Z"/><path d="M10 21v-4h4v4"/>'),download:Q('<path d="M12 3v12M7 10l5 5 5-5M4 20h16" stroke-width="2.2"/>'),play:Q('<path d="M7 4.5v15l13-7.5-13-7.5Z"/>'),heart:Q('<path d="M12 20S4 14.5 4 9a4 4 0 0 1 8-1 4 4 0 0 1 8 1c0 5.5-8 11-8 11Z"/>'),box:Q('<path d="M3 7.5 12 3l9 4.5v9L12 21l-9-4.5v-9Z"/><path d="M3 7.5 12 12l9-4.5M12 12v9"/>'),gamepad:Q('<rect x="3" y="8" width="18" height="10" rx="4"/><path d="M8 11v4M6 13h4M15.5 12h.01M18 14h.01"/>'),coffee:Q('<path d="M4 9h13v4a5 5 0 0 1-5 5H9a5 5 0 0 1-5-5V9Z"/><path d="M17 10h2a2 2 0 0 1 0 4h-2M6 3v2M10 3v2M14 3v2"/>')};function Zo(t,e){let n=bt[t]||bt.sparkle;return`<span class="gqi${e?" "+e:""}">${n}</span>`}function Xo(t,e,n={}){let a=t.createEl("span",{attr:{class:"gqi"+(n.cls?" "+n.cls:"")}});return n.style&&a.setAttribute("style",n.style),a.innerHTML=bt[e]||bt.sparkle,a}var el=`
.gqi{display:inline-flex;align-items:center;justify-content:center;vertical-align:-0.15em;width:1.15em;height:1.15em;line-height:0}
.gqi svg{width:100%;height:100%;display:block}
.gqi.lg{width:1.5em;height:1.5em}
.gqi.xl{width:2.2em;height:2.2em}`;_r.exports={GI:bt,giHTML:Zo,giEl:Xo,GICON_CSS:el}});var $n=Y((qu,Sr)=>{"use strict";async function tl(t,e){let n=(Array.isArray(e)?e:[]).map(i=>String(i||"").trim()).filter(Boolean),a=[],s=new Set;for(let i of n){let r=i.indexOf("*");if(r<0){s.has(i)||(s.add(i),a.push(i));continue}let o=i.slice(0,r).replace(/\/$/,""),l=i.slice(r+1).replace(/^\//,""),c=null;try{c=await t.list(o)}catch(p){c=null}if(!(!c||!Array.isArray(c.folders)))for(let p of c.folders){let d=l?`${p}/${l}`:p;s.has(d)||(s.add(d),a.push(d))}}return a}Sr.exports={expandRoots:tl}});var xt=Y((Nu,Ir)=>{"use strict";var{expandRoots:nl}=$n(),Ae="engram-quest/lessons";async function Tr(t,e){try{return JSON.parse(await t.read(e))}catch(n){return null}}function Fn(t){return!t||typeof t!="object"?null:{title:String(t.title||""),topic:String(t.topic||""),icon:String(t.icon||"\u{1F4D8}"),description:String(t.description||""),tags:Array.isArray(t.tags)?t.tags.map(String):[],colorScheme:String(t.colorScheme||""),starred:!!t.starred,archived:!!t.archived,createdAt:t.createdAt||null,lessons:Array.isArray(t.lessons)?t.lessons.filter(e=>e&&e.id).map(e=>({id:String(e.id),title:String(e.title||e.file||""),file:e.file?String(e.file):null,source:e.source==="import"?"import":"skill",...e.importedAt?{importedAt:e.importedAt}:{}})):[],completion:t.completion&&typeof t.completion=="object"?t.completion:{}}}function at(t,e){let n=t&&t.completion&&t.completion[e]||{};return{viewed:!!n.viewed,completed:!!n.completed,starred:!!n.starred,lastViewed:n.lastViewed||null}}function Cr(t,e){let n=t.lessons.find(s=>s.id===e);if(n&&!n.file)return"planned";let a=at(t,e);return a.completed?"completed":a.viewed?"viewed":"new"}function Lr(t){let e=t.lessons.length,n=0;for(let a of t.lessons)at(t,a.id).completed&&n++;return{completed:n,total:e,pct:e>0?Math.round(n/e*100):0}}async function al(t,e){let n=await nl(t,e&&e.length?e:[Ae]),s=(await Promise.all(n.map(async i=>{let r=null;try{r=await t.list(i)}catch(o){r=null}return!r||!Array.isArray(r.folders)?[]:Promise.all(r.folders.map(async o=>{let l=String(o).split("/").pop();if(!l)return null;let c=Fn(await Tr(t,`${i}/${l}/meta.json`));return c?{slug:l,meta:c,root:i}:null}))}))).flat().filter(Boolean);return s.sort((i,r)=>i.meta.starred!==r.meta.starred?i.meta.starred?-1:1:String(r.meta.createdAt||"").localeCompare(String(i.meta.createdAt||""))),s}async function ze(t,e,n=Ae){return Fn(await Tr(t,`${n}/${e}/meta.json`))}async function Pe(t,e,n,a=Ae){await t.mkdir(`${a}/${e}`).catch(()=>{}),await t.write(`${a}/${e}/meta.json`,JSON.stringify(n,null,2))}async function rl(t,e,n,a={},s=Ae){let i=await ze(t,e,s);if(!i)return null;let o={...at(i,n)};for(let l of["viewed","completed","starred"])a[l]!==void 0&&(o[l]=!!a[l]);return a.viewed&&(o.lastViewed=new Date().toISOString()),i.completion[n]=o,await Pe(t,e,i,s),i}async function sl(t,{title:e,description:n="",icon:a="\u{1F4D8}",outline:s=[],root:i=Ae}){let r=Qn(e,`course-${Date.now()}`),o=new Date,l={title:String(e||r),topic:String(e||r),icon:a,description:String(n||""),tags:[],colorScheme:Ar[Math.abs(il(r))%Ar.length],starred:!1,archived:!1,createdAt:o.toISOString().slice(0,10),lessons:[],completion:{}};return s.filter(c=>String(c||"").trim()).forEach((c,p)=>{let d=`lsn-${Date.now()+p}`;l.lessons.push({id:d,title:String(c).trim(),file:null,source:"skill"}),l.completion[d]={viewed:!1,completed:!1,starred:!1}}),await Pe(t,r,l,i),{slug:r,meta:l,root:i}}var Ar=["indigo","green","amber","rose","cyan","violet","blue","teal","lime","orange","fuchsia","slate"];function il(t){let e=0;for(let n=0;n<t.length;n++)e=e*31+t.charCodeAt(n)|0;return e}async function ol(t,e,n,a=Ae){let s=await ze(t,e,a);if(!s)return null;let i=`lsn-${Date.now()}`,r={id:i,title:String(n||"").trim(),file:null,source:"skill"};return s.lessons.push(r),s.completion[i]={viewed:!1,completed:!1,starred:!1},await Pe(t,e,s,a),r}async function ll(t,e,n=Ae){let a=await ze(t,e,n);return a?(a.starred=!a.starred,await Pe(t,e,a,n),a.starred):null}async function cl(t,e,n=Ae){let a=await ze(t,e,n);return a?(a.archived=!a.archived,await Pe(t,e,a,n),a.archived):null}function Qn(t,e="import"){return String(t||"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40)||e}function Rr(t){let e=/<title[^>]*>([\s\S]*?)<\/title>/i.exec(String(t||""));return e&&e[1].replace(/\s+/g," ").trim()||null}async function dl(t,e,n,a,s=Ae){let i=await ze(t,e,s);if(!i)return null;let r=`lsn-${Date.now()}`,o=Rr(n)||String(a||"Imported lesson"),l=`${r}-${Qn(a||o)}.html`;await t.mkdir(`${s}/${e}`).catch(()=>{}),await t.write(`${s}/${e}/${l}`,String(n||""));let c={id:r,title:o,file:l,source:"import",importedAt:new Date().toISOString()};return i.lessons.push(c),i.completion[r]={viewed:!1,completed:!1,starred:!1},await Pe(t,e,i,s),c}async function Qt(t,e){try{if(t.exists&&!await t.exists(e))return!0}catch(n){}try{if(typeof t.trashSystem=="function"&&await t.trashSystem(e))return!0}catch(n){console.warn("EngramQuest: lesson trashSystem failed",n)}try{if(typeof t.trashLocal=="function")return await t.trashLocal(e),!0}catch(n){console.warn("EngramQuest: lesson trashLocal failed",n)}try{if(typeof t.remove=="function")return await t.remove(e),!0}catch(n){console.warn("EngramQuest: lesson remove failed",n)}return!1}async function pl(t,e,n,a=Ae){let s=await ze(t,e,a);if(!s)return null;let i=s.lessons.find(r=>r.id===n);return i&&i.file&&await Qt(t,`${a}/${e}/${i.file}`),s.lessons=s.lessons.filter(r=>r.id!==n),await Pe(t,e,s,a),s}async function hl(t,e,n=Ae){let a=await ze(t,e,n);if(a)for(let s of a.lessons)s.file&&await Qt(t,`${n}/${e}/${s.file}`);await Qt(t,`${n}/${e}/meta.json`);try{typeof t.rmdir=="function"&&await t.rmdir(`${n}/${e}`,!0)}catch(s){console.warn("EngramQuest: course folder cleanup failed",s)}}async function ul(t,e,n,a=Ae){let s=await ze(t,e,a);if(!s)return null;let i=new Map(s.lessons.map(o=>[o.id,o])),r=[];for(let o of n||[])i.has(o)&&(r.push(i.get(o)),i.delete(o));return s.lessons=[...r,...i.values()],await Pe(t,e,s,a),s}function gl(t,e=5){let n=[];for(let{slug:a,meta:s,root:i}of t)for(let r of s.lessons){let o=at(s,r.id);o.lastViewed&&n.push({slug:a,root:i,courseTitle:s.title,lesson:r,status:Cr(s,r.id),starred:o.starred,lastViewed:o.lastViewed})}return n.sort((a,s)=>String(s.lastViewed).localeCompare(String(a.lastViewed))),n.slice(0,e)}function ml(t){let e=0,n=0,a=0,s=0;for(let{meta:i}of t){let r=Lr(i);e+=r.total,n+=r.completed,r.total>0&&r.completed===r.total?s++:(r.completed>0||i.lessons.some(o=>at(i,o.id).viewed))&&a++}return{pct:e>0?Math.round(n/e*100):0,coursesInProgress:a,coursesCompleted:s,estimatedHours:Math.round(n*.5*10)/10}}Ir.exports={LESSONS_DIR:Ae,normalizeMeta:Fn,lessonCompletion:at,lessonStatus:Cr,courseProgress:Lr,listCourses:al,loadCourse:ze,saveCourse:Pe,markLesson:rl,createCourse:sl,addPlannedLesson:ol,toggleCourseStar:ll,toggleCourseArchive:cl,safeSlugPart:Qn,extractHtmlTitle:Rr,importLesson:dl,trashPath:Qt,deleteLesson:pl,deleteCourse:hl,reorderLessons:ul,recentLessons:gl,overallStats:ml}});var Bn=Y((Du,Nr)=>{"use strict";var Bt=".achievements",Ut=`${Bt}/state.json`;function Mr(){return{version:1,unlocked:{},stats:{}}}async function qr(t){try{if(!await t.exists(Ut))return Mr();let e=JSON.parse(await t.read(Ut));return{version:e.version||1,unlocked:e.unlocked&&typeof e.unlocked=="object"?e.unlocked:{},stats:e.stats&&typeof e.stats=="object"?e.stats:{}}}catch(e){return Mr()}}async function fl(t,e,n=new Date().toISOString()){if(!(!e||!e.length))try{await t.exists(Bt)||await t.mkdir(Bt).catch(()=>{});let a=await qr(t),s=!1;for(let i of e){let r=typeof i=="string"?i:i&&i.id;if(!r)continue;let o=typeof i=="object"?i.name:null,l=typeof i=="object"?i.rarity:null,c=a.unlocked[r];c?(o&&c.name!==o||l&&c.rarity!==l)&&(o&&(c.name=o),l&&(c.rarity=l),s=!0):(a.unlocked[r]={unlockedAt:n,...o?{name:o}:{},...l?{rarity:l}:{}},s=!0)}s&&await t.write(Ut,JSON.stringify(a,null,2))}catch(a){console.warn("EngramQuest: shared achievement ledger write failed",a)}}Nr.exports={SHARED_DIR:Bt,SHARED_PATH:Ut,readSharedState:qr,writeUnlockedIds:fl}});var jn=Y((Ou,zr)=>{"use strict";var Dr={cyan:{light:"#e8fbff",mid:"#5cd6ff",deep:"#1e7fc4",dark:"#0c3a5c",fire:!1},gold:{light:"#fff3c4",mid:"#ffd76b",deep:"#c8971f",dark:"#5c3d05",fire:!1},violet:{light:"#f3e8ff",mid:"#c99bff",deep:"#7a3fd0",dark:"#331058",fire:!1},green:{light:"#e8ffe8",mid:"#7dffb0",deep:"#1f9e5c",dark:"#0c3d24",fire:!1},rose:{light:"#ffe8ef",mid:"#ff8fae",deep:"#c23a63",dark:"#4d0f24",fire:!1},ice:{light:"#f0fbff",mid:"#bfe9ff",deep:"#6aa8d8",dark:"#274a63",fire:!1},fire:{light:"#ffe98a",mid:"#ffab2e",deep:"#ff5a1e",dark:"#5c1e00",fire:!0}},Or={UC:["#eef2f8","#9aa6b8","#59637a"],R:["#cdefff","#49a8e8","#1c5aa0"],LEG:["#ffe9a8","#f5c451","#b8862f"]},bl=[[-52,-46,1.3,.8],[46,-50,1,.6],[54,14,1.3,.65],[-56,20,1,.5],[-30,54,1.1,.6],[34,50,.9,.5],[10,-56,1,.6],[-14,-38,.8,.5]],Un={first_card:{tone:"cyan",body:'<rect x="-22" y="-30" width="44" height="60" rx="6" fill="%S%"/><path d="M-12 -6 H12 M-12 4 H12 M-12 -16 H6" stroke="%D%" stroke-width="2.4"/>'},ten_cards:{tone:"cyan",body:'<path d="M0 -26 C -14 -34 -34 -34 -44 -26 L -44 26 C -34 18 -14 18 0 26 Z" fill="%S%"/><path d="M0 -26 C 14 -34 34 -34 44 -26 L 44 26 C 34 18 14 18 0 26 Z" fill="%S%" opacity="0.92"/><path d="M0 -26 L0 26" stroke="%D%" stroke-width="2.4"/><path d="M-36 -18 L-10 -22 M-36 -8 L-10 -12 M36 -18 L10 -22 M36 -8 L10 -12" stroke="%D%" stroke-width="1.6" opacity="0.55"/>'},fifty_cards:{tone:"cyan",body:'<path d="M0 -20 C -14 -28 -34 -28 -44 -20 L -44 30 C -34 22 -14 22 0 30 Z" fill="%S%"/><path d="M0 -20 C 14 -28 34 -28 44 -20 L 44 30 C 34 22 14 22 0 30 Z" fill="%S%" opacity="0.92"/><path d="M0 -20 L0 30" stroke="%D%" stroke-width="2.2"/><path d="M0 -50 l3 6.6 7.2 .8 -5.3 4.8 1.5 7.1 -6.4 -3.7 -6.4 3.7 1.5 -7.1 -5.3 -4.8 7.2 -.8 Z" fill="%L%"/>'},century:{tone:"cyan",body:'<path d="M-22 -34 h34 a8 8 0 0 1 8 8 v44 a8 8 0 0 0 8 8 H-14 a8 8 0 0 1 -8 -8 Z" fill="%S%"/><path d="M-12 -20 H14 M-12 -8 H14 M-12 4 H6" stroke="%D%" stroke-width="2.2" opacity="0.7"/>'},five_hundred:{tone:"cyan",body:'<rect x="-24" y="-14" width="48" height="44" rx="6" fill="%S%"/><path d="M-16 -22 H26 a6 6 0 0 1 6 6 V18" fill="none" stroke="%S%" stroke-width="5" opacity="0.7"/><path d="M-12 4 H14" stroke="%D%" stroke-width="2.4"/>'},one_thousand:{tone:"violet",body:'<path d="M-26 -14 h52 l13 18 -39 40 -39 -40 Z" fill="%S%"/><path d="M-26 -14 l13 18 M26 -14 l-13 18 M-39 4 h78 M-13 4 L0 44 M13 4 L0 44" stroke="%D%" stroke-width="1.8" opacity="0.6" fill="none"/>'},two_thousand:{tone:"fire",body:'<path d="M0 -30 C 8 -20 10 -8 6 2 L -6 2 C -10 -8 -8 -20 0 -30 Z" fill="%S%"/><path d="M-4 2 C -30 -6 -50 -20 -54 -40 C -38 -30 -20 -26 -8 -18 C -18 -20 -30 -16 -40 -10 C -26 -8 -12 -6 -4 4 Z" fill="%S%"/><path d="M4 2 C 30 -6 50 -20 54 -40 C 38 -30 20 -26 8 -18 C 18 -20 30 -16 40 -10 C 26 -8 12 -6 4 4 Z" fill="%S%"/><path d="M-6 2 C -6 20 -2 36 0 46 C 2 36 6 20 6 2 Z" fill="%S%"/><circle cx="0" cy="-30" r="5.5" fill="%L%"/>'},four_thousand:{tone:"gold",body:'<path d="M-40 -14 l16 16 L0 -24 l24 26 16 -16 -7 44 h-66 Z" fill="%S%"/><path d="M-30 30 h60" stroke="%D%" stroke-width="2.4"/>'},ten_thousand:{tone:"gold",body:'<path d="M-40 -14 l16 16 L0 -24 l24 26 16 -16 -7 44 h-66 Z" fill="%S%"/><path d="M-30 30 h60" stroke="%D%" stroke-width="2.4"/><circle cx="-24" cy="-2" r="2.4" fill="%L%"/><circle cx="0" cy="-14" r="2.6" fill="%L%"/><circle cx="24" cy="-2" r="2.4" fill="%L%"/>'},streak_3:{tone:"fire",body:'<path d="M0 -34 C 12 -18 24 -8 24 10 A24 24 0 0 1 -24 10 C -24 -2 -18 -8 -12 -14 C -9 -2 -3 2 2 -2 C 2 -14 -2 -24 0 -34 Z" fill="%S%"/>'},streak_7:{tone:"fire",body:'<circle cx="0" cy="4" r="40" fill="none" stroke="%S%" stroke-width="3" opacity="0.5"/><path d="M0 -30 C 10 -16 20 -6 20 10 A20 20 0 0 1 -20 10 C -20 0 -15 -5 -10 -11 C -7 -1 -3 2 1 -1 C 1 -12 -2 -20 0 -30 Z" fill="%S%"/>'},streak_30:{tone:"fire",body:'<path d="M40 -20 A34 34 0 1 0 40 24 A26 26 0 1 1 40 -20 Z" fill="%S%" opacity="0.4"/><path d="M0 -28 C 10 -14 20 -5 20 11 A20 20 0 0 1 -20 11 C -20 1 -15 -4 -10 -10 C -7 0 -3 3 1 0 C 1 -11 -2 -19 0 -28 Z" fill="%S%"/>'},streak_100:{tone:"fire",body:'<path d="M-12 -26 C 0 -12 8 -4 8 11 A18 18 0 0 1 -28 11 C -28 2 -23 -3 -19 -9 C -16 0 -13 2 -10 0 C -10 -10 -13 -18 -12 -26 Z" fill="%S%"/><path d="M14 -20 C 24 -8 30 -1 30 12 A16 16 0 0 1 0 12 C 0 4 4 0 8 -5 C 10 2 13 3 15 1 C 15 -8 13 -14 14 -20 Z" fill="%S%" opacity="0.9"/>'},streak_365:{tone:"fire",body:'<g stroke="%S%" stroke-width="1.6" opacity="0.6"><path d="M0 -46 v8 M0 46 v-8 M-46 0 h8 M46 0 h-8 M-33 -33 l6 6 M33 33 l-6 -6 M33 -33 l-6 6 M-33 33 l6 -6"/></g><path d="M0 -28 C 10 -14 20 -5 20 11 A20 20 0 0 1 -20 11 C -20 1 -15 -4 -10 -10 C -7 0 -3 3 1 0 C 1 -11 -2 -19 0 -28 Z" fill="%S%"/>'},daily_20:{tone:"cyan",body:'<path d="M8 -38 L-16 6 H2 L-6 38 L22 -8 H4 Z" fill="%S%"/>'},daily_50:{tone:"cyan",body:'<path d="M-2 -38 L-24 2 H-10 L-16 34 L6 -6 H-8 Z" fill="%S%"/><path d="M18 -30 L2 -2 H12 L8 22 L24 -6 H14 Z" fill="%S%" opacity="0.9"/>'},daily_80:{tone:"cyan",body:'<circle cx="0" cy="0" r="40" fill="none" stroke="%S%" stroke-width="3" opacity="0.45"/><path d="M8 -34 L-14 6 H2 L-6 34 L20 -6 H4 Z" fill="%S%"/>'},daily_100:{tone:"cyan",body:'<path d="M2 -38 L-20 0 H-6 L-12 30 L8 -6 H-6 Z" fill="%S%"/><path d="M22 -24 L8 2 H18 L14 24 L28 0 H18 Z" fill="%S%" opacity="0.85"/><path d="M-22 -18 L-30 0 M22 18 L30 26" stroke="%S%" stroke-width="2" opacity="0.5"/>'},daily_150:{tone:"cyan",body:'<circle cx="0" cy="0" r="26" fill="%S%"/><circle cx="0" cy="0" r="26" fill="none" stroke="%L%" stroke-width="1.2" opacity="0.6"/><path d="M-16 -4 C -6 -14 6 6 16 -4" fill="none" stroke="%D%" stroke-width="2.4" opacity="0.6"/><path d="M-16 8 C -6 -2 6 18 16 8" fill="none" stroke="%D%" stroke-width="2.4" opacity="0.5"/>'},daily_200:{tone:"gold",body:'<g stroke="%S%" stroke-width="2.2" opacity="0.55"><path d="M0 -44 v10 M0 44 v-10 M-44 0 h10 M44 0 h-10 M-31 -31 l7 7 M31 31 l-7 -7 M31 -31 l-7 7 M-31 31 l7 -7"/></g><path d="M8 -32 L-14 6 H2 L-6 32 L20 -6 H4 Z" fill="%S%"/>'},synapse_awaken:{tone:"violet",body:'<circle cx="0" cy="0" r="10" fill="%S%"/><g fill="%S%"><circle cx="-30" cy="-24" r="5"/><circle cx="30" cy="-24" r="5"/><circle cx="-30" cy="26" r="5"/><circle cx="30" cy="26" r="5"/></g><path d="M-8 -6 L-26 -20 M8 -6 L26 -20 M-8 6 L-26 22 M8 6 L26 22" stroke="%S%" stroke-width="2.2"/>'},master_50:{tone:"violet",body:'<path d="M-4 -30 C -20 -30 -30 -18 -28 -6 C -34 2 -30 16 -20 20 C -18 30 -6 32 -4 24 Z" fill="%S%"/><path d="M4 -30 C 20 -30 30 -18 28 -6 C 34 2 30 16 20 20 C 18 30 6 32 4 24 Z" fill="%S%" opacity="0.92"/><path d="M0 -28 V26" stroke="%D%" stroke-width="2" opacity="0.5"/>'},master_200:{tone:"violet",body:'<path d="M-4 -24 C -20 -24 -30 -12 -28 0 C -34 8 -30 22 -20 26 C -18 34 -6 36 -4 28 Z" fill="%S%"/><path d="M4 -24 C 20 -24 30 -12 28 0 C 34 8 30 22 20 26 C 18 34 6 36 4 28 Z" fill="%S%" opacity="0.92"/><path d="M-20 -34 l4 8 8 1 -6 5.5 1.6 8 -7.6 -4.2 -7.6 4.2 1.6 -8 -6 -5.5 8 -1 Z" fill="%L%"/><path d="M20 -34 l4 8 8 1 -6 5.5 1.6 8 -7.6 -4.2 -7.6 4.2 1.6 -8 -6 -5.5 8 -1 Z" fill="%L%"/>'},synapse_network:{tone:"violet",body:'<circle cx="0" cy="0" r="8" fill="%S%"/><g fill="%S%"><circle cx="-34" cy="-30" r="4"/><circle cx="34" cy="-30" r="4"/><circle cx="-40" cy="6" r="4"/><circle cx="40" cy="6" r="4"/><circle cx="-20" cy="34" r="4"/><circle cx="20" cy="34" r="4"/><circle cx="0" cy="-40" r="4"/></g><g stroke="%S%" stroke-width="1.8" opacity="0.75"><path d="M-6 -5 L-31 -27 M6 -5 L31 -27 M-7 -3 L-37 5 M7 -3 L37 5 M-5 7 L-18 31 M5 7 L18 31 M0 -8 L0 -37"/></g>'},first_memory_map:{tone:"green",body:'<rect x="-40" y="-28" width="80" height="56" rx="8" fill="none" stroke="%S%" stroke-width="3"/><g fill="%S%"><circle cx="-18" cy="-8" r="5"/><circle cx="18" cy="-12" r="5"/><circle cx="2" cy="14" r="5"/></g><path d="M-14 -6 L-2 12 M14 -10 L2 12 M-13 -10 L13 -12" stroke="%S%" stroke-width="2"/>'},first_quest_map:{tone:"green",body:'<path d="M-40 -22 l22 -8 20 8 22 -8 v52 l-22 8 -20 -8 -22 8 Z" fill="%S%" opacity="0.85"/><path d="M-18 -30 v52 M22 -22 v52" stroke="%D%" stroke-width="1.8" opacity="0.5"/><path d="M6 8 L6 -22 M6 -22 L24 -16 L6 -10" fill="%L%" stroke="%L%" stroke-width="1.5"/>'},quest_cleared:{tone:"gold",body:'<path d="M-16 34 V-32 h30 l-6 8.5 6 8.5 h-30" fill="%S%"/><path d="M-16 34 V-32" stroke="%D%" stroke-width="2.6"/><path d="M2 4 l7 7 12 -14" fill="none" stroke="%L%" stroke-width="3.4"/>'},multiple_decks:{tone:"cyan",body:'<rect x="-4" y="-26" width="34" height="46" rx="5" fill="%S%" opacity="0.6" transform="rotate(12 13 -3)"/><rect x="-30" y="-24" width="34" height="46" rx="5" fill="%S%" opacity="0.8" transform="rotate(-12 -13 -1)"/><rect x="-15" y="-16" width="34" height="46" rx="5" fill="%S%"/>'},perfect_session:{tone:"gold",body:'<circle cx="0" cy="-6" r="24" fill="%S%"/><path d="M0 -22 l4.8 9.7 10.7 1.6 -7.8 7.5 1.9 10.6 -9.6 -5 -9.6 5 1.9 -10.6 -7.8 -7.5 10.7 -1.6 Z" fill="%D%" opacity="0.55"/><path d="M-14 16 L-19 40 L0 30 L19 40 L14 16" fill="none" stroke="%S%" stroke-width="3"/>'},hundred_again:{tone:"cyan",body:'<path d="M-30 0 A30 30 0 0 1 22 -20" fill="none" stroke="%S%" stroke-width="4"/><path d="M22 -30 v12 h-12" fill="none" stroke="%S%" stroke-width="4"/><path d="M30 0 A30 30 0 0 1 -22 20" fill="none" stroke="%S%" stroke-width="4"/><path d="M-22 30 v-12 h12" fill="none" stroke="%S%" stroke-width="4"/>'},first_lesson:{tone:"cyan",body:'<path d="M0 -22 L44 -6 L0 10 L-44 -6 Z" fill="%S%"/><path d="M-26 0 v16 C -26 24 26 24 26 16 V0" fill="none" stroke="%S%" stroke-width="4"/><path d="M40 -5 v18" stroke="%S%" stroke-width="2.4"/>'},course_finisher:{tone:"cyan",body:'<path d="M0 -26 L44 -10 L0 6 L-44 -10 Z" fill="%S%"/><path d="M-24 -3 v14 C -24 19 24 19 24 11 V-3" fill="none" stroke="%S%" stroke-width="4"/><path d="M-10 26 l7 7 14 -16" fill="none" stroke="%L%" stroke-width="3.4"/>'},lesson_marathon:{tone:"cyan",body:'<path d="M0 -24 L42 -9 L0 6 L-42 -9 Z" fill="%S%"/><path d="M-22 -1 v13 C -22 20 22 20 22 12 V-1" fill="none" stroke="%S%" stroke-width="3.6"/><path d="M-30 8 C -40 20 -40 34 -30 40 M30 8 C 40 20 40 34 30 40" fill="none" stroke="%L%" stroke-width="2.4" opacity="0.7"/>'},first_quest_step:{tone:"rose",body:'<circle cx="0" cy="0" r="34" fill="none" stroke="%S%" stroke-width="4"/><circle cx="0" cy="0" r="18" fill="none" stroke="%S%" stroke-width="4"/><circle cx="0" cy="0" r="5" fill="%S%"/>'},quest_veteran:{tone:"rose",body:'<circle cx="0" cy="0" r="30" fill="none" stroke="%S%" stroke-width="3.6"/><circle cx="0" cy="0" r="14" fill="none" stroke="%S%" stroke-width="3.6"/><circle cx="0" cy="0" r="4" fill="%S%"/><g stroke="%S%" stroke-width="3"><path d="M0 -44 v10 M0 44 v-10 M-44 0 h10 M44 0 h-10"/></g>'},first_charted:{tone:"gold",body:'<path d="M-26 12 L0 -18 L26 12" fill="none" stroke="%S%" stroke-width="6"/><path d="M0 -28 l4 8.5 9.4 1 -7 6.4 2 9.2 -8.4 -4.8 -8.4 4.8 2 -9.2 -7 -6.4 9.4 -1 Z" fill="%L%" opacity="0.9"/>'},navigator_reached:{tone:"gold",body:'<path d="M-26 2 L0 -28 L26 2" fill="none" stroke="%S%" stroke-width="6"/><path d="M-26 22 L0 -8 L26 22" fill="none" stroke="%S%" stroke-width="6"/>'},pioneer_reached:{tone:"gold",body:'<path d="M-20 -8 l5 11 12 1.4 -9 8 2.4 12 -10.4 -6.2 -10.4 6.2 2.4 -12 -9 -8 12 -1.4 Z" fill="%S%"/><path d="M20 -8 l5 11 12 1.4 -9 8 2.4 12 -10.4 -6.2 -10.4 6.2 2.4 -12 -9 -8 12 -1.4 Z" fill="%S%"/>'},starfarer_reached:{tone:"gold",body:'<ellipse cx="0" cy="0" rx="42" ry="15" fill="none" stroke="%S%" stroke-width="2.4" opacity="0.6" transform="rotate(-22)"/><path d="M0 -34 l7 15 16.5 2 -12 11 3 16.5 -14.5 -8.5 -14.5 8.5 3 -16.5 -12 -11 16.5 -2 Z" fill="%S%"/>'},first_colonized:{tone:"ice",body:'<ellipse cx="0" cy="4" rx="48" ry="15" fill="none" stroke="%S%" stroke-width="2.4" opacity="0.7" transform="rotate(-18 0 4)"/><circle cx="0" cy="6" r="28" fill="%S%"/><g fill="%L%"><circle cx="-8" cy="0" r="2"/><circle cx="6" cy="6" r="2.2"/><circle cx="-12" cy="12" r="1.8"/><circle cx="12" cy="14" r="1.9"/><circle cx="16" cy="2" r="1.6"/></g><path d="M-2 -22 v-20 M-2 -42 l18 6 -18 5" fill="%L%" stroke="%L%" stroke-width="1.6"/>'},colony_ten:{tone:"ice",body:'<circle cx="-14" cy="10" r="22" fill="%S%"/><circle cx="20" cy="-14" r="12" fill="%S%" opacity="0.85"/><g fill="%L%"><circle cx="-20" cy="4" r="2"/><circle cx="-8" cy="10" r="2.2"/><circle cx="-16" cy="18" r="1.8"/><circle cx="18" cy="-16" r="1.6"/><circle cx="24" cy="-10" r="1.6"/></g><circle cx="30" cy="18" r="3" fill="%L%" opacity="0.7"/>'},constellation_complete:{tone:"gold",body:'<path d="M-24 -18 L4 -28 L26 -8 L14 20 L-16 16 Z M-16 16 L-30 32" fill="none" stroke="%S%" stroke-width="2" opacity="0.7"/><g fill="%S%"><circle cx="-24" cy="-18" r="3.4"/><circle cx="4" cy="-28" r="4"/><circle cx="26" cy="-8" r="3.4"/><circle cx="14" cy="20" r="4"/><circle cx="-16" cy="16" r="3.4"/><circle cx="-30" cy="32" r="2.6"/></g>'},deep_space:{tone:"gold",body:'<circle cx="0" cy="0" r="40" fill="none" stroke="%S%" stroke-width="1.6" opacity="0.25"/><circle cx="0" cy="0" r="30" fill="none" stroke="%S%" stroke-width="2" opacity="0.45"/><circle cx="0" cy="0" r="20" fill="none" stroke="%S%" stroke-width="2.4" opacity="0.7"/><circle cx="0" cy="0" r="9" fill="%S%"/><path d="M0 -40 l3.4 6.6 -6.8 0 Z" fill="%L%"/>'},cartographer:{tone:"gold",body:'<circle cx="0" cy="0" r="34" fill="none" stroke="%S%" stroke-width="2.4" opacity="0.7"/><path d="M0 -40 L6 -6 L40 0 L6 6 L0 40 L-6 6 L-40 0 L-6 -6 Z" fill="%S%"/><circle cx="0" cy="0" r="4" fill="%L%"/>'}};function xl(t,e,n=88){let a=Un[t]||Un.first_quest_step,s=Dr[a.tone]||Dr.cyan,i=Or[e]||Or.UC,r="b"+t.replace(/[^a-z0-9]/gi,""),o=s.fire?`<linearGradient id="${r}s" x1="0" y1="1" x2="0" y2="0"><stop offset="0%" stop-color="${s.deep}"/><stop offset="50%" stop-color="${s.mid}"/><stop offset="100%" stop-color="${s.light}"/></linearGradient>`:`<radialGradient id="${r}s" cx="42%" cy="38%" r="70%"><stop offset="0%" stop-color="${s.light}"/><stop offset="50%" stop-color="${s.mid}"/><stop offset="100%" stop-color="${s.deep}"/></radialGradient>`,l=a.body.replace(/%S%/g,`url(#${r}s)`).replace(/%D%/g,s.dark).replace(/%L%/g,"#fff7d6"),c=bl.map(([p,d,h,u])=>`<circle cx="${p}" cy="${d}" r="${h}" fill="#ffffff" opacity="${u}"/>`).join("");return`<svg viewBox="0 0 140 140" width="${n}" height="${n}" xmlns="http://www.w3.org/2000/svg"><defs><radialGradient id="${r}bg" cx="50%" cy="40%" r="72%"><stop offset="0%" stop-color="#2c2560"/><stop offset="48%" stop-color="#171236"/><stop offset="100%" stop-color="#080614"/></radialGradient><linearGradient id="${r}fr" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="${i[0]}"/><stop offset="50%" stop-color="${i[1]}"/><stop offset="100%" stop-color="${i[2]}"/></linearGradient>`+o+`<filter id="${r}g" x="-60%" y="-60%" width="220%" height="220%"><feGaussianBlur stdDeviation="2.6" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs><rect x="6" y="6" width="128" height="128" rx="28" fill="url(#${r}bg)" stroke="url(#${r}fr)" stroke-width="3.5"/><rect x="6" y="6" width="128" height="128" rx="28" fill="none" stroke="#ffffff" stroke-width="0.6" opacity="0.22"/><g transform="translate(70,70)">${c}<g filter="url(#${r}g)">${l}</g></g></svg>`}zr.exports={BADGE_ART:Un,badgeSvg:xl}});var Wn=Y((Pu,Br)=>{"use strict";var vt=require("obsidian"),{t:de}=ge(),{giHTML:zu}=be(),{getReviewStatus:yl}=Se(),{listCourses:vl,courseProgress:wl}=xt(),{readSharedState:El,writeUnlockedIds:kl}=Bn(),{badgeSvg:Pr}=jn(),yt=[{id:"first_card",icon:"assets/icons/first_card.webp",rarity:"UC",threshold:1,field:"totalCardsReviewed",nameKey:"ACH_FIRST_CARD_NAME",descKey:"ACH_FIRST_CARD_DESC"},{id:"ten_cards",icon:"assets/icons/ten_cards.webp",rarity:"UC",threshold:10,field:"totalCardsReviewed",nameKey:"ACH_TEN_CARDS_NAME",descKey:"ACH_TEN_CARDS_DESC"},{id:"fifty_cards",icon:"assets/icons/fifty_cards.webp",rarity:"R",threshold:50,field:"totalCardsReviewed",nameKey:"ACH_FIFTY_CARDS_NAME",descKey:"ACH_FIFTY_CARDS_DESC"},{id:"century",icon:"assets/icons/century.webp",rarity:"R",threshold:100,field:"totalCardsReviewed",nameKey:"ACH_CENTURY_NAME",descKey:"ACH_CENTURY_DESC"},{id:"five_hundred",icon:"assets/icons/five_hundred.webp",rarity:"LEG",threshold:500,field:"totalCardsReviewed",nameKey:"ACH_FIVE_HUNDRED_NAME",descKey:"ACH_FIVE_HUNDRED_DESC"},{id:"one_thousand",icon:"assets/icons/one_thousand.webp",rarity:"LEG",threshold:1e3,field:"totalCardsReviewed",nameKey:"ACH_ONE_THOUSAND_NAME",descKey:"ACH_ONE_THOUSAND_DESC"},{id:"two_thousand",icon:"assets/icons/two_thousand.webp",rarity:"LEG",threshold:2e3,field:"totalCardsReviewed",nameKey:"ACH_TWO_THOUSAND_NAME",descKey:"ACH_TWO_THOUSAND_DESC"},{id:"streak_3",icon:"assets/icons/streak_3.webp",rarity:"UC",threshold:3,field:"longestStreak",nameKey:"ACH_STREAK_3_NAME",descKey:"ACH_STREAK_3_DESC"},{id:"streak_7",icon:"assets/icons/streak_7.webp",rarity:"R",threshold:7,field:"longestStreak",nameKey:"ACH_STREAK_7_NAME",descKey:"ACH_STREAK_7_DESC"},{id:"streak_30",icon:"assets/icons/streak_30.webp",rarity:"LEG",threshold:30,field:"longestStreak",nameKey:"ACH_STREAK_30_NAME",descKey:"ACH_STREAK_30_DESC"},{id:"daily_20",icon:"assets/icons/daily_20.webp",rarity:"UC",threshold:20,field:"maxDaily",nameKey:"ACH_DAILY_20_NAME",descKey:"ACH_DAILY_20_DESC"},{id:"daily_50",icon:"assets/icons/daily_50.webp",rarity:"R",threshold:50,field:"maxDaily",nameKey:"ACH_DAILY_50_NAME",descKey:"ACH_DAILY_50_DESC"},{id:"daily_80",icon:"assets/icons/daily_80.webp",rarity:"LEG",threshold:80,field:"maxDaily",nameKey:"ACH_DAILY_80_NAME",descKey:"ACH_DAILY_80_DESC"},{id:"daily_100",icon:"assets/icons/daily_100.webp",rarity:"LEG",threshold:100,field:"maxDaily",nameKey:"ACH_DAILY_100_NAME",descKey:"ACH_DAILY_100_DESC"},{id:"daily_150",icon:"assets/icons/daily_150.webp",rarity:"LEG",threshold:150,field:"maxDaily",nameKey:"ACH_DAILY_150_NAME",descKey:"ACH_DAILY_150_DESC"},{id:"daily_200",icon:"assets/icons/daily_200.webp",rarity:"LEG",threshold:200,field:"maxDaily",nameKey:"ACH_DAILY_200_NAME",descKey:"ACH_DAILY_200_DESC"},{id:"master_50",icon:"assets/icons/master_50.webp",rarity:"LEG",threshold:50,field:"masteredCards",nameKey:"ACH_MASTER_50_NAME",descKey:"ACH_MASTER_50_DESC"},{id:"four_thousand",icon:"assets/icons/four_thousand.webp",rarity:"LEG",threshold:4e3,field:"totalCardsReviewed",nameKey:"ACH_FOUR_THOUSAND_NAME",descKey:"ACH_FOUR_THOUSAND_DESC"},{id:"ten_thousand",icon:"assets/icons/ten_thousand.webp",rarity:"LEG",threshold:1e4,field:"totalCardsReviewed",nameKey:"ACH_TEN_THOUSAND_NAME",descKey:"ACH_TEN_THOUSAND_DESC"},{id:"streak_100",icon:"assets/icons/streak_100.webp",rarity:"LEG",threshold:100,field:"longestStreak",nameKey:"ACH_STREAK_100_NAME",descKey:"ACH_STREAK_100_DESC"},{id:"streak_365",icon:"assets/icons/streak_365.webp",rarity:"LEG",threshold:365,field:"longestStreak",nameKey:"ACH_STREAK_365_NAME",descKey:"ACH_STREAK_365_DESC"},{id:"master_200",icon:"assets/icons/master_200.webp",rarity:"LEG",threshold:200,field:"masteredCards",nameKey:"ACH_MASTER_200_NAME",descKey:"ACH_MASTER_200_DESC"},{id:"synapse_awaken",icon:"assets/icons/synapse_awaken.webp",rarity:"R",threshold:10,field:"masteredCards",nameKey:"ACH_SYNAPSE_AWAKEN_NAME",descKey:"ACH_SYNAPSE_AWAKEN_DESC"},{id:"synapse_network",icon:"assets/icons/synapse_network.webp",rarity:"LEG",threshold:300,field:"masteredCards",nameKey:"ACH_SYNAPSE_NETWORK_NAME",descKey:"ACH_SYNAPSE_NETWORK_DESC"},{id:"first_memory_map",icon:"assets/icons/first_memory_map.webp",rarity:"R",threshold:1,field:"memoryMapCount",nameKey:"ACH_FIRST_MEMORY_MAP_NAME",descKey:"ACH_FIRST_MEMORY_MAP_DESC"},{id:"first_quest_map",icon:"assets/icons/first_quest_map.webp",rarity:"R",threshold:1,field:"questMapCount",nameKey:"ACH_FIRST_QUEST_MAP_NAME",descKey:"ACH_FIRST_QUEST_MAP_DESC"},{id:"quest_cleared",icon:"assets/icons/quest_cleared.webp",rarity:"LEG",threshold:1,field:"clearedQuestCount",nameKey:"ACH_QUEST_CLEARED_NAME",descKey:"ACH_QUEST_CLEARED_DESC"},{id:"multiple_decks",icon:"assets/icons/multiple_decks.webp",rarity:"R",threshold:3,field:"deckCount",nameKey:"ACH_MULTIPLE_DECKS_NAME",descKey:"ACH_MULTIPLE_DECKS_DESC"},{id:"perfect_session",icon:"assets/icons/perfect_session.webp",rarity:"LEG",threshold:1,field:"perfectSessions",nameKey:"ACH_PERFECT_SESSION_NAME",descKey:"ACH_PERFECT_SESSION_DESC"},{id:"hundred_again",icon:"assets/icons/hundred_again.webp",rarity:"R",threshold:100,field:"totalAgainCount",nameKey:"ACH_HUNDRED_AGAIN_NAME",descKey:"ACH_HUNDRED_AGAIN_DESC"},{id:"first_lesson",icon:"assets/icons/first_lesson.webp",rarity:"UC",threshold:1,field:"completedLessons",nameKey:"ACH_FIRST_LESSON_NAME",descKey:"ACH_FIRST_LESSON_DESC"},{id:"course_finisher",icon:"assets/icons/course_finisher.webp",rarity:"R",threshold:1,field:"completedCourses",nameKey:"ACH_COURSE_FINISHER_NAME",descKey:"ACH_COURSE_FINISHER_DESC"},{id:"lesson_marathon",icon:"assets/icons/lesson_marathon.webp",rarity:"R",threshold:10,field:"completedLessons",nameKey:"ACH_LESSON_MARATHON_NAME",descKey:"ACH_LESSON_MARATHON_DESC"},{id:"first_quest_step",icon:"assets/icons/first_quest_step.webp",rarity:"UC",threshold:1,field:"completedQuestNodes",nameKey:"ACH_FIRST_QUEST_STEP_NAME",descKey:"ACH_FIRST_QUEST_STEP_DESC"},{id:"quest_veteran",icon:"assets/icons/quest_veteran.webp",rarity:"R",threshold:10,field:"completedQuestNodes",nameKey:"ACH_QUEST_VETERAN_NAME",descKey:"ACH_QUEST_VETERAN_DESC"},{id:"first_charted",icon:"assets/icons/first_quest_map.webp",rarity:"UC",threshold:1,field:"chartedNotes",nameKey:"ACH_FIRST_CHARTED_NAME",descKey:"ACH_FIRST_CHARTED_DESC"},{id:"navigator_reached",icon:"assets/icons/first_quest_map.webp",rarity:"UC",threshold:5,field:"chartedNotes",nameKey:"ACH_NAVIGATOR_REACHED_NAME",descKey:"ACH_NAVIGATOR_REACHED_DESC"},{id:"pioneer_reached",icon:"assets/icons/first_quest_map.webp",rarity:"R",threshold:30,field:"chartedNotes",nameKey:"ACH_PIONEER_REACHED_NAME",descKey:"ACH_PIONEER_REACHED_DESC"},{id:"starfarer_reached",icon:"assets/icons/first_quest_map.webp",rarity:"LEG",threshold:120,field:"chartedNotes",nameKey:"ACH_STARFARER_REACHED_NAME",descKey:"ACH_STARFARER_REACHED_DESC"},{id:"first_colonized",icon:"assets/icons/quest_cleared.webp",rarity:"R",threshold:1,field:"colonizedNotes",nameKey:"ACH_FIRST_COLONIZED_NAME",descKey:"ACH_FIRST_COLONIZED_DESC"},{id:"colony_ten",icon:"assets/icons/quest_cleared.webp",rarity:"LEG",threshold:10,field:"colonizedNotes",nameKey:"ACH_COLONY_TEN_NAME",descKey:"ACH_COLONY_TEN_DESC"},{id:"constellation_complete",icon:"assets/icons/quest_cleared.webp",rarity:"R",threshold:1,field:"curriculaComplete",nameKey:"ACH_CONSTELLATION_NAME",descKey:"ACH_CONSTELLATION_DESC"},{id:"deep_space",icon:"assets/icons/first_quest_map.webp",rarity:"R",threshold:1,field:"deepCharted",nameKey:"ACH_DEEP_SPACE_NAME",descKey:"ACH_DEEP_SPACE_DESC"},{id:"cartographer",icon:"assets/icons/first_quest_map.webp",rarity:"LEG",threshold:1,field:"domainCharted",nameKey:"ACH_CARTOGRAPHER_NAME",descKey:"ACH_CARTOGRAPHER_DESC"}],jt={UC:{badge:"#9ca3af",bg:"linear-gradient(145deg,#2d3748,#1a202c)",glow:"rgba(156,163,175,0.4)",ring:"rgba(156,163,175,0.15)"},R:{badge:"#60a5fa",bg:"linear-gradient(145deg,#1a2f5e,#1e3a8a)",glow:"rgba(96,165,250,0.6)",ring:"rgba(96,165,250,0.18)"},LEG:{badge:"#f59e0b",bg:"linear-gradient(145deg,#1c1a0f,#2d2000)",glow:"rgba(245,158,11,0.8)",ring:"rgba(245,158,11,0.22)"}},Gn={UC:{badge:"#6b7280",bg:"linear-gradient(145deg,#f8fafc,#f1f5f9)",glow:"rgba(156,163,175,0.25)",ring:"rgba(156,163,175,0.1)"},R:{badge:"#3b82f6",bg:"linear-gradient(145deg,#eff6ff,#dbeafe)",glow:"rgba(59,130,246,0.3)",ring:"rgba(59,130,246,0.12)"},LEG:{badge:"#d97706",bg:"linear-gradient(145deg,#fffbeb,#fef3c7)",glow:"rgba(245,158,11,0.4)",ring:"rgba(245,158,11,0.15)"}};function _l(t,e){let n=t.mastered+t.learning+t.due+t.unseen,a=e?"rgba(255,255,255,0.1)":"#e5e7eb",s=e?"#e2e8f0":"#1f2937",i=e?"#94a3b8":"#6b7280";if(n===0)return`<svg width="140" height="140" viewBox="0 0 140 140"><circle cx="70" cy="70" r="52" fill="none" stroke="${a}" stroke-width="24"/><text x="70" y="75" text-anchor="middle" font-size="11" fill="${i}">0 cards</text></svg>`;let r=70,o=70,l=52,c=30,p=[{n:t.mastered,c:"#22c55e"},{n:t.learning,c:"#f59e0b"},{n:t.due,c:"#ef4444"},{n:t.unseen,c:"#94a3b8"}],d="",h=-Math.PI/2;for(let x of p){if(!x.n)continue;let m=x.n/n*2*Math.PI,g=h+m,b=m>Math.PI?1:0,f=v=>v.toFixed(2);d+=`<path d="M${f(r+l*Math.cos(h))} ${f(o+l*Math.sin(h))} A${l} ${l} 0 ${b} 1 ${f(r+l*Math.cos(g))} ${f(o+l*Math.sin(g))} L${f(r+c*Math.cos(g))} ${f(o+c*Math.sin(g))} A${c} ${c} 0 ${b} 0 ${f(r+c*Math.cos(h))} ${f(o+c*Math.sin(h))} Z" fill="${x.c}"/>`,h=g}let u=Math.round(t.mastered/n*100);return`<svg width="140" height="140" viewBox="0 0 140 140">${d}<text x="70" y="66" text-anchor="middle" font-size="20" font-weight="800" fill="${s}">${u}%</text><text x="70" y="82" text-anchor="middle" font-size="9" fill="${i}" font-weight="700">MASTERED</text></svg>`}function Sl(t,e,n){let a=T=>String(T).padStart(2,"0"),s=T=>`${T.getFullYear()}-${a(T.getMonth()+1)}-${a(T.getDate())}`,i=new Date,r=s(i),o=[];for(let T=e-1;T>=0;T--){let q=new Date(i);q.setDate(q.getDate()-T),o.push(s(q))}let l=o.map(T=>t[T]||0),c=Math.max(...l,1),p=l.filter(T=>T>0).length,d=l.reduce((T,q)=>T+q,0),h=p>0?(d/p).toFixed(1):0,u=520,x=140,m=28,g=4,b=6,f=22,v=u-m-g,y=x-b-f,w=Math.max(2,v/e-1.2),k=(v-w)/Math.max(e-1,1),E="";o.forEach((T,q)=>{let I=(m+q*k).toFixed(1),R=l[q],O=R>0?Math.max(3,R/c*y):2,$=(b+y-O).toFixed(1),B=T===r?"#818cf8":R>0?"#6366f1":n?"rgba(255,255,255,0.08)":"#e5e7eb",P=R>0?"1":"0.6";E+=`<rect x="${I}" y="${$}" width="${w.toFixed(1)}" height="${O.toFixed(1)}" fill="${B}" rx="2" opacity="${P}"/>`});let S=e<=7?1:e<=30?7:14,_=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],C="";o.forEach((T,q)=>{if(q%S!==0&&q!==e-1)return;let I=(m+q*k+w/2).toFixed(1),R=new Date(T),O=e<=7?_[R.getDay()]:`${a(R.getMonth()+1)}/${a(R.getDate())}`;C+=`<text x="${I}" y="${x-2}" text-anchor="middle" font-size="7.5" fill="#9ca3af">${O}</text>`});let L=`<text x="${m-3}" y="${b+8}" text-anchor="end" font-size="7.5" fill="#9ca3af">${c}</text>`,A=`<line x1="${m}" y1="${b+y}" x2="${u-g}" y2="${b+y}" stroke="${n?"rgba(255,255,255,0.12)":"#e5e7eb"}" stroke-width="1"/>`;return{svg:`<svg width="100%" height="${x}" viewBox="0 0 ${u} ${x}">${A}${E}${L}${C}</svg>`,studyDays:p,total:d,avgActive:h,days:e}}function Hr(t,e,n,a,s,i){t.empty();let r=i?"#252538":"#f8faff",o=i?"#3a3a5a":"#e5e7eb",l=i?"#e2e8f0":"#374151",c=t.createEl("div",{attr:{style:"display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;"}});c.createEl("span",{text:de(n,"ACH_ACTIVITY"),attr:{style:`font-size:13px;font-weight:700;color:${l};`}});let p=c.createEl("div",{attr:{style:"display:flex;gap:4px;"}});[{days:7,label:de(n,"ACH_PERIOD_7")},{days:30,label:de(n,"ACH_PERIOD_30")},{days:90,label:de(n,"ACH_PERIOD_90")}].forEach(y=>{let w=y.days===a,k=w?i?"#3730a3":"#eef2ff":i?"#2a2a3e":"#fff",E=w?"#818cf8":i?"#94a3b8":"#6b7280",S=w?"#6366f1":o;p.createEl("button",{text:y.label,attr:{style:`padding:3px 10px;border-radius:99px;font-size:11px;font-weight:600;cursor:pointer;border:1.5px solid ${S};background:${k};color:${E};`}}).addEventListener("click",()=>Hr(t,e,n,y.days,s,i))});let{svg:h,studyDays:u,total:x,avgActive:m,days:g}=Sl(e,a,i);t.createEl("div",{attr:{class:"lh-ach-chart-box",style:`background:${r};border:1.5px solid ${o};border-radius:12px;padding:10px 12px 6px;margin-bottom:10px;`}}).appendChild(vt.sanitizeHTMLToDom(h));let f=Math.round(u/g*100),v=t.createEl("div",{attr:{class:"lh-ach-stats-row",style:"display:flex;gap:8px;"}});[{label:de(n,"ACH_STUDY_DAYS"),val:`${u}/${g}`,sub:`${f}%`},{label:de(n,"ACH_PERIOD_REVIEWS"),val:String(x),sub:""},{label:de(n,"ACH_DAILY_AVG"),val:String(m),sub:de(n,"ACH_PER_DAY")}].forEach(y=>{let w=v.createEl("div",{attr:{style:`flex:1;padding:8px 10px;border-radius:10px;background:${r};border:1.5px solid ${o};text-align:center;`}});w.createEl("div",{text:y.val,attr:{style:`font-size:16px;font-weight:900;color:${l};line-height:1.1;`}}),y.sub&&w.createEl("div",{text:y.sub,attr:{style:"font-size:9px;color:#818cf8;font-weight:700;"}}),w.createEl("div",{text:y.label,attr:{style:"font-size:9px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.04em;margin-top:2px;"}})})}function $r(t,e,n,a,s,i,r,o){let l=s,c=a>=n.threshold,p=Math.min(1,a/n.threshold),d=activeDocument.body.classList.contains("theme-dark"),h=(d?jt:Gn)[n.rarity]||jt.UC,u={UC:"#6b7280",R:"#3b82f6",LEG:"#d97706"},x=d?"#0f111a":"#ffffff",m=d?"#fff":"#1e293b",g=d?"rgba(255,255,255,0.6)":"#64748b",b=d?"rgba(255,255,255,0.7)":"#374151",f=d?"rgba(255,255,255,0.08)":"#e5e7eb",v=d?"rgba(255,255,255,0.1)":"rgba(0,0,0,0.08)",y=d?"filter:grayscale(1) brightness(0.4);":"filter:grayscale(1) brightness(0.7) opacity(0.5);",w=new vt.Modal(t);w.modalEl.style.cssText=`width:min(92vw,480px);padding:0;border-radius:20px;overflow:hidden;background:${x};`;let k=w.contentEl;k.style.cssText=`padding:0;background:${x};color:${m};`;let E=k.createEl("div",{attr:{style:`background:${h.bg};padding:28px 24px 20px;text-align:center;position:relative;border-bottom:1px solid ${f};`}});E.createEl("div",{text:n.rarity,attr:{style:`display:inline-block;background:${u[n.rarity]};color:#fff;font-size:10px;font-weight:800;padding:2px 10px;border-radius:99px;letter-spacing:.06em;margin-bottom:12px;`}});let S=E.createEl("div",{attr:{style:"position:relative;display:inline-block;margin-bottom:12px;"}});c&&S.createEl("div",{attr:{style:`position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:110px;height:110px;border-radius:50%;background:radial-gradient(circle,${h.ring} 0%,transparent 70%);box-shadow:0 0 30px ${h.glow};pointer-events:none;`}}),S.createEl("div",{attr:{style:`display:flex;align-items:center;justify-content:center;position:relative;z-index:1;${c?"":y}`}}).appendChild(vt.sanitizeHTMLToDom(Pr(n.id,n.rarity,104))),E.createEl("div",{text:de(l,n.nameKey),attr:{style:`font-size:18px;font-weight:800;color:${m};margin-bottom:4px;`}}),E.createEl("div",{text:de(l,n.descKey),attr:{style:`font-size:12px;color:${g};line-height:1.5;`}});let C=k.createEl("div",{attr:{style:"padding:16px 20px 0;"}}),L=C.createEl("div",{attr:{style:"display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;"}});L.createEl("span",{text:c?"\u2713 Unlocked":"Progress",attr:{style:`font-size:11px;font-weight:700;color:${c?h.badge:"#9ca3af"};`}}),L.createEl("span",{text:`${a} / ${n.threshold}`,attr:{style:`font-size:12px;font-weight:700;color:${b};`}});let T=C.createEl("div",{attr:{style:`height:6px;background:${v};border-radius:99px;overflow:hidden;margin-bottom:14px;`}}).createEl("div",{attr:{style:`height:100%;border-radius:99px;background:${h.badge};width:0%;transition:width 0.6s ease;`}});window.setTimeout(()=>{T.style.width=`${Math.round(p*100)}%`},80),w.open()}function Fr(t={},e=[],n=[],a=[],s=[],i={}){let r=0,o={mastered:0,learning:0,due:0,unseen:0,total:0};for(let x of e){for(let m of x.cards||[]){let g=yl(m.srMeta);o[g]=(o[g]||0)+1,o.total++}r+=(x.cards||[]).filter(m=>{var g;return m.srMeta&&((g=m.srMeta.stability)!=null?g:0)>=21}).length}let l=0,c=t.dailyReviewLog||{},p=Object.values(c);p.length>0&&(l=Math.max(...p));let d=0,h=0;for(let x of s||[]){let m=x&&x.meta;if(!m)continue;let g=wl(m);d+=g.completed||0,g.total>0&&g.completed>=g.total&&h++}let u=(n||[]).reduce((x,m)=>Number.isFinite(m&&m.completedCount)?x+m.completedCount:Array.isArray(m&&m.nodes)?x+m.nodes.filter(g=>g&&g.completed).length:x,0);return{evalCtx:{totalCardsReviewed:t.totalCardsReviewed||0,longestStreak:t.longestStreak||0,maxDaily:l,masteredCards:r,memoryMapCount:a.length,questMapCount:n.length,clearedQuestCount:n.filter(x=>x.completed).length,deckCount:e.length,perfectSessions:t.perfectSessions||0,totalAgainCount:t.totalAgainCount||0,completedLessons:d,completedCourses:h,completedQuestNodes:u,chartedNotes:i.chartedNotes||0,colonizedNotes:i.colonizedNotes||0,deepCharted:i.deepCharted||0,domainCharted:i.domainCharted||0,curriculaComplete:i.curriculaComplete||0},counts:o,dailyLog:c,maxDaily:l,totalReviewed:t.totalCardsReviewed||0,currentStreak:t.currentStreak||0}}var Al=new Set(["chartedNotes","colonizedNotes","deepCharted","domainCharted","curriculaComplete"]);async function Qr(t,e,n){let a=yt.filter(s=>!Al.has(s.field)&&(e[s.field]||0)>=s.threshold).map(s=>({id:s.id,rarity:s.rarity,name:n?de(n,s.nameKey):s.id}));await kl(t,a)}async function Tl(t,e,n,a=[],s=[],i=null){if(!i)try{i=await vl(e.app.vault.adapter,e.settings.lessonRoots)}catch(I){console.warn("EngramQuest: lesson achievements scan failed",I),i=[]}let r={};try{r=(await El(e.app.vault.adapter)).stats}catch(I){console.warn("EngramQuest: shared achievement state read failed",I)}let o=e.settings,l=o._stats||{},c=activeDocument.body.classList.contains("theme-dark"),p=c?"#252538":"#f8faff",d=c?"#3a3a5a":"#e5e7eb",h=c?"rgba(255,255,255,0.08)":"#f1f5f9",u=c?"#e2e8f0":"#374151",x=c?"#94a3b8":"#9ca3af",{evalCtx:m,counts:g,dailyLog:b,totalReviewed:f,currentStreak:v}=Fr(l,n,a,s,i,r);Qr(e.app.vault.adapter,m,o).catch(()=>{});let y=t.createEl("div",{attr:{class:"lh-card",style:"margin-top:0;border-radius:0;flex:1;display:flex;flex-direction:column;overflow-y:auto;"}});y.createEl("div",{attr:{class:"lh-card-header"}}).createEl("span",{text:de(o,"TAB_ACHIEVEMENT"),attr:{class:"lh-card-title"}});let k=y.createEl("div",{attr:{class:"lh-ach-stats-row",style:"display:flex;gap:10px;padding:0 20px 16px;flex-wrap:wrap;"}});[{label:de(o,"ACH_TOTAL_REVIEWED"),val:f,color:"#6366f1"},{label:de(o,"ACH_MASTERED"),val:g.mastered,color:"#22c55e"},{label:de(o,"TOTAL"),val:g.total,color:"#f59e0b"},{label:de(o,"ACH_STREAK"),val:v+"d",color:"#ef4444"}].forEach(I=>{let R=k.createEl("div",{attr:{style:`flex:1;min-width:72px;padding:10px 12px;border-radius:12px;background:${p};border:1.5px solid ${d};text-align:center;`}});R.createEl("div",{text:String(I.val),attr:{class:"lh-ach-stat-val",style:`font-size:22px;font-weight:900;color:${I.color};line-height:1.1;`}}),R.createEl("div",{text:I.label,attr:{style:`font-size:9px;font-weight:700;color:${x};text-transform:uppercase;letter-spacing:.05em;margin-top:2px;`}})});let E=y.createEl("div",{attr:{class:"lh-ach-familiarity",style:`display:flex;align-items:center;gap:20px;padding:0 20px 20px;flex-wrap:wrap;border-bottom:1px solid ${h};`}});E.createEl("div",{attr:{style:"flex-shrink:0;"}}).appendChild(vt.sanitizeHTMLToDom(_l(g,c)));let _=E.createEl("div",{attr:{style:"display:flex;flex-direction:column;gap:8px;flex:1;min-width:110px;"}});[{color:"#22c55e",key:"ACH_MASTERED",n:g.mastered},{color:"#f59e0b",key:"ACH_LEARNING",n:g.learning},{color:"#ef4444",key:"ACH_DUE",n:g.due},{color:"#94a3b8",key:"ACH_UNSEEN",n:g.unseen}].forEach(I=>{let R=g.total>0?Math.round(I.n/g.total*100):0,O=_.createEl("div",{attr:{style:"display:flex;align-items:center;gap:8px;"}});O.createEl("div",{attr:{style:`width:10px;height:10px;border-radius:50%;background:${I.color};flex-shrink:0;`}}),O.createEl("span",{text:de(o,I.key),attr:{style:`font-size:12px;color:${u};flex:1;`}}),O.createEl("span",{text:`${I.n} (${R}%)`,attr:{style:`font-size:11px;font-weight:700;color:${x};`}})});let C=y.createEl("div",{attr:{class:"lh-ach-activity",style:"padding:16px 20px 8px;"}});Hr(C,b,o,30,f,c);let L=y.createEl("div",{attr:{style:`display:flex;align-items:center;gap:8px;padding:16px 20px 12px;border-top:1px solid ${h};`}});L.createEl("span",{text:de(o,"ACH_COLLECTION"),attr:{style:`font-size:14px;font-weight:700;color:${u};`}});let A=yt.filter(I=>(m[I.field]||0)>=I.threshold).length;L.createEl("span",{text:`${A} / ${yt.length}`,attr:{class:"lh-card-count"}});let T=y.createEl("div",{attr:{style:`padding:4px 20px 28px;margin:0;position:relative;background:${c?"linear-gradient(180deg,#0c0c18 0%,#111827 100%)":"#f1f5f9"};`}});if(c){let I=e.app.vault.adapter.getResourcePath?e.app.vault.adapter.getResourcePath(e.app.vault.configDir+"/plugins/engram-quest/bg_dark.webp"):"";I&&T.createEl("div",{attr:{style:`position:absolute;inset:0;background-image:url('${I}');background-size:cover;background-position:center 30%;opacity:0.13;pointer-events:none;z-index:0;border-radius:inherit;`}}),T.createEl("div",{attr:{style:`position:absolute;inset:0;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Ccircle cx='12' cy='18' r='1' fill='rgba(255,255,255,0.35)'/%3E%3Ccircle cx='55' cy='7' r='0.8' fill='rgba(255,255,255,0.25)'/%3E%3Ccircle cx='90' cy='32' r='1.2' fill='rgba(255,255,255,0.3)'/%3E%3Ccircle cx='140' cy='10' r='0.9' fill='rgba(255,255,255,0.2)'/%3E%3Ccircle cx='175' cy='45' r='1' fill='rgba(255,255,255,0.3)'/%3E%3Ccircle cx='30' cy='65' r='0.7' fill='rgba(255,255,255,0.2)'/%3E%3Ccircle cx='110' cy='55' r='1.1' fill='rgba(255,255,255,0.25)'/%3E%3Ccircle cx='160' cy='80' r='0.8' fill='rgba(255,255,255,0.3)'/%3E%3Ccircle cx='70' cy='95' r='1' fill='rgba(255,255,255,0.2)'/%3E%3Ccircle cx='190' cy='120' r='1.2' fill='rgba(255,255,255,0.25)'/%3E%3Ccircle cx='20' cy='140' r='0.9' fill='rgba(255,255,255,0.3)'/%3E%3Ccircle cx='130' cy='150' r='1' fill='rgba(255,255,255,0.2)'/%3E%3Ccircle cx='80' cy='170' r='0.8' fill='rgba(255,255,255,0.25)'/%3E%3Ccircle cx='50' cy='185' r='1.1' fill='rgba(255,255,255,0.2)'/%3E%3Ccircle cx='170' cy='175' r='0.7' fill='rgba(255,255,255,0.3)'/%3E%3C/svg%3E");pointer-events:none;z-index:0;opacity:0.5;`}})}let q=T.createEl("div",{attr:{class:"lh-ach-grid",style:"display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:12px;padding-top:8px;position:relative;z-index:1;"}});for(let I of yt){let R=m[I.field]||0,O=R>=I.threshold,$=Math.min(1,R/I.threshold),z=c?jt[I.rarity]:Gn[I.rarity],B=c?"#fff":"#1e293b",P=c?"rgba(255,255,255,0.55)":"#64748b",K=c?"rgba(255,255,255,0.15)":"rgba(0,0,0,0.08)",Z=c?O?"rgba(255,255,255,0.85)":"rgba(255,255,255,0.4)":O?"#374151":"#94a3b8",W=O?` lh-ach-${I.rarity.toLowerCase()}`:"",V=q.createEl("div",{attr:{class:"lh-ach-card"+(O?" unlocked":"")+W}});O?V.style.cssText=`background:${z.bg};box-shadow:0 0 20px ${z.glow},0 6px 16px rgba(0,0,0,${c?"0.5":"0.12"});cursor:pointer;`:V.style.cssText=c?"background:linear-gradient(145deg,#1e2433,#151b27);border:1.5px solid rgba(75,85,99,0.5);opacity:0.5;cursor:pointer;":"background:#e2e8f0;border:1.5px solid #cbd5e1;opacity:0.6;cursor:pointer;",V.addEventListener("click",()=>$r(e.app,e,I,R,o,n,a,s));let X={UC:"#6b7280",R:"#3b82f6",LEG:"#d97706"};V.createEl("div",{text:I.rarity,attr:{style:`z-index:10;background:${X[I.rarity]};color:#fff;font-size:9px;font-weight:800;padding:2px 7px;border-radius:99px;position:absolute;top:8px;right:8px;letter-spacing:.04em;box-shadow:0 1px 4px rgba(0,0,0,0.4);`}});let oe=V.createEl("div",{attr:{style:"text-align:center;margin:14px 0 6px;position:relative;"}});O&&oe.createEl("div",{attr:{style:`position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:84px;height:84px;border-radius:50%;background:radial-gradient(circle,${z.ring} 0%,transparent 70%);box-shadow:0 0 18px ${z.glow};pointer-events:none;`}});let re=c?"filter:grayscale(0.9) brightness(0.5) opacity(0.55);":"filter:grayscale(0.85) brightness(0.9) opacity(0.5);";oe.createEl("div",{attr:{style:`display:flex;align-items:center;justify-content:center;position:relative;z-index:1;${O?"":re}`}}).appendChild(vt.sanitizeHTMLToDom(Pr(I.id,I.rarity,84))),V.createEl("div",{text:de(o,I.nameKey),attr:{style:`font-size:11px;font-weight:700;color:${B};text-align:center;padding:0 6px;line-height:1.3;margin-bottom:4px;`}}),V.createEl("div",{text:de(o,I.descKey),attr:{style:`font-size:9px;color:${P};text-align:center;padding:0 6px;line-height:1.4;margin-bottom:8px;`}}),V.createEl("div",{attr:{style:`height:3px;background:${K};border-radius:99px;margin:0 8px 8px;overflow:hidden;`}}).createEl("div",{attr:{style:`height:100%;border-radius:99px;background:${O?z.badge:c?"#6b7280":"#94a3b8"};width:${Math.round($*100)}%;`}}),V.createEl("div",{text:O?"\u2713 "+de(o,"ACH_UNLOCKED"):`${R} / ${I.threshold}`,attr:{style:`font-size:9px;text-align:center;margin-bottom:10px;color:${Z};font-weight:${O?"700":"400"};`}})}}Br.exports={renderAchievementTab:Tl,openAchievementDetail:$r,buildAchievementEvalCtx:Fr,evaluateAndPersistUnlocks:Qr,ACHIEVEMENTS:yt,RARITY_DARK:jt,RARITY_LIGHT:Gn}});var Kn=Y((Hu,as)=>{"use strict";var{loadSrData:Wr,saveSrData:Yr,srFileName:Cl,parseCommentCardBlock:Ll,cardFencePattern:Rl,CARD_FENCE:Ur,isFencedQaOpener:Il}=Se(),jr=t=>Rl.test(t);function rt(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Ie(t){return String(t||"").replace(/^[*_=]+|[*_=]+$/g,"").trim()}function wt(t){return[...new Set([t.rawFront,t.front,Ie(t.rawFront),Ie(t.front)].filter(e=>typeof e=="string"&&e.trim()))]}function Et(t,e){return[...new Set([t.rawBack,e,t.back,Ie(t.rawBack),Ie(e),Ie(t.back)].filter(n=>typeof n=="string"&&n.trim()))]}function Kr(t){let e=String(t.back||"").split(`
`);return[Ur,`Q: ${t.front}`,`A: ${e[0]||""}`,...e.slice(1),Ur].join(`
`)}function Ml(t,e,n,a){let s=wt(e),i=Et(e,a),r=String(n.back||"").includes(`
`),o=(l,c)=>r?Kr(n):`${l}${n.front}${c}:: ${n.back}`;for(let l of s)for(let c of i){let p=new RegExp(`^([ 	]*)${rt(l)}([ 	]*)::[ 	]*${rt(c)}[ 	]*$`,"m");if(p.test(t))return{content:t.replace(p,(d,h,u)=>o(h,u)),modified:!0}}for(let l of s){let c=new RegExp(`^([ 	]*)${rt(l)}([ 	]*)::(.*)$`,"m");if(c.test(t))return{content:t.replace(c,(p,d,h)=>o(d,h)),modified:!0}}return{content:t,modified:!1}}function Vr(t){let e=t.split(`
`),n=[],a=0;for(;a<e.length;){if(!jr(e[a])){a++;continue}let s=a,i=a+1;for(;i<e.length&&!jr(e[i]);)i++;if(i>=e.length)break;let r=Ll(e.slice(s+1,i).join(`
`));r&&n.push({front:r.front,back:r.back,start:s,end:i+1}),a=i+1}return n}function Jr(t,e,n){let a=new Set(wt(e)),s=new Set(Et(e,n)),i=Vr(t.join(`
`));for(let r of i)if(!(!a.has(r.front)&&!a.has(Ie(r.front)))&&!(!s.has(r.back)&&!s.has(Ie(r.back))))return{start:r.start,end:r.end};return null}function ql(t,e,n,a){let s=t.endsWith(`
`),i=t.split(`
`);s&&i.pop();let r=Jr(i,e,a);if(!r)return{content:t,modified:!1};let o=Kr(n).split(`
`);return i.splice(r.start,r.end-r.start,...o),{content:i.join(`
`)+(s?`
`:""),modified:!0}}function Gr(t){for(;t.length>0&&t[t.length-1].trim()==="";)t.pop();return t}function Zr(t){let e=[],n=!1,a=0;for(;a<t.length;){if(/^---\s*$/.test(t[a])){n?n=!1:Il(t,a)&&(n=!0),a++;continue}let s=t[a].match(/^\s*Q:\s*(.+)/i);if(!s){a++;continue}let i=[s[1]],r=a+1,o=0;for(;r<t.length&&!/^\s*A:\s*/i.test(t[r]);)if(t[r].trim()===""){if(o++,!n&&o>=2)break;r++}else o=0,i.push(t[r]),r++;if(r>=t.length||!/^\s*A:\s*/i.test(t[r])){a++;continue}let l=t[r].match(/^\s*A:\s*(.*)/i),c=[l?l[1]:""],p=r+1,d=0,h=!1,u="",x=0;for(;p<t.length;){let b=t[p].match(/^[ \t]*(`{3,}|~{3,})/);if(b){let f=b[1][0],v=b[1].length;h?f===u&&v>=x&&(h=!1):(h=!0,u=f,x=v),c.push(t[p]),d=0,p++;continue}if(h){c.push(t[p]),p++;continue}if(/^\s*Q:\s*/i.test(t[p])||/^---\s*$/.test(t[p])||/\{\{c\d+::/.test(t[p]))break;if(t[p].trim()===""){if(d++,!n&&d>=2)break;c.push(t[p]),p++}else d=0,c.push(t[p]),p++}let m=Gr([...i]).join(`
`).trim(),g=Gr([...c]).join(`
`).trim();for(;p>a&&t[p-1].trim()==="";)p--;e.push({front:m,back:g,start:a,end:p}),a=p}return e}function Xr(t,e,n){let a=new Set(wt(e)),s=new Set(Et(e,n));for(let i of Zr(t))if(!(!a.has(i.front)&&!a.has(Ie(i.front)))&&!(!s.has(i.back)&&!s.has(Ie(i.back))))return{start:i.start,end:i.end};return null}function Nl(t,e,n,a){let s=t.endsWith(`
`),i=t.split(`
`);s&&i.pop();let r=Xr(i,e,a);if(!r)return{content:t,modified:!1};let o=[`Q: ${n.front}`],l=String(n.back||"").split(`
`);return o.push(`A: ${l[0]||""}`),o.push(...l.slice(1)),i.splice(r.start,r.end-r.start,...o),{content:i.join(`
`)+(s?`
`:""),modified:!0}}function es(t,e,n,a){let s=ql(t,e,n,a);if(s.modified)return s;let i=Ml(t,e,n,a);return i.modified?i:Nl(t,e,n,a)}function Dl(t){let e=[],n=/^([ \t]*)(.+?)([ \t]*)::[ \t]*(.*?)[ \t]*$/gm,a;for(;(a=n.exec(t))!==null;)e.push({front:a[2].trim(),back:a[4].trim()});return e}function ts(t,e){let n=new Set(wt(e)),a=new Set(Et(e,e.back)),s=u=>n.has(u.front)||n.has(Ie(u.front)),i=u=>a.has(u.back)||a.has(Ie(u.back)),r=Vr(t),o=r.find(u=>s(u)&&i(u));if(o)return{front:o.front,back:o.back};let l=Dl(t),c=l.find(u=>s(u)&&i(u));if(c)return{front:c.front,back:c.back,rawFront:c.front,rawBack:c.back};let p=Zr(t.split(`
`)),d=p.find(u=>s(u)&&i(u));if(d)return{front:d.front,back:d.back};let h=[...r,...l,...p].filter(s);if(h.length===1){let u=h[0];return{front:u.front,back:u.back,rawFront:u.front,rawBack:u.back}}return null}async function Ol(t,e){if(!e.notePath)return!0;let n=t.vault.getAbstractFileByPath(e.notePath);if(!n)return!1;let a=await t.vault.read(n),s=ts(a,e);return s?(e.front=s.front,e.back=s.back,(e.rawFront!==void 0||s.rawFront!==void 0)&&(e.rawFront=s.rawFront||s.front),(e.rawBack!==void 0||s.rawBack!==void 0)&&(e.rawBack=s.rawBack||s.back),!0):!1}function zl(t,e){t.rawFront!==void 0&&(t.rawFront=e.front),t.rawBack!==void 0&&(t.rawBack=e.back)}async function Pl(t,e,n){if(!e.notePath)return!1;let a=t.vault.getAbstractFileByPath(e.notePath);if(!a)return!1;let s=await t.vault.read(a),i=es(s,e,n,e.back);if(!i.modified)return!1;await t.vault.modify(a,i.content);let o=`engram-review/hints/${e.notePath.split("/").pop().replace(/\.md$/i,"")}.json`;try{let l={note:e.notePath,generated:new Date().toISOString().split("T")[0],cards:{}};await t.vault.adapter.exists(o)&&(l=JSON.parse(await t.vault.adapter.read(o)));let c=l.cards[e.front]||{};delete l.cards[e.front],l.cards[n.front]={l1:n.hint_l1!==void 0?n.hint_l1:c.l1||"",l2:n.hint_l2!==void 0?n.hint_l2:c.l2||"",l3:n.hint_l3!==void 0?n.hint_l3:c.l3||""},await t.vault.adapter.write(o,JSON.stringify(l,null,2))}catch(l){console.warn("review-edit: hints update failed",l)}if(e.front!==n.front)try{let l=await Wr(t.vault.adapter,e.notePath);l[e.front]&&(l[n.front]=l[e.front],delete l[e.front],await Yr(t.vault.adapter,e.notePath,l))}catch(l){console.warn("review-edit: sr update failed",l)}return zl(e,n),!0}function ns(t,e,n){let a=t.split(`
`),s=-1,i=a.length;for(let d=0;d<a.length;d++)if(a[d].trim()===`- front: ${e.front}`){s=d;for(let h=d+1;h<a.length;h++)if(a[h].trimStart().startsWith("- front:")){i=h;break}break}if(s===-1)return t;let r=a[s].match(/^([ \t]*)/)[1],o=r+"  ",l=a.slice(s,i).find(d=>d.trim().startsWith("emoji:")),c=l?l.trim().slice(6).trim():"",p=[`${r}- front: ${n.front}`,`${o}back: ${n.back}`];return c&&p.push(`${o}emoji: ${c}`),n.hint_l1&&p.push(`${o}hint_l1: ${n.hint_l1}`),n.hint_l2&&p.push(`${o}hint_l2: ${n.hint_l2}`),n.hint_l3&&p.push(`${o}hint_l3: ${n.hint_l3}`),[...a.slice(0,s),...p,...a.slice(i)].join(`
`)}async function Hl(t,e,n,a){let s=t.vault.getAbstractFileByPath(e);if(!s)return;let i=await t.vault.read(s);i=i.replace(/```review-deck\n([\s\S]*?)```/g,(r,o)=>o.split(`
`).some(l=>l.trim()===`- front: ${n.front}`)?"```review-deck\n"+ns(o,n,a)+"```":r),await t.vault.modify(s,i)}as.exports={saveTagSourceCard:Pl,saveInlineCard:Hl,replaceCardInBlock:ns,deleteTagSourceCard:Bl,deleteDeckCards:Ql,removeCardFromContent:Yn,applyFormatToCardBack:$l,refreshTagSourceCard:Ol,findCurrentSourceCard:ts};async function $l(t,e,n,a){if(!e.notePath)return!1;let s=t.vault.getAbstractFileByPath(e.notePath);if(!s)return!1;let i=await t.vault.read(s),r=es(i,e,{front:e.front,back:a},n);return r.modified?(await t.vault.modify(s,r.content),e.rawBack!==void 0&&(e.rawBack=a),!0):!1}function Fl(t,e,n){let a=wt(e),s=Et(e,n);for(let i of a)for(let r of s){let o=new RegExp(`^[ 	]*${rt(i)}[ 	]*::[ 	]*${rt(r)}[ 	]*
?`,"m");if(o.test(t))return{content:t.replace(o,""),modified:!0}}for(let i of a){let r=new RegExp(`^[ 	]*${rt(i)}[ 	]*::.*
?`,"m");if(r.test(t))return{content:t.replace(r,""),modified:!0}}return{content:t,modified:!1}}function Yn(t,e,n){let a=t.endsWith(`
`),s=t.split(`
`);a&&s.pop();let i=c=>c.join(`
`)+(a?`
`:""),r=Jr(s,e,n);if(r)return s.splice(r.start,r.end-r.start),{content:i(s),modified:!0};let o=Fl(i(s),e,n);if(o.modified)return{content:o.content,modified:!0};let l=Xr(s,e,n);return l?(s.splice(l.start,l.end-l.start),{content:i(s),modified:!0}):{content:t,modified:!1}}async function Ql(t,e){let n=[...new Set(e.cards.map(a=>a.notePath).filter(Boolean))];for(let a of n){if(a.startsWith("engram-review/ai-cards/")){let i=t.vault.getAbstractFileByPath(a);i&&await t.fileManager.trashFile(i)}else{let i=t.vault.getAbstractFileByPath(a);if(i){let r=e.cards.filter(l=>l.notePath===a),o=await t.vault.read(i);for(let l of r){let c=Yn(o,l,l.back);c.modified&&(o=c.content)}await t.vault.modify(i,o)}}try{let i=t.vault.getAbstractFileByPath(`engram-review/sr/${Cl(a)}.json`);i&&await t.fileManager.trashFile(i)}catch(i){console.warn("deleteDeckCards: sr cleanup failed",i)}try{let i=a.split("/").pop().replace(/\.md$/i,""),r=t.vault.getAbstractFileByPath(`engram-review/hints/${i}.json`);r&&await t.fileManager.trashFile(r)}catch(i){console.warn("deleteDeckCards: hints cleanup failed",i)}}}async function Bl(t,e){if(!e.notePath)return;let n=t.vault.getAbstractFileByPath(e.notePath);if(n){let i=await t.vault.read(n),r=Yn(i,e,e.back);r.modified&&await t.vault.modify(n,r.content)}try{let i=await Wr(t.vault.adapter,e.notePath);i[e.front]&&(delete i[e.front],await Yr(t.vault.adapter,e.notePath,i))}catch(i){console.warn("review-edit: sr delete failed",i)}let s=`engram-review/hints/${e.notePath.split("/").pop().replace(/\.md$/i,"")}.json`;try{if(await t.vault.adapter.exists(s)){let i=JSON.parse(await t.vault.adapter.read(s));i.cards&&i.cards[e.front]&&(delete i.cards[e.front],await t.vault.adapter.write(s,JSON.stringify(i,null,2)))}}catch(i){console.warn("review-edit: hints delete failed",i)}}});var kt=Y(($u,rs)=>{"use strict";function Ul(t){if(!t)return!1;let e=t.tagName;return e==="INPUT"||e==="TEXTAREA"||t.isContentEditable===!0}function jl(t){if(!t||!t.scope||typeof t.scope.register!="function"||t._selCopyRegistered)return;t._selCopyRegistered=!0;let e=()=>typeof activeWindow!="undefined"?activeWindow:window;t.scope.register(["Mod"],"c",()=>{let n=e(),a=n.getSelection&&n.getSelection();if(!a||a.rangeCount===0)return!0;let s=a.toString(),i=a.anchorNode||a.focusNode;return s&&s.trim()&&t.modalEl&&t.modalEl.contains(i)&&navigator.clipboard?(navigator.clipboard.writeText(s).catch(r=>console.error("EngramQuest: copy failed",r)),!1):!0}),t.scope.register(["Mod"],"a",()=>{let n=e(),a=n.document||(typeof activeDocument!="undefined"?activeDocument:document);if(Ul(a.activeElement))return!0;let s=n.getSelection&&n.getSelection();if(!s||!t.modalEl||typeof a.createRange!="function")return!0;let i=a.createRange();return i.selectNodeContents(t.modalEl),s.removeAllRanges(),s.addRange(i),!1})}rs.exports={registerSelectionCopy:jl}});var St=Y((Fu,is)=>{"use strict";var{computeFsrs:Gl,getLocalDateStr:Wl}=$t(),Jn="engram-quest/quadrant",st="engram-quest/quadrant/sr",Zn="engram-quest/quadrant/upgrade-queue.json",_t=Wl;function Xn(t,e=_t()){return!t||!t.fsrs?!0:String(t.fsrs.due||"")<=e}function ea(t){return!t||!t.fsrs}function Yl(t){var n,a;return!t||!t.fsrs||t.fsrs.state!==2?!1:((a=(n=t.fsrs.stability)!=null?n:t.fsrs.interval)!=null?a:0)>=21}function Kl(t,e,n=_t()){let a={};for(let s of t){let i=(e?e(s):null)||"Quadrant";a[i]||(a[i]={name:i,cards:[],due:0,unseen:0,total:0});let r=a[i];r.cards.push(s),r.total++,ea(s)?r.unseen++:Xn(s,n)&&r.due++}return Object.values(a).sort((s,i)=>{let r=s.due+s.unseen;return i.due+i.unseen-r||i.total-s.total||String(s.name).localeCompare(String(i.name))})}function ss(t){let e=Number(t);return e>=85?3:e>=40?2:1}async function ta(t,e){try{return JSON.parse(await t.read(e))}catch(n){return null}}async function na(t,e){return ta(t,`${st}/${e}.json`)}async function aa(t,e,n){await t.mkdir(st).catch(()=>{}),await t.write(`${st}/${e}.json`,JSON.stringify(n,null,2))}async function Vl(t,e=_t()){let n={cards:[],due:0,new:0,total:0},a=null;try{a=await t.list(st)}catch(r){a=null}if(!a||!Array.isArray(a.files))return n;let s=a.files.filter(r=>r.endsWith(".json")),i=await Promise.all(s.map(r=>ta(t,r)));for(let r of i){if(!r||!r.cardId)continue;let o=Xn(r,e),l=ea(r);n.cards.push({...r,_due:o,_new:l}),n.total++,o&&n.due++,l&&n.new++}return n.cards.sort((r,o)=>r._due!==o._due?r._due?-1:1:String(r.title||r.cardId).localeCompare(String(o.title||o.cardId))),n}async function Jl(t,e,n,a={}){let s=await na(t,e)||{cardId:e},i=ss(n),r=Gl(i,s.fsrs||null,a);return s.fsrs=r,s.lastReview=_t(),s.lastScore=Math.max(0,Math.min(100,Math.round(Number(n)||0))),await aa(t,e,s),s}async function Gt(t){let e=await ta(t,Zn);return e&&Array.isArray(e.entries)?e:{entries:[]}}async function Wt(t,e){await t.mkdir(Jn).catch(()=>{}),await t.write(Zn,JSON.stringify(e,null,2))}async function Zl(t,{source:e,q:n,a}){let s=await Gt(t),i=s.entries.some(o=>o.source===e&&String(o.q||"")===String(n||""));i||(s.entries.push({source:e||null,q:n||"",a:a||"",status:"pending",cardId:null,markedAt:new Date().toISOString()}),await Wt(t,s));let r=s.entries.filter(o=>o.status==="pending").length;return{added:!i,pending:r}}function Xl(t,e,n){return!t||!Array.isArray(t.entries)?!1:t.entries.some(a=>a.source===e&&String(a.q||"")===String(n||"")&&a.status==="pending")}async function Vn(t,e){try{if(t.exists&&!await t.exists(e))return!0}catch(n){}try{if(typeof t.trashSystem=="function"&&await t.trashSystem(e))return!0}catch(n){console.warn("EngramQuest: quadrant trashSystem failed",n)}try{if(typeof t.trashLocal=="function")return await t.trashLocal(e),!0}catch(n){console.warn("EngramQuest: quadrant trashLocal failed",n)}try{if(typeof t.remove=="function")return await t.remove(e),!0}catch(n){console.warn("EngramQuest: quadrant remove failed",n)}return!1}async function ec(t,e){if(!e)return;if(!await Vn(t,`${st}/${e}.json`))throw new Error(`EngramQuest: could not delete quadrant card "${e}"`);await Vn(t,`${Jn}/${e}.html`);try{let a=await Gt(t),s=a.entries.length;a.entries=a.entries.filter(i=>i.cardId!==e),a.entries.length!==s&&await Wt(t,a)}catch(a){console.warn("EngramQuest: quadrant queue cleanup failed",a)}}async function tc(t,e,n={}){if(!e)return null;let a=await na(t,e);if(!a)return null;for(let s of["title","q1","q2","q3","q4"])n[s]!==void 0&&(a[s]=n[s]);return await aa(t,e,a),a}async function nc(t,{cardId:e,source:n,title:a,q1:s,q2:i,q3:r,q4:o}){if(!e)return{added:!1,pending:0};let l=await Gt(t),c=l.entries.find(h=>h.cardId===e&&h.status==="pending"),p={source:n||null,title:a||"",q:s||"",a:i||"",q1:s||"",q2:i||"",q3:r||"",q4:o||"",regenerate:!0,status:"pending",cardId:e,markedAt:new Date().toISOString()};c?Object.assign(c,p):l.entries.push(p),await Wt(t,l);let d=l.entries.filter(h=>h.status==="pending").length;return{added:!c,pending:d}}is.exports={QUADRANT_DIR:Jn,SR_DIR:st,QUEUE_PATH:Zn,todayStr:_t,isCardDue:Xn,isCardNew:ea,isCardMastered:Yl,groupQuadrantDecks:Kl,scoreToRating:ss,loadCardSr:na,saveCardSr:aa,scanQuadrantCards:Vl,applyQuadrantRating:Jl,readUpgradeQueue:Gt,writeUpgradeQueue:Wt,addToUpgradeQueue:Zl,isQueued:Xl,trashPath:Vn,deleteQuadrantCard:ec,updateCardContent:tc,enqueueRegenerate:nc}});var ia=Y((Uu,bs)=>{"use strict";var ie=require("obsidian"),{computeFsrs:os}=$t(),{t:F,tAlt:He,getLocale:ac}=ge(),{anySrPattern:Qu,getReviewStatus:Bu,loadSrData:rc,saveSrData:sc}=Se(),{loadSynapseStatus:ic,loadSynapseBatch:oc,attachSynapseToCards:lc,isSynapseEnabled:ls}=Hn(),{ACHIEVEMENTS:cc,RARITY_DARK:dc,RARITY_LIGHT:pc,evaluateAndPersistUnlocks:hc}=Wn(),{readSharedState:uc}=Bn(),{badgeSvg:gc}=jn(),{saveTagSourceCard:mc,saveInlineCard:fc,deleteTagSourceCard:bc,applyFormatToCardBack:cs,refreshTagSourceCard:xc}=Kn(),{registerSelectionCopy:yc}=kt(),{addToUpgradeQueue:vc,readUpgradeQueue:wc,isQueued:Ec}=St(),{giHTML:he}=be(),kc={get locale(){try{return ie.moment&&ie.moment.locale&&ie.moment.locale()}catch(t){return"en"}}};function Yt(t){return ac(t,kc.locale)}var ds="engram-quest-review-mobile-patch";function _c(){if(activeDocument.getElementById(ds))return;let t=activeDocument.createElement("style");t.id=ds,t.textContent=`
body.is-phone .lh-review-nav {
  overflow:hidden !important;
}
body.is-phone .lh-review-nav > .lh-review-tabs {
  flex:1 1 auto;
  min-width:0;
  overflow-x:auto;
  overflow-y:hidden;
  -webkit-overflow-scrolling:touch;
  scrollbar-width:none;
}
body.is-phone .lh-review-nav > .lh-review-tabs::-webkit-scrollbar { display:none; }
body.is-phone .lh-review-nav .lh-review-tab { flex:0 0 auto; }
body.is-phone .lh-review-card .lh-rc-top {
  flex-wrap:wrap;
  gap:8px;
}
body.is-phone .lh-review-card .lh-rc-top > div {
  min-width:0;
  max-width:100%;
}
body.is-phone .lh-review-card .lh-rc-top > div:first-of-type {
  flex:1 1 100%;
  margin-left:0 !important;
  display:flex !important;
  flex-wrap:wrap;
  gap:6px;
}
body.is-phone .lh-review-card .lh-rc-top > div:last-of-type {
  width:100%;
  display:flex !important;
  flex-wrap:wrap;
  justify-content:flex-start !important;
  gap:6px;
}
body.is-phone .lh-review-card .lh-rc-edit-btn {
  margin-left:0;
  max-width:100%;
}
body.is-phone .lh-review-card .lh-rc-badge {
  font-size:11px;
  padding:5px 12px;
}
body.is-phone .lh-review-footer .lh-pill-row {
  flex-wrap:wrap;
}
body.is-phone .lh-review-footer .lh-pill-btn {
  flex:1 1 calc(50% - 6px);
}
`,activeDocument.head.appendChild(t)}function ra(t){return!!(t&&t.notePath&&t.notePath.startsWith("engram-review/ai-cards/"))}function Kt(t){return[...new Set((t||[]).filter(e=>typeof e=="string"&&e.trim()))]}function Sc(t){return!t||!t.includes("/")?"":t.slice(0,t.lastIndexOf("/"))}function Tt(t){return(t||"").split("/").pop().replace(/\.[^.]+$/i,"")}function Vt(t){let e=[];return Array.isArray(t.relatedNotePaths)&&e.push(...t.relatedNotePaths),Array.isArray(t.sourceNotePaths)&&e.push(...t.sourceNotePaths),t.primarySourceNotePath&&e.push(t.primarySourceNotePath),t.sourceNotePath&&e.push(t.sourceNotePath),!ra(t)&&t.notePath&&e.push(t.notePath),Kt(e)}function hs(t){return ra(t)?Vt(t):Kt([t.notePath,t.sourceNotePath,...Array.isArray(t.relatedNotePaths)?t.relatedNotePaths:[]])}function us(t){return t.vault.getFiles().filter(e=>e.name.endsWith("-memory.canvas"))}function At(t,e,n,a,s=null){!n||!n.path||e.has(n.path)||(e.add(n.path),t.push({file:n,path:n.path,reason:a,relatedPath:s}))}function sa(t){return Kt(t.map(Sc))}function Ac(t){let e=sa(t);if(e.length===0)return"";let n=e.map(i=>i.split("/").filter(Boolean)),a=Math.min(...n.map(i=>i.length)),s=[];for(let i=0;i<a;i++){let r=n[0][i];if(n.every(o=>o[i]===r))s.push(r);else break}return s.join("/")}function gs(t){let e=sa(t),n=[],a=Ac(t);if(a&&!e.includes(a)&&n.push(a),e.length===1){let s=e[0].split("/").filter(Boolean);s.length>1&&n.push(s.slice(0,-1).join("/"))}return Kt(n)}function ms(t,e,n,a){var h;let s=Vt(e);if(s.length===0)return[];let i=[],r=new Set,o=us(t),l=new Set(sa(s));for(let u of o)l.has(((h=u.parent)==null?void 0:h.path)||"")&&At(i,r,u,"same-folder");if(i.length>0)return i;let c=n.memoryMapFolder;if(c)for(let u of s){let x=`${c}/${Tt(u)}-memory.canvas`,m=t.vault.getAbstractFileByPath(x)||t.metadataCache.getFirstLinkpathDest(x.split("/").pop(),"")||null;m&&At(i,r,m,"configured-folder",u)}if(i.length>0)return i;let p=gs(s);if(p.length>0)for(let u of o)p.some(x=>u.path.startsWith(x+"/"))&&At(i,r,u,"topic-folder");if(i.length>0)return i;let d=String(a||"").trim().toLowerCase();if(d)for(let u of o)`${u.path} ${Tt(u.path)}`.toLowerCase().includes(d)&&At(i,r,u,"deck-token");return i}async function fs(t,e,n=[]){let a=Vt(e);if(a.length===0)return n;let s=new Set(a),i=[...n],r=new Set(n.map(o=>o.path));for(let o of us(t))try{let c=(JSON.parse(await t.vault.read(o)).nodes||[]).filter(p=>p.type==="file"&&p.file);for(let p of c)if(s.has(p.file)){At(i,r,o,"file-node",p.file);break}}catch(l){}return i}var Tc=["png","jpg","jpeg","gif","bmp","svg","webp","avif"];function Cc(t,e,n){t.findAll&&t.findAll(".internal-embed").forEach(a=>{let s=a.getAttribute("src");if(!s)return;let i=s.replace(/#.*$/,""),r=e.metadataCache.getFirstLinkpathDest(i,n||"");r&&Tc.includes(r.extension.toLowerCase())&&(a.empty(),a.createEl("img",{attr:{src:e.vault.getResourcePath(r),width:a.getAttribute("width")||"100%"}}),a.addClasses(["image-embed","is-loaded"]))})}function Lc(t){t.querySelectorAll("img").forEach(e=>{e.dataset.eqZoom||(e.dataset.eqZoom="1",e.classList.add("eq-zoomable"),e.addEventListener("click",n=>{n.stopPropagation();let a=activeDocument.body.createEl("div",{attr:{class:"eq-lightbox"}}),s=a.createEl("button",{attr:{class:"eq-lightbox-close"}});s.innerHTML=he("x");let i=a.createEl("img",{attr:{src:e.src,alt:e.alt||""}}),r=()=>a.remove();a.addEventListener("click",r),i.addEventListener("click",o=>o.stopPropagation()),s.addEventListener("click",r)}))})}function ps(t){let e=Object.values(t&&t.dailyReviewLog||{});return{totalCardsReviewed:t&&t.totalCardsReviewed||0,longestStreak:t&&t.longestStreak||0,maxDaily:e.length>0?Math.max(...e):0,perfectSessions:t&&t.perfectSessions||0,totalAgainCount:t&&t.totalAgainCount||0}}var Rc=class extends ie.Modal{constructor(t,e,n,a,s,i={}){super(t),this.cards=e,this.deckName=n,this.plugin=a,this.idx=0,this.hintLevel=0,this.answerShown=!1,this.onBack=s||null,this.browseOnly=!!i.browseOnly,this._rating_locked=!1,this.synapseOpen=!1,this._synapseStatus=null,this._synapseExpandedAnchorIdx=-1,this._timeboxMinutes=i.timeboxMinutes||0,this._sessionStartMs=0,this._timerIntervalId=null,this._timerEl=null,this._timeboxExpired=!1}onOpen(){_c(),this.plugin.settings._reviewProgress={deck:this.deckName,idx:this.idx},this.plugin.saveData(this.plugin.settings),this._preSessionEvalCtx=ps(this.plugin.settings._stats),this._galaxyStatsCache={},uc(this.app.vault.adapter).then(o=>{this._galaxyStatsCache=o.stats||{}}).catch(()=>{});let t=activeDocument.body.classList.contains("theme-dark"),e=t?"#1e1e2e":"#ffffff",n=t?"#252538":"#f3f4f6",a=t?"#e2e8f0":"#1f2937",s=t?"#94a3b8":"#6b7280";this.modalEl.addClass("lh-hub"),t&&this.modalEl.addClass("lh-dark"),yc(this),this.modalEl.style.cssText="width:min(95vw,700px);max-width:none;height:min(90vh,640px);max-height:none;padding:0;overflow:hidden;border-radius:24px",this.modalEl.style.setProperty("--background-primary",e,"important"),this.modalEl.style.setProperty("--background-secondary",n,"important"),this.modalEl.style.setProperty("--text-normal",a,"important"),this.modalEl.style.setProperty("--text-muted",s,"important"),this.modalEl.style.setProperty("--background-modifier-border","#e5e7eb","important");let i=t?"bg_dark.webp":"bg.png",r=this.app.vault.adapter.getResourcePath(this.app.vault.configDir+"/plugins/engram-quest/"+i);this.contentEl.style.cssText=`padding:0;display:flex;flex-direction:column;height:100%;overflow:hidden;background-image:url('${r}');background-size:cover;background-position:center top;color:${t?"#e2e8f0":"#1f2937"}`,this._timeboxMinutes>0&&(this._sessionStartMs=Date.now()),this.renderCard(),this._loadSynapseAsync(),this._timeboxMinutes>0&&(this._timerIntervalId=window.setInterval(()=>this._tickTimer(),1e3))}_tickTimer(){let t=this.plugin.settings,e=Math.floor((Date.now()-this._sessionStartMs)/1e3),n=this._timeboxMinutes*60-e;if(n<=0&&!this._timeboxExpired&&(this._timeboxExpired=!0,window.clearInterval(this._timerIntervalId),this._timerIntervalId=null),!(!this._timerEl||!this._timerEl.isConnected))if(n<=0)this._timerEl.textContent=`\u23F1 ${F(t,"TIMEBOX_TIMES_UP")}`,this._timerEl.style.color="#ef4444";else{let a=Math.floor(n/60),s=n%60;this._timerEl.textContent=`\u23F1 ${a}:${s.toString().padStart(2,"0")}`,this._timerEl.style.color=n<=60?"#f59e0b":"var(--lh-cyan,#22d3ee)"}}async _loadSynapseAsync(){try{if(!this.plugin.settings.licenseValid){this._synapseStatus={enabled:!1,reason:"not-pro"};return}let t=this.app.vault.adapter;if(this._synapseStatus=await ic(t),!ls(this._synapseStatus))return;let e=this.cards.map(a=>a&&a.notePath).filter(Boolean),n=await oc(t,e);lc(this.cards,n),!this.answerShown&&this.cards[this.idx]&&this.contentEl&&this.contentEl.isConnected&&this._renderCardContent(this.cards[this.idx])}catch(t){console.warn("synapse: load failed",t)}}renderCard(){this.hintLevel=0,this.answerShown=!1,this._rating_locked=!1,this.synapseOpen=!1,this._synapseExpandedAnchorIdx=-1,this.plugin.settings._reviewProgress={deck:this.deckName,idx:this.idx},this.plugin.saveData(this.plugin.settings),this._renderCardContent(this.cards[this.idx])}_renderComplete(){let t=this.plugin.settings;delete this.plugin.settings._reviewProgress,this.plugin.saveData(this.plugin.settings);let e=ps(this.plugin.settings._stats),n=this._preSessionEvalCtx||{},a=cc.filter(p=>{var u,x;let d=(u=n[p.field])!=null?u:-1,h=(x=e[p.field])!=null?x:-1;return d<0||h<0?!1:d<p.threshold&&h>=p.threshold});hc(this.app.vault.adapter,{...e,...this._galaxyStatsCache||{}},this.plugin.settings).catch(()=>{});let s=activeDocument.body.classList.contains("theme-dark"),i=s?dc:pc;this.contentEl.empty();let r=this.contentEl.createEl("div",{attr:{class:"lh-complete-screen",style:"flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:32px 24px;text-align:center;gap:16px;overflow-y:auto;"}});r.createEl("div",{attr:{style:"font-size:56px;line-height:1;color:#7dd3fc;"}}).innerHTML=he("rocket"),r.createEl("div",{attr:{class:"lh-complete-title",style:"font-size:24px;font-weight:800;color:var(--text-normal, #1e293b);"}}).textContent=F(t,"REVIEW_COMPLETE");{let p=`${this.deckName} \xB7 ${this.cards.length} ${F(t,"CARDS_REVIEWED")}`;if(this._timeboxMinutes>0){let d=Math.floor((Date.now()-this._sessionStartMs)/1e3);p+=` \xB7 ${Math.floor(d/60)}:${(d%60).toString().padStart(2,"0")}`}r.createEl("div",{attr:{style:"font-size:14px;color:var(--text-muted, #64748b);line-height:1.6;max-width:260px;"}}).textContent=p}if(a.length>0){let p=r.createEl("div",{attr:{style:"width:100%;max-width:320px;margin-top:4px;"}});p.createEl("div",{attr:{style:"font-size:12px;font-weight:700;color:#fbbf24;text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px;"}}).innerHTML=he("trophy")+" "+F(t,"ACH_SESSION_UNLOCKED");let d=p.createEl("div",{attr:{style:"display:flex;flex-wrap:wrap;gap:12px;justify-content:center;"}});for(let h of a){let u=i[h.rarity],x=d.createEl("div",{attr:{style:`display:flex;flex-direction:column;align-items:center;gap:6px;padding:12px 10px 10px;border-radius:16px;background:${u.bg};box-shadow:0 0 20px ${u.glow};width:94px;position:relative;`}});x.createEl("div",{attr:{style:`font-size:9px;font-weight:800;color:${u.badge};background:${u.badge}22;padding:2px 8px;border-radius:99px;`}}).textContent=h.rarity;let m=x.createEl("div",{attr:{style:"position:relative;margin:4px 0;"}});m.createEl("div",{attr:{style:`position:absolute;inset:-10px;border-radius:50%;background:radial-gradient(circle,${u.glow} 0%,transparent 70%);pointer-events:none;`}}),m.createEl("div",{attr:{style:"display:flex;align-items:center;justify-content:center;position:relative;z-index:1;"}}).appendChild(ie.sanitizeHTMLToDom(gc(h.id,h.rarity,52))),x.createEl("div",{attr:{style:`font-size:10px;font-weight:700;color:${s?"#e2e8f0":"#1e293b"};text-align:center;line-height:1.3;padding:0 2px;`}}).textContent=F(t,h.nameKey)}}let o=r.createEl("div",{attr:{style:"display:flex;flex-direction:column;gap:10px;width:100%;max-width:280px;margin-top:8px;"}}),l=o.createEl("button",{attr:{class:"lh-complete-btn",style:"border-radius:99px;padding:14px 24px;font-size:15px;font-weight:700;cursor:pointer;border:none;background:linear-gradient(135deg,#4f46e5,#818cf8);color:#fff;box-shadow:0 4px 16px rgba(79,70,229,0.4);min-height:52px;"}});l.textContent=F(t,"BACK_TO_HUB"),l.addEventListener("click",()=>{this.close(),this.onBack&&this.onBack()});let c=o.createEl("button",{attr:{class:"lh-complete-btn",style:"border-radius:99px;padding:14px 24px;font-size:15px;font-weight:600;cursor:pointer;border:1.5px solid #e2e8f0;background:#f8faff;color:#475569;min-height:52px;"}});c.textContent=F(t,"HUB_CLOSE"),c.addEventListener("click",()=>this.close())}_renderCardContent(t){let e=this.plugin.settings,n=Yt(e)==="zh-tw"?"\u5DF2\u958B\u555F":"Opened";this.contentEl.empty();let a=(_,C)=>{_.empty(),ie.MarkdownRenderer.renderMarkdown(C||"",_,t.notePath||"",null),Cc(_,this.app,t.notePath||""),Lc(_)},s=this.contentEl.createEl("div",{attr:{class:"lh-review-nav"}});s.createEl("span",{text:F(e,"HUB_TITLE"),attr:{class:"lh-review-logo"}});let i=s.createEl("div",{attr:{class:"lh-review-tabs"}});[{label:F(e,"TAB_REVIEW"),key:"review"},{label:F(e,"TAB_MEMORY"),key:"memory"},{label:F(e,"TAB_QUEST"),key:"quest"}].forEach(_=>{let C=i.createEl("button",{text:_.label,attr:{class:"lh-review-tab"+(_.key==="review"?" active":"")}});_.key!=="review"&&(C.style.opacity="0.6")});let r=s.createEl("button",{attr:{class:"lh-review-back"}});r.innerHTML=he("back")+" "+F(e,"BACK"),r.addEventListener("click",()=>{this.close(),this.onBack&&this.onBack()});let o=s.createEl("button",{attr:{class:"lh-review-back",style:"font-size:16px;padding:4px 8px;margin-left:4px;"}});o.textContent="\u23EC",o.title="Minimize",o.addEventListener("click",()=>this._minimize()),this._timeboxMinutes>0&&(this._timerEl=s.createEl("span",{attr:{class:"lh-timebox-clock"}}),this._tickTimer());let l=this.contentEl.createEl("div",{attr:{class:"lh-review-body"}}).createEl("div",{attr:{class:"lh-review-card"}}),c=l.createEl("div",{attr:{class:"lh-rc-top"}});c.createEl("span",{text:this.deckName,attr:{class:"lh-rc-badge"}}),this.browseOnly&&c.createEl("span",{text:F(e,"BROWSE_ONLY"),attr:{class:"lh-rc-badge"}});let p=hs(t);if(p.length>0){let _=c.createEl("div",{attr:{style:"display:flex;align-items:center;gap:4px;margin-left:auto;flex-shrink:1;min-width:0;"}});p.forEach(C=>{let L=C.split("/").pop().replace(/\.md$/i,""),A=_.createEl("button",{attr:{class:"lh-rc-edit-btn",style:"font-size:11px;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#38bdf8;",title:C}});A.textContent=L,A.insertAdjacentHTML("afterbegin",he("file")+" "),A.addEventListener("click",T=>{T.stopPropagation();let q=this.app.vault.getAbstractFileByPath(C);if(q)this.app.workspace.openLinkText(q.path,"",!1);else{let I=C.split("/").pop().replace(/\.md$/i,""),R=this.app.metadataCache.getFirstLinkpathDest(I,"");R?this.app.workspace.openLinkText(R.path,"",!1):new ie.Notice("Source note not found: "+I)}})})}p.length>0&&c.querySelectorAll(".lh-rc-top .lh-rc-edit-btn").forEach(_=>{if(_.dataset.feedbackBound==="1")return;let C=_.innerHTML;_.dataset.feedbackBound="1",_.addEventListener("click",()=>{_.innerHTML=he("check")+" "+n,_.style.color="#10b981",_._feedbackTimer&&window.clearTimeout(_._feedbackTimer),_._feedbackTimer=window.setTimeout(()=>{_.innerHTML=C,_.style.color="#38bdf8",_._feedbackTimer=null},1200)})});let d=c.createEl("div",{attr:{style:"display:flex;align-items:center;gap:4px;flex-shrink:0;"}}),h=d.createEl("button",{attr:{class:"lh-rc-edit-btn"}});h.innerHTML=he("copy")+" Copy",h.title="Copy",h.addEventListener("click",()=>{let _=[t.front];t.hint_l1&&_.push("L1: "+t.hint_l1),t.hint_l2&&_.push("L2: "+t.hint_l2),t.hint_l3&&_.push("L3: "+t.hint_l3),_.push("A: "+t.back),navigator.clipboard.writeText(_.join(`
`)).then(()=>{h.innerHTML=he("check")+" Copied",window.setTimeout(()=>h.innerHTML=he("copy")+" Copy",1500)})});let u=d.createEl("button",{attr:{class:"lh-rc-edit-btn"}});u.innerHTML=he("edit")+" "+F(e,"EDIT_CARD"),u.addEventListener("click",async()=>{if(t.notePath)try{if(!await xc(this.app,t)){new ie.Notice(F(e,"CREATE_CARD_SAVE_FAILED"));return}}catch(_){console.error("review-edit: refresh failed",_),new ie.Notice(F(e,"CREATE_CARD_SAVE_FAILED"));return}this._renderEditForm(t)});let x=d.createEl("button",{attr:{class:"lh-rc-edit-btn",style:"color:#ef4444;"}});x.innerHTML=he("trash"),x.title=F(e,"DELETE"),x.addEventListener("click",()=>this._renderDeleteConfirm(t)),t.emoji&&(l.createEl("span",{attr:{class:"lh-rc-emoji"}}).textContent=t.emoji),t.token&&l.createEl("span",{text:t.token,attr:{class:"lh-rc-token"}});let m=l.createEl("div",{attr:{class:"lh-rc-question"}});a(m,t.front);let g=[{key:"hint_l1",cls:"lh-hint-l1",label:"L1 \xB7 Active Recall"},{key:"hint_l2",cls:"lh-hint-l2",label:"L2 \xB7 Contextual Anchor"},{key:"hint_l3",cls:"lh-hint-l3",label:"L3 \xB7 Narrowing Hint"}],b=l.createEl("div",{attr:{class:"lh-hints-container"}}),f=l.createEl("div",{attr:{class:"lh-synapse-container"}}),v=this.plugin.settings.licenseValid&&ls(this._synapseStatus),y=()=>{if(b.empty(),!this.synapseOpen)for(let _=0;_<this.hintLevel;_++){let C=g[_],L=b.createEl("div",{attr:{class:`lh-hint ${C.cls}`}});L.createEl("div",{text:C.label,attr:{class:"lh-hint-label"}});let A=L.createEl("div",{attr:{class:"lh-hint-text"}});a(A,t[C.key]||He("NO_HINT",e))}},w=()=>{f.empty(),this.synapseOpen&&this._renderSynapsePanel(f,t,e,a,w)};if(y(),w(),this.answerShown){let _=l.createEl("div",{attr:{class:"lh-answer-block"}}),C=_.createEl("div",{attr:{class:"lh-answer-header"}});C.createEl("div",{text:F(e,"ANSWER"),attr:{class:"lh-answer-label"}});let L,A="",T=()=>{let R=window.getSelection&&window.getSelection();if(!R||R.rangeCount===0)return A="","";let O=R.toString();if(!O||!O.trim())return A="","";let $=R.anchorNode,z=R.focusNode;return!L||!L.contains($)&&!L.contains(z)?(A="",""):(A=O.trim(),A)},q=C.createEl("div",{attr:{class:"lh-answer-fmt-btns"}}),I=Array.isArray(t.synapseAnchors)?t.synapseAnchors:[];if(v&&I.length>0){let R=q.createEl("button",{attr:{class:"lh-rc-edit-btn lh-fmt-btn",title:F(e,"SYNAPSE_HINT")}});R.innerHTML=he("bolt"),R.addEventListener("click",()=>{this.synapseOpen=!this.synapseOpen,w()})}if(t.notePath){let R=()=>{a(L,t.back),A=""},O=async P=>{let K=(T()||A).replace(/\r\n/g,`
`);if(!K){new ie.Notice(F(e,"FORMAT_SELECT_TEXT"));return}let Z=K;if(!t.back.includes(K)){let X=K.replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/ /g,"\\s+"),oe=new RegExp(X).exec(t.back);if(!oe){new ie.Notice(F(e,"FORMAT_SELECT_TEXT"));return}Z=oe[0]}let W=t.back,V=t.back.replace(Z,P+Z+P);try{if(!await cs(this.app,t,W,V)){new ie.Notice(F(e,"CREATE_CARD_SAVE_FAILED"));return}t.back=V,R()}catch(X){console.error("format-apply failed",X),new ie.Notice(F(e,"CREATE_CARD_SAVE_FAILED"))}},$=q.createEl("button",{text:F(e,"FORMAT_HIGHLIGHT"),attr:{class:"lh-rc-edit-btn lh-fmt-btn"}});$.addEventListener("mousedown",P=>{T(),P.preventDefault()}),$.addEventListener("click",()=>O("=="));let z=async()=>{let P=(T()||A||"").trim();if(!P){new ie.Notice(F(e,"FORMAT_SELECT_PARA"));return}let K=t.back.split(`
`),Z=P.split(`
`).map(D=>D.trim()).filter(Boolean),W=-1,V=-1;for(let D=0;D<K.length;D++){let N=K[D].replace(/^>\s*/,"").trim();if(W===-1&&Z[0]&&N.includes(Z[0])&&(W=D),W!==-1&&Z[Z.length-1]&&N.includes(Z[Z.length-1])){V=D;break}}W===-1&&(W=0,V=K.length-1),V===-1&&(V=W);let X=t.back,re=K.map((D,N)=>N>=W&&N<=V&&!D.startsWith("> ")?"> "+D:D).join(`
`);if(re!==X)try{if(!await cs(this.app,t,X,re)){new ie.Notice(F(e,"CREATE_CARD_SAVE_FAILED"));return}t.back=re,R()}catch(D){console.error("blockquote-apply failed",D),new ie.Notice(F(e,"CREATE_CARD_SAVE_FAILED"))}},B=q.createEl("button",{text:F(e,"FORMAT_BLOCKQUOTE"),attr:{class:"lh-rc-edit-btn lh-fmt-btn"}});B.addEventListener("mousedown",P=>{T(),P.preventDefault()}),B.addEventListener("click",z)}L=_.createEl("div",{attr:{class:"lh-answer-text"}}),a(L,t.back),L.addEventListener("mouseup",T),L.addEventListener("keyup",T),this.browseOnly&&l.createEl("div",{text:F(e,"BROWSE_NOTE"),attr:{class:"lh-browse-note"}})}if(this.answerShown)if(this.browseOnly){let _=this.contentEl.createEl("div",{attr:{class:"lh-browse-row"}});_.createEl("button",{text:F(e,"BACK_TO_QUESTION"),attr:{class:"lh-browse-btn"}}).addEventListener("click",()=>{this.answerShown=!1,this._renderCardContent(t)});let C=_.createEl("button",{text:F(e,"PREVIOUS"),attr:{class:"lh-browse-btn"}});this.idx===0&&(C.disabled=!0),C.addEventListener("click",()=>{this.idx!==0&&(this.idx--,this.renderCard())}),_.createEl("button",{text:this.idx>=this.cards.length-1?F(e,"BACK"):F(e,"NEXT"),attr:{class:"lh-browse-btn"}}).addEventListener("click",()=>{if(this.idx>=this.cards.length-1){this.close(),this.onBack&&this.onBack();return}this.idx++,this.renderCard()})}else{let _=t.srMeta||null,C=this.contentEl.createEl("div",{attr:{class:"lh-rating-row"}});if(e.licenseValid){let A=this.contentEl.createEl("div",{attr:{style:"display:flex;justify-content:center;margin-top:8px;"}}).createEl("button",{text:F(e,"QUADRANT_UPGRADE_BTN"),attr:{class:"lh-rc-edit-btn",style:"font-size:12px;"}});A.addEventListener("click",async()=>{try{let T=await vc(this.app.vault.adapter,{source:t.notePath,q:t.front,a:t.back});new ie.Notice(T.added?F(e,"QUADRANT_MARKED"):F(e,"QUADRANT_ALREADY")),A.disabled=!0,A.style.opacity="0.6"}catch(T){console.error("EngramQuest: quadrant queue failed",T)}}),wc(this.app.vault.adapter).then(T=>{Ec(T,t.notePath,t.front)&&(A.textContent=F(e,"QUADRANT_ALREADY"),A.disabled=!0,A.style.opacity="0.6")}).catch(()=>{})}[{q:1,label:He("AGAIN",e),icon:"AG",cls:"lh-rb-again"},{q:2,label:He("HARD",e),icon:"HD",cls:"lh-rb-hard"},{q:3,label:He("GOOD",e),icon:"GD",cls:"lh-rb-good"},{q:4,label:He("EASY",e),icon:"OK",cls:"lh-rb-easy"}].forEach(L=>{let A=os(L.q,_,e),T=C.createEl("button",{attr:{class:`lh-rating-btn ${L.cls}`}}),q=T.createEl("div",{attr:{class:"lh-rb-top"}});q.createEl("span",{attr:{class:"lh-rb-icon"}}).textContent=L.icon,q.createEl("span",{attr:{class:"lh-rb-label"}}).textContent=L.label,T.createEl("div",{text:`${A.interval}${He("DAYS",e)}`,attr:{class:"lh-rb-days"}}),T.addEventListener("click",async()=>{if(this._rating_locked)return;this._rating_locked=!0,T.style.opacity="0.6",T.style.pointerEvents="none",C.querySelectorAll(".lh-rating-btn").forEach(R=>{R.style.opacity="0.5",R.style.pointerEvents="none"});let I=os(L.q,_,e);if(t.notePath)try{let R=await rc(this.app.vault.adapter,t.notePath);R[t.front]={due:I.due,interval:I.interval,stability:I.stability,difficulty:I.difficulty,state:I.state,repetitions:I.repetitions},await sc(this.app.vault.adapter,t.notePath,R)}catch(R){console.error("review-deck write failed",R)}try{let R=new Date,O=K=>String(K).padStart(2,"0"),$=R.getFullYear()+"-"+O(R.getMonth()+1)+"-"+O(R.getDate()),z=new Date(R);z.setDate(z.getDate()-1);let B=z.getFullYear()+"-"+O(z.getMonth()+1)+"-"+O(z.getDate()),P=this.plugin.settings._stats||{};P.totalCardsReviewed=(P.totalCardsReviewed||0)+1,P.dailyReviewLog=P.dailyReviewLog||{},P.dailyReviewLog[$]=(P.dailyReviewLog[$]||0)+1,P.lastReviewDate===$||(P.lastReviewDate===B?(P.currentStreak=(P.currentStreak||1)+1,P.longestStreak=Math.max(P.longestStreak||0,P.currentStreak)):P.currentStreak=1),P.lastReviewDate=$,L.q===1?(P.totalAgainCount=(P.totalAgainCount||0)+1,this._perfectStreak=0):(this._perfectStreak=(this._perfectStreak||0)+1,this._perfectStreak>=20&&!this._perfectSessionAwarded&&(P.perfectSessions=(P.perfectSessions||0)+1,this._perfectSessionAwarded=!0)),this.plugin.settings._stats=P,this.plugin.saveData(this.plugin.settings)}catch(R){console.error("achievement stats failed",R)}if(t.srMeta={due:I.due,interval:I.interval,stability:I.stability,difficulty:I.difficulty,state:I.state,repetitions:I.repetitions},t.srComment="",this.idx++,this.idx>=this.cards.length||this._timeboxExpired){this._renderComplete();return}this.renderCard()})})}else{let _=this.contentEl.createEl("div",{attr:{class:"lh-review-footer"}}),C=_.createEl("div",{attr:{class:"lh-pill-row"}}),L=C.createEl("button",{attr:{class:"lh-pill-btn lh-pill-show"}});L.textContent="TIP "+He("SHOW_ANSWER",e),L.addEventListener("click",()=>{this.answerShown=!0,this._renderCardContent(t)});let A=t.hint_l1||t.hint_l2||t.hint_l3,T=C.createEl("button",{attr:{class:"lh-pill-btn lh-pill-recall"}});T.textContent="L1 "+(this.hintLevel===0?He("RECALL",e):He("HINT_NEXT",e)),!A||this.hintLevel>=3?this._disablePillBtn(T):T.addEventListener("click",()=>{this.hintLevel++,this.synapseOpen=!1,this._renderCardContent(t)});let q=C.createEl("button",{attr:{class:"lh-pill-btn lh-pill-memory"}});q.textContent=F(e,"MEMORY_MAP");let I=null,R=P=>{if(!P||P.length===0){I=null,this._disablePillBtn(q);return}q.disabled=!1,q.style.opacity="",q.style.cursor="",I=()=>this._openMemoryMapChooser(P)};q.addEventListener("click",()=>{I&&I()});let O=ms(this.app,t,e,this.deckName);if(O.length>0)R(O);else{R([]);let P=this._mmGen=(this._mmGen||0)+1;fs(this.app,t,O).then(K=>{this._mmGen===P&&q.isConnected&&R(K||[])}).catch(()=>{})}if(v){C.classList.add("lh-pill-row-4");let P=C.createEl("button",{attr:{class:"lh-pill-btn lh-pill-synapse"}}),K=Array.isArray(t.synapseAnchors)?t.synapseAnchors:[];P.innerHTML=he("bolt")+" "+F(e,"SYNAPSE"),K.length===0?this._disablePillBtn(P,F(e,"SYNAPSE_EMPTY")):(P.title=F(e,"SYNAPSE_HINT"),P.addEventListener("click",()=>{this.synapseOpen=!this.synapseOpen,y(),w()}))}let $=_.createEl("div",{attr:{class:"lh-footer-meta"}}),z=$.createEl("button",{attr:{class:"lh-pill-reset"}});z.textContent="Reset",z.addEventListener("click",()=>{this.hintLevel=0,this.answerShown=!1,this._renderCardContent(t)});let B=$.createEl("div",{attr:{class:"lh-dots"}});for(let P=0;P<3;P++)B.createEl("div",{attr:{class:"lh-dot"+(P<this.hintLevel?" lit":"")}})}let k=this.contentEl.createEl("div",{attr:{class:"lh-review-progress"}}),E=k.createEl("div",{attr:{class:"lh-review-prog-wrap"}}),S=Math.round(this.idx/this.cards.length*100);E.createEl("div",{attr:{class:"lh-review-prog-bar",style:`width:${S}%`}}),k.createEl("span",{text:`${this.idx+1} / ${this.cards.length}`,attr:{class:"lh-review-badge"}})}_disablePillBtn(t,e){t.disabled=!0,t.style.opacity="0.38",t.style.cursor="not-allowed",e&&(t.title=e)}_renderSynapsePanel(t,e,n,a,s){let i=Array.isArray(e.synapseAnchors)?e.synapseAnchors:[],r=t.createEl("div",{attr:{class:"lh-synapse-panel"}});if(r.createEl("div",{attr:{class:"lh-synapse-title"}}).innerHTML=he("bolt")+" "+F(n,"SYNAPSE"),r.createEl("div",{text:F(n,"SYNAPSE_HINT"),attr:{class:"lh-synapse-hint"}}),i.length===0){r.createEl("div",{text:F(n,"SYNAPSE_EMPTY"),attr:{class:"lh-synapse-empty"}});return}i.forEach((o,l)=>{let c=r.createEl("div",{attr:{class:"lh-synapse-card"}}),p=c.createEl("div",{attr:{class:"lh-synapse-card-head"}}),d=typeof o.stability=="number"?o.stability:0,h=d>=30?"crown":d>=15?"star":"sparkle";p.createEl("span",{attr:{class:"lh-synapse-card-badge"}}).innerHTML=he(h),p.createEl("span",{text:o.front||"",attr:{class:"lh-synapse-card-front"}}),o.reason&&c.createEl("div",{text:F(n,"SYNAPSE_REASON_PREFIX")+o.reason,attr:{class:"lh-synapse-card-reason"}});let u=this._synapseExpandedAnchorIdx===l;if(u){let x=this._lookupAnchorBack(o),m=c.createEl("div",{attr:{class:"lh-synapse-card-back"}});x&&typeof a=="function"?a(m,x):x?m.textContent=x:m.textContent=F(n,"SYNAPSE_NO_BACK")}c.addEventListener("click",x=>{x.stopPropagation(),this._synapseExpandedAnchorIdx=u?-1:l,typeof s=="function"?s():this._renderCardContent(e)})})}_lookupAnchorBack(t){if(!t||!t.front)return"";if(t.back)return t.back;let e=t.notePath||"",n=this.cards.find(a=>a&&a.front===t.front&&(e?a.notePath===e:!0));return n&&n.back||""}_openMemoryMapChooser(t){let e=new ie.Modal(this.app),n=Yt(this.plugin.settings)==="zh-tw",a={"same-folder":n?"\u540C\u8CC7\u6599\u593E":"Same folder","configured-folder":n?"\u8A2D\u5B9A\u8CC7\u6599\u593E":"Configured folder","topic-folder":n?"\u540C\u4E3B\u984C":"Same topic","deck-token":n?"\u540C deck":"Deck match","file-node":n?"\u95DC\u806F\u7BC0\u9EDE":"Linked node"},s={"same-folder":"background:rgba(34,197,94,0.16);color:#22c55e;border:1px solid rgba(34,197,94,0.28);","configured-folder":"background:rgba(59,130,246,0.16);color:#60a5fa;border:1px solid rgba(96,165,250,0.28);","topic-folder":"background:rgba(245,158,11,0.16);color:#f59e0b;border:1px solid rgba(245,158,11,0.28);","deck-token":"background:rgba(168,85,247,0.16);color:#c084fc;border:1px solid rgba(192,132,252,0.28);","file-node":"background:rgba(99,102,241,0.16);color:#818cf8;border:1px solid rgba(129,140,248,0.28);"};e.modalEl.style.cssText="width:min(92vw,620px);padding:0;border-radius:18px;overflow:hidden;background:var(--background-primary,#141722);box-shadow:0 20px 50px rgba(0,0,0,0.35);";let i=e.contentEl;i.style.cssText="padding:24px;display:flex;flex-direction:column;gap:14px;",i.createEl("div",{text:n?"\u9078\u64C7 Memory Map":"Select Memory Map",attr:{style:"font-size:18px;font-weight:800;color:var(--text-normal,#111);"}}),i.createEl("div",{text:n?"\u9078\u64C7\u8981\u958B\u555F\u7684\u5730\u5716":"Choose the map to open",attr:{style:"font-size:12px;color:var(--text-muted,#6b7280);margin-top:-6px;"}}),t.forEach(o=>{var h;let l=i.createEl("div",{attr:{style:"display:flex;flex-direction:column;gap:12px;padding:16px 18px;border-radius:16px;border:1px solid rgba(148,163,184,0.18);background:linear-gradient(180deg,rgba(255,255,255,0.035),rgba(255,255,255,0.015));cursor:pointer;transition:transform .12s ease,border-color .12s ease,background .12s ease;outline:none;"}});l.setAttr("role","button"),l.tabIndex=0,l.addEventListener("mouseenter",()=>{l.style.transform="translateY(-1px)",l.style.borderColor="rgba(99,102,241,0.42)",l.style.background="linear-gradient(180deg,rgba(99,102,241,0.10),rgba(255,255,255,0.02))"}),l.addEventListener("mouseleave",()=>{l.style.transform="",l.style.borderColor="rgba(148,163,184,0.18)",l.style.background="linear-gradient(180deg,rgba(255,255,255,0.035),rgba(255,255,255,0.015))"}),l.addEventListener("focus",()=>{l.style.borderColor="rgba(99,102,241,0.55)",l.style.boxShadow="0 0 0 3px rgba(99,102,241,0.12)"}),l.addEventListener("blur",()=>{l.style.borderColor="rgba(148,163,184,0.18)",l.style.boxShadow=""});let c=l.createEl("div",{attr:{style:"width:100%;display:flex;align-items:flex-start;justify-content:space-between;gap:12px;"}});c.createEl("div",{text:Tt(o.path),attr:{style:"flex:1 1 auto;min-width:0;font-size:17px;font-weight:800;color:var(--text-normal,#111);line-height:1.35;word-break:break-word;overflow-wrap:anywhere;"}}),c.createEl("div",{text:a[o.reason]||o.reason,attr:{style:`flex:0 0 auto;align-self:flex-start;padding:4px 8px;border-radius:999px;font-size:11px;font-weight:700;white-space:nowrap;${s[o.reason]||"background:rgba(148,163,184,0.16);color:#cbd5e1;border:1px solid rgba(203,213,225,0.24);"}`}});let p=l.createEl("div",{attr:{style:"display:flex;flex-wrap:wrap;gap:8px;width:100%;"}});p.createEl("div",{text:((h=o.file.parent)==null?void 0:h.path)||o.path,attr:{style:"max-width:100%;padding:5px 10px;border-radius:999px;background:rgba(148,163,184,0.10);color:var(--text-muted,#6b7280);font-size:12px;line-height:1.45;word-break:break-word;overflow-wrap:anywhere;"}}),o.relatedPath&&p.createEl("div",{text:n?`\u95DC\u806F\uFF1A${Tt(o.relatedPath)}`:`Related: ${Tt(o.relatedPath)}`,attr:{style:"max-width:100%;padding:5px 10px;border-radius:999px;background:rgba(99,102,241,0.14);color:#818cf8;font-size:12px;line-height:1.45;word-break:break-word;overflow-wrap:anywhere;"}});let d=()=>{e.close(),this.app.workspace.openLinkText(o.path,"",!1)};l.addEventListener("click",d),l.addEventListener("keydown",u=>{(u.key==="Enter"||u.key===" ")&&(u.preventDefault(),d())})}),i.createEl("button",{text:n?"\u53D6\u6D88":"Cancel",attr:{style:"margin-top:4px;padding:10px 12px;border-radius:12px;border:1px solid rgba(148,163,184,0.18);background:rgba(148,163,184,0.08);color:var(--text-muted,#6b7280);cursor:pointer;font-size:13px;font-weight:600;"}}).addEventListener("click",()=>e.close()),e.open()}_minimize(){if(this._minimized)return;this._minimized=!0,this.containerEl.style.display="none";try{this.app.keymap.popScope(this.scope)}catch(e){}if(this._fab)return;let t=activeDocument.createElement("div");t.className="engram-fab",t.innerHTML=he("book"),t.title="Resume review",t.style.cssText="position:fixed;bottom:calc(env(safe-area-inset-bottom, 0px) + 88px);right:20px;width:48px;height:48px;border-radius:50%;background:linear-gradient(135deg,#4f46e5,#818cf8);color:#fff;display:flex;align-items:center;justify-content:center;font-size:22px;cursor:pointer;z-index:9999;box-shadow:0 4px 16px rgba(79,70,229,0.45);user-select:none;-webkit-tap-highlight-color:transparent;transition:transform 0.15s;",t.addEventListener("mouseenter",()=>{t.style.transform="scale(1.12)"}),t.addEventListener("mouseleave",()=>{t.style.transform="scale(1)"}),t.addEventListener("click",()=>this._restore()),activeDocument.body.appendChild(t),this._fab=t}_restore(){if(this._minimized){this._minimized=!1,this.containerEl.style.display="";try{this.app.keymap.pushScope(this.scope)}catch(t){}this._fab&&(this._fab.remove(),this._fab=null)}}onClose(){this._timerIntervalId&&(window.clearInterval(this._timerIntervalId),this._timerIntervalId=null),this._timerEl=null,this._fab&&(this._fab.remove(),this._fab=null),this._minimized=!1,this._preSessionEvalCtx=null}_renderEditForm(t){let e=this.plugin.settings;this.contentEl.empty();let n=this.contentEl.createEl("div",{attr:{class:"lh-review-nav"}});n.createEl("span",{text:F(e,"HUB_TITLE"),attr:{class:"lh-review-logo"}});let a=n.createEl("button",{attr:{class:"lh-review-back"}});a.innerHTML=he("back")+" "+F(e,"BACK"),a.addEventListener("click",()=>this.renderCard());let s=this.contentEl.createEl("div",{attr:{class:"lh-edit-form"}});function i(E,S){let _=s.createEl("div",{attr:{class:"lh-edit-field"}});_.createEl("label",{text:F(e,E),attr:{class:"lh-edit-label"}});let C=_.createEl("textarea",{attr:{class:"lh-edit-textarea"}});return C.value=S||"",C}let r=i("EDIT_FRONT",t.front),o=s.createEl("div",{attr:{class:"lh-edit-field"}}),l=o.createEl("div",{attr:{class:"lh-edit-field-header"}});l.createEl("label",{text:F(e,"EDIT_BACK"),attr:{class:"lh-edit-label"}});let c=l.createEl("div",{attr:{class:"lh-edit-fmt-bar"}}),p=o.createEl("textarea",{attr:{class:"lh-edit-textarea"}});p.value=t.back||"",p.style.minHeight="140px";function d(E){E.style.height="auto",E.style.height=E.scrollHeight+"px"}d(p),p.addEventListener("input",()=>d(p));let h=(E,S)=>{let _=E.selectionStart,C=E.selectionEnd;if(_===C){new ie.Notice(F(e,"FORMAT_SELECT_TEXT"));return}let L=E.value.slice(_,C);E.value=E.value.slice(0,_)+S+L+S+E.value.slice(C),E.selectionStart=_+S.length,E.selectionEnd=C+S.length,E.focus(),d(E)};c.createEl("button",{text:F(e,"FORMAT_HIGHLIGHT"),attr:{class:"lh-rc-edit-btn lh-fmt-btn"}}).addEventListener("click",()=>h(p,"=="));let x=E=>{let S=E.value.lastIndexOf(`
`,E.selectionStart-1)+1,_=E.value.indexOf(`
`,E.selectionEnd);_===-1&&(_=E.value.length);let C=E.value.slice(S,_);if(!C.trim())return;let L=C.split(`
`).map(A=>A.startsWith("> ")?A:"> "+A).join(`
`);E.value=E.value.slice(0,S)+L+E.value.slice(_),E.selectionStart=S,E.selectionEnd=S+L.length,E.focus(),d(E)};c.createEl("button",{text:F(e,"FORMAT_BLOCKQUOTE"),attr:{class:"lh-rc-edit-btn lh-fmt-btn"}}).addEventListener("click",()=>x(p));let g=s.createEl("div",{attr:{class:"lh-edit-hints-section"}});g.createEl("div",{text:F(e,"EDIT_HINTS")+" (L1 / L2 / L3)",attr:{class:"lh-edit-hints-label"}});let b=g.createEl("textarea",{attr:{class:"lh-edit-textarea lh-edit-hint-ta",placeholder:"L1"}});b.value=t.hint_l1||"";let f=g.createEl("textarea",{attr:{class:"lh-edit-textarea lh-edit-hint-ta",placeholder:"L2"}});f.value=t.hint_l2||"";let v=g.createEl("textarea",{attr:{class:"lh-edit-textarea lh-edit-hint-ta",placeholder:"L3"}});v.value=t.hint_l3||"";let y=s.createEl("div",{attr:{class:"lh-edit-btn-row"}}),w=y.createEl("button",{attr:{class:"lh-edit-save-btn"}});w.textContent=F(e,"EDIT_SAVE"),w.addEventListener("click",async()=>{let E={front:r.value.trim(),back:p.value.trim(),hint_l1:b.value.trim(),hint_l2:f.value.trim(),hint_l3:v.value.trim()};if(!(!E.front||!E.back)){w.disabled=!0;try{let S=!1;if(t.notePath)S=await mc(this.app,t,E);else{let _=this.app.workspace.getActiveViewOfType&&this.app.workspace.getActiveViewOfType(ie.MarkdownView),C=_&&_.file&&_.file.path;C&&(await fc(this.app,C,t,E),S=!0)}if(!S){new ie.Notice(F(e,"CREATE_CARD_SAVE_FAILED")),w.disabled=!1;return}t.front=E.front,t.back=E.back,t.hint_l1=E.hint_l1,t.hint_l2=E.hint_l2,t.hint_l3=E.hint_l3}catch(S){console.error("review-edit: save failed",S),new ie.Notice(F(e,"CREATE_CARD_SAVE_FAILED")),w.disabled=!1;return}this.renderCard()}});let k=y.createEl("button",{attr:{class:"lh-edit-cancel-btn"}});k.textContent=F(e,"EDIT_CANCEL"),k.addEventListener("click",()=>this.renderCard())}_renderDeleteConfirm(t){let e=this.plugin.settings;if(!(t.notePath&&t.notePath.startsWith("engram-review/ai-cards/"))){let o=t.notePath||"",l=new ie.Modal(this.app);l.modalEl.style.cssText="width:min(92vw,400px);padding:0;border-radius:16px;overflow:hidden;";let c=l.contentEl;c.style.cssText="padding:24px;display:flex;flex-direction:column;gap:14px;",c.createEl("div",{attr:{style:"font-size:16px;font-weight:700;color:var(--text-normal,#111);"}}).innerHTML=he("edit")+" "+F(e,"EDIT_CARD"),c.createEl("div",{text:Yt(e)==="zh-tw"?`\u9019\u662F\u300C${o}\u300D\u7684\u624B\u5BEB\u7B46\u8A18\u5361\u7247\uFF0C\u8ACB\u76F4\u63A5\u5728\u7B46\u8A18\u4E2D\u522A\u9664\u5C0D\u61C9\u7684 question :: answer \u884C\u3002`:`This is a hand-written card from "${o}". Delete the matching "question :: answer" line directly in that note.`,attr:{style:"font-size:13px;color:var(--text-muted,#6b7280);line-height:1.6;"}});let p=c.createEl("div",{attr:{style:"display:flex;gap:8px;justify-content:flex-end;"}});p.createEl("button",{text:F(e,"DELETE_CANCEL_BTN"),attr:{style:"padding:7px 16px;border-radius:8px;border:1px solid var(--background-modifier-border, #d1d5db);background:var(--background-secondary, #fff);color:var(--text-muted, inherit);cursor:pointer;font-size:13px;"}}).addEventListener("click",()=>l.close()),p.createEl("button",{text:Yt(e)==="zh-tw"?"\u958B\u555F\u7B46\u8A18":"Open Note",attr:{style:"padding:7px 16px;border-radius:8px;border:none;background:#38bdf8;color:#04121f;cursor:pointer;font-size:13px;font-weight:600;"}}).addEventListener("click",()=>{l.close(),this.app.workspace.openLinkText(o,"",!1)}),l.open();return}let a=new ie.Modal(this.app);a.modalEl.style.cssText="width:min(92vw,400px);padding:0;border-radius:16px;overflow:hidden;";let s=a.contentEl;s.style.cssText="padding:24px;display:flex;flex-direction:column;gap:14px;",s.createEl("div",{text:F(e,"DELETE_CONFIRM_TITLE").replace("{name}",t.front),attr:{style:"font-size:16px;font-weight:700;color:var(--text-normal,#111);"}}),s.createEl("div",{text:F(e,"DELETE_CONFIRM_FILE"),attr:{style:"font-size:13px;color:var(--text-muted,#6b7280);line-height:1.6;"}});let i=s.createEl("div",{attr:{style:"display:flex;gap:8px;justify-content:flex-end;"}});i.createEl("button",{text:F(e,"DELETE_CANCEL_BTN"),attr:{style:"padding:7px 16px;border-radius:8px;border:1px solid var(--background-modifier-border, #d1d5db);background:var(--background-secondary, #fff);color:var(--text-muted, inherit);cursor:pointer;font-size:13px;"}}).addEventListener("click",()=>a.close()),i.createEl("button",{text:F(e,"DELETE_CONFIRM_BTN"),attr:{style:"padding:7px 16px;border-radius:8px;border:none;background:#ef4444;color:#fff;cursor:pointer;font-size:13px;font-weight:600;"}}).addEventListener("click",async()=>{a.close();try{await bc(this.app,t)}catch(o){console.error("review-delete failed",o)}if(this.cards.splice(this.idx,1),this.cards.length===0){this._renderComplete();return}this.idx>=this.cards.length&&(this.idx=this.cards.length-1),this.renderCard()}),a.open()}};bs.exports={ReviewSessionModal:Rc,__private:{isAiReviewCard:ra,getCardRelatedNotePaths:Vt,getCardDisplayNotePaths:hs,getTopicFolders:gs,findMemoryMapCandidatesSync:ms,findMemoryMapCandidatesByCanvasContent:fs}}});var Zt=Y((Gu,ws)=>{"use strict";var $e=require("obsidian"),{getLocale:xs}=ge(),{giHTML:oa}=be(),ys={review:{emoji:"cards",img:"reviewdeck","zh-tw":{title:"Review Deck \u2014 \u4E09\u968E\u56DE\u60F3\u8907\u7FD2",lines:["\u628A\u7B46\u8A18\u8B8A\u6210\u9583\u5361\uFF0CFSRS \u5728\u4F60<strong>\u5FEB\u5FD8\u8A18\u7684\u77AC\u9593</strong>\u6392\u8907\u7FD2\u3002","<strong>L1</strong> \u53EA\u7D66\u554F\u984C\u903C\u4F60\u56DE\u60F3 \u2192 <strong>L2</strong> \u5F9E\u4F60\u7684 vault \u627E\u51FA\u7576\u521D\u5B78\u5B83\u7684\u8108\u7D61 \u2192 <strong>L3</strong> \u7D66\u95DC\u9375\u5B57\u65B9\u5411\u3002"],pickPrompt:"\u4F60\u60F3\u600E\u9EBC\u958B\u59CB\uFF1F",options:[{id:"manual",emoji:"edit",label:"\u624B\u52D5\u5EFA\u7ACB",sub:"\u81EA\u5DF1\u5BEB\u5361\u3001\u6216\u8CBC\u4E0A AI \u56DE\u7B54\uFF0C\u81EA\u52D5\u6536\u9304"},{id:"ai",emoji:"robot",label:"\u7528 AI \u5EFA\u7ACB",sub:"\u5B89\u88DD Skills\uFF0C\u8B93 AI \u5DE5\u5177\u81EA\u52D5\u751F\u6210"},{id:"advanced",emoji:"cap",label:"\u9032\u968E\u6280\u5DE7",sub:"SVG \u984C\u3001\u5BA2\u88FD\u898F\u5247\u3001\u6311\u7B46\u8A18\u751F\u6210"}],pages:{manual:{title:"\u624B\u52D5\u5EFA\u7ACB Review Deck",body:[{h:"\u53EA\u8981\u5169\u500B\u689D\u4EF6\uFF0C\u7B46\u8A18\u5C31\u6703\u88AB<strong>\u81EA\u52D5\u5075\u6E2C</strong>\u3001\u52A0\u9032\u724C\u7D44\uFF1A<br>\u2460 \u6253\u4E0A <code>#flashcards/\u4E3B\u984C</code> tag\u3000\u2461 \u81F3\u5C11\u5BEB\u4E00\u5F35\u5361\uFF08\u6700\u5E38\u7528 <code>Q:/A:</code>\uFF0C\u9023\u7E8C\u5169\u500B\u7A7A\u884C\u7D50\u675F\u4E00\u5F35\u5361\uFF09\u3002"},{v:"XC0NsPVfcwE",label:"\u793A\u7BC4\uFF1A\u81EA\u5DF1\u624B\u5BEB\u5361\u7247"},{h:"<strong>\u4E5F\u53EF\u4EE5\u8CBC\u4E0A AI \u56DE\u7B54</strong>\uFF1A\u554F ChatGPT \u6216\u4EFB\u610F AI\uFF0C\u628A\u6574\u6BB5\u7B54\u6848\u8907\u88FD\u3001\u7528 <code>%%card%%</code> \u5305\u9032\u7B46\u8A18\u5C31\u8B8A\u6210\u5361\u7247 \u2014\u2014 <strong>\u9023 AI \u751F\u6210\u7684\u5716\u7247\u4E5F\u80FD\u4E00\u8D77\u52A0\u9032\u724C\u7D44</strong>\u3002"},{v:"pMf5mfCswBo",label:"\u793A\u7BC4\uFF1A\u8CBC\u4E0A AI \u56DE\u7B54\u5FEB\u901F\u5EFA\u5361"}],first:"\u6253\u958B\u4EFB\u4E00\u7B46\u8A18\uFF0C\u52A0 <code>#flashcards/\u4E3B\u984C</code> + \u4E00\u5F35 <code>Q: \u554F\u984C / A: \u7B54\u6848</code> \u5361\uFF0C\u56DE\u5230\u9019\u5206\u9801\u5C31\u770B\u5230\u724C\u7D44\u3002"},ai:{title:"\u7528 AI \u5EFA\u7ACB Review Deck",body:[{h:"\u2460 \u5230 <strong>\u8A2D\u5B9A \u2192 AI Skills</strong> \u70BA\u4F60\u7684\u5DE5\u5177\u9EDE\u5B89\u88DD\u3002\u6703\u88DD <strong>5 \u500B skills</strong>\uFF0C\u5176\u4E2D <code>engram-review-deck</code> \u8CA0\u8CAC\u5EFA Review Deck\u3002"},{h:"\u2461 \u7528 Claude Code / Codex / Antigravity / Gemini CLI / Cursor\uFF0C<strong>\u5F9E Obsidian \u7B46\u8A18\u6240\u5728\u7684\u8CC7\u6599\u593E</strong>\u6253\u958B\uFF0C\u76F4\u63A5\u8DDF\u5B83\u8AAA\u300C\u5E6B\u6211\u5EFA\u7ACB Review Deck\u300D\u3002"},{v:"X9RhfC-l8d4",label:"\u793A\u7BC4\uFF1A\u7528 AI \u5EFA\u7ACB Review Deck \u5B8C\u6574\u6D41\u7A0B"},{h:"\u7BC4\u4F8B\uFF1A<br>\u30FB\u300C\u5E6B\u6211\u67E5\u4ECA\u5929\u7684\u7B46\u8A18\uFF0C\u628A\u91AB\u5B78\u76F8\u95DC\u540D\u8A5E\u5EFA\u6210 Review Deck\u300D<br>\u30FB\u300C\u628A topic \u7269\u7406\u3001\u529B\u5B78\u76F8\u95DC\u7684\u5EFA\u6210 Review Deck\uFF0C\u53EF\u4EE5\u7528 SVG \u7576\u984C\u76EE\u548C\u89E3\u7B54\u300D<br>\u30FB\u300C\u5E6B\u6211\u67E5\u6700\u65B0\u7684 AI \u8CC7\u8A0A\uFF0C\u5EFA\u4E00\u500B Review Deck\u300D"}],first:"\u5148\u5230 \u8A2D\u5B9A \u2192 AI Skills \u5B89\u88DD\u4F60\u7684\u5DE5\u5177\uFF1B\u4E0D\u60F3\u5B89\u88DD\u5C31\u6539\u7528\u300C\u624B\u52D5\u5EFA\u7ACB\u300D\u8CBC\u4E0A AI \u56DE\u7B54\u3002"},advanced:{title:"\u9032\u968E\u6280\u5DE7\uFF08best practice\uFF09",body:[{h:"<strong>\u7528 AI \u5EFA SVG \u5716</strong>\u7576\u984C\u76EE\u6216\u89E3\u7B54\uFF0C\u505A\u5716\u50CF\u8A18\u61B6 \u2014\u2014 \u4F8B\u5982\u8ACB Claude\u300C\u7528 SVG \u756B\u51FA\u9019\u500B\u7D50\u69CB\u7576\u89E3\u7B54\u300D\u3002"},{h:"<strong>\u5BA2\u88FD\u5316 AI \u98A8\u683C</strong>\uFF1A\u5728 <code>CLAUDE.md</code> / <code>AGENTS.md</code> / <code>GEMINI.md</code> \u52A0\u898F\u5247\uFF0C\u4F8B\u5982\u300C\u7B54\u6848\u90FD\u7528\u5716\u89E3\u5448\u73FE\u300D\u300C\u6BCF\u500B ==highlight== \u90FD\u8981\u5EFA\u4E00\u5F35\u5361\u300D\u3002"},{h:"<strong>\u5148\u9078\u518D\u751F\u6210</strong>\uFF1A\u8B93 AI \u5148\u5217\u51FA\u4F60\u6700\u8FD1\u7684\u7B46\u8A18\uFF0C\u4F60\u76F4\u63A5\u6311\u8981\u5EFA\u6210 Review Deck \u7684\u5167\u5BB9\u3002"}],first:"\u6311\u4E00\u500B best practice \u8A66\u8A66 \u2014\u2014 AI \u5361\u7247\u90FD\u5B58\u5728 <code>engram-review/</code>\uFF0C\u6C38\u9060\u4E0D\u52D5\u4F60\u7684\u4F86\u6E90\u7B46\u8A18\u3002"}}},en:{title:"Review Deck \u2014 Three-Stage Recall",lines:["Turn notes into flashcards; FSRS schedules them <strong>right before you forget</strong>.","<strong>L1</strong> question-only recall \u2192 <strong>L2</strong> AI rebuilds the moment you first learned it from your vault \u2192 <strong>L3</strong> a keyword nudge."],pickPrompt:"How do you want to start?",options:[{id:"manual",emoji:"edit",label:"Create manually",sub:"Write cards or paste an AI answer \u2014 auto-detected"},{id:"ai",emoji:"robot",label:"Build with AI",sub:"Install Skills, let your AI tool generate them"},{id:"advanced",emoji:"cap",label:"Pro tips",sub:"SVG questions, custom rules, pick-then-build"}],pages:{manual:{title:"Create a Review Deck manually",body:[{h:"Two conditions and a note is <strong>auto-detected</strong> into a deck:<br>\u2460 tag it <code>#flashcards/topic</code>\u3000\u2461 write at least one card (usually <code>Q:/A:</code> \u2014 two blank lines end a card)."},{v:"XC0NsPVfcwE",label:"Demo: write your own cards"},{h:"<strong>You can also paste an AI answer</strong>: ask ChatGPT or any AI, copy the whole answer, wrap it in <code>%%card%%</code> in a note \u2014 it becomes cards, and <strong>AI-generated images go into the deck too</strong>."},{v:"pMf5mfCswBo",label:"Demo: build a deck by pasting an AI answer"}],first:"Open any note, add <code>#flashcards/topic</code> + one <code>Q: \u2026 / A: \u2026</code> card \u2014 the deck appears in this tab."},ai:{title:"Build a Review Deck with AI",body:[{h:"\u2460 In <strong>Settings \u2192 AI Skills</strong>, click Install for your tool. It installs <strong>5 skills</strong>; <code>engram-review-deck</code> is the one that builds review decks."},{h:'\u2461 In Claude Code / Codex / Antigravity / Gemini CLI / Cursor \u2014 opened <strong>from the folder your Obsidian notes live in</strong> \u2014 just say "Build a Review Deck."'},{v:"X9RhfC-l8d4",label:"Demo: the full Build-with-AI flow"},{h:`Examples:<br>\u30FB"Check today's notes and build a Review Deck from the medical terms."<br>\u30FB"Build a Review Deck on physics / mechanics, using SVG for questions and answers."<br>\u30FB"Look up the latest AI news and build a Review Deck."`}],first:'Install your tool in Settings \u2192 AI Skills; or skip it and paste an AI answer via "Create manually".'},advanced:{title:"Pro tips (best practices)",body:[{h:'<strong>Use AI to draw SVG</strong> as the question or answer for visual memory \u2014 e.g. ask Claude to "draw this structure in SVG as the answer."'},{h:'<strong>Customize AI style</strong>: add rules to <code>CLAUDE.md</code> / <code>AGENTS.md</code> / <code>GEMINI.md</code>, e.g. "always answer with diagrams" or "every ==highlight== becomes a card."'},{h:"<strong>Pick then build</strong>: have the AI list your recent notes first, then choose which ones to turn into a Review Deck."}],first:"Try one best practice \u2014 AI cards stay in <code>engram-review/</code> and never touch your source notes."}}}},quest:{emoji:"map",img:"questmap","zh-tw":{title:"Quest Map \u2014 \u628A\u7B46\u8A18\u8B8A\u6210\u904A\u6232\u95DC\u5361",lines:["\u4E00\u7BC7\u7B46\u8A18 \u2192 \u4E00\u5F35\u5CF6\u5DBC\u5730\u5716\uFF0C<strong>15 \u7A2E\u4E92\u52D5\u984C\u578B</strong>\u9010\u95DC\u95D6\u904E\u53BB\u3002","\u96E3\u5EA6 Easy / Medium / Hard\uFF0C\u7AE0\u672B\u6709 <strong>Boss Battle</strong> \u7D9C\u5408\u6E2C\u9A57\u3002"],pickPrompt:"\u4F60\u60F3\u4E86\u89E3\u4EC0\u9EBC\uFF1F",options:[{id:"build",emoji:"tools",label:"\u5982\u4F55\u5EFA\u7ACB",sub:"\u5B89\u88DD Skills\uFF0C\u53EB AI \u628A\u7B46\u8A18\u8B8A\u95DC\u5361"},{id:"play",emoji:"gamepad",label:"\u73A9\u6CD5 & \u984C\u578B",sub:"15 \u7A2E\u6311\u6230\u3001\u96E3\u5EA6\u3001Boss Battle"},{id:"advanced",emoji:"cap",label:"\u9032\u968E\u6280\u5DE7",sub:"\u4F9D topic \u5EFA\u7ACB\u3001\u9078\u96E3\u5EA6\u7684\u8A23\u7AC5"}],pages:{build:{title:"\u5982\u4F55\u5EFA\u7ACB Quest Map",body:[{h:"\u2460 <strong>\u5B89\u88DD Skills</strong> \u2014 \u5230 \u8A2D\u5B9A \u2192 AI Skills\uFF0C\u9078\u4F60\u7684\u5DE5\u5177\uFF08Claude Code / Codex / Antigravity / Gemini CLI / Cursor\uFF09\u6309\u300C\u5B89\u88DD\u300D\u3002"},{img:"install-skills.png",label:"\u5728 \u8A2D\u5B9A \u2192 AI Skills \u5B89\u88DD"},{h:"\u2461 <strong>\u5728 AI \u5DE5\u5177\u88E1\u76F4\u63A5\u8AAA</strong> \u2014 \u5F9E Obsidian \u7B46\u8A18\u6240\u5728\u7684\u8CC7\u6599\u593E\u6253\u958B AI\uFF0C\u4F8B\u5982\u8AAA\u300C\u5E6B\u6211\u5EFA\u7ACB\u95DC\u65BC\u9AD8\u4E09\u6578\u5B78\u7684 quest-map\uFF0Ceasy \u96E3\u5EA6\u300D\u3002"}],first:"\u8DDF AI \u8AAA\u300C\u628A {\u7B46\u8A18\u6216\u4E3B\u984C} \u505A\u6210 quest-map\uFF0C{\u96E3\u5EA6}\u300D\uFF0C\u5730\u5716\u5C31\u51FA\u73FE\u5728\u9019\u5206\u9801\u3002"},play:{title:"\u73A9\u6CD5 & \u984C\u578B",body:[{h:"<strong>15 \u7A2E\u984C\u578B</strong>\uFF1A\u9078\u64C7\u3001\u662F\u975E\u3001\u514B\u6F0F\u5B57\u3001\u8F38\u5165\u3001\u6392\u5E8F\u3001\u914D\u5C0D\u3001\u5012\u6578\u8A08\u6642\u3001\u5FEB\u7167\u8A18\u61B6\u3001\u7AF6\u6A19\uFF08\u62BC\u7C4C\u78BC\uFF09\u3001\u6642\u9593\u8EF8\u3001\u5E8F\u5217\u63A5\u9F8D\u3001\u8A18\u61B6\u5BAE\u6BBF\u3001\u5716\u7247\u984C\u3001\u5716\u7247\u906E\u7F69\u3001iframe \u4E92\u52D5\u6A21\u64EC\u3002"},{h:"\u96E3\u5EA6 <strong>Easy / Medium / Hard</strong>\uFF1B\u7AE0\u672B <strong>Boss Battle</strong> \u7D9C\u5408\u6E2C\u9A57\u3002<br><strong>5 \u7A2E\u4E3B\u984C</strong>\uFF1A\u5929\u7A7A\u5CF6\u3001\u79D1\u5E7B\u3001RPG\u3001\u6D77\u6D0B\u3001\u6975\u7C21\u3002"}],first:"\u9EDE\u4EFB\u4E00 quest \u5361\u9032\u5165\u5730\u5716\uFF0C\u9010\u95DC\u5B8C\u6210\u3001\u6311\u6230 Boss\u3002"},advanced:{title:"\u9032\u968E\u6280\u5DE7\uFF08best practice\uFF09",body:[{h:"<strong>\u4F9D\u4F60\u81EA\u5DF1\u7684\u7B46\u8A18\u5167\u5BB9 / topic \u5EFA\u7ACB</strong>\u6548\u679C\u6700\u597D \u2014\u2014 AI \u5C0D\u4F60\u771F\u6B63\u8B80\u904E\u7684\u6771\u897F\u6700\u6E96\u3002"},{h:"<strong>\u53EF\u6307\u5B9A\u96E3\u5EA6</strong>\uFF08easy / medium / hard\uFF09\uFF1B<strong>\u95DC\u5361\u8D8A\u591A\u3001\u751F\u6210\u8D8A\u4E45</strong>\uFF0C\u60F3\u5FEB\u5C31\u5148\u505A\u5C0F\u7BC4\u570D\u3002"},{h:"\u7BC4\u4F8B\uFF1A\u300C\u5E6B\u6211\u5EFA\u7ACB\u95DC\u65BC\u9AD8\u4E09\u6578\u5B78\u7684 easy \u96E3\u5EA6 quest-map\u300D\u3002"},{h:"\u9032\u5EA6\u8207\u5206\u6578\u548C quest \u5167\u5BB9<strong>\u5206\u958B\u5132\u5B58</strong>\uFF0CAI \u66F4\u65B0\u95DC\u5361\u4E0D\u6703\u6D17\u6389\u4F60\u7684\u9032\u5EA6\u3002"}],first:"\u6311\u4E00\u500B\u4F60\u719F\u7684 topic\uFF0C\u6307\u5B9A\u96E3\u5EA6\u8ACB AI \u751F\u6210\u4E00\u5F35 quest-map\u3002"}}},en:{title:"Quest Map \u2014 Turn Notes into Challenges",lines:["One note \u2192 an island map you clear through <strong>15 interaction types</strong>.","Easy / Medium / Hard difficulty, with a final applied <strong>Boss Battle</strong>."],pickPrompt:"What do you want to know?",options:[{id:"build",emoji:"tools",label:"How to create",sub:"Install Skills, ask AI to turn a note into a map"},{id:"play",emoji:"gamepad",label:"Play & types",sub:"15 challenges, difficulty, Boss Battle"},{id:"advanced",emoji:"cap",label:"Pro tips",sub:"Build from your topic, pick difficulty"}],pages:{build:{title:"How to create a Quest Map",body:[{h:"\u2460 <strong>Install Skills</strong> \u2014 in Settings \u2192 AI Skills, click Install for your tool (Claude Code / Codex / Antigravity / Gemini CLI / Cursor)."},{img:"install-skills.png",label:"Install in Settings \u2192 AI Skills"},{h:'\u2461 <strong>Just tell your AI</strong> \u2014 open the AI from the folder your Obsidian notes live in and say, e.g. "Build an easy quest-map about Grade-12 math."'}],first:'Tell your AI "Turn {note or topic} into a quest-map, {difficulty}" \u2014 the map appears in this tab.'},play:{title:"Play & challenge types",body:[{h:"<strong>15 types</strong>: quiz, true/false, cloze, input, ordering, matching, countdown, snapshot, auction (bet coins), timeline, chain, memory palace, image quiz, image occlusion, interactive iframe simulations."},{h:"Difficulty <strong>Easy / Medium / Hard</strong>; a final <strong>Boss Battle</strong> applied test.<br><strong>5 themes</strong>: Sky Island, Sci-Fi, RPG, Ocean, Minimal."}],first:"Click any quest card to enter the map, clear missions and beat the Boss."},advanced:{title:"Pro tips (best practices)",body:[{h:"<strong>Build from your own note content / topic</strong> \u2014 AI is most accurate on what you've actually studied."},{h:"<strong>Pick a difficulty</strong> (easy / medium / hard); <strong>more levels take longer to generate</strong> \u2014 keep the scope small for speed."},{h:'Example: "Build an easy quest-map about Grade-12 math."'},{h:"Progress and scores are <strong>stored separately</strong> from quest content, so AI can update a quest without wiping your progress."}],first:"Pick a topic you know, set a difficulty, and ask AI to generate a quest-map."}}}},lesson:{emoji:"cap",img:"lesson_academy","zh-tw":{title:"\u6559\u6750 \u2014 AI \u70BA\u4F60\u6253\u9020\u8AB2\u7A0B",lines:["\u544A\u8A34 AI \u60F3\u5B78\u4EC0\u9EBC\uFF0C\u5B83<strong>\u5148\u554F\u4F60\u7684\u76EE\u6A19\u548C\u7A0B\u5EA6</strong>\uFF0C\u518D\u751F\u6210\u53EF\u5728 Obsidian \u5167\u7814\u8B80\u7684\u4E92\u52D5\u8AB2\u7A0B\u3002","\u6BCF\u5802\u8AB2\u9644\u6E2C\u9A57\uFF0C\u5B78\u5B8C\u53EF\u4E00\u9375\u8F49\u6210 Review Deck \u6216 Quest Map\u3002"],pickPrompt:"\u4F60\u60F3\u600E\u9EBC\u958B\u59CB\uFF1F",options:[{id:"build",emoji:"robot",label:"\u7528 AI \u5EFA\u7ACB\u8AB2\u7A0B",sub:"\u4E00\u53E5\u8A71\u751F\u6210\u6574\u5957\u8AB2\u7A0B"},{id:"plan",emoji:"card",label:"\u81EA\u5DF1\u898F\u5283 / \u532F\u5165",sub:"\u5148\u6392\u8AB2\u7DB1\uFF0C\u6216\u532F\u5165\u73FE\u6210 HTML"},{id:"advanced",emoji:"cap",label:"\u9032\u968E\u6280\u5DE7",sub:"\u76EE\u6A19\u5C0E\u5411\u3001\u8AB2\u7DB1\u5148\u9078\u518D\u751F\u6210"}],pages:{build:{title:"\u7528 AI \u5EFA\u7ACB\u8AB2\u7A0B",body:[{h:"\u2460 <strong>\u5B89\u88DD Skills</strong> \u2014 \u5230 \u8A2D\u5B9A \u2192 AI Skills\uFF0C\u9078\u4F60\u7684\u5DE5\u5177\u6309\u300C\u5B89\u88DD\u300D\u3002"},{img:"install-skills.png",label:"\u5728 \u8A2D\u5B9A \u2192 AI Skills \u5B89\u88DD"},{h:"\u2461 <strong>\u5728 AI \u5DE5\u5177\u88E1\u76F4\u63A5\u8AAA</strong> \u2014 \u4F8B\u5982\u300C\u6559\u6211 SEO\uFF0C\u5EFA\u4E00\u5957\u8AB2\u7A0B\u300D\uFF0C\u5B83\u6703<strong>\u5148\u554F\u4F60\u7684\u76EE\u6A19\u548C\u7A0B\u5EA6</strong>\u518D\u751F\u6210\u3002"},{v:"G8HxBl9Hf9o",label:"\u793A\u7BC4\uFF1A\u7528 Claude Code \u5EFA\u7ACB\u8AB2\u7A0B"}],first:"\u8DDF AI \u8AAA\u300C\u6559\u6211 {\u4E3B\u984C}\uFF0C\u5EFA\u4E00\u5957\u8AB2\u7A0B\u300D\uFF0C\u5148\u56DE\u7B54\u5B83\u7684\u76EE\u6A19 / \u7A0B\u5EA6\u554F\u984C\u3002"},plan:{title:"\u81EA\u5DF1\u898F\u5283 / \u532F\u5165",body:[{h:"\u6309\u300C<strong>\u5EFA\u7ACB\u65B0\u8AB2\u7A0B</strong>\u300D\u53EF\u5148\u6392\u8AB2\u7DB1\uFF08\u53EA\u6709\u6A19\u984C\u7684\u5F85\u751F\u6210\u8AB2\u6642\uFF09\uFF0C\u4E4B\u5F8C AI \u518D\u88DC\u5167\u5BB9\u3001<strong>\u9032\u5EA6\u5EF6\u7E8C</strong>\u3002"},{h:"<strong>\u532F\u5165</strong>\uFF1A\u628A\u5225\u8655 AI \u751F\u6210\u7684\u8AB2\u7A0B HTML \u539F\u6A23\u532F\u5165\uFF0C\u4E0D\u8F49\u63DB\u3002"}],first:"\u6309\u300C\u5EFA\u7ACB\u65B0\u8AB2\u7A0B\u300D\u6392\u4F60\u7684\u7B2C\u4E00\u4EFD\u8AB2\u7DB1\u3002"},advanced:{title:"\u9032\u968E\u6280\u5DE7\uFF08best practice\uFF09",body:[{h:"\u6838\u5FC3\u662F<strong>\u76EE\u6A19\u5C0E\u5411</strong>\u5B78\u7FD2\uFF08\u300A\u8D85\u901F\u5B78\u7FD2 Ultralearning\u300B\uFF09\uFF1A\u8DDF AI \u8AAA\u4F60<strong>\u60F3\u9054\u6210\u7684\u76EE\u6A19</strong>\uFF0C\u8B93\u5B83\u5E6B\u4F60\u6392<strong>\u8AB2\u7DB1</strong>\u3002"},{h:"\u4F60<strong>\u6311\u60F3\u8981\u7684\u8AB2\u7DB1</strong>\u751F\u6210\u5C31\u597D\uFF0C<strong>\u4E0D\u4E00\u5B9A\u8981\u5168\u90E8\u5EFA\u7ACB</strong> \u2014\u2014 \u6539\u5927\u7DB1\u4FBF\u5B9C\u3001\u91CD\u751F\u6210\u8AB2\u7A0B\u8CB4\u3002"},{h:"\u8AB2\u7A0B<strong>\u9023\u7D50\u4F60\u7684 vault</strong>\uFF1B\u5B78\u5B8C\u4E00\u9375<strong>\u8F49\u6210 Review Deck \u6216 Quest Map</strong>\u3002"}],first:"\u8DDF AI \u8AAA\u4F60\u7684\u5B78\u7FD2\u76EE\u6A19\uFF08\u4F8B\uFF1A\u300C\u6211\u60F3\u80FD\u7368\u7ACB\u5BEB React\uFF0C\u5E6B\u6211\u6392\u8AB2\u7DB1\u300D\uFF09\uFF0C\u5148\u770B\u8AB2\u7DB1\u518D\u6311\u8981\u751F\u6210\u7684\u3002"}}},en:{title:"Lesson Academy \u2014 AI-Built Courses",lines:["Tell AI a topic; it <strong>asks your goal and level first</strong>, then builds an interactive course you study inside Obsidian.","Every lesson ends with a quiz \u2014 convert a finished course into a Review Deck or Quest Map."],pickPrompt:"How do you want to start?",options:[{id:"build",emoji:"robot",label:"Build with AI",sub:"Generate a whole course in one sentence"},{id:"plan",emoji:"card",label:"Plan / import",sub:"Outline it first, or import existing HTML"},{id:"advanced",emoji:"cap",label:"Pro tips",sub:"Goal-first; pick outline before generating"}],pages:{build:{title:"Build a course with AI",body:[{h:"\u2460 <strong>Install Skills</strong> \u2014 in Settings \u2192 AI Skills, click Install for your tool."},{img:"install-skills.png",label:"Install in Settings \u2192 AI Skills"},{h:'\u2461 <strong>Just tell your AI</strong> \u2014 e.g. "Teach me SEO, build a course." It <strong>asks your goal and level first</strong>, then generates.'},{v:"G8HxBl9Hf9o",label:"Demo: build a course with Claude Code"}],first:'Say "Teach me {topic}, build a course" \u2014 answer its goal / level questions first.'},plan:{title:"Plan it yourself / import",body:[{h:'Click <strong>New course</strong> to plan an outline first (title-only lessons marked "to generate"); AI fills them in later and <strong>your progress carries over</strong>.'},{h:"<strong>Import</strong>: bring in a course HTML generated elsewhere, kept as-is."}],first:"Click New course and lay out your first outline."},advanced:{title:"Pro tips (best practices)",body:[{h:"The core is <strong>goal-first learning</strong> (\xE0 la <em>Ultralearning</em>): tell AI <strong>the goal you want to reach</strong> and let it draft the <strong>outline</strong>."},{h:"<strong>Pick only the outline parts you want</strong> \u2014 you don't have to build them all. Changing the outline is cheap; regenerating lessons is not."},{h:"Courses <strong>link to your vault</strong>; convert a finished one into a <strong>Review Deck or Quest Map</strong> in one click."}],first:'Tell AI your goal (e.g. "I want to build React on my own \u2014 draft an outline"), review the outline, then pick.'}}}},memory:{emoji:"brain",img:"memory_map","zh-tw":{title:"Memory Map \u2014 \u770B\u898B\u77E5\u8B58\u5168\u5C40",lines:["\u7528 Obsidian Canvas \u628A\u62BD\u8C61\u6982\u5FF5\u756B\u6210<strong>\u8996\u89BA\u7D50\u69CB</strong>\uFF0C\u4E32\u9023\u4F60\u65E2\u6709\u7684\u7B46\u8A18\u3002","\u8907\u7FD2\u5361\u7247\u6642\u53EF\u4E00\u9375\u8DF3\u5230\u5C0D\u61C9\u7684\u6982\u5FF5\u5716\u3002"],pickPrompt:"\u4F60\u60F3\u4E86\u89E3\u4EC0\u9EBC\uFF1F",options:[{id:"build",emoji:"tools",label:"\u5982\u4F55\u5EFA\u7ACB",sub:"AI \u751F\u6210\uFF0C\u6216\u81EA\u5EFA canvas"},{id:"use",emoji:"link",label:"\u600E\u9EBC\u7528 & \u9023\u52D5\u8907\u7FD2",sub:"\u81EA\u52D5\u5075\u6E2C + \u8907\u7FD2\u8DF3\u8F49"}],pages:{build:{title:"\u5982\u4F55\u5EFA\u7ACB Memory Map",body:[{h:"\u2460 <strong>\u5B89\u88DD Skills</strong> \u2014 \u5230 \u8A2D\u5B9A \u2192 AI Skills\uFF0C\u9078\u4F60\u7684\u5DE5\u5177\u6309\u300C\u5B89\u88DD\u300D\u3002"},{img:"install-skills.png",label:"\u5728 \u8A2D\u5B9A \u2192 AI Skills \u5B89\u88DD"},{h:"\u2461 <strong>\u5728 AI \u5DE5\u5177\u88E1\u76F4\u63A5\u8AAA</strong> \u2014 \u4F8B\u5982\u300C\u5E6B \u4F5C\u696D\u7CFB\u7D71\u6982\u8AD6.md \u5EFA\u7ACB memory-map\u300D\u3002\u4E5F\u53EF\u81EA\u5EFA\u6A94\u540D <code>{\u7B46\u8A18\u540D}-memory.canvas</code>\uFF0CHub \u6703\u81EA\u52D5\u6536\u9304\u3002"},{h:"AI \u7528<strong>\u8A18\u61B6\u79D1\u5B78</strong>\u751F\u6210\uFF1A\u53CD\u76F4\u89BA\u3001\u9023\u7D50\u65E2\u6709\u7B46\u8A18\u3001\u5206\u7D44\u3001\u985E\u6BD4\u3002"}],first:"\u8DDF AI \u8AAA\u300C\u5E6B {\u7B46\u8A18}.md \u5EFA\u7ACB memory-map\u300D\uFF0C\u6982\u5FF5\u5716\u5C31\u51FA\u73FE\u5728\u9019\u5206\u9801\u3002"},use:{title:"\u600E\u9EBC\u7528 & \u9023\u52D5\u8907\u7FD2",body:[{h:"\u4EFB\u4F55 <code>{\u7B46\u8A18\u540D}-memory.canvas</code> \u6703\u5728 <strong>Hub \u2192 Memory Map</strong> \u81EA\u52D5\u5075\u6E2C\u3002"},{h:"\u8907\u7FD2\u5361\u7247\u6642\u6309 <strong>Memory Map \u9215</strong>\uFF0C\u81EA\u52D5\u627E\u5230\u4E26\u6253\u958B\u5C0D\u61C9\u7684 canvas\u3002"}],first:"\u8907\u7FD2\u4E00\u5F35\u5361\u6642\u9EDE Memory Map \u9215\uFF0C\u770B\u5B83\u8DF3\u5230\u5C0D\u61C9\u6982\u5FF5\u5716\u3002"}}},en:{title:"Memory Map \u2014 See the Big Picture",lines:["Lay abstract concepts onto an Obsidian Canvas as <strong>visual structure</strong>, linked to your existing notes.","Jump from a review card straight to its matching concept map."],pickPrompt:"What do you want to know?",options:[{id:"build",emoji:"tools",label:"How to create",sub:"AI-generated, or build a canvas yourself"},{id:"use",emoji:"link",label:"Use & link review",sub:"Auto-detect + jump from review"}],pages:{build:{title:"How to create a Memory Map",body:[{h:"\u2460 <strong>Install Skills</strong> \u2014 in Settings \u2192 AI Skills, click Install for your tool."},{img:"install-skills.png",label:"Install in Settings \u2192 AI Skills"},{h:'\u2461 <strong>Just tell your AI</strong> \u2014 e.g. "Create a memory-map for OS-overview.md." Or make a file named <code>{note-name}-memory.canvas</code> yourself \u2014 Hub auto-detects it.'},{h:"AI generates using <strong>memory science</strong>: novelty, association to your notes, chunking, analogy."}],first:'Tell AI "Create a memory-map for {note}.md" \u2014 the map appears in this tab.'},use:{title:"Use it & link to review",body:[{h:"Any <code>{note-name}-memory.canvas</code> is auto-detected in <strong>Hub \u2192 Memory Map</strong>."},{h:"During review, click the <strong>Memory Map button</strong> to find and open the matching canvas automatically."}],first:"While reviewing a card, click the Memory Map button to jump to its concept map."}}}}};function vs(t){return Object.prototype.hasOwnProperty.call(ys,t)}var Jt=class extends $e.Modal{constructor(e,n,a){super(e),this.plugin=n,this.tab=a,this.page=null}onOpen(){let e=ys[this.tab];if(!e){this.close();return}this.def=e,this.locale=xs(this.plugin.settings)==="zh-tw"?"zh-tw":"en";let n=activeDocument.body.classList.contains("theme-dark");this.t={isDark:n,bg:n?"#1a1a2e":"#ffffff",text:n?"#e2e8f0":"#1f2937",muted:n?"#94a3b8":"#6b7280",border:n?"#2a2a44":"#e5e7eb",accentBg:n?"rgba(99,102,241,0.16)":"#eef2ff",accentBorder:n?"rgba(129,140,248,0.5)":"#c7d2fe",accentText:n?"#c7d2fe":"#4338ca",codeBg:n?"rgba(255,255,255,0.08)":"#f3f4f6",cardBg:n?"rgba(255,255,255,0.04)":"#f8faff",hoverBg:n?"rgba(99,102,241,0.16)":"#eef2ff",imgBg:n?"#12121f":"#f8faff"},this.modalEl.style.cssText=`width:min(92vw,500px);max-width:none;max-height:88vh;padding:0;overflow:hidden;border-radius:18px;background:${this.t.bg};color:${this.t.text};display:flex;flex-direction:column`,this.render()}onClose(){this._stopVideos(),activeDocument.querySelectorAll(".eq-guide-lightbox").forEach(e=>e.remove())}_stopVideos(){this.contentEl.querySelectorAll("iframe").forEach(e=>{try{e.src="about:blank"}catch(n){}})}render(){let{contentEl:e}=this;this._stopVideos(),e.empty(),e.style.cssText=`padding:0;color:${this.t.text};display:flex;flex-direction:column;min-height:0;flex:1;overflow:hidden`;let n=this.def[this.locale];n.options?this.page?this.renderPage(n):this.renderMenu(n):this.renderLegacy(n),e.querySelectorAll("code").forEach(a=>{a.style.cssText=`background:${this.t.codeBg};padding:1px 6px;border-radius:5px;font-size:12px;color:${this.t.text}`})}_footer(e){let n=this.t,a=this.contentEl.createEl("div",{attr:{style:`flex-shrink:0;padding:11px 22px;border-top:1px solid ${n.border};display:flex;align-items:center;justify-content:space-between;gap:10px;background:${n.bg}`}});a.createEl("span",{text:e,attr:{style:`font-size:11px;color:${n.muted}`}}),a.createEl("button",{text:this.locale==="zh-tw"?"\u958B\u59CB":"Got it",attr:{style:"padding:8px 22px;border-radius:9px;border:none;background:linear-gradient(135deg,#4f46e5,#818cf8);color:#fff;font-size:13px;font-weight:600;cursor:pointer;flex-shrink:0"}}).addEventListener("click",()=>this.close())}_hero(e){let n=this.t,a=`${this.def.img}_${this.locale==="zh-tw"?"\u4E2D\u6587":"english"}.webp`,s=this.app.vault.adapter.getResourcePath($e.normalizePath(`${this.plugin.manifest.dir}/assets/guide/${a}`)),i=this.contentEl.createEl("div",{attr:{style:`position:relative;flex-shrink:0;width:100%;background:${n.imgBg};border-bottom:1px solid ${n.border};cursor:zoom-in`}});i.createEl("img",{attr:{src:s,alt:e,style:"width:100%;display:block;max-height:32vh;object-fit:contain"}}).addEventListener("error",()=>{i.style.display="none"}),i.createEl("div",{text:this.locale==="zh-tw"?"\u9EDE\u64CA\u653E\u5927":"Click to enlarge",attr:{style:"position:absolute;right:8px;bottom:8px;padding:3px 9px;border-radius:99px;background:rgba(0,0,0,0.62);color:#fff;font-size:11px;font-weight:600;pointer-events:none"}}),i.addEventListener("click",()=>la(s,e))}_bodyImage(e,n,a){let s=this.t,i=this.app.vault.adapter.getResourcePath($e.normalizePath(`${this.plugin.manifest.dir}/assets/${n}`)),r=e.createEl("div",{attr:{style:"margin:11px 0"}});a&&r.createEl("div",{text:a,attr:{style:`font-size:12px;font-weight:600;margin-bottom:5px;color:${s.muted}`}});let o=r.createEl("div",{attr:{style:`position:relative;border-radius:10px;overflow:hidden;border:1px solid ${s.border};background:${s.imgBg};cursor:zoom-in`}});o.createEl("img",{attr:{src:i,alt:a||"",style:"width:100%;display:block;max-height:30vh;object-fit:contain"}}).addEventListener("error",()=>{r.style.display="none"}),o.createEl("div",{text:this.locale==="zh-tw"?"\u9EDE\u64CA\u653E\u5927":"Click to enlarge",attr:{style:"position:absolute;right:8px;bottom:8px;padding:3px 9px;border-radius:99px;background:rgba(0,0,0,0.62);color:#fff;font-size:11px;font-weight:600;pointer-events:none"}}),o.addEventListener("click",()=>la(i,a||""))}renderMenu(e){let n=this.t;this._hero(e.title);let a=this.contentEl.createEl("div",{attr:{style:"flex:1;min-height:0;overflow-y:auto;padding:16px 22px 10px"}}),s=a.createEl("div",{attr:{style:"display:flex;align-items:center;gap:10px;margin-bottom:8px"}});Object.assign(s.createEl("span",{attr:{style:"font-size:22px;line-height:1"}}),{innerHTML:oa(this.def.emoji)}),s.createEl("span",{text:e.title,attr:{style:"font-size:16.5px;font-weight:700"}}),e.lines.forEach(i=>{a.createEl("p",{attr:{style:`margin:7px 0;font-size:13px;line-height:1.6;color:${n.text}`}}).appendChild($e.sanitizeHTMLToDom(i))}),a.createEl("div",{text:e.pickPrompt,attr:{style:`margin:14px 0 6px;font-size:12.5px;font-weight:700;color:${n.muted}`}}),e.options.forEach(i=>{let r=a.createEl("button",{attr:{style:`width:100%;text-align:left;display:flex;align-items:center;gap:12px;padding:12px 14px;margin:8px 0;border-radius:12px;border:1px solid ${n.border};background:${n.cardBg};cursor:pointer;color:${n.text}`}});Object.assign(r.createEl("span",{attr:{style:"font-size:21px;flex-shrink:0"}}),{innerHTML:oa(i.emoji)});let o=r.createEl("div",{attr:{style:"flex:1;min-width:0"}});o.createEl("div",{text:i.label,attr:{style:"font-size:14px;font-weight:700"}}),o.createEl("div",{text:i.sub,attr:{style:`font-size:11.5px;color:${n.muted};margin-top:2px`}}),r.createEl("span",{text:"\u203A",attr:{style:`font-size:20px;color:${n.muted};flex-shrink:0`}}),r.addEventListener("mouseenter",()=>{r.style.background=n.hoverBg}),r.addEventListener("mouseleave",()=>{r.style.background=n.cardBg}),r.addEventListener("click",()=>{this.page=i.id,this.render()})}),this._footer(this.locale==="zh-tw"?"\u4E4B\u5F8C\u53EF\u9EDE\u6A19\u984C\u65C1\u7684 ? \u518D\u770B\u4E00\u6B21":"Reopen via the ? next to the title")}renderPage(e){let n=this.t,a=e.pages[this.page];if(!a){this.page=null,this.render();return}let s=this.contentEl.createEl("div",{attr:{style:"flex:1;min-height:0;overflow-y:auto;padding:14px 22px 10px"}}),i=s.createEl("button",{attr:{style:`display:inline-flex;align-items:center;gap:5px;padding:5px 11px;margin-bottom:12px;border-radius:8px;border:1px solid ${n.border};background:transparent;cursor:pointer;color:${n.accentText};font-size:12px;font-weight:600`}});if(i.setText(this.locale==="zh-tw"?"\u2190 \u8FD4\u56DE":"\u2190 Back"),i.addEventListener("click",()=>{this.page=null,this.render()}),s.createEl("div",{text:a.title,attr:{style:"font-size:16px;font-weight:700;margin-bottom:10px"}}),a.body.forEach(r=>{r.v?Ic(s,r.v,r.label,this.locale,n):r.img?this._bodyImage(s,r.img,r.label):r.h&&s.createEl("div",{attr:{style:`margin:8px 0;font-size:13px;line-height:1.65;color:${n.text}`}}).appendChild($e.sanitizeHTMLToDom(r.h))}),a.first){let r=s.createEl("div",{attr:{style:`margin-top:14px;padding:11px 14px;background:${n.accentBg};border:1px solid ${n.accentBorder};border-radius:12px`}});r.createEl("div",{text:this.locale==="zh-tw"?"\u7B2C\u4E00\u6B65":"First step",attr:{style:`font-size:12px;font-weight:700;color:${n.accentText};margin-bottom:5px;letter-spacing:0.02em`}}),r.createEl("div",{attr:{style:`font-size:13px;line-height:1.6;color:${n.text}`}}).appendChild($e.sanitizeHTMLToDom(a.first))}this._footer(this.locale==="zh-tw"?"\u2190 \u8FD4\u56DE\u53EF\u770B\u5176\u4ED6\u5EFA\u7ACB\u65B9\u5F0F":"\u2190 Back for other ways to build")}renderLegacy(e){let n=this.t;this._hero(e.title);let a=this.contentEl.createEl("div",{attr:{style:"flex:1;min-height:0;overflow-y:auto;padding:16px 22px 6px"}}),s=a.createEl("div",{attr:{style:"display:flex;align-items:center;gap:10px;margin-bottom:8px"}});Object.assign(s.createEl("span",{attr:{style:"font-size:22px;line-height:1"}}),{innerHTML:oa(this.def.emoji)}),s.createEl("span",{text:e.title,attr:{style:"font-size:16.5px;font-weight:700"}}),e.lines.forEach(r=>{a.createEl("p",{attr:{style:`margin:7px 0;font-size:13px;line-height:1.6;color:${n.text}`}}).appendChild($e.sanitizeHTMLToDom(r))});let i=a.createEl("div",{attr:{style:`margin-top:12px;padding:11px 14px;background:${n.accentBg};border:1px solid ${n.accentBorder};border-radius:12px`}});i.createEl("div",{text:this.locale==="zh-tw"?"\u7B2C\u4E00\u6B65":"First step",attr:{style:`font-size:12px;font-weight:700;color:${n.accentText};margin-bottom:5px;letter-spacing:0.02em`}}),i.createEl("div",{attr:{style:`font-size:13px;line-height:1.6;color:${n.text}`}}).appendChild($e.sanitizeHTMLToDom(e.first)),this._footer(this.locale==="zh-tw"?"\u4E4B\u5F8C\u53EF\u9EDE\u6A19\u984C\u65C1\u7684 ? \u518D\u770B\u4E00\u6B21":"Reopen via the ? next to the title")}};function Ic(t,e,n,a,s){let i=t.createEl("div",{attr:{style:"margin:11px 0"}});n&&i.createEl("div",{text:n,attr:{style:`font-size:12px;font-weight:600;margin-bottom:5px;color:${s.muted}`}}),i.createEl("div",{attr:{style:`position:relative;width:100%;padding-bottom:56.25%;border-radius:10px;overflow:hidden;border:1px solid ${s.border};background:#000`}}).createEl("iframe",{attr:{src:`https://www.youtube-nocookie.com/embed/${e}`,style:"position:absolute;inset:0;width:100%;height:100%;border:0",allow:"accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen",allowfullscreen:"true",loading:"lazy"}});let o=i.createEl("a",{text:a==="zh-tw"?"\u25B6 \u82E5\u7121\u6CD5\u64AD\u653E\uFF0C\u5728\u700F\u89BD\u5668\u958B\u555F":"\u25B6 Can't play? Open in browser",attr:{href:`https://youtu.be/${e}`,target:"_blank",rel:"noopener",style:"display:inline-block;margin-top:5px;font-size:11px;color:#818cf8;text-decoration:none"}});return o.addEventListener("mouseenter",()=>{o.style.textDecoration="underline"}),o.addEventListener("mouseleave",()=>{o.style.textDecoration="none"}),i}function la(t,e){let n=activeDocument,a=n.createElement("div");a.className="eq-guide-lightbox",a.style.cssText="position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,0.88);display:flex;align-items:center;justify-content:center;cursor:zoom-out;padding:24px;-webkit-tap-highlight-color:transparent";let s=n.createElement("img");s.src=t,s.alt=e||"",s.style.cssText="max-width:96vw;max-height:96vh;object-fit:contain;border-radius:8px;box-shadow:0 8px 40px rgba(0,0,0,0.55)",a.appendChild(s);let i=()=>{a.remove(),n.removeEventListener("keydown",r)},r=o=>{o.key==="Escape"&&i()};return a.addEventListener("click",i),n.addEventListener("keydown",r),n.body.appendChild(a),a}function Mc(t,e,n,a){if(!vs(a))return;let s=activeDocument.body.classList.contains("theme-dark"),i=s?"rgba(99,102,241,0.18)":"#eef2ff",r=s?"rgba(99,102,241,0.34)":"#e0e7ff",o=t.createEl("button",{attr:{class:"lh-feature-info",title:xs(n.settings)==="zh-tw"?"\u4F7F\u7528\u8AAA\u660E":"How it works","aria-label":"guide"}});return o.style.cssText=`width:26px;height:26px;border-radius:50%;border:1px solid ${s?"rgba(129,140,248,0.55)":"#c7d2fe"};background:${i};cursor:pointer;color:${s?"#c7d2fe":"#4f46e5"};display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;padding:0`,o.appendChild($e.sanitizeHTMLToDom('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>')),o.addEventListener("mouseenter",()=>{o.style.background=r}),o.addEventListener("mouseleave",()=>{o.style.background=i}),o.addEventListener("click",l=>{l.stopPropagation(),new Jt(e,n,a).open()}),o}ws.exports={FeatureGuideModal:Jt,attachInfoButton:Mc,hasFeatureGuide:vs,openLightbox:la}});var en=Y((Wu,Es)=>{"use strict";var Xt=require("obsidian"),{t:we,getLocale:qc}=ge(),{giHTML:j}=be(),{registerSelectionCopy:Nc}=kt(),{FeatureGuideModal:Dc,openLightbox:Oc}=Zt(),zc=class extends Xt.Modal{constructor(t,e){super(t),this.plugin=e}onClose(){var t;(t=activeDocument.getElementById("lh-help-styles"))==null||t.remove(),activeDocument.querySelectorAll(".eq-guide-lightbox").forEach(e=>e.remove())}onOpen(){let t=this.plugin.settings,e=qc(t),n=activeDocument.body.classList.contains("theme-dark"),a=n?"#1e1e2e":"#ffffff",s=n?"#252538":"#f3f4f6",i=n?"#e2e8f0":"#1f2937",r=n?"#94a3b8":"#6b7280",o=n?"#3a3a5a":"#e5e7eb",l=n?"#1a1a2e":"#f8faff",c=this.app.vault.adapter.getResourcePath(Xt.normalizePath(`${this.plugin.manifest.dir}/assets/install-skills.png`));this.modalEl.addClass("lh-help"),n&&this.modalEl.addClass("lh-dark"),Nc(this),this.modalEl.style.cssText=`width:min(95vw,800px);max-width:none;height:min(90vh,700px);max-height:none;padding:0;overflow:hidden;border-radius:20px;background:${l}`,this.modalEl.style.setProperty("--background-primary",a,"important"),this.modalEl.style.setProperty("--background-secondary",s,"important"),this.modalEl.style.setProperty("--text-normal",i,"important"),this.modalEl.style.setProperty("--text-muted",r,"important"),this.modalEl.style.setProperty("--background-modifier-border",o,"important"),this.contentEl.style.cssText=`padding:0;height:100%;display:flex;flex-direction:column;overflow:hidden;background:${l};color:${i}`;let{contentEl:p}=this;p.empty();let d=p.createEl("div",{attr:{style:"padding:24px;border-bottom:1px solid var(--background-modifier-border);display:flex;justify-content:space-between;align-items:center"}});d.createEl("h2",{text:we(t,"HELP_TITLE"),attr:{style:"margin:0;font-size:18px;font-weight:700"}}),d.createEl("button",{text:"\xD7",attr:{style:"width:32px;height:32px;background:transparent;border:none;cursor:pointer;font-size:20px;line-height:1;color:var(--text-muted)"}}).addEventListener("click",()=>this.close());let h=activeDocument.createElement("style");h.textContent=`
      .lh-help-acc { border:1px solid #e5e7eb; border-radius:10px; margin-bottom:10px; overflow:hidden; background:#fff; }
      .lh-help-acc-hdr { display:flex; align-items:center; gap:10px; padding:14px 16px; cursor:pointer; user-select:none; background:#fff; transition:background 0.15s; }
      .lh-help-acc-hdr:hover { background:#f8faff; }
      .lh-help-acc-hdr.open { background:#f0f7ff; }
      .lh-help-acc-icon { font-size:18px; flex-shrink:0; }
      .lh-help-acc-title { flex:1; font-weight:600; font-size:14px; color:#1f2937; }
      .lh-help-acc-tag { font-size:11px; font-weight:500; padding:2px 8px; border-radius:99px; background:#f3f4f6; color:#6b7280; flex-shrink:0; }
      .lh-help-acc-arrow { color:#9ca3af; transition:transform 0.2s; flex-shrink:0; }
      .lh-help-acc-hdr.open .lh-help-acc-arrow { transform:rotate(90deg); }
      .lh-help-acc-body { display:none; padding:0 16px 16px; font-size:13.5px; line-height:1.65; color:#374151; border-top:1px solid #e5e7eb; }
      .lh-help-acc-body.open { display:block; }
      .lh-help-acc-body p { margin:8px 0; }
      .lh-help-acc-body ul, .lh-help-acc-body ol { margin:6px 0 6px 18px; }
      .lh-help-acc-body li { margin:4px 0; }
      .lh-help-acc-body code { background:#f3f4f6; padding:1px 6px; border-radius:4px; font-size:12.5px; }
      .lh-help-acc-body table { width:100%; border-collapse:collapse; margin:10px 0; font-size:13px; }
      .lh-help-acc-body th { background:#f9fafb; padding:8px 10px; text-align:left; border:1px solid #e5e7eb; font-weight:600; }
      .lh-help-acc-body td { padding:8px 10px; border:1px solid #e5e7eb; vertical-align:top; }
      .lh-help-acc-body tr:nth-child(even) td { background:#fafafa; }
      .lh-help-sub { display:flex; gap:8px; align-items:flex-start; padding:10px 12px; background:#f8faff; border-radius:8px; margin:8px 0; border-left:3px solid #3b82f6; }
      .lh-help-sub-icon { font-size:20px; flex-shrink:0; line-height:1; }
      .lh-help-intro { background:linear-gradient(135deg,#f0f7ff,#faf5ff); border-radius:10px; padding:16px; margin-bottom:14px; }
      .lh-help-intro p { margin:0; font-size:13.5px; color:#374151; line-height:1.6; }
      .lh-help-chips { display:flex; gap:8px; flex-wrap:wrap; margin-top:10px; }
      .lh-help-chip { padding:4px 12px; border-radius:99px; font-size:12px; font-weight:600; }
      .lh-help-acc-body pre { background:#f3f4f6; padding:8px 10px; border-radius:6px; font-size:12px; margin:6px 0; overflow-x:auto; white-space:pre-wrap; }
      .lh-help-fmt-chip { padding:2px 6px; border-radius:6px; font-weight:700; }
      .lh-help-fmt-chip code { background:transparent !important; color:inherit !important; padding:0 !important; }
      .lh-help.lh-dark .lh-help-acc { border-color:var(--background-modifier-border); background:var(--background-secondary); }
      .lh-help.lh-dark .lh-help-acc-hdr { background:var(--background-secondary); }
      .lh-help.lh-dark .lh-help-acc-hdr:hover { background:rgba(99,102,241,0.12); }
      .lh-help.lh-dark .lh-help-acc-hdr.open { background:rgba(99,102,241,0.18); }
      .lh-help.lh-dark .lh-help-acc-title { color:var(--text-normal); }
      .lh-help.lh-dark .lh-help-acc-tag { background:rgba(99,102,241,0.22); color:#c7d2fe; font-weight:600; }
      .lh-help.lh-dark .lh-help-acc-arrow { color:var(--text-muted); }
      .lh-help.lh-dark .lh-help-acc-body { color:var(--text-normal); border-top-color:var(--background-modifier-border); }
      .lh-help.lh-dark .lh-help-acc-body code,
      .lh-help.lh-dark .lh-help-acc-body pre { background:rgba(255,255,255,0.06); color:var(--text-normal); }
      .lh-help.lh-dark .lh-help-acc-body th { background:rgba(255,255,255,0.06); border-color:var(--background-modifier-border); color:var(--text-normal); }
      .lh-help.lh-dark .lh-help-acc-body td { border-color:var(--background-modifier-border); color:var(--text-normal); }
      .lh-help.lh-dark .lh-help-acc-body tr:nth-child(even) td { background:rgba(255,255,255,0.03); }
      .lh-help.lh-dark .lh-help-sub { background:rgba(255,255,255,0.04); }
      .lh-help.lh-dark .lh-help-intro { background:linear-gradient(135deg,rgba(59,130,246,0.16),rgba(124,58,237,0.14)); }
      .lh-help.lh-dark .lh-help-intro p { color:var(--text-normal); }
      .lh-help .modal-close-button { display:none !important; }
    `,h.id="lh-help-styles",activeDocument.head.appendChild(h);let u=p.createEl("div",{attr:{style:"flex:1;overflow-y:auto;padding:20px;font-size:14px;line-height:1.6"}}),x=u.createEl("div",{attr:{class:"lh-help-intro"}});x.innerHTML=e==="zh-tw"?`
      <p><strong>EngramQuest</strong> \u628A\u4F60\u7684\u7B46\u8A18\u8B8A\u6210\u5B8C\u6574\u5B78\u7FD2\u9589\u74B0\uFF1A<strong>\u5B78 \u2192 \u7DF4 \u2192 \u8A18 \u2192 \u9023</strong>\u3002\u6BCF\u500B\u6A21\u7D44\u53EF\u55AE\u7368\u7528\uFF0C\u4E5F\u80FD\u4E32\u63A5\u3002</p>
      <div class="lh-help-chips">
        <span class="lh-help-chip" style="background:#dbeafe;color:#1d4ed8">${j("stack")} \u8A18\u61B6 \u2014 Review Deck \xB7 Memory Map</span>
        <span class="lh-help-chip" style="background:#d1fae5;color:#065f46">${j("cap")} \u5B78\u7FD2 \u2014 \u6559\u6750 \xB7 Quest Map</span>
        <span class="lh-help-chip" style="background:#fef3c7;color:#92400e">${j("trophy")} \u6210\u5C31 \u2014 \u91CC\u7A0B\u7891\u8FFD\u8E64</span>
      </div>
      <p style="margin-top:10px;font-size:12.5px;line-height:1.6">${j("pointer")} <strong>\u7B2C\u4E00\u6B65</strong>\uFF1A\u514D AI \u5C31\u6253 <code>#flashcards/\u4E3B\u984C</code> + \u4E00\u5F35 <code>Q:/A:</code> \u5361\uFF1B\u7528 AI \u5C31\u5230\u300C\u8A2D\u5B9A \u2192 AI Skills\u300D\u5B89\u88DD\u5F8C\u5C0D AI \u8AAA\u8A71\u3002\u6BCF\u500B\u529F\u80FD\u5206\u9801\u7B2C\u4E00\u6B21\u958B\u555F\u90FD\u6703\u8DF3\u51FA\u5716\u89E3\u8AAA\u660E\u3002</p>
    `:`
      <p><strong>EngramQuest</strong> turns your notes into a complete learning loop: <strong>Learn \u2192 Practice \u2192 Remember \u2192 Connect</strong>. Each module works alone or chains into the next.</p>
      <div class="lh-help-chips">
        <span class="lh-help-chip" style="background:#dbeafe;color:#1d4ed8">${j("stack")} Memory \u2014 Review Deck \xB7 Memory Map</span>
        <span class="lh-help-chip" style="background:#d1fae5;color:#065f46">${j("cap")} Learn \u2014 Lessons \xB7 Quest Map</span>
        <span class="lh-help-chip" style="background:#fef3c7;color:#92400e">${j("trophy")} Achieve \u2014 milestone tracking</span>
      </div>
      <p style="margin-top:10px;font-size:12.5px;line-height:1.6">${j("pointer")} <strong>First step</strong>: No AI? Tag a note <code>#flashcards/topic</code> + one <code>Q:/A:</code> card. With AI? Install Skills in Settings \u2192 AI Skills, then talk to your AI. Each feature tab pops a visual guide the first time you open it.</p>
    `;{let S=this.plugin.manifest.dir,_=this.app.vault.adapter,C=u.createEl("div",{attr:{style:"margin:0 0 16px;border:1px solid var(--background-modifier-border);border-radius:12px;overflow:hidden"}});C.createEl("div",{text:e==="zh-tw"?"\u9023\u63A5 AI \u5DE5\u5177":"Connect an AI Tool",attr:{style:`padding:8px 14px;font-size:12px;font-weight:700;color:${r};background:${s};border-bottom:1px solid var(--background-modifier-border)`}});let L=C.createEl("div",{attr:{style:`display:flex;background:${s};border-bottom:1px solid var(--background-modifier-border)`}}),A=C.createEl("div",{attr:{style:`padding:12px 14px;background:${l}`}}),T=[{key:"desktop",zh:"Claude Desktop",en:"Claude Desktop"},{key:"code",zh:"\u2328\uFE0F Claude Code",en:"\u2328\uFE0F Claude Code"},{key:"codex",zh:"Codex App",en:"Codex App"}],q={desktop:{zh:`<p style="margin:0 0 8px;font-size:13px;line-height:1.6;color:${i}">\u4E0D\u9700\u8981 terminal\u3002\u8CBC\u4E00\u6BB5 Setup Prompt\uFF0CClaude Desktop \u81EA\u52D5\u9023\u4E0A vault \u4E26\u8F09\u5165 EngramQuest skills\u3002\u5167\u5EFA\u7DB2\u8DEF\u641C\u5C0B\uFF0C\u53EF\u751F\u6210 SVG \u5716\u89E3\u548C Mermaid \u5716\u8868\u3002</p><ol style="margin:0 0 10px 18px;font-size:13px;line-height:1.8;color:${i}"><li>\u5B89\u88DD <strong>Filesystem MCP</strong>\uFF08\u8B93 Claude Desktop \u8B80\u5BEB\u4F60\u7684 vault\uFF09</li><li>Obsidian\uFF1A<strong>\u8A2D\u5B9A \u2192 EngramQuest \u2192 AI Skills \u2192 \u5B89\u88DD</strong></li><li>\u8907\u88FD\u4E0B\u65B9 Prompt\uFF0C\u8CBC\u5165 Claude Desktop\uFF0C\u8DDF\u8457\u7CBE\u9748\u8D70\u4E00\u6B21\uFF08\u7D04 2 \u5206\u9418\uFF09</li><li>\u5B8C\u6210\u5F8C\u5C31\u53EF\u4EE5\u958B\u59CB\u5EFA\u7ACB\u5167\u5BB9\uFF0C\u4F8B\u5982\uFF1A<br>\u300C\u628A\u5FAE\u7A4D\u5206\u7B46\u8A18\u505A\u6210\u8AB2\u7A0B\u300D<br>\u300C\u628A math \u7B46\u8A18\u5EFA\u6210 Review Deck\u300D<br>\u300C\u628A\u9019\u7BC7 .md \u505A\u6210 quest-map medium\u300D</li></ol>`,en:`<p style="margin:0 0 8px;font-size:13px;line-height:1.6;color:${i}">No terminal needed. Paste one Setup Prompt \u2014 Claude Desktop connects to your vault, loads EngramQuest skills, and remembers the rules. Built-in web search, generates SVG diagrams and Mermaid charts.</p><ol style="margin:0 0 10px 18px;font-size:13px;line-height:1.8;color:${i}"><li>Install <strong>Filesystem MCP</strong> so Claude Desktop can read your vault</li><li>In Obsidian: <strong>Settings \u2192 EngramQuest \u2192 AI Skills \u2192 Install</strong></li><li>Copy the Setup Prompt below, paste into Claude Desktop, follow the wizard once (~2 min)</li><li>Now build content, e.g.:<br>"Build a course from my calculus notes"<br>"Make a Review Deck from math notes"<br>"Turn this note into a quest-map medium"</li></ol>`},code:{zh:`<p style="margin:0 0 8px;font-size:13px;line-height:1.6;color:${i}">\u5F9E vault \u6839\u76EE\u9304\u958B\u555F Claude Code\uFF0Cskills \u653E\u5728 <code>.claude/skills/</code>\uFF0C\u76F4\u63A5\u7528\u81EA\u7136\u8A9E\u8A00\u8AAA\u8981\u505A\u4EC0\u9EBC\u3002</p><ol style="margin:0 0 10px 18px;font-size:13px;line-height:1.8;color:${i}"><li>Obsidian\uFF1A<strong>\u8A2D\u5B9A \u2192 EngramQuest \u2192 AI Skills</strong> \u2192 \u9078 <strong>Claude Code</strong> \u2192 \u5B89\u88DD</li><li>Terminal \u5728 vault \u6839\u76EE\u9304\u57F7\u884C\uFF1A<code>claude</code></li><li>\u73FE\u5728\u5C31\u53EF\u4EE5\u958B\u59CB\u5EFA\u7ACB\u5167\u5BB9\uFF0C\u4F8B\u5982\uFF1A<br>\u300C\u628A math \u7B46\u8A18\u505A\u6210 Review Deck\u300D<br>\u300C\u5EFA\u4E00\u5957 TypeScript \u8AB2\u7A0B\u300D<br>\u300C\u628A\u5FAE\u7A4D\u5206.md \u505A\u6210 quest-map medium\u300D</li></ol>`,en:`<p style="margin:0 0 8px;font-size:13px;line-height:1.6;color:${i}">Open Claude Code from your vault root. Skills live in <code>.claude/skills/</code> \u2014 just talk naturally.</p><ol style="margin:0 0 10px 18px;font-size:13px;line-height:1.8;color:${i}"><li>In Obsidian: <strong>Settings \u2192 EngramQuest \u2192 AI Skills</strong> \u2192 select <strong>Claude Code</strong> \u2192 Install</li><li>Run <code>claude</code> in a terminal at your vault root</li><li>Now build content, e.g.:<br>"Build a Review Deck from my math notes"<br>"Create a TypeScript course"<br>"Turn calculus.md into a quest-map medium"</li></ol>`},codex:{zh:`<p style="margin:0 0 8px;font-size:13px;line-height:1.6;color:${i}">Codex / Antigravity \u8207 Claude Code \u539F\u7406\u76F8\u540C\uFF0C\u5B89\u88DD skills \u6642\u9078\u5C0D\u61C9\u7684\u5DE5\u5177\u5373\u53EF\u3002</p><ol style="margin:0 0 10px 18px;font-size:13px;line-height:1.8;color:${i}"><li>Obsidian\uFF1A<strong>\u8A2D\u5B9A \u2192 EngramQuest \u2192 AI Skills</strong> \u2192 \u9078 <strong>Codex / Antigravity</strong> \u2192 \u5B89\u88DD</li><li>\u5F9E vault \u6839\u76EE\u9304\u958B\u555F Codex / Antigravity App</li><li>\u73FE\u5728\u5C31\u53EF\u4EE5\u958B\u59CB\u5EFA\u7ACB\u5167\u5BB9\uFF0C\u4F8B\u5982\uFF1A<br>\u300C\u628A math \u7B46\u8A18\u505A\u6210 Review Deck\u300D<br>\u300C\u5EFA\u4E00\u5957 TypeScript \u8AB2\u7A0B\u300D<br>\u300C\u628A\u9019\u7BC7 .md \u505A\u6210 quest-map medium\u300D</li></ol>`,en:`<p style="margin:0 0 8px;font-size:13px;line-height:1.6;color:${i}">Codex / Antigravity works the same as Claude Code \u2014 just pick the right tool when installing skills.</p><ol style="margin:0 0 10px 18px;font-size:13px;line-height:1.8;color:${i}"><li>In Obsidian: <strong>Settings \u2192 EngramQuest \u2192 AI Skills</strong> \u2192 select <strong>Codex / Antigravity</strong> \u2192 Install</li><li>Open Codex / Antigravity App from your vault root</li><li>Now build content, e.g.:<br>"Build a Review Deck from my math notes"<br>"Create a TypeScript course"<br>"Turn this note into a quest-map medium"</li></ol>`}},I=R=>{var B;let O=e==="zh-tw"?"zh":"en",$=q[R][O];if(R==="desktop"){let P=e==="zh-tw"?"\u8907\u88FD Setup Prompt":"Copy Setup Prompt",K=e==="zh-tw"?"\u5DF2\u8907\u88FD\uFF01":"Copied!";$+=`<button id="eq-copy-cdprompt" style="display:block;width:100%;margin-top:4px;padding:9px 16px;background:${n?"#4f46e5":"#6366f1"};color:#fff;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer">${P}</button>`,A.innerHTML=$,(B=A.querySelector("#eq-copy-cdprompt"))==null||B.addEventListener("click",async function(){try{let Z=_.getResourcePath(Xt.normalizePath(`${S}/assets/claude-desktop-prompt.txt`)),V=await(await fetch(Z)).text();await navigator.clipboard.writeText(V),this.textContent=K,window.setTimeout(()=>{this.textContent=P},2e3)}catch(Z){this.textContent=e==="zh-tw"?"\u8ACB\u624B\u52D5\u8907\u88FD Prompt":"Copy failed \u2014 copy manually"}})}else A.innerHTML=$;L.querySelectorAll(".eq-ai-tab-btn").forEach(P=>{P.style.borderBottomColor="transparent",P.style.color=r,P.style.fontWeight="500"});let z=L.querySelector(`[data-aitab="${R}"]`);z&&(z.style.borderBottomColor=n?"#818cf8":"#6366f1",z.style.color=n?"#c7d2fe":"#4f46e5",z.style.fontWeight="700")};T.forEach((R,O)=>{let $=L.createEl("button",{attr:{class:"eq-ai-tab-btn","data-aitab":R.key,style:`flex:1;padding:8px 4px;font-size:12px;font-weight:${O===0?"700":"500"};border:none;border-bottom:2px solid ${O===0?n?"#818cf8":"#6366f1":"transparent"};cursor:pointer;background:transparent;color:${O===0?n?"#c7d2fe":"#4f46e5":r};transition:color 0.15s`}});$.textContent=e==="zh-tw"?R.zh:R.en,$.addEventListener("click",()=>I(R.key))}),I("desktop")}let m=this.app.vault.adapter.getResourcePath(Xt.normalizePath(`${this.plugin.manifest.dir}/assets/guide/\u6574\u9AD4\u6982\u5FF5_${e==="zh-tw"?"\u4E2D\u6587":"\u82F1\u6587"}.webp`)),g=u.createEl("div",{attr:{style:`position:relative;margin:0 0 16px;border-radius:12px;overflow:hidden;border:1px solid var(--background-modifier-border);cursor:zoom-in;background:${s}`}});g.createEl("img",{attr:{src:m,alt:"EngramQuest",style:"width:100%;display:block;max-height:340px;object-fit:contain"}}).addEventListener("error",()=>{g.style.display="none"}),g.createEl("div",{text:e==="zh-tw"?"\u9EDE\u64CA\u653E\u5927":"Click to enlarge",attr:{style:"position:absolute;right:8px;bottom:8px;padding:3px 9px;border-radius:99px;background:rgba(0,0,0,0.62);color:#fff;font-size:11px;font-weight:600;pointer-events:none"}}),g.addEventListener("click",()=>Oc(m,"EngramQuest")),u.createEl("div",{text:e==="zh-tw"?"\u529F\u80FD\u7E3D\u89BD \u2014 \u9EDE\u5361\u7247\u770B\u5716\u89E3\u8AAA\u660E":"Features \u2014 tap a card for its visual guide",attr:{style:"font-size:12.5px;font-weight:700;color:var(--text-muted);margin:0 0 8px"}});let f=u.createEl("div",{attr:{style:"display:grid;grid-template-columns:repeat(2,1fr);gap:10px;margin-bottom:20px"}});[{tab:"review",emoji:"cards",zh:"Review Deck",en:"Review Deck",zhs:"\u4E09\u968E\u56DE\u60F3\u9583\u5361",ens:"Three-stage recall flashcards"},{tab:"quest",emoji:"map",zh:"Quest Map",en:"Quest Map",zhs:"\u628A\u7B46\u8A18\u8B8A\u6210\u904A\u6232\u95DC\u5361",ens:"Notes into game levels"},{tab:"lesson",emoji:"cap",zh:"\u6559\u6750",en:"Lessons",zhs:"AI \u70BA\u4F60\u6253\u9020\u8AB2\u7A0B",ens:"AI-built courses"},{tab:"memory",emoji:"brain",zh:"Memory Map",en:"Memory Map",zhs:"\u770B\u898B\u77E5\u8B58\u5168\u5C40",ens:"See the big picture"}].forEach(S=>{let _=f.createEl("button",{attr:{style:`text-align:left;display:flex;align-items:center;gap:10px;padding:12px 14px;border-radius:12px;border:1px solid var(--background-modifier-border);background:${l};cursor:pointer;color:${i}`}});_.createEl("span",{attr:{style:"font-size:22px;flex-shrink:0"}}).innerHTML=j(S.emoji);let C=_.createEl("div",{attr:{style:"flex:1;min-width:0"}});C.createEl("div",{text:e==="zh-tw"?S.zh:S.en,attr:{style:"font-size:13.5px;font-weight:700"}}),C.createEl("div",{text:e==="zh-tw"?S.zhs:S.ens,attr:{style:`font-size:11px;color:${r};margin-top:2px`}}),_.createEl("span",{text:e==="zh-tw"?"\u770B\u8AAA\u660E \u203A":"Guide \u203A",attr:{style:`font-size:11px;font-weight:700;color:${n?"#c7d2fe":"#4f46e5"};flex-shrink:0`}}),_.addEventListener("click",()=>{new Dc(this.app,this.plugin,S.tab).open()})});let v='<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>',y=0,w=[];e==="zh-tw"&&(w=[{icon:"cards",title:"\u5EFA\u7ACB\u7B2C\u4E00\u500B Review Deck",tag:"\u624B\u52D5\u958B\u59CB",html:`
            <p><strong>Step 1\uFF1A\u5728\u7B46\u8A18\u52A0\u4E0A\u6A19\u7C64\uFF08Deck \u540D\u7A31\uFF09</strong></p>
            <p>\u5728\u4F60\u7684\u7B46\u8A18\u4EFB\u610F\u4F4D\u7F6E\u52A0\u4E0A <code>#flashcards/\u4E3B\u984C</code>\u3002\u4F8B\u5982 <code>#flashcards/\u82F1\u6587</code> \u5C31\u6703\u5EFA\u7ACB\u4E00\u500B\u540D\u70BA\u300C\u82F1\u6587\u300D\u7684 Deck\u3002</p>
            <p><strong>Step 2\uFF1A\u64B0\u5BEB\u5361\u7247\u5167\u5BB9</strong></p>
            <p>\u652F\u63F4\u4E09\u7A2E\u683C\u5F0F\uFF0C\u63A8\u85A6\u4F7F\u7528 <code>Q:/A:</code>\uFF08\u652F\u63F4\u591A\u884C\u3001\u5716\u7247\u3001\u8868\u683C\uFF09\uFF1A</p>
            <pre>Q: \u8AB0\u63D0\u51FA\u4E86\u76F8\u5C0D\u8AD6\uFF1F
A: \u611B\u56E0\u65AF\u5766

Q: \u4EC0\u9EBC\u662F\u9593\u9694\u91CD\u8907\uFF1F
A: \u5728\u5FEB\u5FD8\u8A18\u6642\u8907\u7FD2\uFF0C\u53EF\u4EE5\u7528\u6700\u5C11\u6642\u9593\u9054\u5230\u6700\u9AD8\u8A18\u61B6\u4FDD\u7559\u7387\u3002
   \u6BCF\u6B21\u6210\u529F\u56DE\u60F3\u5F8C\uFF0C\u4E0B\u6B21\u8907\u7FD2\u7684\u9593\u9694\u6703\u81EA\u52D5\u62C9\u9577\u3002

{{c1::\u9593\u9694\u91CD\u8907}} \u662F\u6700\u6709\u6548\u7684\u5B78\u7FD2\u65B9\u6CD5

\u7562\u6C0F\u5B9A\u7406 :: a\xB2 + b\xB2 = c\xB2</pre>
            <p><strong>Step 3\uFF1A\u5230 Hub \u958B\u59CB\u8907\u7FD2</strong></p>
            <p>\u9EDE\u5074\u908A\u6B04\u5716\u793A\u958B\u555F Hub \u2192 <strong>Review Deck</strong> \u2192 \u4F60\u5C31\u6703\u770B\u5230\u525B\u5EFA\u7ACB\u7684\u724C\u7D44\u4E86\uFF01</p>
          `},{icon:"rocket",title:"\u958B\u59CB\u4F7F\u7528 AI",tag:"AI \u81EA\u52D5\u5316",html:`
            <p><strong>Step 1\uFF1A\u5B89\u88DD AI Skills</strong></p>
            <ol>
              <li>Obsidian \u8A2D\u5B9A \u2192 <strong>EngramQuest \u2192 AI Skills</strong></li>
              <li>\u9078\u4F60\u7528\u7684\u5DE5\u5177\uFF1AClaude Code / Codex / Antigravity / Gemini CLI / Cursor</li>
              <li>\u5B89\u88DD\u5F8C AI \u5C31\u77E5\u9053\u600E\u9EBC\u5E6B\u4F60\u5EFA\u7ACB\u4E09\u500B\u6A21\u7D44\u7684\u5167\u5BB9</li>
            </ol>
            <img src="${c}" alt="Install Skills \u793A\u610F\u5716" style="width:100%;border-radius:8px;margin:10px 0;border:1px solid #e5e7eb;" />
            <p><strong>Step 2\uFF1A\u8DDF AI \u8AAA</strong></p>
            <div class="lh-help-sub" style="border-color:#2563eb"><div class="lh-help-sub-icon">${j("cards")}</div><div>\u300C\u628A tag:math \u7684\u7B46\u8A18\u90FD\u505A\u6210 Review Deck\u300D<br>\u300C\u628A\u548C\u884C\u92B7\u76F8\u95DC\u7684\u7B46\u8A18\u505A\u6210 Review Deck\u300D</div></div>
            <div class="lh-help-sub" style="border-color:#059669"><div class="lh-help-sub-icon">${j("map")}</div><div>\u300C\u628A\u5FAE\u7A4D\u5206.md \u505A\u6210 quest-map medium\u300D<br>\u300C\u628A tag:math \u7684\u7B46\u8A18\u5408\u6210\u4E00\u500B quest-map hard\u300D</div></div>
            <div class="lh-help-sub" style="border-color:#7c3aed"><div class="lh-help-sub-icon">${j("brain")}</div><div>\u300C\u5E6B\u4F5C\u696D\u7CFB\u7D71\u6982\u8AD6.md \u5EFA\u7ACB memory-map\u300D<br>\u300C\u628A\u548C\u7DB2\u8DEF\u5354\u5B9A\u76F8\u95DC\u7684\u7B46\u8A18\u505A\u6210 memory-map\u300D</div></div>
            <div class="lh-help-sub" style="border-color:#d97706"><div class="lh-help-sub-icon">${j("cap")}</div><div>\u300C\u6559\u6211 SEO\uFF0C\u5EFA\u4E00\u5957\u8AB2\u7A0B\u300D<br>\u300C\u6211\u60F3\u5B78 .NET\uFF0C\u505A\u6210\u6559\u6750\u300D</div></div>
            <p><strong>Step 3\uFF1A\u958B Hub \u958B\u59CB\u5B78</strong></p>
            <p style="margin:4px 0">\u9EDE\u5074\u908A\u6B04\u7684 EngramQuest \u5716\u793A \u2192 \u5207\u5230\u5C0D\u61C9\u5206\u9801 \u2192 \u958B\u59CB\u5B78\u7FD2\u3002</p>
          `},{icon:"cards",title:"Review Deck",tag:we(t,"HELP_REVIEW_TAG"),html:`
            <ol>
              <li>\u8DDF AI \u8AAA\uFF1A\u300C\u628A tag:math \u7684\u7B46\u8A18\u505A\u6210 Review Deck\u300D</li>
              <li>AI \u8B80\u53D6\u4F60\u7684\u7B46\u8A18\uFF0C\u5728 <code>engram-review/ai-cards/</code> \u5EFA\u7ACB\u5361\u7247\u6A94\uFF08\u542B <code>#flashcards/math</code> tag \u548C <code>question :: answer</code> \u683C\u5F0F\uFF09</li>
              <li>\u958B Hub \u2192 Review Deck \u2192 \u958B\u59CB\u8907\u7FD2</li>
            </ol>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">${j("bulb")}</div><div>\u4F60\u7684\u4E00\u822C\u7B46\u8A18\u4E0D\u9700\u8981\u6709\u4EFB\u4F55 tag\uFF0CAI \u53EF\u4EE5\u76F4\u63A5\u8B80\u53D6\u3002AI <strong>\u7522\u751F\u7684\u5361\u7247\u6A94</strong>\u5B58\u5728 <code>engram-review/ai-cards/</code>\uFF0C<strong>\u4E0D\u6703\u4FEE\u6539\u4F60\u7684\u539F\u59CB\u7B46\u8A18</strong>\u3002\u4F60\u81EA\u5DF1\u624B\u5BEB\u7684\u5361\u7247\uFF0C\u5728\u8907\u7FD2\u6642\u4F7F\u7528\u300C\u7DE8\u8F2F\u300D\u3001\u300C\u9AD8\u4EAE\u300D\u6216\u300C\u5F15\u8FF0\u300D\u529F\u80FD\uFF0C\u8B8A\u66F4\u6703\u76F4\u63A5\u5BEB\u56DE\u539F\u59CB\u7B46\u8A18\u3002\u5361\u7247\u6A94\u9700\u8981\u6709 <code>#flashcards/...</code> tag\uFF0C\u63D2\u4EF6\u624D\u80FD\u5075\u6E2C\u5230\u3002</div></div>
            <p><strong>\u76F8\u5BB9\u6A21\u5F0F\uFF1A</strong>\u5982\u679C\u4F60\u6709\u820A\u5F0F <code>::</code> \u5361\u7247\u7B46\u8A18\uFF0C\u53EF\u4EE5\u5230\u8A2D\u5B9A\u4E2D\u958B\u555F legacy <code>::</code> \u6383\u63CF\u3002\u9019\u662F\u9077\u79FB\u6A21\u5F0F\uFF0C\u4E0D\u662F\u65B0\u624B\u9810\u8A2D\u3002</p>
          `},{icon:"cards",title:"AI \u9577\u7B54\u6848\u5361\u7247",tag:"%%card%%",html:`
            <p><strong>Q\uFF1A\u5982\u4F55\u628A AI \u9577\u56DE\u7B54\u505A\u6210 Review Deck \u5361\u7247\uFF1F</strong></p>
            <p><strong>A\uFF1A</strong>\u5E73\u5E38\u5BEB\u5361\u7247\u4ECD\u7136\u7528 <span class="lh-help-fmt-chip" style="background:#dcfce7;color:#166534;"><code>Q:/A:</code> \u65E5\u5E38\u683C\u5F0F</span>\u3002\u5982\u679C\u4F60\u8981\u8CBC\u4E0A\u5F88\u9577\u7684 AI \u56DE\u7B54\uFF0C\u800C\u4E14\u7B54\u6848\u88E1\u53EF\u80FD\u6709 <code>---</code>\u3001\u8868\u683C\u3001\u7A0B\u5F0F\u78BC\u5340\u584A\u6216\u5F88\u591A\u7A7A\u884C\uFF0C\u5C31\u7528 <span class="lh-help-fmt-chip" style="background:#fef3c7;color:#92400e;"><code>%%card%%</code> \u5B89\u5168\u6A21\u5F0F</span> \u5305\u4F4F\u4E00\u5F35\u5361\u3002</p>
            <pre>#flashcards/ai

%%card%%
Q: \u5982\u4F55\u89E3\u91CB agentic testing\uFF1F
A:
Agentic testing \u6AA2\u67E5\u7684\u662F AI \u7CFB\u7D71\u80FD\u4E0D\u80FD\u53EF\u9760\u5B8C\u6210\u4EFB\u52D9\uFF0C\u800C\u4E0D\u53EA\u662F\u67D0\u500B\u51FD\u5F0F\u6709\u6C92\u6709\u56DE\u50B3\u6B63\u78BA\u503C\u3002

---

\u7BC4\u4F8B\uFF1A
- \u7D66 agent \u4E00\u500B\u771F\u5BE6\u4EFB\u52D9
- \u9A57\u8B49\u6700\u5F8C\u7522\u7269
- \u6AA2\u67E5 logs\u3001tool calls\u3001\u5931\u6557\u5FA9\u539F

&#96;&#96;&#96;js
expect(result.completed).toBe(true)
&#96;&#96;&#96;
%%card%%</pre>
            <p><strong>\u91CD\u9EDE\uFF1A</strong><code>A:</code> \u5F8C\u9762\u7684\u5167\u5BB9\u6703\u4E00\u76F4\u4FDD\u7559\u5230\u7D50\u5C3E\u7684 <span class="lh-help-fmt-chip" style="background:#fef3c7;color:#92400e;"><code>%%card%%</code></span>\u3002\u4E2D\u9593\u7684 <span class="lh-help-fmt-chip" style="background:#dbeafe;color:#1d4ed8;"><code>---</code></span> \u4E0D\u6703\u622A\u65B7\u5361\u7247\uFF1B\u65E2\u6709 <code>---</code> fenced \u5361\u7247\u4E5F\u4ECD\u7136\u652F\u63F4\uFF0C\u4E0D\u9700\u8981\u91CD\u5EFA\u3002</p>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">${j("bolt")}</div><div><strong>\u6700\u5FEB\u8F38\u5165\u65B9\u5F0F\uFF1A</strong>\u5728\u7A7A\u884C\u6309 <code>Ctrl+/</code>\uFF08Mac \u70BA <code>Cmd+/</code>\uFF09\uFF0CObsidian \u6703\u76F4\u63A5\u5E6B\u4F60\u63D2\u5165 <code>%% %%</code> \u4E26\u628A\u6E38\u6A19\u653E\u4E2D\u9593\u3002\u8F38\u5165 <code>card</code> \u5C31\u6210\u4E86 <code>%% card %%</code>\uFF0C\u53EF\u4EE5\u76F4\u63A5\u7528\u3002\u7A7A\u683C\u548C\u5927\u5C0F\u5BEB\u90FD\u8A8D\uFF0C<code>%%card%%</code>\u3001<code>%% card %%</code>\u3001<code>%%CARD%%</code> \u4E09\u7A2E\u5BEB\u6CD5\u6548\u679C\u4E00\u6A23\u3002</div></div>
          `},{icon:"map",title:"Quest Map",tag:we(t,"HELP_QUEST_TAG"),html:`
            <ol>
              <li>\u8DDF AI \u8AAA\uFF1A\u300C\u628A\u5FAE\u7A4D\u5206.md \u505A\u6210 quest-map medium\u300D</li>
              <li>AI \u5728 <code>engram-quest/quests/</code> \u5EFA\u7ACB plugin \u5167\u958B\u555F\u7684\u4EFB\u52D9\u5957\u4EF6\u8207\u672C\u6A5F HTML \u95DC\u5361</li>
              <li>\u6BCF\u500B\u4EFB\u52D9 / Boss \u53EF\u7528 self-contained HTML \u5448\u73FE\u5100\u8868\u677F\u3001\u8A08\u7B97\u3001\u8A3A\u65B7\u3001cascade \u6C7A\u7B56\u6216\u4E92\u52D5\u6A21\u64EC</li>
              <li>\u9032\u5EA6\u6703\u53E6\u5916\u5132\u5B58\u5728 <code>engram-quest/state/</code>\uFF0CAI \u66F4\u65B0 quest \u6642\u4E0D\u6703\u6E05\u6389\u5DF2\u5B8C\u6210\u7BC0\u9EDE</li>
              <li>\u820A\u7248 YAML \u984C\u578B quest \u4ECD\u53EF\u6B63\u5E38\u958B\u555F</li>
              <li>\u958B Hub \u2192 Quest Map \u2192 \u9EDE\u4EFB\u52D9\u5361\uFF0C\u5728 plugin viewer \u88E1\u4F9D\u5E8F\u5B8C\u6210\u4EFB\u52D9\u3001\u6848\u4F8B\u8207 Boss Battle</li>
            </ol>
            <table>
              <tr><th>\u96E3\u5EA6</th><th>\u9069\u5408</th></tr>
              <tr><td><strong>easy</strong></td><td>\u521D\u6B21\u63A5\u89F8</td></tr>
              <tr><td><strong>medium</strong></td><td>\u77E5\u8B58\u978F\u56FA</td></tr>
              <tr><td><strong>hard</strong></td><td>\u81EA\u6211\u6E2C\u9A57</td></tr>
            </table>
          `},{icon:"brain",title:"Memory Map",tag:we(t,"HELP_MEMORY_TAG"),html:`
            <p><strong>\u5EFA\u7ACB\u65B9\u5F0F\uFF08\u4E09\u7A2E\u90FD\u53EF\u4EE5\uFF09</strong></p>
            <ol>
              <li><strong>AI \u751F\u6210\uFF1A</strong>\u8DDF AI \u8AAA\u300C\u5E6B\u4F5C\u696D\u7CFB\u7D71\u6982\u8AD6.md \u5EFA\u7ACB memory-map\u300D\uFF0CAI \u6703\u81EA\u52D5\u5EFA\u7ACB <code>\u4F5C\u696D\u7CFB\u7D71\u6982\u8AD6-memory.canvas</code></li>
              <li><strong>\u624B\u52D5\u5EFA\u7ACB\uFF1A</strong>\u5728 Obsidian \u65B0\u589E Canvas \u6A94\u6848\uFF0C\u547D\u540D\u70BA <code>{\u7B46\u8A18\u540D}-memory.canvas</code>\uFF0C\u63D2\u4EF6\u5C31\u6703\u81EA\u52D5\u5075\u6E2C</li>
              <li><strong>\u5B58\u653E\u4F4D\u7F6E\uFF1A</strong>\u9810\u8A2D\u653E\u5728\u4F86\u6E90\u7B46\u8A18\u540C\u8CC7\u6599\u593E\u3002\u4E5F\u53EF\u4EE5\u5728\u8A2D\u5B9A\u4E2D\u6307\u5B9A Memory Map \u8CC7\u6599\u593E\uFF0C\u96C6\u4E2D\u7BA1\u7406</li>
            </ol>
            <p><strong>\u5075\u6E2C\u898F\u5247</strong></p>
            <p>\u63D2\u4EF6\u6703\u6383\u63CF vault \u4E2D\u6240\u6709\u6A94\u540D\u4EE5 <code>-memory.canvas</code> \u7D50\u5C3E\u7684\u6A94\u6848\uFF0C\u81EA\u52D5\u6536\u9304\u5230 Hub \u2192 Memory Map \u9801\u7C64\u3002</p>
            <p><strong>\u8207 Review Deck \u7684\u95DC\u806F</strong></p>
            <p>\u8907\u7FD2\u5361\u7247\u6642\uFF0C\u5E95\u90E8\u7684\u300CMemory Map\u300D\u6309\u9215\u6703\u81EA\u52D5\u5C0B\u627E\u5C0D\u61C9\u7684 canvas\uFF1A</p>
            <ol>
              <li>\u5148\u627E\u540C\u540D\u7684 <code>-memory.canvas</code>\uFF08\u6839\u64DA\u4F86\u6E90\u7B46\u8A18\u540D\u7A31\uFF09</li>
              <li>\u627E\u4E0D\u5230\u6642\uFF0C\u6703\u6383\u63CF\u6240\u6709 memory canvas \u7684\u5167\u5BB9\uFF0C\u5982\u679C canvas \u88E1\u6709\u6307\u5411\u8A72\u7B46\u8A18\u7684 file node\uFF0C\u5C31\u6703\u81EA\u52D5\u95DC\u806F</li>
            </ol>
            <div class="lh-help-sub" style="border-color:#7c3aed"><div class="lh-help-sub-icon">${j("bulb")}</div><div>\u5EFA\u8B70\u8B93 canvas \u88E1\u5305\u542B\u4E00\u500B\u6307\u5411\u4F86\u6E90\u7B46\u8A18\u7684 file node\uFF08AI \u751F\u6210\u6642\u6703\u81EA\u52D5\u52A0\u5165\uFF09\uFF0C\u9019\u6A23\u5373\u4F7F\u6A94\u6848\u79FB\u52D5\u6216\u6539\u540D\uFF0CObsidian \u4E5F\u6703\u81EA\u52D5\u66F4\u65B0\u8DEF\u5F91\uFF0C\u95DC\u806F\u4E0D\u6703\u65B7\u6389\u3002</div></div>
          `},{icon:"cap",title:"\u8AB2\u7A0B\u5B78\u9662\uFF08\u6559\u6750\uFF09",tag:"AI \u6559\u5B78",html:`
            <p>\u544A\u8A34 AI \u4F60\u60F3\u5B78\u4EC0\u9EBC\uFF0C\u5B83\u6703\u5EFA\u7ACB\u4E00\u5957\u5C08\u5C6C\u8AB2\u7A0B\uFF1A\u4E92\u52D5\u5F0F HTML \u6559\u6750\uFF0C\u76F4\u63A5\u5728 plugin \u88E1\u5B78\u7FD2\u3001\u8FFD\u8E64\u9032\u5EA6\u3002</p>
            <p><strong>\u904B\u4F5C\u6A5F\u5236</strong></p>
            <ol>
              <li>\u8DDF AI \u8AAA\uFF1A\u300C\u6559\u6211 SEO\u300D\u6216\u300C\u6211\u60F3\u5B78 .NET\uFF0C\u5E6B\u6211\u5EFA\u8AB2\u7A0B\u300D\uFF08Lesson Academy skill\uFF09</li>
              <li>AI \u8A62\u554F\u4F60\u7684\u76EE\u6A19\u548C\u7A0B\u5EA6\uFF0C\u8A2D\u8A08\u8AB2\u7A0B\u5927\u7DB1\uFF0C\u9010\u8AB2\u751F\u6210 HTML \u6559\u6750\u5230 <code>engram-quest/lessons/</code></li>
              <li>\u6253\u958B plugin \u2192 \u5B78\u7FD2 \u2192 \u6559\u6750\uFF0C\u9EDE\u4EFB\u4E00\u8AB2\u6642\u5373\u53EF\u95B1\u8B80\uFF0C\u9644\u4E92\u52D5\u6E2C\u9A57</li>
              <li>\u770B\u5B8C\u6309\u300C\u6A19\u8A18\u5B8C\u6210\u300D\uFF0C\u8AB2\u7A0B\u5361\u6703\u986F\u793A\u9032\u5EA6\uFF1B\u53EF\u52A0\u661F\u6A19\u8A18\u91CD\u9EDE\u8AB2\u6642</li>
              <li>\u4E5F\u53EF\u4EE5\u7528\u300C\u532F\u5165 HTML\u300D\u628A\u5176\u4ED6 AI\uFF08\u5982 Gemini\uFF09\u751F\u6210\u7684\u6559\u6750\u52A0\u9032\u8AB2\u7A0B</li>
              <li>\u8AB2\u7A0B\u591A\u6642\u7528\u5361\u7247\u4E0A\u65B9\u7684\u300C\u985E\u5225\uFF0F\u9032\u5EA6\uFF0F\u6392\u5E8F\u300D\u4E0B\u62C9\u7BE9\u9078\u6392\u5E8F\uFF0C\u9078\u64C7\u6703\u81EA\u52D5\u8A18\u4F4F</li>
            </ol>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">${j("bulb")}</div><div>\u4EFB\u4F55\u4E3B\u984C\u90FD\u53EF\u4EE5\uFF1A\u7A0B\u5F0F\u3001\u884C\u92B7\u3001\u91AB\u5B78\u3001\u745C\u73C8\u3002\u5B78\u5B8C\u4E00\u5957\u8AB2\u7A0B\u5F8C\uFF0C\u53EF\u4EE5\u8ACB AI \u628A\u6559\u6750\u8F49\u6210 Review Deck \u5361\u7247\u6216 Quest Map \u7E7C\u7E8C\u978F\u56FA\u3002</div></div>
          `},{icon:"bolt",title:"Synapse (Pro)",tag:"Pro",html:`
            <p>\u8907\u7FD2\u4E00\u5F35\u96E3\u8A18\u7684\u5361\u6642\uFF0C\u81EA\u52D5\u9023\u7D50\u5230\u4F60\u5DF2\u638C\u63E1\u7684\u76F8\u95DC\u5361\uFF0C\u4F5C\u70BA\u300C\u8A18\u61B6\u9328\u9EDE\u300D\u3002</p>
            <p><strong>\u904B\u4F5C\u6A5F\u5236</strong></p>
            <ol>
              <li>\u8DDF AI \u8AAA\u300C\u8DD1 engram-synapse\u300D</li>
              <li>AI \u6383\u63CF\u6240\u6709 SR \u7D00\u9304\u627E\u51FA mastered pool\uFF08FSRS stability \u2265 7\uFF09</li>
              <li>\u5C0D\u6BCF\u5F35\u5361\u627E\u51FA\u6700\u591A 3 \u5F35\u5F37\u9023\u7D50\u9328\u9EDE\uFF0C\u5B58\u9032 <code>engram-review/synapse/</code></li>
              <li>\u8907\u7FD2\u6642\uFF0CReview session \u770B\u7B54\u6848\u524D\u6703\u591A\u4E00\u500B ${j("bolt")} Synapse \u6309\u9215</li>
            </ol>
            <p><strong>\u4E09\u5927\u8A18\u61B6\u6A5F\u5236</strong></p>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">EE</div><div><strong>\u7CBE\u7DFB\u5316\u7DE8\u78BC</strong> \u2014 \u628A\u96E3\u8A18\u7684\u5361\u639B\u5728\u5DF2\u719F\u6089\u7684\u5361\u4E0A</div></div>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">AR</div><div><strong>\u4E3B\u52D5\u63D0\u53D6</strong> \u2014 \u5F37\u8FEB\u56DE\u60F3\u9328\u9EDE\u7B54\u6848\uFF0C\u96D9\u500D\u8A18\u61B6\u5F37\u5316</div></div>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">CA</div><div><strong>\u60C5\u7BC0\u9328\u5B9A</strong> \u2014 \u5DF2\u638C\u63E1\u7684\u5361\u4F5C\u70BA\u719F\u6089\u7684\u8A18\u61B6\u60C5\u5883</div></div>
            <p><strong>\u9700\u8981 Pro license</strong>\u3002Mastered pool \u81F3\u5C11\u8981 10 \u5F35\u5361\u624D\u6703\u555F\u7528\u3002</p>
          `},{icon:"clock",title:"Time-boxed Review (Pro)",tag:"Pro",html:`
            <p>\u6C92\u6642\u9593\u8907\u7FD2\u5168\u90E8\uFF1F\u9078 5 / 10 / 15 \u5206\u9418\uFF0C\u7CFB\u7D71\u6311\u51FA\u6700\u8A72\u8907\u7FD2\u7684 N \u5F35\u5361\uFF081 \u5361\u7D04 1 \u5206\u9418\uFF09\u3002</p>
            <p><strong>\u6311\u5361\u512A\u5148\u9806\u5E8F</strong></p>
            <ol>
              <li>\u5F9E\u672A\u8907\u7FD2\u904E\u7684\u65B0\u5361\uFF08unseen\uFF09</li>
              <li>\u5DF2\u904E\u671F\u6700\u4E45\u7684\u5361\uFF08overdue\uFF09</li>
              <li>\u6700\u5FEB\u5230\u671F\u7684\u5361\uFF08\u5373\u5C07 due\uFF09</li>
            </ol>
            <p>\u8DDF\u300C\u5168\u90E8\u8907\u7FD2\u300D\u5171\u7528\u540C\u4E00\u500B review session UI \u8207 SR \u5BEB\u56DE\u6D41\u7A0B\uFF0C\u6C92\u6709\u984D\u5916\u98A8\u96AA\u3002</p>
            <p><strong>\u9700\u8981 Pro license</strong>\u3002</p>
          `},{icon:"cards",title:"\u56DB\u8C61\u9650\u5361 (Pro)",tag:"Pro",html:`
            <p>\u628A\u4E00\u5F35 Q/A \u5361\u5347\u7D1A\u6210\u4E00\u5F35 A4\u300C\u8D85\u8A18\u61B6\u5361\u300D\uFF0C\u7528\u56DB\u500B\u8C61\u9650\u5F9E\u4E0D\u540C\u89D2\u5EA6\u7DE8\u78BC\u540C\u4E00\u500B\u6982\u5FF5\uFF0C\u5C08\u6CBB\u6700\u96E3\u8A18\u3001\u6700\u6838\u5FC3\u7684\u89C0\u5FF5\u3002</p>
            <p><strong>\u56DB\u500B\u8C61\u9650</strong></p>
            <ol>
              <li><strong>Q1 \u554F\u984C</strong> \u2014 \u8981\u8A18\u4F4F\u7684\u63D0\u554F</li>
              <li><strong>Q2 \u7B54\u6848</strong> \u2014 \u7CBE\u6E96\u7684\u6B63\u89E3</li>
              <li><strong>Q3 \u6587\u5B57\u6BD4\u55BB</strong> \u2014 \u7528\u53E3\u8A23\uFF0F\u985E\u6BD4\u628A\u62BD\u8C61\u8B8A\u597D\u61C2</li>
              <li><strong>Q4 \u8996\u89BA\u5716\u50CF</strong> \u2014 \u4E00\u5F35\u756B\u9762\u6216 emoji \u5834\u666F\uFF0C\u8B93\u5927\u8166\u7528\u5716\u50CF\u8A18</li>
            </ol>
            <p><strong>\u6709\u4EC0\u9EBC\u7528</strong></p>
            <div class="lh-help-sub" style="border-color:#0078d4"><div class="lh-help-sub-icon">DC</div><div><strong>\u96D9\u91CD\u7DE8\u78BC</strong> \u2014 \u540C\u6642\u7528\u6587\u5B57\u8207\u5716\u50CF\u8A18\uFF0C\u6BD4\u7D14\u6587\u5B57\u66F4\u7262</div></div>
            <div class="lh-help-sub" style="border-color:#0078d4"><div class="lh-help-sub-icon">EE</div><div><strong>\u7CBE\u7DFB\u5316\u7DE8\u78BC</strong> \u2014 \u6BD4\u55BB + \u5716\u50CF\u628A\u62BD\u8C61\u6982\u5FF5\u639B\u4E0A\u719F\u6089\u7684\u9264\u5B50</div></div>
            <div class="lh-help-sub" style="border-color:#0078d4"><div class="lh-help-sub-icon">SR</div><div><strong>\u7368\u7ACB\u6392\u7A0B</strong> \u2014 \u6BCF\u5F35\u56DB\u8C61\u9650\u5361\u6709\u81EA\u5DF1\u7684 FSRS \u6392\u7A0B\uFF0C\u8207 Review Deck \u5206\u958B</div></div>
            <p><strong>\u600E\u9EBC\u7528</strong></p>
            <ol>
              <li>\u8DDF AI \u8AAA\u300C\u56DB\u8C61\u9650\u5361 / \u56DB\u683C\u5361 / \u8D85\u8A18\u61B6\u5361 / Quadrant Card\u300D\u4EFB\u4E00\u540D\u7A31\uFF0C\u6216\u5728\u8907\u7FD2\u6642\u628A\u5361\u6A19\u8A18\u300C\u5347\u7D1A\u300D</li>
              <li>\u5230 Hub \u2192\u300C\u56DB\u8C61\u9650\u5361\u300D\u5206\u9801\u8907\u7FD2\uFF0C\u81EA\u8A55\u5F8C\u81EA\u52D5\u66F4\u65B0\u6392\u7A0B</li>
              <li>\u8907\u7FD2\u5361\u4E0A\u53EF\u6309 ${j("file")} \u958B\u555F\u4F86\u6E90\u7B46\u8A18\u3001${j("clipboard")} \u8907\u88FD\u554F\u7B54\u3001${j("edit")} \u7DE8\u8F2F\uFF08\u6A19\u984C\u5373\u6642\u751F\u6548\uFF1B\u6539 Q1\u2013Q4 \u5167\u5BB9\u5F8C\u91CD\u8DD1 skill \u8AAA\u300C\u5347\u7D1A\u300D\u624D\u6703\u91CD\u5EFA\u5361\u7247\u8996\u89BA\uFF09</li>
              <li>\u4E0D\u8981\u7684\u5361\u53EF\u5728\u724C\u7D44\u5217\u6216\u8907\u7FD2\u5361\u5167\u6309 ${j("trash")} \u522A\u9664</li>
            </ol>
            <p><strong>\u9700\u8981 Pro license</strong>\u3002\u9069\u5408\u7528\u5728\u6700\u91CD\u8981\u3001\u6700\u96E3\u8A18\u7684\u6838\u5FC3\u6982\u5FF5\uFF0C\u4E0D\u5FC5\u6BCF\u5F35\u5361\u90FD\u5347\u7D1A\u3002</p>
          `},{icon:"search",title:"Learning Science",tag:we(t,"HELP_SCIENCE_TAG"),html:`
            <div class="lh-help-sub"><div class="lh-help-sub-icon">SR</div><div><strong>Spaced Repetition</strong><br>\u5728\u5FEB\u5FD8\u8A18\u6642\u8907\u7FD2\uFF0C\u63D0\u5347\u6548\u7387\u3002Review Deck \u4F7F\u7528 FSRS \u81EA\u52D5\u5B89\u6392\u4E0B\u6B21\u8907\u7FD2\u3002</div></div>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">RP</div><div><strong>Retrieval Practice</strong><br>\u5148\u56DE\u60F3\uFF0C\u518D\u770B\u7B54\u6848\uFF0C\u6BD4\u91CD\u8B80\u66F4\u6709\u6548\u3002Review Deck \u8A13\u7DF4\u8A18\u61B6\uFF1BQuest Map \u628A\u8A18\u61B6\u653E\u9032\u60C5\u5883\u4E2D\u7DF4\u7FD2\u61C9\u7528\u3002</div></div>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">EE</div><div><strong>Elaborative Encoding</strong><br>\u628A\u62BD\u8C61\u6587\u5B57\u8B8A\u6210\u5177\u9AD4\u7D50\u69CB\uFF0C\u66F4\u5BB9\u6613\u8A18\u4F4F\u3002Memory Map \u5C31\u662F\u5728\u505A\u9019\u4EF6\u4E8B\u3002</div></div>
          `},{icon:"tools",title:"\u8A2D\u5B9A",tag:we(t,"HELP_SETTINGS_TAG"),html:`
            <p>\u5230 Obsidian \u8A2D\u5B9A\u4E2D\u7684 <strong>EngramQuest</strong> \u8ABF\u6574\u3002</p>
            <p><strong>Flashcard tag prefixes</strong>\uFF08\u9810\u8A2D\uFF1A<code>flashcards</code>\uFF09</p>
            <p>\u6C7A\u5B9A\u54EA\u4E9B\u7B46\u8A18\u6703\u88AB\u63D2\u4EF6\u6383\u63CF\u70BA Review Deck \u5361\u7247\u3002</p>
            <ul>
              <li>\u9810\u8A2D <code>flashcards</code> \u2192 <code>#flashcards/math</code>\u3001<code>#flashcards/\u884C\u92B7</code> \u6703\u88AB\u5075\u6E2C\u5230</li>
              <li>\u6539\u6210 <code>cards</code> \u2192 \u53EA\u6709 <code>#cards/math</code> \u6703\u88AB\u5075\u6E2C\uFF0C<code>#flashcards/math</code> \u5C31\u4E0D\u6703</li>
              <li>\u53EF\u4EE5\u8A2D\u591A\u500B\uFF0C\u7528\u9017\u865F\u5206\u9694\uFF0C\u4F8B\u5982 <code>flashcards, cards, anki</code></li>
            </ul>
            <p><strong>\u8B93\u63D2\u4EF6\u5075\u6E2C\u5230\u7684\u689D\u4EF6\uFF08\u5169\u500B\u90FD\u8981\u6EFF\u8DB3\uFF09\uFF1A</strong></p>
            <ol>
              <li>\u7B46\u8A18\u88E1\u6709\u5361\u7247\uFF08<code>::</code>\u3001<code>Q:/A:</code>\u3001Cloze \u4E09\u7A2E\u683C\u5F0F\u5747\u652F\u63F4\uFF09\uFF0C\u4F8B\u5982\uFF1A<pre>#flashcards/\u6578\u5B78

\u7562\u6C0F\u5B9A\u7406\u662F\u4EC0\u9EBC :: \u76F4\u89D2\u4E09\u89D2\u5F62\u4E2D\uFF0Ca\xB2 + b\xB2 = c\xB2

Q: \u5C0E\u6578\u7684\u5B9A\u7FA9\uFF1F
A: \u51FD\u6578\u5728\u67D0\u9EDE\u7684\u77AC\u6642\u8B8A\u5316\u7387

{{c1::\u5FAE\u7A4D\u5206}} \u662F\u9AD8\u4E2D\u6578\u5B78\u6700\u91CD\u8981\u7684\u4E00\u9580\u5B78\u79D1</pre></li>
              <li>\u7B46\u8A18\u6709\u7B26\u5408\u524D\u7DB4\u7684 tag\uFF0C\u4F8B\u5982 <code>#flashcards/\u6578\u5B78</code>\uFF08\u5982\u4E0A\u4F8B\u7B2C\u4E00\u884C\uFF09</li>
            </ol>
            <p><strong>Include legacy <code>::</code> notes</strong>\uFF08\u9810\u8A2D\uFF1AOff\uFF09</p>
            <p>\u958B\u555F\u5F8C\uFF0C\u6C92\u6709\u4EFB\u4F55 tag \u7684\u820A\u5F0F <code>::</code> \u7B46\u8A18\u4E5F\u6703\u88AB\u6383\u63CF\u3002\u9077\u79FB\u6A21\u5F0F\uFF0C\u65B0\u624B\u4E0D\u9700\u8981\u958B\u3002</p>
            <p><strong>Max review interval</strong>\uFF08\u9810\u8A2D\uFF1A36525 \u5929\uFF09</p>
            <p>FSRS \u6392\u7A0B\u7684\u6700\u5927\u9593\u9694\u4E0A\u9650\u3002\u8ABF\u5C0F\u53EF\u4EE5\u8B93\u719F\u6089\u7684\u5361\u7247\u4ECD\u4FDD\u6301\u5B9A\u671F\u51FA\u73FE\u3002</p>
            <p><strong>Requested retention</strong>\uFF08\u9810\u8A2D\uFF1A0.9\uFF09</p>
            <p>FSRS \u76EE\u6A19\u8A18\u61B6\u4FDD\u7559\u7387\u3002\u8D8A\u9AD8\u8907\u7FD2\u8D8A\u983B\u7E41\uFF0C\u8D8A\u4F4E\u9593\u9694\u8D8A\u9577\u3002</p>
          `},{icon:"target",title:"FAQ",tag:we(t,"HELP_FAQ_TAG"),html:`
            <p><strong>Q1\uFF1A\u6C92\u6709\u5B89\u88DD Skills \u4E5F\u80FD\u7528\u55CE\uFF1F</strong></p>
            <p>\u53EF\u4EE5\u3002\u4F60\u53EF\u4EE5\u624B\u52D5\u5EFA\u7ACB <code>question :: answer</code> \u5361\u7247\uFF0C\u4E26\u52A0\u4E0A\u50CF <code>#flashcards/math</code> \u9019\u7A2E tag\u3002</p>
            <p><strong>Q2\uFF1A\u5982\u679C\u4F86\u6E90\u7B46\u8A18\u6C92\u6709\u4EFB\u4F55 tag\uFF0CAI \u9084\u8B80\u5F97\u5230\u55CE\uFF1F</strong></p>
            <p>\u53EF\u4EE5\u3002AI \u8B80\u4F86\u6E90\u7B46\u8A18\u6642\uFF0C\u4E0D\u8981\u6C42\u4F86\u6E90\u7B46\u8A18\u5148\u6709 <code>flashcards</code> tag\u3002AI \u7522\u751F\u7684\u5361\u7247\u6703\u5B58\u5230 <code>engram-review/ai-cards/</code>\uFF0C\u4E0D\u6703\u52D5\u4F60\u7684\u539F\u59CB\u7B46\u8A18\u3002</p>
            <p><strong>Q3\uFF1A\u70BA\u4EC0\u9EBC\u6211\u770B\u4E0D\u5230 Review Deck\uFF1F</strong></p>
            <p>\u5E38\u898B\u539F\u56E0\u6709\uFF1A\u9084\u6C92\u6709\u5EFA\u7ACB\u5361\u7247\u7B46\u8A18\u3001\u5361\u7247\u7B46\u8A18\u6C92\u6709\u7B26\u5408\u76EE\u524D\u8A2D\u5B9A\u7684 tag prefix\uFF0C\u6216\u4F60\u671F\u5F85\u7684\u662F\u820A\u5F0F <code>::</code> \u6383\u63CF\u4F46 legacy \u6A21\u5F0F\u9084\u6C92\u958B\u3002</p>
            <p><strong>Q4\uFF1A\u6211\u6709\u4E00\u822C\u7B46\u8A18\uFF0C\u6C92\u6709\u4EFB\u4F55 tag\u3002\u8981\u600E\u9EBC\u505A Review Deck\uFF1F</strong></p>
            <p>\u76F4\u63A5\u5C0D AI \u8AAA\uFF1A\u300C\u628A\u548C[\u4E3B\u984C]\u76F8\u95DC\u7684\u7B46\u8A18\u505A\u6210 Review Deck\u300D\u3002AI \u6703\u8B80\u53D6\u4F60\u7684\u7B46\u8A18\uFF0C\u5728 <code>engram-review/ai-cards/</code> \u5EFA\u7ACB\u542B tag \u7684\u5361\u7247\u6A94\uFF0C\u4E0D\u6703\u52D5\u4F60\u7684\u539F\u59CB\u7B46\u8A18\u3002\u56DE\u5230 Hub \u7684 Review Deck \u5C31\u80FD\u770B\u5230\u3002</p>
            <p><strong>Q5\uFF1A\u6211\u60F3\u624B\u52D5\u505A\u5361\u7247\uFF0C\u4E0D\u9760 AI\u3002\u8981\u600E\u9EBC\u505A\uFF1F</strong></p>
            <p><strong>Step 1\uFF1A\u52A0 tag\uFF08\u6C7A\u5B9A\u653E\u5230\u54EA\u500B Deck\uFF09</strong></p>
            <p>\u5728\u7B46\u8A18\u4EFB\u610F\u4F4D\u7F6E\u52A0\u4E0A <code>#flashcards/\u4E3B\u984C</code>\u3002\u659C\u7DDA\u5F8C\u9762\u7684\u540D\u7A31\u5C31\u662F Deck \u7684\u540D\u5B57\u3002</p>
            <ul>
              <li><code>#flashcards/\u82F1\u6587</code> \u2192 \u5EFA\u7ACB\u300C\u82F1\u6587\u300DDeck</li>
              <li><code>#flashcards/math</code> \u2192 \u5EFA\u7ACB\u300Cmath\u300DDeck</li>
              <li>\u524D\u7DB4\u9810\u8A2D\u662F <code>flashcards</code>\uFF0C\u53EF\u5728\u8A2D\u5B9A\u4E2D\u6539\u6210\u5176\u4ED6\u540D\u7A31\uFF08\u5982 <code>cards</code>\u3001<code>anki</code>\uFF09</li>
            </ul>
            <p><strong>Step 2\uFF1A\u5BEB\u5361\u7247\uFF08\u652F\u63F4\u4E09\u7A2E\u683C\u5F0F\uFF0C\u53EF\u81EA\u7531\u6DF7\u7528\uFF09</strong></p>
            <pre>#flashcards/\u5B78\u7FD2\u79D1\u5B78

\u7562\u6C0F\u5B9A\u7406 :: a\xB2 + b\xB2 = c\xB2

Q: \u9593\u9694\u91CD\u8907\u7684\u539F\u7406\u662F\u4EC0\u9EBC\uFF1F
A:
1.\u5728\u5FEB\u5FD8\u8A18\u6642\u8907\u7FD2\uFF0C\u6548\u679C\u6700\u4F73
2.\u6BCF\u6B21\u6210\u529F\u56DE\u60F3\u5F8C\uFF0C\u4E0B\u6B21\u8907\u7FD2\u7684\u9593\u9694\u81EA\u52D5\u62C9\u9577

{{c1::\u9593\u9694\u91CD\u8907}} \u662F\u6700\u6709\u6548\u7684\u9577\u671F\u8A18\u61B6\u65B9\u6CD5\u4E4B\u4E00
\u6CD5\u570B\u9996\u90FD {{c1::\u5DF4\u9ECE}}\uFF0C\u65E5\u672C\u9996\u90FD {{c2::\u6771\u4EAC}}</pre>
            <table>
              <tr><th>\u683C\u5F0F</th><th>\u9069\u5408</th><th>\u5BEB\u6CD5</th></tr>
              <tr><td><span class="lh-help-fmt-chip" style="background:#dcfce7;color:#166534;"><code>Q:/A:</code> \u554F\u7B54</span> ${j("star")}</td><td>\u65E5\u5E38\u63A8\u85A6\u683C\u5F0F\u3002\u591A\u884C\u7B54\u6848\u3001\u5716\u7247\u3001\u8868\u683C</td><td><code>Q: \u554F\u984C</code> \u63DB\u884C <code>A: \u7B54\u6848</code>\uFF08\u7B54\u6848\u53EF\u4EE5\u5F9E\u4E0B\u4E00\u884C\u958B\u59CB\uFF0C\u53EF\u6709\u591A\u884C\uFF09</td></tr>
              <tr><td><span class="lh-help-fmt-chip" style="background:#dbeafe;color:#1d4ed8;"><code>---</code> fenced</span> ${j("star")}</td><td>\u65E2\u6709\u9577\u7B54\u6848\u7B46\u8A18\u3001\u4E00\u822C Markdown \u53CB\u5584\u7528\u6CD5</td><td>\u524D\u5F8C\u5404\u52A0\u4E00\u884C <code>---</code> \u5305\u4F4F\u5361\u7247\uFF0C\u88E1\u9762\u7684\u7A7A\u884C\u6C38\u9060\u4E0D\u6703\u88AB\u7576\u6210\u5361\u7247\u908A\u754C</td></tr>
              <tr><td><span class="lh-help-fmt-chip" style="background:#fef3c7;color:#92400e;"><code>%%card%%</code> \u9577\u7B54\u6848</span></td><td>\u8CBC\u4E0A AI \u9577\u7B54\u6848\u7684\u5B89\u5168\u6A21\u5F0F\uFF0C\u9069\u5408\u5167\u5BB9\u53EF\u80FD\u5305\u542B <code>---</code></td><td>\u524D\u5F8C\u5404\u4E00\u884C <code>%%card%%</code> \u5305\u4F4F\u4E00\u5F35\u5361\uFF1B<code>A:</code> \u5F8C\u9762\u76F4\u5230\u7D50\u675F\u6A19\u8A18\u524D\u90FD\u6703\u4FDD\u7559\u70BA\u7B54\u6848</td></tr>
              <tr><td>Cloze \u586B\u7A7A</td><td>\u586B\u7A7A\u8A18\u61B6\uFF0C\u540C Anki \u8A9E\u6CD5</td><td><code>{{c1::\u7B54\u6848}}</code> \u6216 <code>{{c1::\u7B54\u6848::\u63D0\u793A}}</code></td></tr>
              <tr><td><code>::</code> \u554F\u7B54</td><td>\u7C21\u77ED\u7B54\u6848\uFF0C\u50C5\u9650\u4E00\u884C</td><td><code>\u554F\u984C :: \u7B54\u6848</code></td></tr>
            </table>
            <p><strong>Q:/A: \u908A\u754C\u898F\u5247\uFF1A</strong></p>
            <ul>
              <li><code>A:</code> \u5F8C\u9762\u53EF\u4EE5\u4E0D\u5BEB\u4EFB\u4F55\u6771\u897F\uFF0C\u7B54\u6848\u5F9E\u4E0B\u4E00\u884C\u958B\u59CB</li>
              <li>\u7B54\u6848\u88E1\u6709\u4E00\u500B\u7A7A\u884C\u6C92\u95DC\u4FC2\uFF0C\u6703\u7E7C\u7E8C\u6536\u96C6</li>
              <li><strong>\u9023\u7E8C\u5169\u500B\u7A7A\u884C</strong>\u4EE3\u8868\u5361\u7247\u7D50\u675F</li>
              <li>\u9047\u5230\u4E0B\u4E00\u500B <code>Q:</code> \u6216 Cloze \u884C\u4E5F\u6703\u81EA\u52D5\u7D50\u675F</li>
              <li><strong>Fenced \u6A21\u5F0F\uFF1A</strong>\u524D\u5F8C\u5404\u52A0\u4E00\u884C <code>---</code> \u5305\u4F4F\uFF0C\u88E1\u9762\u4E0D\u7BA1\u5E7E\u500B\u7A7A\u884C\u90FD\u4E0D\u6703\u622A\u65B7 \u2014 \u9069\u5408\u8CBC\u4E0A ChatGPT / AI \u7684\u9577\u7B54\u6848\uFF1A<pre>---
Q: Stripe \u7684\u6838\u5FC3\u6A21\u578B\u662F\u4EC0\u9EBC\uFF1F
A: Stripe \u672C\u8CEA\u662F\u4E00\u500B Saga System\u3002

   \u5B83\u8655\u7406\uFF1A
   - payment_intent \u72C0\u614B\u6A5F
   - retry / failure handling

   \u4F60\u53EA\u8981\u300C\u63A5\u7D50\u679C\u300D\u3002
---

---
Q: \u4EC0\u9EBC\u662F Saga \u6A21\u5F0F\uFF1F
A: \u4E00\u9023\u4E32\u7684\u672C\u5730\u4EA4\u6613\u3002

   \u6BCF\u500B\u6B65\u9A5F\u767C\u5E03\u4E00\u500B\u4E8B\u4EF6\u3002
   \u5931\u6557\u6642\uFF0C\u88DC\u511F\u4EA4\u6613\u8CA0\u8CAC\u56DE\u6EFE\u3002
---</pre>
              <strong>\u6CE8\u610F\uFF1A</strong>\u7B46\u8A18\u88E1\u666E\u901A\u7684 <code>---</code> \u6C34\u5E73\u7DDA\uFF08\u5F8C\u9762\u4E0D\u63A5 <code>Q:</code>\uFF09\u6703\u81EA\u52D5\u88AB\u5FFD\u7565\uFF0C\u4E0D\u6703\u8AA4\u89F8\u767C\u3002</li>
            </ul>
            <p><strong>Cloze \u88DC\u5145\u8AAA\u660E\uFF1A</strong></p>
            <ul>
              <li>\u6BCF\u500B <code>{{cX::\u7B54\u6848}}</code> \u81EA\u52D5\u7522\u751F\u4E00\u5F35\u5361\uFF0C\u6B63\u9762\u986F\u793A <code>[...]</code>\uFF0C\u80CC\u9762\u986F\u793A\u7B54\u6848</li>
              <li>\u540C\u4E00\u884C\u6709\u591A\u500B\u586B\u7A7A\uFF08c1\u3001c2\u2026\uFF09\uFF0C\u6BCF\u500B\u5404\u81EA\u7522\u751F\u4E00\u5F35\u5361</li>
              <li>\u6709\u63D0\u793A\u7248\uFF1A<code>{{c1::\u5DF4\u9ECE::\u9996\u90FD}}</code> \u2192 \u6B63\u9762\u986F\u793A <code>[\u9996\u90FD]</code></li>
            </ul>
            <p>\u56DE\u5230 Hub \u7684 Review Deck \u5C31\u80FD\u770B\u5230\u6240\u6709\u5361\u7247\u3002</p>
            <p><strong>Q6\uFF1A\u6211\u6709\u5F88\u591A\u820A <code>::</code> \u5361\u7247\uFF0C\u4F46\u6C92\u6709 tag\u3002\u8981\u600E\u9EBC\u9077\u79FB\uFF1F</strong></p>
            <p>\u5230\u8A2D\u5B9A\u6253\u958B <code>Include legacy :: notes</code>\uFF0C\u4FDD\u7559\u539F\u672C\u7684 <code>question :: answer</code> \u683C\u5F0F\uFF0C\u518D\u56DE\u5230 Hub \u7684 Review Deck \u6AA2\u67E5\u662F\u5426\u5DF2\u88AB\u7D0D\u5165\u3002\u82E5\u8981\u9577\u671F\u7DAD\u8B77\uFF0C\u5EFA\u8B70\u4E4B\u5F8C\u6162\u6162\u88DC\u4E0A <code>#flashcards/...</code> tag\u3002</p>
            <p><strong>Q7\uFF1AReview Deck \u9032\u5EA6\u5B58\u5728\u54EA\u88E1\uFF1F</strong></p>
            <p>\u8907\u7FD2\u6392\u7A0B\u8CC7\u6599\u5B58\u653E\u5728 <code>engram-review/sr/{\u7B46\u8A18\u540D\u7A31}.json</code>\uFF0CAI \u751F\u6210\u7684\u5361\u7247\u5B58\u5728 <code>engram-review/ai-cards/</code>\uFF0C\u5169\u8005\u90FD\u4E0D\u6703\u5BEB\u5165\u4F60\u7684\u539F\u59CB\u7B46\u8A18\u3002\u4F60\u81EA\u5DF1\u624B\u5BEB\u7684\u5361\u7247\uFF0C\u5728\u8907\u7FD2\u6642\u4F7F\u7528\u300C\u7DE8\u8F2F\u300D\u3001\u300C\u9AD8\u4EAE\u300D\u6216\u300C\u5F15\u8FF0\u300D\u529F\u80FD\uFF0C\u8B8A\u66F4\u6703\u76F4\u63A5\u5BEB\u56DE\u539F\u59CB\u7B46\u8A18\u3002</p>
            <p><strong>Q8\uFF1A\u624B\u6A5F\u53EF\u4EE5\u7528\u55CE\uFF1F</strong></p>
            <p>Review Deck \u53EF\u4EE5\u3002Quest Map \u8207 Memory Map \u76EE\u524D\u4ECD\u8F03\u9069\u5408\u684C\u9762\u7248\u3002</p>
            <p><strong>Q9\uFF1A\u624B\u6A5F\u4E0A\u7684\u63D2\u4EF6\u4E0D\u6703\u81EA\u52D5\u66F4\u65B0\u600E\u9EBC\u8FA6\uFF1F</strong></p>
            <p>\u63D2\u4EF6\u7684 <code>main.js</code> \u5B58\u5728 vault \u7684 <code>.obsidian/plugins/engram-quest/</code> \u88E1\u3002\u53EA\u8981 vault \u6709\u7528 git \u540C\u6B65\uFF08Obsidian Git \u63D2\u4EF6\uFF09\uFF0C\u96FB\u8166\u7AEF\u66F4\u65B0\u5F8C push\uFF0C\u624B\u6A5F\u7AEF pull \u5C31\u6703\u81EA\u52D5\u62FF\u5230\u65B0\u7248\u672C\u3002\u5982\u679C\u624B\u6A5F\u6C92\u66F4\u65B0\uFF0C\u8ACB\u78BA\u8A8D\uFF1A</p>
            <ol>
              <li>vault \u7684 <code>.gitignore</code> \u6C92\u6709\u628A <code>/.obsidian/plugins/engram-quest/</code> \u6574\u500B\u6392\u9664</li>
              <li>\u624B\u6A5F\u7684 Obsidian Git \u63D2\u4EF6\u6709\u958B\u555F auto-pull</li>
              <li>pull \u5B8C\u5F8C\u5728 Obsidian \u8A2D\u5B9A\u4E2D\u505C\u7528\u518D\u555F\u7528\u63D2\u4EF6\uFF0C\u6216\u91CD\u555F App</li>
            </ol>
            <p><strong>Q10\uFF1A\u7B46\u8A18\u5F88\u96DC\u4E82\u6216\u5167\u5BB9\u975E\u5E38\u591A\uFF0C\u4E0D\u77E5\u9053\u5F9E\u54EA\u88E1\u958B\u59CB\u600E\u9EBC\u8FA6\uFF1F</strong></p>
            <p>\u4E0D\u7528\u5148\u6574\u7406\u3002\u76F4\u63A5\u8ACB AI \u5E6B\u4F60\u6383\u63CF\u4E26\u5217\u51FA\u7B46\u8A18\u7684\u4E3B\u984C\u5206\u985E\uFF0C\u518D\u5F9E\u5217\u8868\u4E2D\u6311\u4F60\u60F3\u5B78\u7684\u65B9\u5411\uFF1A</p>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">${j("chat")}</div><div>\u300C\u5E6B\u6211\u5217\u51FA vault \u88E1\u6240\u6709\u7B46\u8A18\u7684\u4E3B\u984C\u5206\u985E\u300D<br>\u300C\u5217\u51FA\u548C[\u79D1\u76EE]\u76F8\u95DC\u7684\u6240\u6709\u7B46\u8A18\u6A19\u984C\u300D</div></div>
            <p>\u770B\u5230\u5217\u8868\u5F8C\uFF0C\u76F4\u63A5\u6307\u5B9A\u4F60\u8981\u7684\u7B46\u8A18\u8B93 AI \u5EFA\u7ACB\u5167\u5BB9\uFF1A</p>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">${j("chat")}</div><div>\u300C\u7528\u7B2C 3 \u9805\u7684\u7B46\u8A18\u505A\u6210 Quest Map medium\u300D<br>\u300C\u628A\u884C\u92B7\u76F8\u95DC\u7684\u90A3\u5E7E\u7BC7\u505A\u6210 Review Deck\u300D</div></div>
            <p>\u5982\u679C\u7B46\u8A18\u672C\u8EAB\u4E5F\u5F88\u96F6\u6563\uFF0C\u53EF\u4EE5\u5148\u8ACB AI \u6574\u7406\u5206\u985E\u518D\u5EFA\u7ACB\uFF1A</p>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">${j("chat")}</div><div>\u300C\u5148\u628A\u9019\u4E9B\u7B46\u8A18\u6574\u7406\u6210\u7D50\u69CB\u5316\u7684\u5927\u7DB1\uFF0C\u518D\u5EFA\u7ACB Quest Map\u300D</div></div>
            <p><strong>Q11\uFF1AAI \u7522\u751F\u7684\u5167\u5BB9\u6211\u4E0D\u6EFF\u610F\uFF0C\u60F3\u8981\u5BA2\u88FD\u5316\u600E\u9EBC\u505A\uFF1F</strong></p>
            <p>\u53EF\u4EE5\u5728 AI \u8A2D\u5B9A\u6A94\uFF08<code>CLAUDE.md</code>\u3001<code>GEMINI.md</code>\u3001<code>AGENTS.md</code>\uFF09\u88E1\u52A0\u5165\u81EA\u8A02\u6307\u4EE4\uFF0CAI \u5C31\u6703\u7167\u8457\u4F60\u7684\u898F\u5247\u7522\u751F\u5167\u5BB9\u3002</p>
            <pre>\u5EFA\u7ACB Review Deck \u6642\uFF0C\u6BCF\u5F35\u5361\u7247\u90FD\u5FC5\u9808\u9644\u4E0A\u4E00\u500B\u5BE6\u969B\u61C9\u7528\u7684\u4F8B\u53E5\u3002
\u5B57\u5361\u96E3\u5EA6\u4E0D\u8981\u592A\u57FA\u790E\uFF0C\u9700\u5305\u542B\u5206\u6790\u8207\u61C9\u7528\u5C64\u6B21\u7684\u554F\u984C\u3002
\u6240\u6709\u5361\u7247\u554F\u984C\u8ACB\u7528\u7E41\u9AD4\u4E2D\u6587\u64B0\u5BEB\u3002</pre>
            <p><strong>Q12\uFF1A\u5982\u4F55\u8B93 AI \u6BCF\u6B21\u5EFA\u7ACB Review Deck \u6642\u90FD\u4F9D\u7167\u6211\u60F3\u8981\u7684\u56FA\u5B9A\u6A21\u5F0F\uFF1F</strong></p>
            <p>\u7528\u4F60\u7FD2\u6163\u7684\u4EFB\u4F55\u8A9E\u6CD5\u5728\u7B46\u8A18\u4E2D\u6A19\u8A18\u91CD\u8981\u7B54\u6848\uFF0C\u4F8B\u5982 Obsidian \u9AD8\u4EAE <code>==\u6587\u5B57==</code>\u3001\u5F15\u7528\u5340\u584A <code>&gt; \u6587\u5B57</code>\uFF0C\u6216\u4EFB\u4F55\u81EA\u8A02\u8A18\u865F\u90FD\u53EF\u4EE5\u3002\u63A5\u8457\u5728 AI \u8A2D\u5B9A\u6A94\uFF08<code>CLAUDE.md</code>\u3001<code>GEMINI.md</code> \u6216 <code>AGENTS.md</code>\uFF09\u4E2D\u52A0\u5165\u5C0D\u61C9\u6307\u4EE4\uFF0C\u544A\u8A34 AI \u628A\u90A3\u500B\u8A18\u865F\u7576\u4F5C\u5361\u7247\u7B54\u6848\uFF1A</p>
            <pre>IMPORTANT: When building a Review Deck, every highlighted ==text== must be turned into a review card.</pre>
            <p>\u7528\u4F60\u81EA\u5DF1\u6700\u9806\u624B\u7684\u6A19\u8A18\u65B9\u5F0F\u5C31\u597D\uFF0CAI \u6703\u7167\u8457\u898F\u5247\u4E00\u81F4\u57F7\u884C\u3002</p>
            <p><strong>Q13\uFF1AEngramQuest \u652F\u63F4 Anki \u55CE\uFF1F</strong></p>
            <p>\u90E8\u5206\u652F\u63F4\u3002<code>::</code> \u548C <code>{{c1::}}</code> \u683C\u5F0F\u8207 Anki \u76F8\u5BB9\uFF0C\u53EF\u642D\u914D <strong>Obsidian_to_Anki</strong> \u793E\u7FA4\u63D2\u4EF6\u4F7F\u7528\u3002\u53EA\u9700\u5B89\u88DD Obsidian_to_Anki + AnkiConnect\uFF0C\u5728\u8A2D\u5B9A\u4E2D\u958B\u555F RemNote style\uFF08<code>::</code> \u8A9E\u6CD5\uFF09\uFF0C\u540C\u6B65\u5F8C\u5361\u7247\u5C31\u6703\u81EA\u52D5\u51FA\u73FE\u5728 Anki \u4E2D\u3002</p>
            <p><code>Q:/A:</code> \u683C\u5F0F\u662F EngramQuest \u5C08\u5C6C\uFF0C<strong>\u4E0D\u6703</strong>\u540C\u6B65\u5230 Anki \u2014 \u5B83\u662F\u70BA\u591A\u884C\u7B54\u6848\u3001\u5716\u7247\u3001\u8868\u683C\u8A2D\u8A08\u7684\uFF0C\u9019\u4E9B\u5167\u5BB9\u7121\u6CD5\u76F4\u63A5\u5C0D\u61C9\u5230 Anki \u7684\u5361\u7247\u6A21\u578B\u3002\u5982\u679C\u4F60\u9700\u8981\u540C\u6642\u5728\u5169\u908A\u4F7F\u7528\uFF0C\u8ACB\u6539\u7528 <code>::</code> \u6216 <code>{{c1::}}</code>\u3002</p>
            <p><strong>Q14\uFF1AAI \u7522\u751F\u7684 Map \u6211\u60F3\u653E\u5728\u7279\u5B9A\u8CC7\u6599\u593E\uFF0C\u600E\u9EBC\u505A\uFF1F</strong></p>
            <p>\u540C\u6A23\u5728 AI \u8A2D\u5B9A\u6A94\u4E2D\u52A0\u5165\u8DEF\u5F91\u898F\u5247\uFF0CAI \u5EFA\u7ACB\u6A94\u6848\u6642\u5C31\u6703\u9075\u5B88\u3002</p>
            <pre>\u5EFA\u7ACB Quest Map \u6642\uFF0C\u8ACB\u5EFA\u7ACB\u5728 engram-quest/quests/{questSlug}/\uFF0C\u4E26\u4F7F\u7528 meta.json + HTML nodes\u3002
\u6240\u6709 Memory Map \u8ACB\u5B58\u5230 Maps/Memory/ \u76EE\u9304\u3002</pre>
            <p><strong>Q15\uFF1A\u9664\u4E86\u5EFA\u7ACB\u5167\u5BB9\uFF0CAI \u9084\u80FD\u63D0\u4F9B\u4EC0\u9EBC\u5E6B\u52A9\uFF1F</strong></p>
            <p>AI \u4E0D\u53EA\u80FD\u5E6B\u4F60\u300C\u7522\u51FA\u300D\u5361\u7247\u3002\u5B83\u9084\u80FD\u8B80\u53D6\u4F60\u7684 Review Deck\uFF0C\u9032\u884C\u4E92\u52D5\u5F0F\u8907\u7FD2\u6216\u91DD\u5C0D\u932F\u8AA4\u9EDE\u9032\u884C\u6559\u5B78\u89E3\u8AAA\u3002\u6B64\u5916\uFF0C\u5B83\u4E5F\u80FD\u5E6B\u4F60\u6574\u7406\u3001\u5206\u985E\u65E2\u6709\u7684\u724C\u7D44\uFF0C\u6216\u6839\u64DA\u73FE\u6709\u5167\u5BB9\u5EF6\u4F38\u51FA\u66F4\u6709\u6311\u6230\u6027\u7684\u65B0\u984C\u76EE\u3002</p>
          `}]),e==="en"&&(w=[{icon:"cards",title:"Create Your First Review Deck",tag:"Manual Start",html:`
            <p><strong>Step 1: Add a tag to your note (Deck name)</strong></p>
            <p>Add <code>#flashcards/topic</code> anywhere in your note. For example, <code>#flashcards/english</code> creates a Deck named "english".</p>
            <p><strong>Step 2: Write your cards</strong></p>
            <p>Three formats are supported. Recommended: <code>Q:/A:</code> (supports multi-line, images, tables):</p>
            <pre>Q: Who proposed the theory of relativity?
A: Albert Einstein

Q: What is spaced repetition?
A: Reviewing just before you forget \u2014 maximizes retention with minimal time.
   Each successful recall automatically extends the next review interval.

{{c1::Spaced repetition}} is the most effective way to learn

Pythagorean theorem :: a\xB2 + b\xB2 = c\xB2</pre>
            <p><strong>Step 3: Start reviewing in Hub</strong></p>
            <p>Click the ribbon icon to open Hub \u2192 <strong>Review Deck</strong> \u2192 Your new deck is ready!</p>
          `},{icon:"rocket",title:"Get Started AI",tag:"AI Automation",html:`
            <p><strong>Step 1: Install AI Skills</strong></p>
            <ol>
              <li>Open Obsidian Settings \u2192 <strong>EngramQuest \u2192 AI Skills</strong></li>
              <li>Choose your tool: Claude Code / Codex / Antigravity / Gemini CLI / Cursor</li>
              <li>After install, AI knows how to build content for all three modules</li>
            </ol>
            <img src="${c}" alt="Install Skills screenshot" style="width:100%;border-radius:8px;margin:10px 0;border:1px solid #e5e7eb;" />
            <p><strong>Step 2: Ask AI</strong></p>
            <div class="lh-help-sub" style="border-color:#2563eb"><div class="lh-help-sub-icon">${j("cards")}</div><div>"Turn notes tagged with math into a Review Deck"<br>"Make a Review Deck from all notes about marketing"</div></div>
            <div class="lh-help-sub" style="border-color:#059669"><div class="lh-help-sub-icon">${j("map")}</div><div>"Turn calculus.md into a quest-map medium"<br>"Combine tag:math notes into a quest-map hard"</div></div>
            <div class="lh-help-sub" style="border-color:#7c3aed"><div class="lh-help-sub-icon">${j("brain")}</div><div>"Create a memory-map for OS-overview.md"<br>"Make a memory-map from notes about network protocols"</div></div>
            <div class="lh-help-sub" style="border-color:#d97706"><div class="lh-help-sub-icon">${j("cap")}</div><div>"Run engram-macro-review tag:azure"<br>"I have a pile of unseen azure cards \u2014 teach me through them"</div></div>
            <p><strong>Step 3: Open Hub and learn</strong></p>
            <p style="margin:4px 0">Click the EngramQuest ribbon icon \u2192 switch tabs \u2192 start learning.</p>
          `},{icon:"cards",title:"Review Deck",tag:we(t,"HELP_REVIEW_TAG"),html:`
            <ol>
              <li>Ask AI: "Turn notes tagged with math into a Review Deck"</li>
              <li>AI reads your notes and creates card files in <code>engram-review/ai-cards/</code> (with <code>#flashcards/math</code> tag and <code>question :: answer</code> format)</li>
              <li>Open Hub \u2192 Review Deck \u2192 start reviewing</li>
            </ol>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">${j("bulb")}</div><div>Your source notes don't need any tag \u2014 AI can read them directly. Card files AI generates are saved to <code>engram-review/ai-cards/</code> and <strong>never touch your source notes</strong>. Cards you write yourself: Edit, Highlight, and Emphasis tools in the review session write changes back to your source note. Card files need a <code>#flashcards/...</code> tag for the plugin to detect them.</div></div>
            <p><strong>Migration mode:</strong> if you have old plain <code>::</code> flashcard notes, enable legacy <code>::</code> scanning in Settings. Optional, off by default.</p>
          `},{icon:"cards",title:"Long AI Answer Cards",tag:"%%card%%",html:`
            <p><strong>Q: How do I turn a long AI answer into a Review Deck card?</strong></p>
            <p><strong>A:</strong> Use normal <span class="lh-help-fmt-chip" style="background:#dcfce7;color:#166534;"><code>Q:/A:</code> everyday format</span>. If you paste a long AI answer that may contain <code>---</code>, tables, code blocks, or many blank lines, wrap one card with <span class="lh-help-fmt-chip" style="background:#fef3c7;color:#92400e;"><code>%%card%%</code> safe mode</span>.</p>
            <pre>#flashcards/ai

%%card%%
Q: How should I explain agentic testing?
A:
Agentic testing checks whether an AI system can complete a task reliably, not just whether one function returns the right value.

---

Example:
- Give the agent a realistic task
- Verify the final artifact
- Check logs, tool calls, and failure recovery

&#96;&#96;&#96;js
expect(result.completed).toBe(true)
&#96;&#96;&#96;
%%card%%</pre>
            <p><strong>Key point:</strong> everything after <code>A:</code> stays in the answer until the closing <span class="lh-help-fmt-chip" style="background:#fef3c7;color:#92400e;"><code>%%card%%</code></span>. The <span class="lh-help-fmt-chip" style="background:#dbeafe;color:#1d4ed8;"><code>---</code></span> line above will not cut the card early. Existing <code>---</code> fenced cards still work and do not need to be rebuilt.</p>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">${j("bolt")}</div><div><strong>Quickest way to type the fence:</strong> press <code>Ctrl+/</code> (or <code>Cmd+/</code>) on an empty line \u2014 Obsidian inserts <code>%% %%</code> with the cursor between the markers. Type <code>card</code> and you have <code>%% card %%</code>, ready to use. Spacing and case are tolerated, so <code>%%card%%</code>, <code>%% card %%</code>, and <code>%%CARD%%</code> all work the same.</div></div>
          `},{icon:"map",title:"Quest Map",tag:we(t,"HELP_QUEST_TAG"),html:`
            <ol>
              <li>Ask AI: "Turn calculus.md into a quest-map medium"</li>
              <li>AI builds a plugin-native quest package under <code>engram-quest/quests/</code> plus local HTML mission files</li>
              <li>Each mission / boss can use self-contained HTML for dashboards, calculations, diagnosis, cascade decisions, or simulations</li>
              <li>Progress is saved separately in <code>engram-quest/state/</code>, so AI can update a quest without clearing completed nodes</li>
              <li>Legacy YAML challenge quests still open normally</li>
              <li>Open Hub \u2192 Quest Map \u2192 click a quest card and complete missions, cases, and Boss Battles inside the plugin viewer</li>
            </ol>
            <table>
              <tr><th>Difficulty</th><th>Best for</th></tr>
              <tr><td><strong>easy</strong></td><td>First exposure</td></tr>
              <tr><td><strong>medium</strong></td><td>Reinforcing knowledge</td></tr>
              <tr><td><strong>hard</strong></td><td>Self-testing mastery</td></tr>
            </table>
          `},{icon:"brain",title:"Memory Map",tag:we(t,"HELP_MEMORY_TAG"),html:`
            <p><strong>How to create (three ways)</strong></p>
            <ol>
              <li><strong>AI-generated:</strong> Ask AI "Create a memory-map for OS-overview.md" \u2014 it produces <code>OS-overview-memory.canvas</code> automatically</li>
              <li><strong>Manual:</strong> Create a Canvas file in Obsidian and name it <code>{note-name}-memory.canvas</code> \u2014 the plugin detects it automatically</li>
              <li><strong>Save location:</strong> Defaults to the same folder as the source note. You can also set a dedicated Memory Map folder in Settings</li>
            </ol>
            <p><strong>Detection rule</strong></p>
            <p>The plugin scans all files ending with <code>-memory.canvas</code> in your vault and lists them in Hub \u2192 Memory Map.</p>
            <p><strong>Link to Review Deck</strong></p>
            <p>During review, the "Memory Map" button on each card automatically finds the matching canvas:</p>
            <ol>
              <li>First, it looks for a <code>-memory.canvas</code> with the same name as the source note</li>
              <li>If not found, it scans all memory canvases \u2014 if a canvas contains a file node pointing to that note, it links automatically</li>
            </ol>
            <div class="lh-help-sub" style="border-color:#7c3aed"><div class="lh-help-sub-icon">${j("bulb")}</div><div>Tip: include a file node in your canvas that points back to the source note (AI does this automatically). This way, even if files are moved or renamed, Obsidian updates the path and the link stays intact.</div></div>
          `},{icon:"cap",title:"Lesson Academy",tag:"AI teaching",html:`
            <p>Tell the AI what you want to learn and it builds you a course: interactive HTML lessons you study right inside the plugin, with progress tracking.</p>
            <p><strong>How it works</strong></p>
            <ol>
              <li>Ask AI: "Teach me SEO" or "I want to learn .NET \u2014 build me a course" (Lesson Academy skill)</li>
              <li>AI asks about your goal and level, designs an outline, then generates HTML lessons into <code>engram-quest/lessons/</code></li>
              <li>Open the plugin \u2192 Learn \u2192 Lessons; click any lesson to read it, with built-in quizzes</li>
              <li>Press "Mark done" when finished \u2014 the course card shows your progress; star key lessons</li>
              <li>You can also "Import HTML" to add lessons generated elsewhere (e.g. by Gemini)</li>
              <li>With many courses, use the tag / progress / sort dropdowns above the cards \u2014 your picks are remembered</li>
            </ol>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">${j("bulb")}</div><div>Any topic works: programming, marketing, medicine, yoga. After finishing a course, ask the AI to turn the lessons into Review Deck cards or a Quest Map to consolidate.</div></div>
          `},{icon:"bolt",title:"Synapse (Pro)",tag:"Pro",html:`
            <p>While reviewing a hard card, automatically surface 1\u20133 cards you have already mastered that link conceptually \u2014 memory anchors that make the new card easier to remember.</p>
            <p><strong>How it works</strong></p>
            <ol>
              <li>Tell your AI assistant: "run engram-synapse"</li>
              <li>The skill scans every SR file and builds a "mastered pool" (FSRS stability \u2265 7)</li>
              <li>For each card in the vault, the AI picks up to 3 strong anchors and writes them to <code>engram-review/synapse/</code></li>
              <li>During review, a ${j("bolt")} Synapse button appears before you reveal the answer</li>
            </ol>
            <p><strong>Three memory mechanisms triggered</strong></p>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">EE</div><div><strong>Elaborative encoding</strong> \u2014 hang new knowledge onto an existing network</div></div>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">AR</div><div><strong>Active recall</strong> \u2014 recalling the anchor doubles the review effect</div></div>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">CA</div><div><strong>Contextual anchoring</strong> \u2014 mastered cards serve as familiar memory hooks</div></div>
            <p><strong>Requires a Pro license.</strong> Needs at least 10 mastered cards before it activates.</p>
          `},{icon:"clock",title:"Time-boxed Review (Pro)",tag:"Pro",html:`
            <p>Short on time? Pick 5 / 10 / 15 minutes \u2014 the plugin selects the N cards you most need to review (~1 card per minute).</p>
            <p><strong>Picking priority</strong></p>
            <ol>
              <li>Unseen cards (new content)</li>
              <li>Most-overdue cards</li>
              <li>Cards closest to their due date</li>
            </ol>
            <p>Reuses the regular review session UI and SR write-back path, so there is no risk to your scheduling data.</p>
            <p><strong>Requires a Pro license.</strong></p>
          `},{icon:"cards",title:"Quadrant Card (Pro)",tag:"Pro",html:`
            <p>Upgrade a single Q/A card into an A4 "super memory" sheet that encodes one concept from four angles \u2014 built for your hardest, most important ideas.</p>
            <p><strong>The four quadrants</strong></p>
            <ol>
              <li><strong>Q1 Question</strong> \u2014 what you need to recall</li>
              <li><strong>Q2 Answer</strong> \u2014 the precise answer</li>
              <li><strong>Q3 Verbal metaphor</strong> \u2014 a mnemonic or analogy that makes the abstract concrete</li>
              <li><strong>Q4 Visual image</strong> \u2014 a scene or emoji that lets your brain remember by picture</li>
            </ol>
            <p><strong>Why it helps</strong></p>
            <div class="lh-help-sub" style="border-color:#0078d4"><div class="lh-help-sub-icon">DC</div><div><strong>Dual coding</strong> \u2014 words + image together stick better than text alone</div></div>
            <div class="lh-help-sub" style="border-color:#0078d4"><div class="lh-help-sub-icon">EE</div><div><strong>Elaborative encoding</strong> \u2014 metaphor + image hook the abstract onto something familiar</div></div>
            <div class="lh-help-sub" style="border-color:#0078d4"><div class="lh-help-sub-icon">SR</div><div><strong>Own schedule</strong> \u2014 each quadrant card carries its own FSRS schedule, separate from your review decks</div></div>
            <p><strong>How to use it</strong></p>
            <ol>
              <li>Tell the AI any alias \u2014 "Quadrant Card", "quadrant", "\u56DB\u8C61\u9650\u5361", "\u56DB\u683C\u5361", "\u8D85\u8A18\u61B6\u5361" \u2014 or mark a card "upgrade" during review</li>
              <li>Review them in Hub \u2192 Quadrant Card tab; your self-assessment updates the schedule</li>
              <li>On a review card: ${j("file")} opens the source note, ${j("clipboard")} copies the Q&amp;A, ${j("edit")} edits it (title applies instantly; Q1\u2013Q4 content edits rebuild the card visuals after you re-run the skill and say "upgrade")</li>
              <li>Delete unwanted cards with the ${j("trash")} button on a deck row or inside the review card</li>
            </ol>
            <p><strong>Requires a Pro license.</strong> Best for your most important, hardest-to-remember concepts \u2014 you don't need to upgrade every card.</p>
          `},{icon:"search",title:"Why It Works",tag:we(t,"HELP_SCIENCE_TAG"),html:`
            <div class="lh-help-sub"><div class="lh-help-sub-icon">SR</div><div><strong>Spaced Repetition</strong><br>Review at the point of near-forgetting. FSRS calculates the next interval automatically.</div></div>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">RP</div><div><strong>Retrieval Practice</strong><br>Active recall is more effective than re-reading. Review Deck trains memory; Quest Map puts that memory into applied scenarios.</div></div>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">EE</div><div><strong>Elaborative Encoding</strong><br>Concrete images and structures are easier to remember than abstract text. Memory Map turns concepts into visual structure.</div></div>
          `},{icon:"tools",title:"Settings",tag:we(t,"HELP_SETTINGS_TAG"),html:`
            <p>Open Obsidian Settings \u2192 <strong>EngramQuest</strong> to configure.</p>
            <p><strong>Flashcard tag prefixes</strong> (default: <code>flashcards</code>)</p>
            <p>Determines which notes are scanned as Review Deck cards.</p>
            <ul>
              <li>Default <code>flashcards</code> \u2192 <code>#flashcards/math</code>, <code>#flashcards/marketing</code> are detected</li>
              <li>Change to <code>cards</code> \u2192 only <code>#cards/math</code> is detected; <code>#flashcards/math</code> is not</li>
              <li>Multiple prefixes: comma-separated, e.g. <code>flashcards, cards, anki</code></li>
            </ul>
            <p><strong>To be detected, a note needs both:</strong></p>
            <ol>
              <li>Cards in any supported format (<code>::</code>, <code>Q:/A:</code>, or Cloze), for example:<pre>#flashcards/math

What is the Pythagorean theorem? :: In a right triangle, a\xB2 + b\xB2 = c\xB2

Q: What is a derivative?
A: The instantaneous rate of change of a function at a point.
   Formally: lim(h\u21920) [f(x+h) \u2212 f(x)] / h

{{c1::Calculus}} is built on limits, derivatives, and integrals</pre></li>
              <li>A matching tag prefix, e.g. <code>#flashcards/math</code> (first line of the example above)</li>
            </ol>
            <p><strong>Include legacy <code>::</code> notes</strong> (default: Off)</p>
            <p>When on, untagged <code>::</code> notes are also scanned. Migration mode, not needed for new users.</p>
            <p><strong>Max review interval</strong> (default: 36525 days)</p>
            <p>Cap on FSRS scheduling. Lower it to keep well-known cards appearing periodically.</p>
            <p><strong>Requested retention</strong> (default: 0.9)</p>
            <p>FSRS target retention rate. Higher = more frequent reviews; lower = longer intervals.</p>
          `},{icon:"target",title:"FAQ",tag:we(t,"HELP_FAQ_TAG"),html:`
            <p><strong>Q1. Can I use this without installing Skills?</strong></p>
            <p>Yes. You can write cards manually using <code>question :: answer</code> and add a matching tag such as <code>#flashcards/math</code>.</p>
            <p><strong>Q2. Can AI read source notes that do not have tags?</strong></p>
            <p>Yes. AI can read ordinary source notes without <code>flashcards</code> tags. The generated cards are saved to <code>engram-review/ai-cards/</code> \u2014 your original notes are never modified.</p>
            <p><strong>Q3. Why can't I see a Review Deck?</strong></p>
            <p>Common reasons: no card note has been created yet, the card note does not match the current tag prefix setting, or you expected old plain <code>::</code> notes to be included without enabling legacy scan.</p>
            <p><strong>Q4. I have general notes with no tags. How do I make a Review Deck?</strong></p>
            <p>Ask AI: "Make a Review Deck from notes about [topic]". AI reads your notes and creates card files in <code>engram-review/ai-cards/</code> with the correct tags \u2014 your original notes are never modified. Open Hub \u2192 Review Deck to see them.</p>
            <p><strong>Q5. I want to make cards manually without AI. How?</strong></p>
            <p><strong>Step 1: Add a tag (sets the Deck name)</strong></p>
            <p>Add <code>#flashcards/topic</code> anywhere in your note. The name after the slash becomes the Deck name.</p>
            <ul>
              <li><code>#flashcards/english</code> \u2192 creates an "english" Deck</li>
              <li><code>#flashcards/math</code> \u2192 creates a "math" Deck</li>
              <li>Default prefix is <code>flashcards</code>; change it in Settings (e.g. <code>cards</code>, <code>anki</code>)</li>
            </ul>
            <p><strong>Step 2: Write cards (3 formats, freely mixable)</strong></p>
            <pre>#flashcards/learning-science

Pythagorean theorem :: a\xB2 + b\xB2 = c\xB2

Q: What is spaced repetition?
A:
1. Review just before you forget \u2014 best timing for retention
2. Each successful recall pushes the next review further out

%%card%%
Q: When should I use %%card%%?
A:
Use it only for long pasted AI answers that may contain their own --- separators.

---

This separator stays inside the answer.
%%card%%

{{c1::Spaced repetition}} is one of the most effective memory techniques
Capitals: France {{c1::Paris}}, Japan {{c2::Tokyo}}</pre>
            <table>
              <tr><th>Format</th><th>Best for</th><th>Syntax</th></tr>
              <tr><td><span class="lh-help-fmt-chip" style="background:#dcfce7;color:#166534;"><code>Q:/A:</code> Q&amp;A</span> ${j("star")}</td><td>Recommended everyday format. Multi-line, images, tables</td><td><code>Q: question</code> \u2192 <code>A: answer</code> (answer can start on the next line; multiple lines ok)</td></tr>
              <tr><td><code>Q:/A:</code> fenced ${j("star")}</td><td>Long answers with many blank lines (e.g. pasted AI output)</td><td>Wrap with <code>---</code> on its own line before and after \u2014 blank lines inside never end the card</td></tr>
              <tr><td><span class="lh-help-fmt-chip" style="background:#fef3c7;color:#92400e;"><code>%%card%%</code> long answer</span></td><td>Safe mode for pasted AI answers that may include <code>---</code></td><td>Wrap one card between two <code>%%card%%</code> lines; everything after <code>A:</code> stays in the answer until the closing marker</td></tr>
              <tr><td>Cloze</td><td>Fill-in-the-blank, same as Anki</td><td><code>{{c1::answer}}</code> or <code>{{c1::answer::hint}}</code></td></tr>
              <tr><td><code>::</code> Q&amp;A</td><td>Short answers, one line only</td><td><code>question :: answer</code></td></tr>
            </table>
            <p><strong>Q:/A: boundary rules:</strong></p>
            <ul>
              <li><code>A:</code> can be empty \u2014 the answer starts on the next line</li>
              <li>A single blank line inside the answer is fine; collection continues</li>
              <li><strong>Two consecutive blank lines</strong> end the card</li>
              <li>The next <code>Q:</code> line or a Cloze line also ends the answer automatically</li>
              <li><strong>Fenced mode:</strong> wrap the card with <code>---</code> on its own line before and after \u2014 any number of blank lines inside are safe. Ideal for pasting ChatGPT / AI answers:<pre>---
Q: What is Stripe's core model?
A: Stripe is essentially a Saga System.

   It handles:
   - payment_intent state machine
   - retry / failure handling

   You only receive the result.
---

---
Q: What is a Saga pattern?
A: A sequence of local transactions.

   Each step publishes an event.
   On failure, compensating transactions roll back.
---</pre>
              <strong>Note:</strong> a plain <code>---</code> horizontal rule in your note (not followed by <code>Q:</code>) is automatically ignored \u2014 no false triggers.</li>
            </ul>
            <p><strong>Cloze notes:</strong></p>
            <ul>
              <li>Each <code>{{cX::answer}}</code> generates one card \u2014 front shows <code>[...]</code>, back reveals the answer</li>
              <li>Multiple clozes on one line (c1, c2\u2026) each produce their own card</li>
              <li>Optional hint: <code>{{c1::Paris::capital}}</code> \u2192 front shows <code>[capital]</code> instead of <code>[...]</code></li>
            </ul>
            <p>Open Hub \u2192 Review Deck to see all cards.</p>
            <p><strong>Q6. I already have many old <code>::</code> cards without tags. How do I migrate?</strong></p>
            <p>Turn on <code>Include legacy :: notes</code> in Settings, keep the existing <code>question :: answer</code> format, and reopen Hub \u2192 Review Deck. For long-term maintenance, gradually add <code>#flashcards/...</code> tags.</p>
            <p><strong>Q7. Where is review progress stored?</strong></p>
            <p>Review scheduling data is stored in <code>engram-review/sr/{note-name}.json</code>. AI-generated cards live in <code>engram-review/ai-cards/</code> \u2014 neither touches your source notes. Cards you write yourself: Edit, Highlight, and Emphasis tools in the review session write changes directly back to your note.</p>
            <p><strong>Q8. Does this work on mobile?</strong></p>
            <p>Review Deck works on mobile. Quest Map and Memory Map are still best on desktop.</p>
            <p><strong>Q9. Why doesn't the plugin update on my phone?</strong></p>
            <p>The plugin's <code>main.js</code> lives inside your vault at <code>.obsidian/plugins/engram-quest/</code>. As long as your vault is synced via git (Obsidian Git plugin), updates pushed from desktop will be pulled to mobile automatically. If your phone isn't getting updates, check:</p>
            <ol>
              <li>Your vault <code>.gitignore</code> is not excluding <code>/.obsidian/plugins/engram-quest/</code> entirely</li>
              <li>The Obsidian Git plugin on mobile has auto-pull enabled</li>
              <li>After pulling, disable and re-enable the plugin in Settings, or restart the app</li>
            </ol>
            <p><strong>Q10. My notes are messy or there's way too much content. Where do I even start?</strong></p>
            <p>No need to tidy up first. Ask AI to scan and list the topics across your notes, then pick what you want to learn:</p>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">${j("chat")}</div><div>"List all the topic categories in my vault"<br>"List all note titles related to [subject]"</div></div>
            <p>Once you have the list, point AI at what you want:</p>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">${j("chat")}</div><div>"Turn item 3 from that list into a Quest Map medium"<br>"Make a Review Deck from the marketing-related notes"</div></div>
            <p>If the notes themselves are fragmented, ask AI to organize them first:</p>
            <div class="lh-help-sub"><div class="lh-help-sub-icon">${j("chat")}</div><div>"Organize these notes into a structured outline, then build a Quest Map"</div></div>
            <p><strong>Q11. I'm not satisfied with the AI output. How do I customize it?</strong></p>
            <p>Add custom instructions to your AI config files (<code>CLAUDE.md</code>, <code>GEMINI.md</code>, <code>AGENTS.md</code>). AI will follow your rules when generating content.</p>
            <pre>When building a Review Deck, every card must include a real-world application example.
Keep card difficulty at an advanced level \u2014 include analysis and application questions.
Write all card questions in formal academic English.</pre>
            <p><strong>Q12. How can I make AI always follow a specific pattern when building a Review Deck?</strong></p>
            <p>Mark key answers in your notes using any syntax you prefer \u2014 for example, Obsidian's highlight <code>==text==</code>, blockquote <code>&gt; text</code>, or any custom marker. Then add a rule to your AI config file (<code>CLAUDE.md</code>, <code>GEMINI.md</code>, or <code>AGENTS.md</code>) telling it to treat that marker as a card answer:</p>
            <pre>IMPORTANT: When building a Review Deck, every highlighted ==text== must be turned into a review card.</pre>
            <p>Use whatever convention fits your note-taking style \u2014 the AI will follow it consistently.</p>
            <p><strong>Q13. Does EngramQuest support Anki?</strong></p>
            <p>Partially. The <code>::</code> and <code>{{c1::}}</code> formats are Anki-compatible. Pair them with the <strong>Obsidian_to_Anki</strong> community plugin \u2014 install Obsidian_to_Anki + AnkiConnect, enable RemNote-style (<code>::</code>) syntax in its settings, and sync. Your cards will appear in Anki automatically.</p>
            <p>The <code>Q:/A:</code> format is EngramQuest-native and does <strong>not</strong> sync to Anki \u2014 it's designed for rich multi-line answers, images, and tables that don't map cleanly to Anki's card model. Use <code>::</code> or <code>{{c1::}}</code> for cards you want in both places.</p>
            <p><strong>Q14. I want AI-generated maps saved to a specific folder. How?</strong></p>
            <p>Add a path rule to your AI config files and AI will respect it when creating files.</p>
            <pre>All Quest Maps must be saved under engram-quest/quests/{questSlug}/ with meta.json + HTML nodes.
Store all Memory Maps in the Maps/Memory/ directory.</pre>
            <p><strong>Q15. What other assistance can AI provide?</strong></p>
            <p>Beyond content creation, AI can interact with your Review Decks to provide personalized tutoring and guided review sessions. It can also help organize and categorize existing decks, or generate more challenging new questions based on your current knowledge.</p>
          `}]),w.splice(Math.max(w.length-1,0),0,{icon:"robot",title:e==="zh-tw"?"AI \u8A2D\u5B9A":"AI Setup",tag:we(t,"HELP_AI_SETUP_TAG"),html:e==="zh-tw"?`
        <p><strong>AI \u5B8C\u5168\u53EF\u9078\u3002</strong>Skills \u662F\u300C\u57F7\u884C\u908F\u8F2F\u300D\uFF0C\u5B89\u88DD\u4E00\u6B21\u5F8C\uFF0C\u4F60\u7528<strong>\u81EA\u5DF1\u5DF2\u5728\u7528\u7684 AI \u5DE5\u5177</strong>\u5C0D\u5B83\u8AAA\u8A71\u5373\u53EF \u2014 \u4E0D\u9700\u8981\u8A02\u95B1\u53E6\u4E00\u500B\u670D\u52D9\u3002</p>
        <ol>
          <li><strong>\u5B89\u88DD Skills</strong> \u2014 \u8A2D\u5B9A \u2192 EngramQuest \u2192 AI Skills\uFF0C\u9078\u4F60\u7684\u5DE5\u5177\u6309\u300C\u5B89\u88DD\u300D\u3002</li>
          <li><strong>\u7528 AI \u5DE5\u5177\u8DD1\u5C0D\u61C9 skill</strong> \u2014 \u5F9E<strong>\u9019\u500B vault \u8CC7\u6599\u593E</strong>\u958B\u555F Claude Code / Codex / Antigravity / Gemini CLI / Cursor\uFF0C\u76F4\u63A5\u7528\u81EA\u7136\u8A9E\u8A00\u8AAA\uFF0C\u4F8B\u5982\u300C\u628A math \u7B46\u8A18\u505A\u6210 review deck\u300D\u300C\u628A \u5FAE\u7A4D\u5206.md \u505A\u6210 quest-map medium\u300D\u3002</li>
          <li><strong>\u56DE Hub \u5B78\u7FD2</strong> \u2014 \u751F\u6210\u7684\u5167\u5BB9\u6703\u51FA\u73FE\u5728\u5C0D\u61C9\u5206\u9801\u3002</li>
        </ol>
        <table>
          <tr><th>\u5DE5\u5177</th><th>Skills \u8DEF\u5F91</th></tr>
          <tr><td>Claude Code</td><td><code>.claude/skills</code></td></tr>
          <tr><td>Codex / Antigravity</td><td><code>.agents/skills</code></td></tr>
          <tr><td>Gemini CLI</td><td><code>.gemini/skills</code></td></tr>
          <tr><td>Cursor</td><td><code>.cursor/skills</code></td></tr>
        </table>
        <div class="lh-help-sub"><div class="lh-help-sub-icon">${j("bulb")}</div><div><strong>Codex \u8207 Antigravity \u5171\u7528 <code>.agents/skills</code></strong>\uFF1A\u5B89\u88DD Codex \u76EE\u6A19\uFF0CAntigravity \u5F9E\u540C\u4E00 vault \u958B\u555F\u6642\u5C31\u6703\u81EA\u52D5\u8B80\u5230\uFF0C\u4E0D\u9700\u53E6\u5916\u5B89\u88DD\u3002</div></div>
      `:`
        <p><strong>AI is fully optional.</strong> Skills are the execution logic \u2014 install once, then talk to them using the <strong>AI tool you already have</strong>. No extra subscription.</p>
        <ol>
          <li><strong>Install Skills</strong> \u2014 Settings \u2192 EngramQuest \u2192 AI Skills, pick your tool and click Install.</li>
          <li><strong>Run the skill from your AI tool</strong> \u2014 open Claude Code / Codex / Antigravity / Gemini CLI / Cursor <strong>from this vault folder</strong> and just say it in plain language, e.g. "Build a review deck from my math notes" or "Turn calculus.md into a quest-map medium".</li>
          <li><strong>Back to the Hub</strong> \u2014 generated content shows up in the matching tab.</li>
        </ol>
        <table>
          <tr><th>Tool</th><th>Skills path</th></tr>
          <tr><td>Claude Code</td><td><code>.claude/skills</code></td></tr>
          <tr><td>Codex / Antigravity</td><td><code>.agents/skills</code></td></tr>
          <tr><td>Gemini CLI</td><td><code>.gemini/skills</code></td></tr>
          <tr><td>Cursor</td><td><code>.cursor/skills</code></td></tr>
        </table>
        <div class="lh-help-sub"><div class="lh-help-sub-icon">${j("bulb")}</div><div><strong>Codex and Antigravity share <code>.agents/skills</code></strong>: install the Codex target and Antigravity picks it up automatically when opened from the same vault \u2014 no separate install.</div></div>
      `}),w=w.filter(S=>!new Set(["\u5EFA\u7ACB\u7B2C\u4E00\u500B Review Deck","Create Your First Review Deck","Review Deck","Quest Map","Memory Map","\u8AB2\u7A0B\u5B78\u9662\uFF08\u6559\u6750\uFF09","Lesson Academy","Synapse (Pro)","Time-boxed Review (Pro)","\u56DB\u8C61\u9650\u5361 (Pro)","Quadrant Card (Pro)"]).has(S.title)),w.forEach((S,_)=>{let C=u.createEl("div",{attr:{class:"lh-help-acc"}}),L=C.createEl("div",{attr:{class:"lh-help-acc-hdr"+(_===y?" open":"")}});L.createEl("span",{attr:{class:"lh-help-acc-icon"}}).innerHTML=j(S.icon),L.createEl("span",{text:S.title,attr:{class:"lh-help-acc-title"}}),L.createEl("span",{text:S.tag,attr:{class:"lh-help-acc-tag"}});let A=L.createEl("span",{attr:{class:"lh-help-acc-arrow"}});A.innerHTML=v;let T=C.createEl("div",{attr:{class:"lh-help-acc-body"+(_===y?" open":"")}});T.innerHTML=S.html,L.addEventListener("click",()=>{let q=T.classList.contains("open");u.querySelectorAll(".lh-help-acc-body").forEach(I=>I.classList.remove("open")),u.querySelectorAll(".lh-help-acc-hdr").forEach(I=>I.classList.remove("open")),q||(T.classList.add("open"),L.classList.add("open"))})});let k=u.createEl("div",{attr:{style:"margin-top:28px;padding:20px 16px 8px;border-top:1px solid var(--background-modifier-border);text-align:center;"}});k.createEl("div",{text:e==="zh-tw"?"\u89BA\u5F97 EngramQuest \u5C0D\u4F60\u6709\u5E6B\u52A9\uFF1F":"Find EngramQuest helpful?",attr:{style:"font-size:13px;color:var(--text-muted);margin-bottom:10px;"}});let E=k.createEl("a",{attr:{href:"https://ko-fi.com/wen_aidev",target:"_blank",rel:"noopener",style:"display:inline-flex;align-items:center;gap:8px;padding:10px 20px;border-radius:8px;background:#fcd34d;color:#1f2937;text-decoration:none;font-size:14px;font-weight:600;"}});E.createEl("span").innerHTML=j("coffee"),E.createEl("span",{text:"Buy me a coffee"})}};Es.exports={HelpModal:zc}});var da=Y((Ku,_s)=>{"use strict";var ca=[{x:.16653696498054474,y:.22596153846153846},{x:.488715953307393,y:.23076923076923078},{x:.8015564202334631,y:.22596153846153846}],Pc=.5,Hc=.2;function ks(t,e,n){let a=Math.max(t/1285,e/832),s=1285*a,i=832*a;return{left:(n.x*s-(s-t)*Pc)/t*100,top:(n.y*i-(i-e)*Hc)/e*100}}function $c(t,e){let n=()=>{let a=t.clientWidth,s=t.clientHeight;return!a||!s?!1:(e.forEach((i,r)=>{if(!ca[r])return;let o=ks(a,s,ca[r]);i.style.left=o.left+"%",i.style.top=o.top+"%"}),!0)};!n()&&typeof requestAnimationFrame=="function"&&requestAnimationFrame(n)}_s.exports={ORBS:ca,orbPercent:ks,layoutStatOrbs:$c}});var pa=Y((Vu,As)=>{"use strict";var{anySrPattern:Fc,parseSrComment:Qc,saveSrData:Bc,mapLimit:Uc}=Se();async function Ss(t){if(!await t.exists("engram-review/hints")&&await t.exists(".review-deck")){if(await t.mkdir("engram-review"),await t.mkdir("engram-review/hints"),await t.exists(".review-deck/hints")){let e=await t.list(".review-deck/hints");for(let n of e.files){let a=await t.read(n);await t.write("engram-review/hints/"+n.split("/").pop(),a)}}for(let e of["config.json","scan-record.json"]){let n=`.review-deck/${e}`;await t.exists(n)&&await t.write(`engram-review/${e}`,await t.read(n))}console.debug("engram-review: migration from .review-deck/ complete; old folder left for manual cleanup")}}async function jc(t){var a;let e="engram-review/config.json";try{if(await t.vault.adapter.exists(e)&&JSON.parse(await t.vault.adapter.read(e)).srMigrated)return}catch(s){}let n=t.vault.getMarkdownFiles();for(let s of n){let r=(await t.vault.read(s)).split(`
`),o={},l=[],c=!1;for(let p=0;p<r.length;p++){let d=r[p],h=d.indexOf("::");if(h>0){let u=d.slice(0,h).trim(),x=d.slice(h+2).trim();if(u&&x&&p+1<r.length&&Fc.test(r[p+1])){let m=Qc(r[p+1].trim());m&&(o[u]={...m,repetitions:(a=m.repetitions)!=null?a:1}),l.push(d),p++,c=!0;continue}}l.push(d)}c&&(await t.vault.modify(s,l.join(`
`)),await Bc(t.vault.adapter,s.path,o))}try{await t.vault.adapter.mkdir("engram-review").catch(()=>{});let s={};await t.vault.adapter.exists(e)&&(s=JSON.parse(await t.vault.adapter.read(e))),s.srMigrated=!0,await t.vault.adapter.write(e,JSON.stringify(s,null,2))}catch(s){console.warn("engram-review: could not write srMigrated flag",s)}console.debug("engram-review: SR comment migration complete")}async function Gc(t,e,n){var c;try{await Ss(t.vault.adapter)}catch(p){console.warn("engram-review: migration failed, continuing without it",p)}let a=t.vault.getMarkdownFiles();a=a.filter(p=>{var h;if(/^_agent\//.test(p.path)||/^\.claude\//.test(p.path))return!1;let d=(h=t.metadataCache.getFileCache(p))==null?void 0:h.frontmatter;return!(d&&d["kanban-plugin"])});let s={},i=(c=e.enableSRScan)!=null?c:!1,r=p=>t.vault.cachedRead?t.vault.cachedRead(p):t.vault.read(p),o=async p=>{var v;let d=t.metadataCache.getFileCache(p),h=[];if(d!=null&&d.tags&&h.push(...d.tags.map(y=>String(y.tag).replace(/^#/,""))),d!=null&&d.frontmatter&&d.frontmatter.tags){let y=d.frontmatter.tags;typeof y=="string"?h.push(...y.split(",").map(w=>w.trim())):Array.isArray(y)?h.push(...y.map(String)):h.push(String(y))}h=[...new Set(h.filter(Boolean))];let u=n.matchFlashcardTagPrefix(h,e.flashcardTags),x=!d||(!d.tags||d.tags.length===0)&&!d.frontmatter&&(!d.sections||d.sections.length===0)&&(!d.headings||d.headings.length===0);if(!i&&!u&&!x)return null;let m=await r(p);if(!u){let y=[...m.matchAll(/(^|\s)#([\w][\w/-]*)/gm)].map(k=>k[2]),w=[...new Set(y.filter(Boolean))];u=n.matchFlashcardTagPrefix(w,e.flashcardTags)}let g=n.parseFlashcards(m);if(g.length===0)return null;let b=u||((v=p.parent)==null?void 0:v.path)||"/";b||(b="/"),g.forEach(y=>{y.notePath=p.path});let f=p.name.replace(/\.md$/i,"");try{let y=await n.loadSrData(t.vault.adapter,p.path);n.mergeSrIntoCards(g,y)}catch(y){}try{let y=`engram-review/hints/${f}.json`,w=JSON.parse(await t.vault.adapter.read(y));if(n.mergeReviewHints(g,w),p.path.startsWith("engram-review/ai-cards/")){let k=w.note?Array.isArray(w.note)?w.note:[w.note]:[],E=Array.isArray(w.cards)?w.cards.reduce((S,_)=>(_&&_.front&&(S[_.front]=_),S),{}):w.cards||{};g.forEach(S=>{let _=E[S.front],C=_==null?void 0:_.source,L;_&&"source"in _?L=C===null?[]:Array.isArray(C)?C:[C]:L=k,L=[...new Set((L||[]).filter(Boolean))],S.sourceNotePaths=L,S.relatedNotePaths=L,S.primarySourceNotePath=L[0]||null})}}catch(y){}return{deckName:b,cards:g}},l=await Uc(a,8,p=>o(p).catch(d=>(console.warn("engram-review: scan failed for",p.path,d),null)));for(let p of l)p&&(s[p.deckName]||(s[p.deckName]={name:p.deckName,cards:[]}),s[p.deckName].cards.push(...p.cards));return Object.values(s).map(p=>{let d=p.cards.filter(x=>n.getReviewStatus(x.srMeta)==="due").length,h=p.cards.filter(x=>n.getReviewStatus(x.srMeta)==="unseen").length,u=p.cards.length;return{...p,due:d,unseen:h,total:u}}).sort((p,d)=>{let h=p.due+p.unseen;return d.due+d.unseen-h||d.total-p.total})}As.exports={migrateReviewDeckFolder:Ss,migrateSrCommentsToJson:jc,scanReviewDecks:Gc}});var qs=Y((Ju,Ms)=>{"use strict";var{getLocalDateStr:Ts}=Se(),Cs=1e3,Ls=500;function Rs(t,e){if(!t)return 0;let n=Date.parse(Ts(e)+"T00:00:00Z"),a=Date.parse(t+"T00:00:00Z");return!Number.isFinite(n)||!Number.isFinite(a)?0:Math.round((n-a)/(1440*60*1e3))}function Is(t,e=new Date){if(!t||!t.srMeta||!t.srMeta.due)return Cs;let n=Rs(t.srMeta.due,e);return n>=0?Ls+n:n}function Wc(t,e,n){let a=n||(i=>Is(i)),s=t.map((i,r)=>({card:i,idx:r,p:a(i)}));return s.sort((i,r)=>r.p-i.p||i.idx-r.idx),s.slice(0,Math.max(0,e)).map(i=>i.card)}function Yc(t){let e=[];return(t||[]).forEach(n=>{n&&Array.isArray(n.cards)&&n.cards.forEach(a=>{a&&e.push(a)})}),e}Ms.exports={UNSEEN_BASE:Cs,OVERDUE_BASE:Ls,computeCardPriority:Is,pickTopN:Wc,collectAllCards:Yc,diffDays:Rs,getLocalDateStr:Ts}});var Ds=Y((Zu,Ns)=>{"use strict";var nn=require("obsidian"),{t:Ze}=ge(),{giHTML:Kc}=be(),{loadSynapseStatus:Vc,countMasteredFromSr:Jc,shouldShowRefreshBanner:Zc,isSynapseEnabled:Xc}=Hn(),{collectAllCards:ed,pickTopN:td,computeCardPriority:nd}=qs(),tn="engram-synapse";async function ad(t,e,n){if(!e.settings.licenseValid)return;let a=e.settings,s=n.vault.adapter,i=a._synapseBannerDismissedAt||0;if(i&&Date.now()-i<7200*60*1e3)return;let r,o;try{[r,o]=await Promise.all([Vc(s),Jc(s)])}catch(m){console.warn("synapse: status load failed",m);return}let l=!r.generatedAt,c=!!r.generatedAt&&r.enabled===!1&&r.reason==="pool-too-small",p=Xc(r)&&Zc({status:r,currentMastered:o,now:Date.now(),dismissedAt:i}).show;if(!l&&!p&&!c)return;let d=t.createEl("div",{attr:{class:"lh-synapse-banner"}}),h=d.createEl("div",{attr:{class:"lh-synapse-banner-text"}}),u;if(c){let m=r.masteredPoolSize!=null?r.masteredPoolSize:o||0,g=r.threshold!=null?r.threshold:10;u=Ze(a,"SYNAPSE_POOL_TOO_SMALL").replace("{have}",String(m)).replace("{need}",String(g))}else l?u=Ze(a,"SYNAPSE_ONBOARDING_TITLE"):u=Ze(a,"SYNAPSE_REFRESH_BANNER");h.createEl("div",{text:u,attr:{style:"font-weight:600;font-size:13px;line-height:1.4;"}}),c||h.createEl("div",{text:tn,attr:{style:"font-family:monospace;font-size:11px;color:var(--text-muted,#6b7280);margin-top:4px;"}});let x=d.createEl("div",{attr:{class:"lh-synapse-banner-actions"}});if(c||x.createEl("button",{text:Ze(a,"SYNAPSE_REFRESH_BUTTON"),attr:{class:"lh-synapse-banner-btn"}}).addEventListener("click",async()=>{try{await navigator.clipboard.writeText(tn),new nn.Notice("Copied: "+tn)}catch(g){new nn.Notice(tn)}}),!l){let m=x.createEl("button",{attr:{class:"lh-synapse-banner-btn",title:"Dismiss for 24h",style:"padding:6px 10px;"}});m.innerHTML=Kc("x"),m.addEventListener("click",async()=>{e.settings._synapseBannerDismissedAt=Date.now(),await e.saveData(e.settings),d.remove()})}}function rd(t,e,n,a,s,i){if(!e.settings.licenseValid)return;let r=e.settings,o=Ze(r,"TIMEBOX_MIN_SUFFIX"),l=t.createEl("div",{attr:{class:"lh-timebox-row"}});[5,10,15].forEach(c=>{let p=l.createEl("button",{attr:{class:"lh-timebox-btn"}});p.textContent=`\u23F1 ${c} ${o}`,p.addEventListener("click",()=>{let d=ed(a);if(d.length===0){new nn.Notice(Ze(r,"TIMEBOX_NO_CARDS"));return}let h=new Date,u=td(d,c,x=>nd(x,h));if(u.length===0){new nn.Notice(Ze(r,"TIMEBOX_NO_CARDS"));return}s(u,`\u23F1 ${c} ${o}`,i,c)})})}Ns.exports={renderSynapseProBanner:ad,renderTimeboxRow:rd}});var Fs=Y((Xu,$s)=>{"use strict";var an=require("obsidian"),{t:Me,interpolate:Os}=ge(),{CARD_FENCE:zs}=Se();async function Ps(t){await t.exists("engram-review")||await t.mkdir("engram-review"),await t.exists("engram-review/ai-cards")||await t.mkdir("engram-review/ai-cards")}async function Hs(t,e,n,a){let s="engram-review/ai-cards",i=e.replace(/\//g,"__"),r=`${s}/${i}-manual.md`;await Ps(t);let l=a.includes(`
`)?`${zs}
Q: ${n}
A:
${a}
${zs}

`:`${n} :: ${a}
`;if(await t.exists(r)){let c=await t.read(r),p=c.endsWith(`
`)?"":`
`;await t.write(r,c+p+l)}else{let c=`#${e}

`;await t.write(r,c+l)}}function sd(t,e,n,a){let s=new an.Modal(t);s.modalEl.style.cssText="width:min(92vw,480px);max-width:none;padding:0;border-radius:16px;overflow:hidden;";let i=s.contentEl;i.style.cssText="padding:24px;display:flex;flex-direction:column;gap:16px;",i.createEl("div",{text:Os(Me(n,"CREATE_CARD_TITLE"),{deck:e.name}),attr:{style:"font-size:16px;font-weight:700;color:var(--text-normal,#111);"}});let r=i.createEl("div",{attr:{style:"display:flex;flex-direction:column;gap:6px;"}});r.createEl("label",{text:Me(n,"CREATE_CARD_FRONT"),attr:{style:"font-size:12px;font-weight:600;color:var(--text-muted,#6b7280);text-transform:uppercase;letter-spacing:.06em;"}});let o=r.createEl("input",{attr:{type:"text",placeholder:Me(n,"CREATE_CARD_FRONT_PLACEHOLDER"),style:"width:100%;padding:9px 12px;border:1.5px solid var(--background-modifier-border, #e5e7eb);border-radius:8px;font-size:14px;outline:none;background:var(--background-secondary, #f9fafb);color:var(--text-normal, #374151);box-sizing:border-box;"}}),l=i.createEl("div",{attr:{style:"display:flex;flex-direction:column;gap:6px;"}});l.createEl("label",{text:Me(n,"CREATE_CARD_BACK"),attr:{style:"font-size:12px;font-weight:600;color:var(--text-muted,#6b7280);text-transform:uppercase;letter-spacing:.06em;"}});let c=l.createEl("textarea",{attr:{placeholder:Me(n,"CREATE_CARD_BACK_PLACEHOLDER"),rows:"4",style:"width:100%;padding:9px 12px;border:1.5px solid var(--background-modifier-border, #e5e7eb);border-radius:8px;font-size:14px;outline:none;background:var(--background-secondary, #f9fafb);color:var(--text-normal, #374151);resize:vertical;box-sizing:border-box;font-family:inherit;"}}),p=i.createEl("div",{attr:{style:"display:flex;gap:8px;justify-content:flex-end;padding-top:4px;"}});p.createEl("button",{text:Me(n,"CREATE_CARD_CANCEL"),attr:{style:"padding:7px 16px;border-radius:8px;border:1px solid var(--background-modifier-border, #d1d5db);background:var(--background-secondary, #fff);color:var(--text-muted, inherit);cursor:pointer;font-size:13px;"}}).addEventListener("click",()=>s.close());let d=p.createEl("button",{text:Me(n,"CREATE_CARD_SAVE"),attr:{style:"padding:7px 16px;border-radius:8px;border:none;background:#6366f1;color:#fff;cursor:pointer;font-size:13px;font-weight:600;"}});d.addEventListener("click",async()=>{let h=o.value.trim(),u=c.value.trim();if(!h||!u){new an.Notice(Me(n,"CREATE_CARD_REQUIRED"));return}d.disabled=!0;try{await Hs(t.vault.adapter,e.name,h,u),s.close(),new an.Notice(Os(Me(n,"CREATE_CARD_SUCCESS"),{deck:e.name})),a&&a()}catch(x){console.error("create-card: failed to save",x),new an.Notice(Me(n,"CREATE_CARD_SAVE_FAILED")),d.disabled=!1}}),[o,c].forEach(h=>{h.addEventListener("keydown",u=>{(u.ctrlKey||u.metaKey)&&u.key==="Enter"&&d.click()})}),s.open(),window.setTimeout(()=>o.focus(),50)}$s.exports={openCreateCardModal:sd,appendManualCard:Hs,ensureManualCardDirs:Ps}});var Lt=Y((eg,js)=>{"use strict";var id={easy:{showHint:!0,maxRetries:99,labelKey:"DIFF_EASY",color:"#22c55e"},medium:{showHint:!1,maxRetries:99,labelKey:"DIFF_MEDIUM",color:"#3b82f6"},hard:{showHint:!1,maxRetries:3,labelKey:"DIFF_HARD",color:"#ef4444"}};function Te(t){let e=t.match(/\[([^\]]*)\]/);return e?e[1].split(",").map(n=>n.trim()).filter(Boolean):[]}function Ee(t,e){let n=parseFloat(t.slice(e.length).trim());return Number.isFinite(n)?n:null}function Ct(t){let e=String(t!=null?t:"").trim();return e.startsWith('"')&&e.endsWith('"')||e.startsWith("'")&&e.endsWith("'")?e.slice(1,-1):e}function Ue(t){return typeof t=="number"&&Number.isFinite(t)}function rn(t,e,n){return Math.min(Math.max(t,e),n)}function Qs(t){return t&&typeof t=="object"&&t._questMeta||null}function od(t){var e;return Number((e=Qs(t))==null?void 0:e.version)===2}function Bs(t){return(String(t||"inline-quest.md").replace(/\\/g,"/").split("/").pop()||"inline-quest.md").replace(/\.md$/i,"").replace(/-quest$/i,"")||"quest"}function ld(t,e){let n=Ct(t).replace(/\\/g,"/").replace(/^\/+/,"");return n?n.startsWith("engram-quest/html/")||n.includes("/")?n:`engram-quest/html/${Bs(e)}/${n}`:""}function Us(t){return!!(t&&typeof t.html=="string"&&t.html.trim())}function cd(t){let e={version:t.version||1,style:t.style||"ocean",difficulty:t.difficulty||"medium",title:t.title||"",description:t.description||""};return Object.defineProperty(t.nodes,"_questMeta",{value:e,enumerable:!1,configurable:!0}),t}function dd(t){let e=String(t!=null?t:"").match(/```quest-map\s*([\s\S]*?)```/i);e!=null&&e[1]&&(t=e[1]);let n={version:1,style:"ocean",difficulty:"medium",nodes:[]},a=null,s=null,i=!1,r=!1,o=!1;for(let l of t.split(`
`)){let c=l.trim();if(!c)continue;let p=l.length-l.trimStart().length;if(!i){c.startsWith("version:")?n.version=parseInt(c.split(":")[1])||1:c.startsWith("title:")?n.title=Ct(c.slice(6)):c.startsWith("description:")?n.description=Ct(c.slice(12)):c.startsWith("style:")?n.style=c.slice(6).trim():c.startsWith("difficulty:")?n.difficulty=c.slice(11).trim():c==="nodes:"&&(i=!0);continue}if(c.startsWith("- id:")&&p<=2){s&&(a.points.push(s),s=null),a&&n.nodes.push(a),r=!1,o=!1,a={id:c.slice(5).trim(),emoji:"\u{1F4DD}",points:[],boss:!1};continue}if(a){if(c==="points:"){r=!0,o=!1;continue}if(c==="challenge:"){o=!0,r=!1,a.challenge={};continue}if(r){if(c.startsWith("- title:")&&p>=4){s&&a.points.push(s),s={title:c.slice(8).trim(),body:""};continue}if(s&&c.startsWith("body:")){s.body=c.slice(5).trim();continue}p<4&&!c.startsWith("-")&&!c.startsWith("body:")&&(r=!1)}if(o&&a.challenge){let d=a.challenge;if(c.startsWith("type:"))d.type=c.slice(5).trim();else if(c.startsWith("token:"))d.token=c.slice(6).trim();else if(c.startsWith("question:"))d.question=c.slice(9).trim();else if(c.startsWith("statement:"))d.statement=c.slice(10).trim();else if(c.startsWith("sentence:"))d.sentence=c.slice(9).trim();else if(c.startsWith("prompt:"))d.prompt=c.slice(7).trim();else if(c.startsWith("hint:"))d.hint=c.slice(5).trim();else if(c.startsWith("explanation:"))d.explanation=c.slice(12).trim();else if(c.startsWith("explain:"))d.explain=c.slice(8).trim();else if(c.startsWith("link:"))d.link=c.slice(5).trim();else if(c.startsWith("image:"))d.image=c.slice(6).trim();else if(c.startsWith("html:"))d.html=c.slice(5).trim();else if(c.startsWith("mode:"))d.mode=c.slice(5).trim();else if(c.startsWith("options:"))d.options=Te(c);else if(c.startsWith("items:"))d.items=Te(c);else if(c.startsWith("keywords:"))d.keywords=Te(c);else if(c.startsWith("answers:"))d.answers=Te(c);else if(c.startsWith("reveal_answer:"))d.reveal_answer=c.slice(14).trim()==="true";else if(c.startsWith("region_x:"))d.region_x=Ee(c,"region_x:");else if(c.startsWith("region_y:"))d.region_y=Ee(c,"region_y:");else if(c.startsWith("region_width:"))d.region_width=Ee(c,"region_width:");else if(c.startsWith("region_height:"))d.region_height=Ee(c,"region_height:");else if(c.startsWith("region_left_pct:"))d.region_left_pct=Ee(c,"region_left_pct:");else if(c.startsWith("region_top_pct:"))d.region_top_pct=Ee(c,"region_top_pct:");else if(c.startsWith("region_width_pct:"))d.region_width_pct=Ee(c,"region_width_pct:");else if(c.startsWith("region_height_pct:"))d.region_height_pct=Ee(c,"region_height_pct:");else if(c.startsWith("timer:"))d.timer=Ee(c,"timer:");else if(c.startsWith("height:"))d.height=Ee(c,"height:");else if(c.startsWith("coins:"))d.coins=Ee(c,"coins:");else if(c.startsWith("snapshot_time:"))d.snapshot_time=Ee(c,"snapshot_time:");else if(c.startsWith("snapshot_items:"))d.snapshot_items=Te(c);else if(c.startsWith("snapshot_labels:"))d.snapshot_labels=Te(c);else if(c.startsWith("slots:"))d.slots=Te(c);else if(c.startsWith("events:"))d.events=Te(c);else if(c.startsWith("chain_items:"))d.chain_items=Te(c);else if(c.startsWith("palace_items:"))d.palace_items=Te(c);else if(c.startsWith("palace_descs:"))d.palace_descs=Te(c);else if(c.startsWith("palace_time:"))d.palace_time=Ee(c,"palace_time:");else if(c.startsWith("questions_json:")){let u=c.slice(15).trim();try{d.questions_json=JSON.parse(u)}catch(x){d.questions_json=[]}}else if(c.startsWith("answer:")){let u=c.slice(7).trim();u==="true"?d.answer=!0:u==="false"?d.answer=!1:u.startsWith("[")?d.answer=Te(u).map(Number):/^-?\d+$/.test(u)?d.answer=parseInt(u,10):d.answer=u}else if(c==="pairs:")d.pairs=[];else if(c.startsWith("- [")&&Array.isArray(d.pairs)){let u=c.match(/\[([^\]]+)\]/);u&&d.pairs.push(u[1].split(",").map(x=>x.trim()))}let h=["type:","token:","question:","statement:","sentence:","prompt:","hint:","explanation:","explain:","link:","image:","html:","mode:","options:","items:","keywords:","answers:","answer:","reveal_answer:","region_x:","region_y:","region_width:","region_height:","region_left_pct:","region_top_pct:","region_width_pct:","region_height_pct:","pairs:","- [","timer:","height:","coins:","snapshot_time:","snapshot_items:","snapshot_labels:","slots:","events:","chain_items:","palace_items:","palace_descs:","palace_time:","questions_json:"];p<=4&&!h.some(u=>c.startsWith(u))&&(o=!1)}!r&&!o&&(c.startsWith("title:")?a.title=c.slice(6).trim():c.startsWith("emoji:")?a.emoji=c.slice(6).trim():c.startsWith("icon:")?a.icon=c.slice(5).trim():c.startsWith("summary:")?a.summary=c.slice(8).trim():c.startsWith("scenario:")?a.scenario=c.slice(9).trim():c.startsWith("mission_goal:")?a.mission_goal=c.slice(13).trim():c.startsWith("stakes:")?a.stakes=c.slice(7).trim():c.startsWith("insight:")?a.insight=c.slice(8).trim():c.startsWith("type:")?(a.type=Ct(c.slice(5)),a.type==="boss"&&(a.boss=!0)):c.startsWith("html:")?a.html=Ct(c.slice(5)):c.startsWith("height:")?a.height=Ee(c,"height:"):c.startsWith("boss:")?a.boss=c.includes("true"):c.startsWith("completed:")&&(a.completed=c.includes("true")))}}return s&&a&&a.points.push(s),a&&n.nodes.push(a),n.nodes.forEach(l=>{!Us(l)||l.challenge||(l.challenge={type:"iframe",html:l.html,height:l.height,__htmlFirst:!0})}),cd(n)}function pd(t,e,n){if(!t||!Ue(e)||!Ue(n)||e<=0||n<=0)return null;let a=["region_x","region_y","region_width","region_height"].every(f=>Ue(t[f])),s=["region_left_pct","region_top_pct","region_width_pct","region_height_pct"].every(f=>Ue(t[f])),i,r,o,l,c;if(a)i=t.region_x,r=t.region_y,o=t.region_width,l=t.region_height,c="pixel";else if(s)i=t.region_left_pct/100*e,r=t.region_top_pct/100*n,o=t.region_width_pct/100*e,l=t.region_height_pct/100*n,c="percent";else return null;if(!Ue(i)||!Ue(r)||!Ue(o)||!Ue(l))return null;let p=i+o,d=r+l,h=rn(i,0,e),u=rn(r,0,n),x=rn(p,0,e),m=rn(d,0,n),g=x-h,b=m-u;return!(g>0)||!(b>0)?null:{source:c,wasClamped:h!==i||u!==r||x!==p||m!==d,leftPx:h,topPx:u,widthPx:g,heightPx:b,leftPct:h/e*100,topPct:u/n*100,widthPct:g/e*100,heightPct:b/n*100}}function hd(t){t.classList.remove("qm-shake"),t.offsetWidth,t.classList.add("qm-shake"),t.addEventListener("animationend",()=>t.classList.remove("qm-shake"),{once:!0})}function ha(t){return String(t!=null?t:"").trim().toLowerCase().replace(/\s+/g," ")}function ud(t){let e=[];return Array.isArray(t.answers)&&e.push(...t.answers),Array.isArray(t.keywords)&&e.push(...t.keywords),typeof t.answer=="string"&&t.answer.trim()&&e.push(t.answer.trim()),typeof t.sentence=="string"&&(t.sentence.match(/\{\{c\d+::(.*?)\}\}/g)||[]).forEach(n=>{let a=n.replace(/^\{\{c\d+::/,"").replace(/\}\}$/,"").trim();a&&e.push(a)}),Array.from(new Set(e.map(n=>String(n).trim()).filter(Boolean)))}function gd(t,e){let n=ha(t);return n?e.some(a=>{let s=ha(a);return n===s||n.includes(s)}):!1}function ua(t,e,n){let a=String(e!=null?e:"").trim();if(!a)return null;let s=t.vault.getAbstractFileByPath(a);if(s)return s;if(n&&t.metadataCache&&typeof t.metadataCache.getFirstLinkpathDest=="function"){let r=t.metadataCache.getFirstLinkpathDest(a,n)||null;if(r)return r}let i=a.split("/").pop();if(i&&typeof t.vault.getFiles=="function"){let r=t.vault.getFiles().find(o=>o.name===i||o.path.endsWith("/"+i));if(r)return r}return null}function md(t,e,n){let a=ua(t,e,n);if(a!=null&&a.path){t.workspace.openLinkText(a.path,n||"",!1);return}e&&window.open("obsidian://open?file="+encodeURIComponent(e))}function fd(t,e){return t?t.replace(/\{\{c\d+::(.*?)\}\}/g,(n,a)=>e?`<span style="display:inline-block;padding:0 8px;border-radius:8px;background:#dcfce7;color:#166534;font-weight:700">${a}</span>`:'<span style="display:inline-block;min-width:72px;padding:0 8px;border-bottom:2px solid var(--interactive-accent);color:var(--text-faint);font-weight:700;text-align:center">____</span>'):""}function bd(t,e,n){let a=ua(t,e,n);return a!=null&&a.path?t.vault.adapter.getResourcePath(a.path):null}function xd(t,e,n){let a=n[t]||n.ocean;return a.colors[e%a.colors.length]}function yd(t){if(t===0)return[];if(t===1)return[{cx:420,cy:280}];let e=[230,340,215,330,240,345,210,335,225],n=225;return Array.from({length:t},(a,s)=>({cx:160+s*n,cy:e[s%e.length]}))}js.exports={questDifficultyPresets:id,splitInlineList:Te,parseQuestMap:dd,resolveImageOcclusionRect:pd,retriggerShake:hd,normalizeAnswer:ha,collectExpectedAnswers:ud,getQuestHtmlBase:Bs,getQuestMeta:Qs,matchesExpectedAnswer:gd,resolveQuestPath:ua,resolveQuestHtmlPath:ld,openQuestLink:md,renderClozeSentence:fd,getQuestImageResource:bd,getQuestTheme:xd,getQuestNodePositions:yd,isHtmlQuestNode:Us,isQuestVersion2:od}});var Rt=Y((tg,Ks)=>{"use strict";function Ws(t){return String(t||"quest").replace(/\//g,"__").replace(/\.md$/i,"")+".json"}function ga(t){return`engram-quest/state/${Ws(t)}`}function Gs(t){return{questPath:t||"",nodes:{},bestRun:null,lastPlayed:null}}function it(t,e){let n=e&&typeof e=="object"?e:{};return{questPath:n.questPath||t||"",nodes:n.nodes&&typeof n.nodes=="object"?n.nodes:{},bestRun:n.bestRun||null,lastPlayed:n.lastPlayed||null}}async function vd(t,e){let n=ga(e);if(!t||!await t.exists(n))return Gs(e);try{return it(e,JSON.parse(await t.read(n)))}catch(a){return Gs(e)}}async function wd(t,e,n){let a=ga(e);await t.mkdir("engram-quest/state").catch(()=>{}),await t.write(a,JSON.stringify(it(e,n),null,2))}function Ed(t,e){let n=it(t==null?void 0:t.questPath,t);for(let a of e||[])!(a!=null&&a.id)||!a.completed||n.nodes[a.id]||(n.nodes[a.id]={completed:!0,scorePct:100,attempts:0,lastPlayed:null,migratedFromYaml:!0});return n}function Ys(t){let e=Number(t);return Number.isFinite(e)?Math.min(100,Math.max(0,Math.round(e))):100}function kd(t){if(!Array.isArray(t)||t.length===0)return null;let e={};for(let n of t){let a=n&&typeof n.token=="string"?n.token.trim():"";a&&(e[a]||(e[a]={ok:0,miss:0}),n.firstTryCorrect&&!n.revealed?e[a].ok+=1:e[a].miss+=1)}return e}function _d(t,e,n={}){let a=it(t==null?void 0:t.questPath,t);if(!e)return a;let s=n.now||new Date().toISOString(),i=a.nodes[e]||{},r=Ys(n.scorePct),o=kd(n.questionResults);return a.nodes[e]={...i,completed:!0,scorePct:r,attempts:(i.attempts||0)+1,lastPlayed:s,...o?{tokens:o}:{}},a.lastPlayed=s,a}function Sd(t,e,n={}){let a=it(t==null?void 0:t.questPath,t);if(!(e!=null&&e.length))return a;let s=[];for(let o of e){let l=o!=null&&o.id?a.nodes[o.id]:null;if(!(l!=null&&l.completed))return a;s.push(Ys(l.scorePct))}let i=Math.round(s.reduce((o,l)=>o+l,0)/s.length),r=n.now||a.lastPlayed||new Date().toISOString();return(!a.bestRun||i>(a.bestRun.scorePct||0))&&(a.bestRun={scorePct:i,completedAt:r}),a}function Ad(t,e){var l,c;let n=it(e==null?void 0:e.questPath,e),a=new Set,s=0;(t||[]).forEach((p,d)=>{var h;p!=null&&p.id&&((h=n.nodes[p.id])!=null&&h.completed)&&(a.add(d),s++)});let i=(t||[]).findIndex(p=>{var d;return!(p!=null&&p.id)||!((d=n.nodes[p.id])!=null&&d.completed)}),r=i===-1?Math.max(0,(t||[]).length-1):i,o=t!=null&&t.length?Math.round(s/t.length*100):0;return{visitedSet:a,activeIndex:r,completedCount:s,progressPct:o,bestScorePct:(c=(l=n.bestRun)==null?void 0:l.scorePct)!=null?c:null,state:n}}Ks.exports={buildQuestRenderState:Ad,loadQuestState:vd,markQuestNodeComplete:_d,migrateQuestCompletion:Ed,questStateFileName:Ws,questStatePath:ga,saveQuestState:wd,updateQuestBestRun:Sd}});var ei=Y((ng,Xs)=>{"use strict";var{buildQuestRenderState:Td}=Rt(),{expandRoots:Cd}=$n(),je="engram-quest/quests";async function Vs(t,e){try{return JSON.parse(await t.read(e))}catch(n){return null}}function ba(t){return Array.isArray(t)?t:[]}function ma(t){return Number.isFinite(Number(t))}function Ld(t){return["easy","medium","hard"].includes(t)?t:"medium"}function Js(t,e,n,a=je){if(!e||typeof e!="object")return null;let s=String(e.id||`node-${n+1}`).trim();if(!s)return null;let i=e.file?String(e.file):null,r=e.html?Zs(t,e.html,a):i?`${a}/${t}/${i}`:"",o=String(e.type||(e.boss?"boss":r?"mission":"briefing")).trim(),l={id:s,title:String(e.title||s),type:o,emoji:String(e.emoji||e.icon||(o==="boss"?"B":String(n+1))),icon:e.icon?String(e.icon):"",summary:String(e.summary||""),scenario:String(e.scenario||""),mission_goal:String(e.mission_goal||e.missionGoal||""),stakes:String(e.stakes||""),insight:String(e.insight||""),boss:o==="boss"||e.boss===!0,html:r,file:i,height:ma(e.height)?Math.max(220,Math.min(1200,Math.round(Number(e.height)))):null,points:ba(e.points).filter(Boolean),challenge:e.challenge&&typeof e.challenge=="object"?e.challenge:null};return ma(e.x)&&(l.x=Math.max(0,Math.min(100,Number(e.x)))),ma(e.y)&&(l.y=Math.max(0,Math.min(100,Number(e.y)))),l}function Zs(t,e,n=je){let a=String(e||"").replace(/\\/g,"/").replace(/^\/+/,"");return a?a.startsWith(`${n}/`)||a.startsWith("engram-quest/")?a:`${n}/${t}/${a}`:""}function xa(t,e,n=je){if(!e||typeof e!="object")return null;let a=ba(e.nodes).map((s,i)=>Js(t,s,i,n)).filter(Boolean);return{version:Number(e.version)||2,title:String(e.title||t),description:String(e.description||""),topic:String(e.topic||e.title||t),icon:String(e.icon||"Q"),difficulty:Ld(String(e.difficulty||"medium")),style:String(e.style||"mission"),tags:ba(e.tags).map(String),createdAt:e.createdAt||null,updatedAt:e.updatedAt||null,layout:e.layout&&typeof e.layout=="object"?e.layout:{},nodes:a}}async function Rd(t,e){let n=await Cd(t,e&&e.length?e:[je]),s=(await Promise.all(n.map(async i=>{let r=null;try{r=await t.list(i)}catch(o){r=null}return!r||!Array.isArray(r.folders)?[]:Promise.all(r.folders.map(async o=>{let l=String(o).split("/").pop();if(!l)return null;let c=ya(l,i),p=xa(l,await Vs(t,c),i);return p?{slug:l,path:`${i}/${l}`,stateKey:c,root:i,meta:p}:null}))}))).flat().filter(Boolean);return s.sort((i,r)=>String(r.meta.createdAt||"").localeCompare(String(i.meta.createdAt||""))),s}async function Id(t,e,n=je){return xa(e,await Vs(t,ya(e,n)),n)}function ya(t,e=je){return`${e}/${t}/meta.json`}function Md(t,e){let n=t.meta,a=n.nodes,s=Td(a,e),i=!!(a.length&&s.completedCount>=a.length),r=a.findIndex(d=>d&&d.boss),o=r>=0?a[r]:a[a.length-1]||null,l=a.length&&a[s.activeIndex]||null,c=a.filter(d=>d&&(d.html||d.challenge)),p=[...new Set(c.map(d=>{var h;return d.type||((h=d.challenge)==null?void 0:h.type)||"mission"}).filter(Boolean))];return{sourceType:"package",slug:t.slug,root:t.root||je,stateKey:t.stateKey,sourcePath:t.stateKey,title:n.title,description:n.description,difficulty:n.difficulty,tags:n.tags,style:n.style,icon:n.icon,layout:n.layout,ctime:n.createdAt&&Date.parse(n.createdAt)||0,completed:i,nodes:a,nodeCount:a.length,missionCount:c.length,completedCount:s.completedCount,progressPct:s.progressPct,bestScorePct:s.bestScorePct,bossReady:r>=0&&s.activeIndex>=r&&!i,bossTitle:o&&o.title?o.title:"Boss Battle",activeTitle:l&&l.title?l.title:"",scenario:fa(a,["scenario","mission_goal","stakes","summary"])||n.description,missionGoal:fa(a,["mission_goal","scenario","summary"]),stakes:fa(a,["stakes"]),missionTypes:p}}function fa(t,e){for(let n of t||[])for(let a of e){let s=n&&n[a];if(s&&String(s).trim())return String(s).trim()}return""}Xs.exports={QUESTS_DIR:je,listQuestPackages:Rd,loadQuestPackage:Id,normalizeQuestMeta:xa,normalizeQuestNode:Js,questPackageStateKey:ya,questPackageToHubItem:Md,resolveQuestPackageHtmlPath:Zs}});var va=Y((ag,ri)=>{"use strict";var on=require("obsidian"),{isHtmlQuestNode:ti,resolveImageOcclusionRect:qd,resolveQuestHtmlPath:Nd}=Lt(),{giHTML:me}=be();function Dd(t){t.querySelectorAll("img").forEach(e=>{e.dataset.eqZoom||(e.dataset.eqZoom="1",e.classList.add("eq-zoomable"),e.addEventListener("click",n=>{n.stopPropagation();let a=on.activeDocument.body.createEl("div",{attr:{class:"eq-lightbox"}}),s=a.createEl("button",{attr:{class:"eq-lightbox-close"}});s.innerHTML=me("x");let i=a.createEl("img",{attr:{src:e.src,alt:e.alt||""}}),r=()=>a.remove();a.addEventListener("click",r),i.addEventListener("click",o=>o.stopPropagation()),s.addEventListener("click",r)}))})}function ln(t,e){return t.getLanguage(e)==="zh-tw"}function sn(t,e={}){return{nodeId:t,completed:!0,scorePct:Number.isFinite(Number(e.scorePct))?Math.round(Number(e.scorePct)):100,...Array.isArray(e.questionResults)?{questionResults:e.questionResults}:{}}}function Ge(t,e,n){t.forEach(a=>{a.disabled=!0,a.style.background="#22c55e",a.style.color="white",a.style.borderColor="#22c55e",a.style.boxShadow="0 2px 0 #16a34a"}),n&&(n.style.display="flex",n.style.background="rgba(34,197,94,0.12)",n.style.borderColor="#22c55e",n.style.color="#15803d"),window.setTimeout(()=>e(!0,{scorePct:100}),600)}function Od(t){return t.q||t.question||t.prompt||t.statement||""}function zd(t,e){let n=t&&t.token||e&&e.token||"";return typeof n=="string"?n.trim().toLowerCase():""}function Pd(t,e){return t.explanation||t.explain||t.hint||e.explanation||e.explain||e.hint||""}function Hd(t,e,n){let a=t.opts||t.options||e.options||[],s=t.ans!=null?t.ans:t.answer!=null?t.answer:e.answer;return typeof s=="number"&&a[s]?a[s]:typeof s=="boolean"?s?"True":"False":typeof s=="string"&&s.trim()?s.trim():n.collectExpectedAnswers(Object.assign({},e,{sentence:t.sentence||e.sentence,answers:t.answers||e.answers,keywords:t.keywords||e.keywords,answer:s})).join(" / ")}function ni(t,e,n,a,s,i,r,o,l){l||(l={score:0,lives:3,coins:100,streak:0});let c=o.questDifficultyPresets[n]||o.questDifficultyPresets.medium,p=0,d=ln(o,s),h=t.createEl("div",{attr:{style:"margin-top:20px;border-top:1px solid var(--background-modifier-border);padding-top:18px"}}),u=Array.isArray(e.questions_json)?e.questions_json:null;if(u&&u.length>0){let q=function(){L.empty(),u.forEach((B,P)=>{L.createEl("div",{attr:{style:`width:18px;height:3px;border-radius:1px;transition:all .2s;background:${P<f?"var(--interactive-accent)":P===f?"#f59e0b":"var(--background-modifier-border)"}`}})});let z=`${Math.min(f+1,u.length)} / ${u.length}`;e.type==="auction"&&(z+=`  \u25C8 ${v}`),e.type==="countdown"&&(z+=`  \u2665 ${l.lives}`),A.textContent=z},R=function(z){T.empty();let B=e.type==="memory-palace",P=B?e.palace_items||[]:e.snapshot_items||[],K=B?P.map((U,le)=>String.fromCharCode(65+le)):e.snapshot_labels||P.map((U,le)=>String(le+1)),Z=B?e.palace_descs||[]:[],W=B?e.palace_time||15:e.snapshot_time||4,V=T.createEl("div",{attr:{style:`display:inline-block;padding:4px 12px;border-radius:99px;font-size:11px;font-weight:700;margin-bottom:12px;background:${E.bg};color:${E.border};border:1px solid ${E.border}`}});V.innerHTML=me("book")+" "+(d?"\u8A18\u61B6\u968E\u6BB5":"Study Phase");let X=T.createEl("div",{attr:{style:"border-radius:10px;border:1px solid var(--background-modifier-border);background:var(--background-secondary);padding:20px;margin-bottom:10px;min-height:80px"}});if(B){let U=X.createEl("div",{attr:{style:"display:flex;flex-direction:column;gap:8px"}});P.forEach((le,J)=>{let se=U.createEl("div",{attr:{style:"display:flex;align-items:flex-start;gap:10px;background:var(--background-primary);border-radius:8px;padding:10px 12px;border:1px solid var(--background-modifier-border)"}});se.createEl("span",{text:K[J],attr:{style:"width:28px;height:28px;border-radius:6px;background:rgba(124,111,247,0.12);color:var(--interactive-accent);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;flex-shrink:0"}});let ce=se.createEl("div");ce.createEl("div",{text:le,attr:{style:"font-size:13px;font-weight:600;color:var(--text-normal)"}}),Z[J]&&ce.createEl("div",{text:Z[J],attr:{style:"font-size:11px;color:var(--text-muted);margin-top:2px;line-height:1.4"}})})}else{let U=X.createEl("div",{attr:{style:"display:grid;grid-template-columns:repeat(2,1fr);gap:8px"}});P.forEach((le,J)=>{let se=U.createEl("div",{attr:{style:"background:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:6px;padding:10px;text-align:left"}});se.createEl("div",{text:K[J],attr:{style:"font-size:10px;color:var(--text-faint);margin-bottom:2px"}}),se.createEl("div",{text:le,attr:{style:"font-size:13px;font-weight:600;color:var(--text-normal)"}})})}let oe=T.createEl("div",{attr:{style:"height:4px;background:var(--background-modifier-border);border-radius:99px;overflow:hidden;margin-bottom:6px"}}),re=oe.createEl("div",{attr:{style:`height:100%;width:100%;border-radius:99px;background:${E.border};transition:width 1s linear`}}),D=T.createEl("div",{attr:{style:"font-size:12px;color:var(--text-muted);font-weight:600;text-align:center;font-variant-numeric:tabular-nums"}});D.textContent=`${W}s`;let N=W,G=window.setInterval(()=>{N-=1,re.style.width=N/W*100+"%",D.textContent=`${N}s`,N<=0&&(window.clearInterval(G),V.innerHTML=me("target")+" "+(d?"\u4F5C\u7B54\u968E\u6BB5":"Quiz Phase"),V.style.background="rgba(34,197,94,0.08)",V.style.color="#22c55e",V.style.borderColor="#22c55e",X.empty(),X.style.textAlign="center",X.style.display="flex",X.style.alignItems="center",X.style.justifyContent="center",X.style.minHeight="60px",X.createEl("div",{attr:{style:"font-size:36px;opacity:0.3"}}).innerHTML=me(B?"brain":"target"),oe.style.display="none",D.textContent=d?"\u5F9E\u8A18\u61B6\u56DE\u7B54\u4EE5\u4E0B\u554F\u984C":"Answer from memory",I=!0,z())},1e3)},O=function(){if((e.type==="memory-palace"||e.type==="snapshot")&&!I){q(),R(()=>O());return}e.type!=="memory-palace"&&e.type!=="snapshot"&&T.empty(),(e.type==="memory-palace"||e.type==="snapshot")&&f>0&&T.querySelectorAll(".qm-round-q").forEach(X=>X.remove()),q();let z=u[f],B=!1,P=z.opts||z.options||e.options,K=z.ans!=null?z.ans:z.answer!=null?z.answer:e.answer,Z=z.type||(e.type==="memory-palace"||e.type==="snapshot"?"quiz":e.type),W=Object.assign({},e,{type:Z,question:z.q||z.question||"",prompt:z.prompt||e.prompt,options:P,answer:K,statement:z.statement||z.q||z.question||"",sentence:z.sentence||"",keywords:z.keywords||e.keywords,answers:z.answers||e.answers,hint:z.hint||"",explanation:z.explanation||e.explanation,explain:z.explain||e.explain,__onWrong:()=>{B=!0}});delete W.questions_json,delete W.palace_items,delete W.palace_descs,delete W.snapshot_items,delete W.snapshot_labels,e.type==="auction"&&(W.coins=v);let V=T.createEl("div",{attr:{class:"qm-round-q"}});ni(V,W,n,(X,oe={})=>{let re=!!(X&&!B&&!oe.revealed);if(w.push({question:z,correct:!!X,firstTryCorrect:re,revealed:!!oe.revealed,hadWrongAttempt:B}),re?(l.streak++,l.score+=10):(l.streak=0,e.type==="countdown"&&(l.lives=Math.max(0,l.lives-1))),e.type==="auction"){let N=V.textContent.match(/◈\s*(\d+)/);N&&(v=parseInt(N[1])),l.coins=v}f++;let D=e.type==="countdown"&&l.lives<=0||e.type==="auction"&&v<=0;f>=u.length||D?$(D):O()},s,i,r,o,l)},$=function(z){T.empty(),q();let B=w.filter(N=>N.firstTryCorrect).length,P=Math.round(B/u.length*100),K=B===u.length,Z=B>=u.length*.7,W=T.createEl("div",{attr:{style:"text-align:center;padding:20px 16px 12px"}});if(z){let N=e.type==="auction"?d?"\u786C\u5E63\u6B78\u96F6\uFF01":"Out of coins!":d?"\u751F\u547D\u6B78\u96F6\uFF01":"Out of lives!";W.createEl("div",{attr:{style:"font-size:44px;margin-bottom:6px;color:#ef4444"}}).innerHTML=me("alert"),W.createEl("div",{text:N,attr:{style:"font-size:17px;font-weight:800;color:#ef4444;margin-bottom:4px"}})}else{let N=K?"trophy":Z?"star":"flame",G=K?d?"\u5B8C\u7F8E\u901A\u95DC\uFF01":"Perfect!":Z?d?"\u505A\u5F97\u597D\uFF01":"Well done!":d?"\u7E7C\u7E8C\u52A0\u6CB9\uFF01":"Keep going!";W.createEl("div",{attr:{style:`font-size:44px;margin-bottom:6px;color:${K?"#fbbf24":Z?"#38bdf8":"#f97316"}`}}).innerHTML=me(N),W.createEl("div",{text:G,attr:{style:`font-size:17px;font-weight:800;color:${K?"#f59e0b":Z?"#22c55e":"var(--text-normal)"};margin-bottom:4px`}})}let V=W.createEl("div",{attr:{style:"display:flex;align-items:center;justify-content:center;gap:10px;margin-top:8px"}});V.createEl("div",{text:`${B} / ${u.length}`,attr:{style:"font-size:22px;font-weight:900;color:var(--text-normal)"}}),V.createEl("div",{attr:{style:"width:80px;height:8px;background:var(--background-modifier-border);border-radius:99px;overflow:hidden"}}).createEl("div",{attr:{style:`height:100%;width:${P}%;background:${K?"#f59e0b":Z?"#22c55e":"#ef4444"};border-radius:99px;transition:width .6s`}}),e.type==="auction"&&W.createEl("div",{text:`\u25C8 ${v}`,attr:{style:"font-size:12px;color:var(--text-muted);margin-top:4px"}});let oe=T.createEl("div",{attr:{style:"margin-top:4px;padding:14px 16px;border-radius:12px;background:var(--background-secondary);border:1px solid var(--background-modifier-border);text-align:left"}});oe.createEl("div",{attr:{style:"font-size:11px;font-weight:700;color:var(--text-faint);letter-spacing:.06em;text-transform:uppercase;margin-bottom:10px"}}).innerHTML=me("book")+" "+(d?"\u672C\u95DC\u91CD\u9EDE":"Round Review"),u.forEach((N,G)=>{let U=w[G]||{firstTryCorrect:!1,revealed:!1,correct:!1},le=Hd(N,e,o),J=U.firstTryCorrect?d?"\u7B54\u5C0D":"Correct":U.revealed?d?"\u5DF2\u63ED\u9732":"Revealed":d?"\u9700\u8907\u7FD2":"Missed",se=U.firstTryCorrect?"#22c55e":U.revealed?"#f59e0b":"#ef4444",ce=oe.createEl("div",{attr:{style:"margin-bottom:10px;padding-bottom:10px;border-bottom:1px solid var(--background-modifier-border)"}});ce.createEl("div",{text:`${G+1}. ${Od(N)}`,attr:{style:"font-size:13px;color:var(--text-normal);margin-bottom:4px;line-height:1.4"}});let ut=ce.createEl("div",{attr:{style:"display:flex;align-items:center;gap:6px"}});ut.createEl("span",{text:J,attr:{style:`color:${se};font-weight:800;font-size:13px`}}),ut.createEl("span",{text:le,attr:{style:`font-size:13px;color:${se};font-weight:600`}});let nt=Pd(N,e);nt&&ce.createEl("div",{text:nt,attr:{style:"font-size:11px;color:var(--text-muted);margin-top:3px;line-height:1.4"}})});let re=u.map((N,G)=>{let U=w[G]||{};return{token:zd(N,e),firstTryCorrect:!!U.firstTryCorrect,revealed:!!U.revealed}});T.createEl("button",{text:d?"\u7E7C\u7E8C \u2192":"Continue \u2192",attr:{class:"qm-continue-btn"}}).addEventListener("click",()=>a(!0,{scorePct:P,questionResults:re}))},f=0,v=e.coins||l.coins,y=0,w=[],E={countdown:{icon:"clock",title:d?"\u5012\u6578\u70B8\u5F48":"Countdown Bomb",bg:"rgba(239,68,68,0.06)",border:"#ef4444",rules:d?"\u6BCF\u984C\u9650\u6642\u4F5C\u7B54\u3002\u6642\u9593\u5230\u81EA\u52D5\u63ED\u66C9\u7B54\u6848\uFF0C\u7B54\u932F\u6263\u4E00\u689D\u547D\u3002\u2665 \u6B78\u96F6\u5247\u6311\u6230\u5931\u6557\u3002":"Answer before time runs out. Wrong answers cost a life. \u2665 reaches 0 = round over."},auction:{icon:"coin",title:d?"\u77E5\u8B58\u62CD\u8CE3\u5834":"Knowledge Auction",bg:"rgba(245,158,11,0.06)",border:"#f59e0b",rules:d?"\u9078\u64C7\u7B54\u6848\u4E26\u62BC\u6CE8\u786C\u5E63\u3002\u7B54\u5C0D\u8D0F\u56DE\u62BC\u6CE8\u91D1\u984D\uFF0C\u7B54\u932F\u5931\u53BB\u62BC\u6CE8\u3002\u25C8 \u6B78\u96F6\u5247\u6311\u6230\u5931\u6557\u3002":"Pick an answer and bet coins. Correct = win bet, wrong = lose bet. \u25C8 reaches 0 = round over."},snapshot:{icon:"camera",title:d?"\u6982\u5FF5\u5FEB\u7167":"Concept Snapshot",bg:"rgba(59,130,246,0.06)",border:"#3b82f6",rules:d?"\u8CC7\u8A0A\u5C07\u77ED\u66AB\u986F\u793A\u5F8C\u6D88\u5931\u3002\u4ED4\u7D30\u8A18\u4F4F\u5167\u5BB9\uFF0C\u7136\u5F8C\u5F9E\u8A18\u61B6\u56DE\u7B54\u554F\u984C\u3002":"Information will flash briefly then disappear. Memorize it, then answer from memory."},"memory-palace":{icon:"brain",title:d?"\u8A18\u61B6\u5BAE\u6BBF":"Memory Palace",bg:"rgba(124,111,247,0.06)",border:"#7c6ff7",rules:d?"\u77E5\u8B58\u5730\u5716\u5C07\u986F\u793A\u4E00\u6BB5\u6642\u9593\u5F8C\u96B1\u85CF\u3002\u8A18\u4F4F\u6BCF\u500B\u5143\u4EF6\u7684\u540D\u7A31\u548C\u8077\u8CAC\uFF0C\u7136\u5F8C\u56DE\u7B54\u554F\u984C\u3002":"A knowledge map will display then hide. Memorize each component's name and role, then answer."}}[e.type]||{icon:"target",title:d?"\u6311\u6230\u56DE\u5408":"Challenge Round",bg:"var(--background-secondary)",border:"var(--interactive-accent)",rules:""},S=h.createEl("div",{attr:{class:"qm-rules-banner",style:"background:"+E.bg+";border-color:"+E.border}}),_=S.createEl("div",{attr:{class:"qm-rules-banner-title"}});_.createEl("span").innerHTML=me(E.icon),_.createEl("span",{text:E.title}),S.createEl("div",{text:E.rules,attr:{class:"qm-rules-banner-body"}});let C=h.createEl("div",{attr:{style:"display:flex;align-items:center;justify-content:space-between;margin-bottom:14px"}}),L=C.createEl("div",{attr:{style:"display:flex;gap:4px"}}),A=C.createEl("div",{attr:{style:"font-size:12px;color:var(--text-muted);font-weight:600;font-variant-numeric:tabular-nums"}}),T=h.createEl("div"),I=!1;O();return}let x=h.createEl("div",{attr:{style:"display:flex;align-items:center;gap:8px;margin-bottom:14px"}});x.createEl("span").innerHTML=me("map"),x.createEl("span",{text:o.translateKey(s,"TAB_QUEST"),attr:{style:"font-size:12px;font-weight:700;color:var(--text-faint);letter-spacing:.08em;text-transform:uppercase"}}),x.createEl("span",{text:o.translateKey(s,c.labelKey),attr:{style:`font-size:11px;font-weight:700;padding:2px 8px;border-radius:20px;color:white;background:${c.color}`}});let m=null,g=null;function b(f){p+=1,typeof e.__onWrong=="function"&&e.__onWrong(),o.retriggerShake(f),c.showHint&&e.hint&&!m&&(m=h.createEl("div",{attr:{style:"margin-top:10px;padding:10px 14px;border-radius:8px;background:var(--background-secondary);border:1px solid var(--background-modifier-border);font-size:13px;color:var(--text-muted)"}}),m.createEl("span").innerHTML=me("bulb")+" ",m.createEl("span",{text:e.hint})),c.maxRetries!==99&&p>=c.maxRetries&&!g&&(g=h.createEl("div",{attr:{style:"margin-top:12px;padding:12px 16px;border-radius:10px;background:#fef2f2;border:1px solid #fca5a5;font-size:13px;color:#dc2626;display:flex;align-items:center;gap:10px"}}),g.createEl("span",{text:d?"\u5DF2\u9054\u91CD\u8A66\u4E0A\u9650\uFF0C\u8ACB\u5148\u56DE\u53BB\u8907\u7FD2\u9019\u500B\u7AE0\u7BC0\u3002":"Retry limit reached. Review this chapter first."}),e.link&&g.createEl("button",{text:d?"\u958B\u555F\u7B46\u8A18":"Open Note",attr:{style:"margin-left:auto;padding:6px 12px;border-radius:6px;background:#dc2626;color:white;border:none;cursor:pointer;font-size:12px;font-weight:600;white-space:nowrap"}}).addEventListener("click",()=>o.openQuestLink(i,e.link,r)))}if(e.type==="quiz"&&Array.isArray(e.options)){h.createEl("p",{text:e.question||"",attr:{class:"qm-question-text"}});let f=h.createEl("div",{attr:{class:"qm-options-col"}}),v=[],y=["A","B","C","D","E","F"];e.options.forEach((w,k)=>{let E=f.createEl("button",{attr:{class:"qm-ch-btn"}});E.createEl("span",{text:y[k]||String(k+1),attr:{class:"qm-ch-btn-label"}}),E.createEl("span",{text:w,attr:{class:"qm-ch-btn-text"}}),v.push(E),E.addEventListener("click",()=>{E.disabled||(k===e.answer?Ge(v,a):b(E))})});return}if(e.type==="countdown"&&Array.isArray(e.options)){let f=e.timer||15,y=h.createEl("div",{attr:{style:"height:4px;background:var(--background-modifier-border);border-radius:99px;overflow:hidden;margin-bottom:12px"}}).createEl("div",{attr:{style:"height:100%;width:100%;border-radius:99px;background:var(--interactive-accent);transition:width 0.1s linear"}}),w=h.createEl("div",{attr:{style:"font-size:11px;color:var(--text-faint);text-align:right;margin-bottom:12px;font-variant-numeric:tabular-nums"}});w.textContent=f+"s",h.createEl("p",{text:e.question||"",attr:{class:"qm-question-text"}});let k=h.createEl("div",{attr:{class:"qm-options-col"}}),E=[],S=["A","B","C","D","E","F"],_=!1,C=window.setInterval(()=>{if(f-=.1,f<=0){window.clearInterval(C),f=0,y.style.width="0%",w.textContent="0s",!_&&E.every(A=>!A.disabled)&&(_=!0,E.forEach(A=>A.disabled=!0),E[e.answer].style.background="#22c55e",E[e.answer].style.color="white",E[e.answer].style.borderColor="#22c55e",b(h),window.setTimeout(()=>a(!1,{scorePct:0}),1200));return}let L=f/(e.timer||15)*100;y.style.width=L+"%",y.style.background=L>50?"var(--interactive-accent)":L>25?"#f59e0b":"#ef4444",w.textContent=Math.ceil(f)+"s"},100);e.options.forEach((L,A)=>{let T=k.createEl("button",{attr:{class:"qm-ch-btn"}});T.createEl("span",{text:S[A]||String(A+1),attr:{class:"qm-ch-btn-label"}}),T.createEl("span",{text:L,attr:{class:"qm-ch-btn-text"}}),E.push(T),T.addEventListener("click",()=>{T.disabled||_||(window.clearInterval(C),A===e.answer?Ge(E,a):b(T))})});return}if(e.type==="snapshot"&&Array.isArray(e.options)){let L=function(){h.createEl("p",{text:e.question||"",attr:{class:"qm-question-text"}});let A=h.createEl("div",{attr:{class:"qm-options-col"}}),T=["A","B","C","D","E","F"],q=[];e.options.forEach((I,R)=>{let O=A.createEl("button",{attr:{class:"qm-ch-btn"}});O.createEl("span",{text:T[R]||String(R+1),attr:{class:"qm-ch-btn-label"}}),O.createEl("span",{text:I,attr:{class:"qm-ch-btn-text"}}),q.push(O),O.addEventListener("click",()=>{O.disabled||(R===e.answer?Ge(q,a):b(O))})})},f=e.snapshot_items||[],v=e.snapshot_labels||f.map((A,T)=>String(T+1)),y=e.snapshot_time||4,w="show",k=h.createEl("div",{attr:{style:"border-radius:10px;border:1px solid var(--background-modifier-border);background:var(--background-secondary);padding:20px;margin-bottom:14px;min-height:80px;text-align:center"}}),E=h.createEl("div",{attr:{style:"font-size:11px;color:var(--text-faint);margin-bottom:12px;font-variant-numeric:tabular-nums"}}),S=k.createEl("div",{attr:{style:"display:grid;grid-template-columns:repeat(2,1fr);gap:8px"}});f.forEach((A,T)=>{let q=S.createEl("div",{attr:{style:"background:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:6px;padding:10px;text-align:left"}});q.createEl("div",{text:v[T]||"",attr:{style:"font-size:10px;color:var(--text-faint);margin-bottom:2px"}}),q.createEl("div",{text:A,attr:{style:"font-size:13px;font-weight:600;color:var(--text-normal)"}})}),E.textContent=(d?"\u8A18\u4F4F\u4EE5\u4E0A\u5167\u5BB9 \u2014 ":"Memorize \u2014 ")+y+"s";let _=y,C=window.setInterval(()=>{_-=1,_<=0?(window.clearInterval(C),w="quiz",k.empty(),k.createEl("div",{text:"?",attr:{style:"font-size:48px;color:var(--text-faint);font-style:italic"}}),E.textContent=d?"\u5F9E\u8A18\u61B6\u56DE\u7B54\uFF1A":"Answer from memory:",L()):E.textContent=(d?"\u8A18\u4F4F\u4EE5\u4E0A\u5167\u5BB9 \u2014 ":"Memorize \u2014 ")+_+"s"},1e3);return}if(e.type==="auction"&&Array.isArray(e.options)){let f=e.coins||100,v=10,y=-1,w=!1,k=h.createEl("div",{attr:{style:"border-radius:10px;border:1px solid var(--background-modifier-border);background:var(--background-secondary);padding:20px;margin-bottom:14px"}}),E=k.createEl("div",{attr:{style:"display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid var(--background-modifier-border)"}});E.createEl("span",{attr:{style:"font-size:11px;color:var(--text-faint);letter-spacing:.08em"}}).innerHTML=me("coin")+" "+(d?"\u77E5\u8B58\u62CD\u8CE3\u5834":"Knowledge Auction");let S=E.createEl("span",{text:"\u25C8 "+f,attr:{style:"font-size:13px;font-weight:600;color:var(--text-normal)"}});k.createEl("p",{text:e.question||"",attr:{style:"font-size:14px;font-weight:600;color:var(--text-normal);margin-bottom:12px;line-height:1.5"}});let _=k.createEl("div",{attr:{style:"display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px"}}),C=[];e.options.forEach((R,O)=>{let $=_.createEl("div",{attr:{style:"border:1px solid var(--background-modifier-border);border-radius:8px;padding:10px;cursor:pointer;transition:all .15s;background:var(--background-primary)"}});$.createEl("div",{text:R,attr:{style:"font-size:12px;color:var(--text-normal);margin-bottom:6px"}}),C.push($),$.addEventListener("click",()=>{w||(y=O,C.forEach((z,B)=>{z.style.borderColor=B===O?"var(--interactive-accent)":"var(--background-modifier-border)"}))})});let L=k.createEl("div",{attr:{style:"display:flex;align-items:center;gap:8px;margin-bottom:12px"}});L.createEl("span",{text:d?"\u62BC\u6CE8\uFF1A":"Bet:",attr:{style:"font-size:12px;color:var(--text-faint)"}});let A=L.createEl("input",{attr:{type:"range",min:"5",max:String(Math.min(f,80)),step:"5",value:"10",style:"flex:1"}}),T=L.createEl("span",{text:"10",attr:{style:"font-size:12px;font-weight:600;color:var(--text-normal);min-width:28px;text-align:right"}});A.addEventListener("input",()=>{v=parseInt(A.value),T.textContent=String(v)});let q=k.createEl("button",{text:d?"\u78BA\u8A8D\u62BC\u6CE8 \u25C8":"Confirm Bet \u25C8",attr:{style:"width:100%;padding:10px;border-radius:8px;background:var(--interactive-accent);color:white;border:none;cursor:pointer;font-size:13px;font-weight:700"}}),I=k.createEl("div",{attr:{style:"margin-top:10px;font-size:12px;color:var(--text-muted);display:none"}});q.addEventListener("click",()=>{if(w||y<0)return;w=!0,q.style.display="none",A.disabled=!0;let R=y===e.answer;C.forEach(($,z)=>{$.style.cursor="default",z===e.answer?($.style.borderColor="#22c55e",$.style.background="rgba(34,197,94,0.08)"):z===y&&!R&&($.style.borderColor="#ef4444",$.style.background="rgba(239,68,68,0.08)")});let O=R?v:-v;f=Math.max(0,f+O),S.textContent="\u25C8 "+f,I.style.display="block",I.textContent=(R?"\u2713 +":"\u2717 ")+O+" \u25C8 \u2014 "+(d?"\u9918\u984D ":"Balance ")+f,I.style.color=R?"#22c55e":"#ef4444",window.setTimeout(()=>a(R),1200)});return}if(e.type==="timeline"&&Array.isArray(e.slots)&&Array.isArray(e.events)){let f=Array.isArray(e.answer)?e.answer:[],v=new Array(e.slots.length).fill(null),y=null,w=!1;h.createEl("p",{text:e.question||(d?"\u628A\u4E8B\u4EF6\u653E\u5230\u6B63\u78BA\u7684\u6642\u9593\u4F4D\u7F6E":"Place events in the correct time slots"),attr:{style:"font-size:14px;font-weight:600;color:var(--text-normal);margin-bottom:12px;line-height:1.5"}});let k=h.createEl("div",{text:d?"\u9EDE\u9078\u4E8B\u4EF6\uFF0C\u518D\u9EDE\u9078\u6642\u9593\u6B04\u4F4D\u653E\u7F6E\uFF1A":"Click an event, then click a slot to place it:",attr:{style:"font-size:11px;color:var(--text-faint);margin-bottom:8px"}}),E=h.createEl("div",{attr:{style:"display:flex;flex-wrap:wrap;gap:6px;margin-bottom:14px"}}),S=[];e.events.map((T,q)=>q).sort(()=>Math.random()-.5).forEach(T=>{let q=E.createEl("button",{text:e.events[T],attr:{style:"padding:6px 12px;border-radius:6px;border:1px solid var(--background-modifier-border);background:var(--background-primary);font-size:12px;cursor:pointer;transition:all .15s;color:var(--text-normal)"}});q._origIdx=T,S.push(q),q.addEventListener("click",()=>{w||q.disabled||(S.forEach(I=>{I.style.borderColor="var(--background-modifier-border)"}),y=T,q.style.borderColor="var(--interactive-accent)")})});let C=h.createEl("div",{attr:{style:"display:flex;flex-direction:column;gap:6px;margin-bottom:14px;padding-left:12px;border-left:2px solid var(--background-modifier-border)"}}),L=[];e.slots.forEach((T,q)=>{let I=C.createEl("div",{attr:{style:"display:flex;align-items:center;gap:10px"}});I.createEl("span",{text:T,attr:{style:"font-size:11px;color:var(--text-faint);min-width:48px;font-weight:600"}});let R=I.createEl("div",{attr:{style:"flex:1;min-height:36px;border:1px dashed var(--background-modifier-border);border-radius:6px;padding:6px 10px;font-size:12px;color:var(--text-muted);cursor:pointer;transition:all .15s;display:flex;align-items:center"}});R.textContent=d?"\u9EDE\u64CA\u653E\u7F6E":"Click to place",L.push(R),R.addEventListener("click",()=>{if(w||y===null)return;if(v[q]!==null){let $=S.find(z=>z._origIdx===v[q]);$&&($.disabled=!1,$.style.opacity="1")}v[q]=y,R.textContent=e.events[y],R.style.borderStyle="solid",R.style.color="var(--text-normal)",R.style.fontWeight="600";let O=S.find($=>$._origIdx===y);O&&(O.disabled=!0,O.style.opacity="0.3"),y=null,S.forEach($=>{$.style.borderColor="var(--background-modifier-border)"})})});let A=h.createEl("button",{text:d?"\u78BA\u8A8D":"Check",attr:{style:"width:100%;padding:10px;border-radius:8px;background:var(--interactive-accent);color:white;border:none;cursor:pointer;font-size:13px;font-weight:700"}});A.addEventListener("click",()=>{if(w)return;w=!0,A.style.display="none";let T=0;e.slots.forEach((q,I)=>{let R=v[I]===f[I];R&&T++,L[I].style.borderColor=R?"#22c55e":"#ef4444",L[I].style.borderStyle="solid",L[I].style.background=R?"rgba(34,197,94,0.08)":"rgba(239,68,68,0.08)",!R&&f[I]!=null&&(L[I].textContent+=" \u2192 "+e.events[f[I]])}),T===e.slots.length?window.setTimeout(()=>a(!0),800):(b(h),window.setTimeout(()=>a(!1),2e3))});return}if(e.type==="chain"&&Array.isArray(e.chain_items)){let q=function(I){k||(k=!0,window.clearInterval(T),A.forEach(R=>{R.disabled=!0,R.style.cursor="default"}),I?window.setTimeout(()=>a(!0),800):(f.forEach((R,O)=>{let $=A.find(z=>z._origIdx===R);$&&!$.style.borderColor.includes("22c55e")&&($._numEl.textContent=String(O+1),$.style.borderColor="#f59e0b",$.style.background="rgba(245,158,11,0.06)")}),b(h),window.setTimeout(()=>a(!1),2e3)))},f=Array.isArray(e.answer)?e.answer:e.chain_items.map((I,R)=>R),v=e.timer||20,y=0,w=0,k=!1,S=h.createEl("div",{attr:{style:"height:4px;background:var(--background-modifier-border);border-radius:99px;overflow:hidden;margin-bottom:8px"}}).createEl("div",{attr:{style:"height:100%;width:100%;border-radius:99px;background:var(--interactive-accent);transition:width 0.1s linear"}}),_=h.createEl("div",{attr:{style:"font-size:11px;color:var(--text-faint);text-align:right;margin-bottom:12px;font-variant-numeric:tabular-nums"}});_.textContent=v+"s",h.createEl("p",{text:e.question||(d?"\u6309\u6B63\u78BA\u9806\u5E8F\u9EDE\u64CA\uFF1A":"Click in the correct order:"),attr:{style:"font-size:14px;font-weight:600;color:var(--text-normal);margin-bottom:12px;line-height:1.5"}});let C=h.createEl("div",{attr:{style:"display:flex;flex-direction:column;gap:6px;margin-bottom:14px"}}),L=e.chain_items.map((I,R)=>R).sort(()=>Math.random()-.5),A=[];L.forEach(I=>{let R=C.createEl("button",{attr:{style:"display:flex;align-items:center;gap:10px;padding:10px 14px;border-radius:8px;border:1px solid var(--background-modifier-border);background:var(--background-primary);cursor:pointer;transition:all .2s;text-align:left;width:100%"}}),O=R.createEl("span",{text:"?",attr:{style:"width:24px;height:24px;border-radius:6px;background:rgba(124,111,247,0.12);color:var(--interactive-accent);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;flex-shrink:0"}});R.createEl("span",{text:e.chain_items[I],attr:{style:"font-size:13px;color:var(--text-normal)"}}),R._origIdx=I,R._numEl=O,A.push(R),R.addEventListener("click",()=>{if(k||R.disabled)return;let $=f[y];I===$?(y++,R.disabled=!0,R.style.borderColor="#22c55e",R.style.background="rgba(34,197,94,0.06)",O.textContent=String(y),O.style.background="rgba(34,197,94,0.2)",O.style.color="#22c55e",y===f.length&&q(!0)):(w++,o.retriggerShake(R),R.style.borderColor="#ef4444",window.setTimeout(()=>{R.style.borderColor="var(--background-modifier-border)"},400),w>=3&&q(!1))})});let T=window.setInterval(()=>{if(v-=.1,v<=0){window.clearInterval(T),k||q(!1);return}let I=v/(e.timer||20)*100;S.style.width=I+"%",S.style.background=I>50?"var(--interactive-accent)":I>25?"#f59e0b":"#ef4444",_.textContent=Math.ceil(v)+"s"},100);return}if(e.type==="memory-palace"&&Array.isArray(e.palace_items)){let A=function(){h.createEl("p",{text:e.question||"",attr:{style:"font-size:14px;font-weight:600;color:var(--text-normal);margin-bottom:12px;line-height:1.5"}});let T=h.createEl("div",{attr:{style:"display:grid;grid-template-columns:1fr 1fr;gap:8px"}}),q=[...f].sort(()=>Math.random()-.5),I=[];q.forEach(R=>{let O=T.createEl("button",{text:R,attr:{style:"padding:12px;border-radius:8px;border:1px solid var(--background-modifier-border);background:var(--background-primary);font-size:13px;cursor:pointer;transition:all .15s;color:var(--text-normal);text-align:center"}});I.push(O),O.addEventListener("click",()=>{if(O.disabled)return;if(I.forEach(z=>{z.disabled=!0,z.style.cursor="default"}),R.trim().toLowerCase()===w.trim().toLowerCase())O.style.borderColor="#22c55e",O.style.background="rgba(34,197,94,0.08)",O.style.color="#22c55e",window.setTimeout(()=>a(!0),800);else{O.style.borderColor="#ef4444",O.style.background="rgba(239,68,68,0.08)",O.style.color="#ef4444";let z=I.find(B=>B.textContent.trim().toLowerCase()===w.trim().toLowerCase());z&&(z.style.borderColor="#22c55e",z.style.background="rgba(34,197,94,0.08)",z.style.color="#22c55e"),b(h),window.setTimeout(()=>a(!1),2e3)}})})},f=e.palace_items,v=e.palace_descs||[],y=e.palace_time||15,w=typeof e.answer=="string"?e.answer:"",k="study",E=h.createEl("div",{attr:{style:"border-radius:10px;border:1px solid var(--background-modifier-border);background:var(--background-secondary);padding:20px;margin-bottom:14px;min-height:80px"}}),S=h.createEl("div",{attr:{style:"font-size:11px;color:var(--text-faint);margin-bottom:12px;font-variant-numeric:tabular-nums"}}),_=E.createEl("div",{attr:{style:"display:flex;flex-direction:column;gap:8px"}});f.forEach((T,q)=>{let I=_.createEl("div",{attr:{style:"display:flex;align-items:flex-start;gap:10px;background:var(--background-primary);border-radius:8px;padding:10px 12px;border:1px solid var(--background-modifier-border)"}}),R=I.createEl("span",{text:String.fromCharCode(65+q),attr:{style:"width:28px;height:28px;border-radius:6px;background:rgba(124,111,247,0.12);color:var(--interactive-accent);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;flex-shrink:0"}}),O=I.createEl("div");O.createEl("div",{text:T,attr:{style:"font-size:13px;font-weight:600;color:var(--text-normal)"}}),v[q]&&O.createEl("div",{text:v[q],attr:{style:"font-size:11px;color:var(--text-muted);margin-top:2px;line-height:1.4"}})}),S.textContent=(d?"\u8A18\u4F4F\u9019\u5F35\u77E5\u8B58\u5730\u5716 \u2014 ":"Memorize this map \u2014 ")+y+"s";let C=y,L=window.setInterval(()=>{C-=1,C<=0?(window.clearInterval(L),k="recall",E.empty(),E.style.textAlign="center",E.style.display="flex",E.style.alignItems="center",E.style.justifyContent="center",E.createEl("div",{attr:{style:"font-size:48px;opacity:0.3"}}).innerHTML=me("brain"),S.textContent=d?"\u5F9E\u8A18\u61B6\u56DE\u7B54\uFF1A":"Answer from memory:",A()):S.textContent=(d?"\u8A18\u4F4F\u9019\u5F35\u77E5\u8B58\u5730\u5716 \u2014 ":"Memorize this map \u2014 ")+C+"s"},1e3);return}if(e.type==="truefalse"){h.createEl("p",{text:e.statement||"",attr:{class:"qm-question-text"}});let f=h.createEl("div",{attr:{class:"qm-options-col"}}),v=[];[{label:d?"\u2713  \u6B63\u78BA":"\u2713  True",val:!0},{label:d?"\u2717  \u932F\u8AA4":"\u2717  False",val:!1}].forEach(({label:y,val:w})=>{let k=f.createEl("button",{text:y,attr:{class:"qm-ch-btn",style:"font-size:16px;font-weight:700;justify-content:center"}});v.push(k),k.addEventListener("click",()=>{k.disabled||(w===e.answer?Ge(v,a):b(k))})});return}if(e.type==="order"&&Array.isArray(e.items)){h.createEl("p",{text:e.question||(d?"\u8ACB\u4F9D\u6B63\u78BA\u9806\u5E8F\u9EDE\u64CA\uFF1A":"Click in the correct order:"),attr:{style:"font-size:14px;font-weight:600;color:var(--text-normal);margin-bottom:10px"}});let f=Array.isArray(e.answer)?e.answer:e.items.map((E,S)=>S),v=0,y=h.createEl("div",{attr:{style:"min-height:32px;padding:6px 10px;border-radius:8px;background:var(--background-secondary);font-size:12px;color:var(--text-faint);margin-bottom:10px;display:flex;gap:4px;flex-wrap:wrap;align-items:center"}});y.createEl("span",{text:d?"\u9806\u5E8F\uFF1A":"Sequence:"});let w=h.createEl("div",{attr:{style:"display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px"}}),k=[];e.items.forEach((E,S)=>{let _=w.createEl("button",{text:E,attr:{class:"qm-ch-chip"}});k.push(_),_.addEventListener("click",()=>{_.disabled||(S===f[v]?(_.disabled=!0,_.style.background="#22c55e",_.style.color="white",_.style.borderColor="#22c55e",y.createEl("span",{text:v+1+". "+E,attr:{style:"padding:2px 8px;border-radius:10px;background:rgba(34,197,94,0.15);color:#22c55e;font-size:11px;font-weight:600"}}),v++,v===f.length&&window.setTimeout(()=>a(!0),500)):b(_))})});return}if(e.type==="match"&&Array.isArray(e.pairs)){h.createEl("p",{text:d?"\u914D\u5C0D\u984C\uFF1A\u5148\u9EDE\u5DE6\u908A\uFF0C\u518D\u9EDE\u53F3\u908A\u3002":"Match the pairs: click left, then right.",attr:{style:"font-size:13px;color:var(--text-muted);margin-bottom:12px"}});let f=h.createEl("div",{attr:{style:"display:grid;grid-template-columns:1fr 1fr;gap:8px;min-width:0"}}),v=f.createEl("div",{attr:{style:"display:flex;flex-direction:column;gap:6px;min-width:0"}}),y=f.createEl("div",{attr:{style:"display:flex;flex-direction:column;gap:6px;min-width:0"}}),w=e.pairs.map((C,L)=>L).sort(()=>Math.random()-.5),k=null,E=0,S=[],_=[];e.pairs.forEach((C,L)=>{let A=v.createEl("button",{text:C[0],attr:{class:"qm-ch-btn"}});S.push(A),A.addEventListener("click",()=>{A.disabled||(S.forEach(T=>T.classList.remove("qm-selected")),k=L,A.classList.add("qm-selected"))})}),w.forEach(C=>{let L=y.createEl("button",{text:e.pairs[C][1],attr:{class:"qm-ch-btn"}});_.push(L),L.addEventListener("click",()=>{if(!(L.disabled||k===null)){if(k===C){let A=S[k];A.disabled=!0,L.disabled=!0,A.classList.remove("qm-selected"),A.style.background="#22c55e",A.style.color="white",A.style.borderColor="#22c55e",L.style.background="#22c55e",L.style.color="white",L.style.borderColor="#22c55e",k=null,E+=1,E===e.pairs.length&&window.setTimeout(()=>a(!0),500);return}b(L),S[k].classList.remove("qm-selected"),k=null}})});return}if(e.type==="cloze"&&e.sentence){let f=o.collectExpectedAnswers(e),v=e.question||e.q||e.prompt;v&&h.createEl("p",{text:v,attr:{class:"qm-question-text"}});let y=h.createEl("div",{attr:{style:"font-size:15px;line-height:1.8;color:var(--text-normal);margin-bottom:12px;padding:14px 16px;border-radius:10px;background:var(--background-secondary);border:1px solid var(--background-modifier-border)"}});y.appendChild(on.sanitizeHTMLToDom(o.renderClozeSentence(e.sentence,!1)));let w=h.createEl("div",{attr:{style:"display:flex;gap:8px;align-items:center;flex-wrap:wrap"}}),k=w.createEl("input",{attr:{type:"text",placeholder:d?"\u8F38\u5165\u586B\u7A7A\u7B54\u6848":"Type the cloze answer",class:"qm-ch-input"}}),E=w.createEl("button",{text:d?"\u9001\u51FA":"Submit",attr:{class:"qm-ch-btn",style:"width:auto;padding:10px 18px"}}),S=h.createEl("div",{attr:{style:"margin-top:10px;font-size:13px;color:var(--text-muted)"}}),_=null,C=(A=!1)=>{y.empty(),y.appendChild(on.sanitizeHTMLToDom(o.renderClozeSentence(e.sentence,!0))),S.textContent=(d?"\u6B63\u78BA\u7B54\u6848\uFF1A":"Correct answer: ")+f.join(" / "),_&&(_.disabled=!0),A&&(k.disabled=!0,E.disabled=!0,window.setTimeout(()=>a(!1,{revealed:!0}),700))},L=()=>{o.matchesExpectedAnswer(k.value,f)?(C(),Ge([E],a)):(b(E),S.textContent=d?"\u7B54\u6848\u4E0D\u6B63\u78BA\u3002\u4F60\u53EF\u4EE5\u518D\u8A66\u4E00\u6B21\uFF0C\u6216\u76F4\u63A5\u986F\u793A\u7B54\u6848\u3002":"That is not correct. Try once more or reveal the answer.",e.reveal_answer!==!1&&!_&&(_=w.createEl("button",{text:o.translateKey(s,"SHOW_ANSWER"),attr:{style:"padding:10px 16px;border-radius:8px;background:#475569;color:white;border:none;cursor:pointer;font-size:13px;font-weight:700;white-space:nowrap"}}),_.addEventListener("click",()=>C(!0))))};E.addEventListener("click",L),k.addEventListener("keydown",A=>{A.key==="Enter"&&L()});return}if((e.type==="input"||e.type==="text")&&(e.prompt||e.question)){h.createEl("p",{text:e.prompt||e.question,attr:{style:"font-size:14px;font-weight:600;color:var(--text-normal);margin-bottom:10px"}});let f=o.collectExpectedAnswers(e),v=h.createEl("div",{attr:{style:"display:flex;gap:8px;align-items:center;flex-wrap:wrap"}}),y=v.createEl("input",{attr:{type:"text",placeholder:d?"\u8F38\u5165\u7B54\u6848":"Type your answer",class:"qm-ch-input"}}),w=v.createEl("button",{text:d?"\u9001\u51FA":"Submit",attr:{class:"qm-ch-btn",style:"width:auto;padding:10px 18px"}}),k=h.createEl("div",{attr:{style:"margin-top:10px;font-size:13px;color:var(--text-muted)"}}),E=null,S=()=>{k.textContent=(d?"\u6B63\u78BA\u7B54\u6848\uFF1A":"Correct answer: ")+f.join(" / "),y.disabled=!0,w.disabled=!0,E&&(E.disabled=!0),window.setTimeout(()=>a(!1,{revealed:!0}),700)},_=()=>{o.matchesExpectedAnswer(y.value,f)?Ge([w],a):(b(w),k.textContent=d?"\u7B54\u6848\u4E0D\u6B63\u78BA\u3002\u518D\u8A66\u4E00\u6B21\uFF0C\u6216\u986F\u793A\u7B54\u6848\u5F8C\u8907\u7FD2\u539F\u56E0\u3002":"That is not correct. Try once more or reveal the answer.",e.reveal_answer!==!1&&!E&&(E=v.createEl("button",{text:o.translateKey(s,"SHOW_ANSWER"),attr:{style:"padding:10px 16px;border-radius:8px;background:#475569;color:white;border:none;cursor:pointer;font-size:13px;font-weight:700;white-space:nowrap"}}),E.addEventListener("click",S)))};w.addEventListener("click",_),y.addEventListener("keydown",C=>{C.key==="Enter"&&_()});return}if((e.type==="image"||e.type==="image-quiz")&&e.image){let f=o.getQuestImageResource(i,e.image,r);if(!f){h.createEl("p",{text:d?"\u627E\u4E0D\u5230\u984C\u76EE\u5716\u7247\u3002":"Image not found for this challenge.",attr:{style:"color:var(--text-muted)"}});return}h.createEl("p",{text:e.prompt||e.question||(d?"\u8ACB\u9EDE\u64CA\u6B63\u78BA\u5340\u57DF\u3002":"Click the correct region."),attr:{style:"font-size:14px;font-weight:600;color:var(--text-normal);margin-bottom:10px"}});let v=h.createEl("div",{attr:{style:"position:relative;display:inline-block;max-width:100%;border-radius:12px;overflow:hidden;border:1px solid var(--background-modifier-border);margin-bottom:12px"}});if(v.createEl("img",{attr:{src:f,style:"display:block;max-width:100%;height:auto"}}),Array.isArray(e.options)&&e.options.length>0){let w=h.createEl("div",{attr:{class:"qm-options-col"}}),k=[],E=["A","B","C","D","E","F"];e.options.forEach((S,_)=>{let C=w.createEl("button",{attr:{class:"qm-ch-btn"}});C.createEl("span",{text:E[_]||String(_+1),attr:{class:"qm-ch-btn-label"}}),C.createEl("span",{text:S,attr:{class:"qm-ch-btn-text"}}),k.push(C),C.addEventListener("click",()=>{C.disabled||(_===e.answer?Ge(k,a):b(C))})});return}if(e.keywords||e.answers){let w=o.collectExpectedAnswers(e),k=h.createEl("div",{attr:{style:"display:flex;gap:8px;align-items:center;flex-wrap:wrap"}}),E=k.createEl("input",{attr:{type:"text",placeholder:d?"\u8F38\u5165\u7B54\u6848":"Type your answer",class:"qm-ch-input"}}),S=k.createEl("button",{text:d?"\u9001\u51FA":"Submit",attr:{class:"qm-ch-btn",style:"width:auto;padding:10px 18px"}}),_=h.createEl("div",{attr:{style:"margin-top:10px;font-size:13px;color:var(--text-muted)"}}),C=null,L=()=>{_.textContent=(d?"\u6B63\u78BA\u7B54\u6848\uFF1A":"Correct answer: ")+w.join(" / "),E.disabled=!0,S.disabled=!0,C&&(C.disabled=!0),window.setTimeout(()=>a(!1,{revealed:!0}),700)},A=()=>{o.matchesExpectedAnswer(E.value,w)?Ge([S],a):(b(S),_.textContent=d?"\u7B54\u6848\u4E0D\u6B63\u78BA\u3002\u518D\u8A66\u4E00\u6B21\uFF0C\u6216\u986F\u793A\u7B54\u6848\u3002":"That is not correct. Try once more or reveal the answer.",e.reveal_answer!==!1&&!C&&(C=k.createEl("button",{text:o.translateKey(s,"SHOW_ANSWER"),attr:{style:"padding:10px 16px;border-radius:8px;background:#475569;color:white;border:none;cursor:pointer;font-size:13px;font-weight:700;white-space:nowrap"}}),C.addEventListener("click",L)))};S.addEventListener("click",A),E.addEventListener("keydown",T=>{T.key==="Enter"&&A()});return}let y=v.querySelector("img");v.addEventListener("click",w=>{var C,L,A,T,q,I;let k=y.getBoundingClientRect(),E=(w.clientX-k.left)/k.width,S=(w.clientY-k.top)/k.height;E>=((C=e.region_x)!=null?C:0)&&S>=((L=e.region_y)!=null?L:0)&&E<=((A=e.region_x)!=null?A:0)+((T=e.region_width)!=null?T:0)&&S<=((q=e.region_y)!=null?q:0)+((I=e.region_height)!=null?I:0)?window.setTimeout(()=>a(!0),300):b(v)});return}if(e.type==="iframe"){let E=function(S){v.empty(),v.createEl("div",{text:S,attr:{style:"padding:14px 16px;color:#dc2626;background:#fef2f2;border:1px solid #fca5a5;font-size:13px;line-height:1.5"}})},f=Number.isFinite(Number(e.height))?Math.max(180,Math.min(900,Math.round(Number(e.height)))):480,v=h.createEl("div",{attr:{style:"border:1px solid var(--background-modifier-border);border-radius:14px;overflow:hidden;background:var(--background-secondary)"}}),y=h.createEl("div",{attr:{style:"margin-top:10px;font-size:12px;color:var(--text-muted);line-height:1.5"}}),w=null,k=e.html||"";(async()=>{try{if(!k){E(d?"iframe \u984C\u578B\u7F3A\u5C11 html \u6B04\u4F4D\u3002":"Iframe challenge is missing an html field.");return}if(i.vault.adapter.exists&&!await i.vault.adapter.exists(k)){E(d?`\u627E\u4E0D\u5230 HTML \u6A94\u6848\uFF1A${k}`:`HTML file not found: ${k}`);return}let S=await i.vault.adapter.read(k);w=v.createEl("iframe",{attr:{sandbox:"allow-scripts",srcdoc:S,style:`display:block;width:100%;height:${f}px;border:0;background:white;`}}),y.textContent=d?"\u5B8C\u6210\u4E92\u52D5\u5F8C\u6703\u81EA\u52D5\u524D\u9032\u3002":"Complete the interaction to continue.";let _=C=>{if(w&&C.source&&C.source!==w.contentWindow)return;let L=C.data||{};if(!(!L||typeof L!="object")){if(L.type==="engram-quest-resize"){let A=Math.max(180,Math.min(900,Math.round(Number(L.height)||f)));w.style.height=A+"px"}if(L.type==="engram-quest-solved"){let A=Math.max(0,Math.min(100,Math.round(Number(L.score)||0)));y.textContent=d?`\u5DF2\u5B8C\u6210\uFF1A${A}%`:`Completed: ${A}%`,window.setTimeout(()=>a(!0,{scorePct:A}),250)}}};window.addEventListener("message",_),o.registerCleanup&&o.registerCleanup(()=>window.removeEventListener("message",_)),w.addEventListener("load",()=>{var C;(C=w.contentWindow)==null||C.postMessage({type:"engram-quest-theme",dark:activeDocument.body.classList.contains("theme-dark")},"*")})}catch(S){E((d?"HTML \u984C\u578B\u8F09\u5165\u5931\u6557\uFF1A":"Failed to load iframe challenge: ")+String(S&&S.message||S))}})();return}if(e.type==="image-occlusion"&&e.image){let E=function(){return w||(w=h.createEl("div",{attr:{style:"margin-top:10px;padding:10px 14px;border-radius:8px;background:var(--background-secondary);border:1px solid var(--background-modifier-border);font-size:13px;color:var(--text-muted)"}})),w},S=function(z){if(!z){k&&(k.remove(),k=null);return}k||(k=h.createEl("div",{attr:{style:"margin-bottom:12px;padding:12px 14px;border-radius:10px;background:#fff7ed;border:1px solid #fdba74;color:#9a3412;font-size:13px;line-height:1.5"}})),k.textContent=z},_=function(){A.style.background="rgba(34,197,94,0.82)",A.style.borderColor="#22c55e",A.style.color="#052e16",A.textContent=f.join(" / "),E().textContent=(d?"\u6B63\u78BA\u7B54\u6848\uFF1A":"Correct answer: ")+f.join(" / "),y&&(y.disabled=!0)},T=function(){let z=qd(e,L.naturalWidth,L.naturalHeight);if(!z){A.style.display="none",S(d?"\u9019\u5F35 image-occlusion \u7F3A\u5C11\u6709\u6548\u7684\u906E\u7F69\u5EA7\u6A19\u3002\u8ACB\u63D0\u4F9B region_x / region_y / region_width / region_height\uFF0C\u6216\u4F7F\u7528\u76F8\u5BB9\u7684 region_*_pct\u3002":"This image-occlusion challenge is missing a valid bbox. Provide region_x / region_y / region_width / region_height, or legacy region_*_pct values.");return}A.style.display="flex",A.style.left=z.leftPct+"%",A.style.top=z.topPct+"%",A.style.width=z.widthPct+"%",A.style.height=z.heightPct+"%",z.wasClamped?S(d?"\u906E\u7F69\u5EA7\u6A19\u8D85\u51FA\u5716\u7247\u908A\u754C\uFF0C\u5DF2\u81EA\u52D5\u88C1\u5207\u5230\u5716\u7247\u7BC4\u570D\u5167\u3002":"The occlusion bbox exceeded the image bounds and was clamped to the visible image area."):S("")},f=o.collectExpectedAnswers(e),v=o.getQuestImageResource(i,e.image,r),y=null,w=null,k=null;if(!v){h.createEl("div",{text:d?"\u627E\u4E0D\u5230\u5716\u7247\u6A94\u6848\u3002\u8ACB\u4F7F\u7528 vault-relative \u8DEF\u5F91\uFF0C\u6216\u76F8\u5C0D\u65BC\u76EE\u524D\u7B46\u8A18\u7684 note-relative \u8DEF\u5F91\uFF0C\u4F8B\u5982 Folder/assets/image.png \u6216 assets/image.png\u3002":"Image file not found. Use a vault-relative path or a note-relative path from the current note, for example Folder/assets/image.png or assets/image.png.",attr:{style:"padding:12px 14px;border-radius:10px;background:#fef2f2;border:1px solid #fca5a5;color:#dc2626;font-size:13px"}});return}e.prompt&&h.createEl("p",{text:e.prompt,attr:{style:"font-size:14px;font-weight:600;color:var(--text-normal);margin-bottom:12px;line-height:1.5"}});let C=h.createEl("div",{attr:{style:"position:relative;width:min(100%,760px);margin-bottom:12px;border-radius:14px;overflow:hidden;border:1px solid var(--background-modifier-border);background:var(--background-secondary)"}}),L=C.createEl("img",{attr:{src:v,alt:e.prompt||e.answer||"image occlusion",style:"display:block;width:100%;height:auto"}}),A=C.createEl("div",{attr:{style:"position:absolute;background:rgba(15,23,42,0.82);border:2px solid rgba(255,255,255,0.85);border-radius:10px;display:none;align-items:center;justify-content:center;color:#f8fafc;font-weight:700;font-size:13px;text-align:center;padding:6px;box-sizing:border-box;pointer-events:none"}});A.textContent=d?"\u5DF2\u906E\u84CB":"Hidden",L.addEventListener("load",T),L.complete&&T();let q=h.createEl("div",{attr:{style:"display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:12px"}});q.createEl("button",{text:d?"\u67E5\u770B\u539F\u5716":"View Source Image",attr:{style:"padding:8px 14px;border-radius:8px;background:#334155;color:white;border:none;cursor:pointer;font-size:12px;font-weight:700"}}).addEventListener("click",()=>o.openQuestLink(i,e.image,r)),e.link&&q.createEl("button",{text:d?"\u958B\u555F\u4F86\u6E90\u7B46\u8A18":"Open Source Note",attr:{style:"padding:8px 14px;border-radius:8px;background:#475569;color:white;border:none;cursor:pointer;font-size:12px;font-weight:700"}}).addEventListener("click",()=>o.openQuestLink(i,e.link,r));let I=h.createEl("div",{attr:{style:"display:flex;gap:8px;align-items:center;flex-wrap:wrap"}}),R=I.createEl("input",{attr:{type:"text",placeholder:d?"\u8F38\u5165\u88AB\u906E\u4F4F\u7684\u7B54\u6848":"Type the occluded answer",class:"qm-ch-input"}}),O=I.createEl("button",{text:d?"\u9001\u51FA":"Submit",attr:{style:"padding:10px 20px;border-radius:8px;background:var(--interactive-accent);color:white;border:none;cursor:pointer;font-size:13px;font-weight:700;white-space:nowrap"}}),$=()=>{let z=R.value.trim();z&&(o.matchesExpectedAnswer(z,f)?(R.style.borderColor="#22c55e",O.style.background="#22c55e",_(),E().textContent=d?"\u7B54\u5C0D\u4E86":"Correct",R.disabled=!0,O.disabled=!0,window.setTimeout(()=>a(!0),500)):(b(R),E().textContent=d?"\u7B54\u6848\u4E0D\u6B63\u78BA\u3002\u4F60\u53EF\u4EE5\u518D\u8A66\u4E00\u6B21\uFF0C\u6216\u76F4\u63A5\u986F\u793A\u7B54\u6848\u3002":"That is not correct. Try once more or reveal the answer.",e.reveal_answer!==!1&&!y&&(y=I.createEl("button",{text:o.translateKey(s,"SHOW_ANSWER"),attr:{style:"padding:10px 16px;border-radius:8px;background:#475569;color:white;border:none;cursor:pointer;font-size:13px;font-weight:700;white-space:nowrap"}}),y.addEventListener("click",_))))};O.addEventListener("click",$),R.addEventListener("keydown",z=>{z.key==="Enter"&&$()});return}}function ai(t,e,n,a,s,i,r){let o=ln(r,a),l=Nd(e.html,i),c=Number.isFinite(Number(e.height))?Math.max(220,Math.min(1200,Math.round(Number(e.height)))):760,p=t.createEl("div",{attr:{style:"border:1px solid var(--background-modifier-border);border-radius:16px;overflow:hidden;background:var(--background-secondary);min-height:220px"}}),d=t.createEl("div",{attr:{style:"margin-top:10px;font-size:12px;color:var(--text-muted);line-height:1.5"}}),h=null;function u(x){p.empty(),p.createEl("div",{text:x,attr:{style:"padding:14px 16px;color:#dc2626;background:#fef2f2;border:1px solid #fca5a5;font-size:13px;line-height:1.5"}})}(async()=>{try{if(!l){u(o?"\u9019\u500B HTML-first \u95DC\u5361\u7F3A\u5C11 html \u6B04\u4F4D\u3002":"This HTML-first node is missing an html field.");return}if(s.vault.adapter.exists&&!await s.vault.adapter.exists(l)){u(o?`\u627E\u4E0D\u5230 HTML \u6A94\u6848\uFF1A${l}`:`HTML file not found: ${l}`);return}let x=await s.vault.adapter.read(l);h=p.createEl("iframe",{attr:{sandbox:"allow-scripts",srcdoc:x,style:`display:block;width:100%;height:${c}px;border:0;background:white;`}}),d.textContent=o?"\u5B8C\u6210\u4E92\u52D5\u5F8C\u6703\u81EA\u52D5\u524D\u9032\u3002":"Complete the interaction to continue.";let m=g=>{if(h&&g.source&&g.source!==h.contentWindow)return;let b=g.data||{};if(!(!b||typeof b!="object")){if(b.type==="engram-quest-resize"){let f=Math.max(220,Math.min(1200,Math.round(Number(b.height)||c)));h.style.height=f+"px"}if(b.type==="engram-quest-solved"){let f=Number(b.score),v=Number.isFinite(f)?Math.max(0,Math.min(100,Math.round(f))):100;d.textContent=o?`\u5DF2\u5B8C\u6210\uFF1A${v}%`:`Completed: ${v}%`,window.setTimeout(()=>n(!0,{scorePct:v}),250)}}};window.addEventListener("message",m),r.registerCleanup&&r.registerCleanup(()=>window.removeEventListener("message",m)),h.addEventListener("load",()=>{var g;(g=h.contentWindow)==null||g.postMessage({type:"engram-quest-theme",dark:activeDocument.body.classList.contains("theme-dark")},"*")})}catch(x){u((o?"HTML \u95DC\u5361\u8F09\u5165\u5931\u6557\uFF1A":"Failed to load HTML quest node: ")+String(x&&x.message||x))}})()}function $d(t,e,n,a,s,i,r,o,l){let c=new on.Modal(t);c.modalEl.addClass("qm-modal");let p=n,d={score:0,lives:3,coins:100,streak:0},h=[],u=[];function x(v){typeof v=="function"&&u.push(v)}function m(){let v=u;u=[],v.forEach(y=>{try{y()}catch(w){console.warn("EngramQuest: quest cleanup failed",w)}})}c.onClose=m;let g=o.getQuestTheme(a,p,{ocean:{colors:[{g1:"#60a5fa",g2:"#2563eb"}]},forest:{colors:[{g1:"#4ade80",g2:"#16a34a"}]},galaxy:{colors:[{g1:"#c084fc",g2:"#7c3aed"}]},"sky-island":{colors:[{g1:"#7dd3fc",g2:"#38bdf8"}]}});function b(){let v=ln(o,i);c.contentEl.empty(),c.modalEl.style.cssText="width:min(92vw,480px);max-width:none;max-height:90vh;overflow:hidden;display:flex;flex-direction:column",c.contentEl.style.cssText="padding:0;flex:1;display:flex;flex-direction:column;overflow:hidden;min-height:0";let y=c.contentEl.createEl("div",{attr:{style:"flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 32px;text-align:center;gap:16px;"}});if(y.createEl("div",{attr:{style:"font-size:64px;line-height:1;color:#fbbf24;"}}).innerHTML=me("trophy"),y.createEl("div",{attr:{style:"font-size:22px;font-weight:800;color:var(--text-normal);"}}).textContent=o.t?o.t(i,"QUEST_COMPLETE"):v?"Quest \u901A\u95DC\uFF01":"Quest Complete!",y.createEl("div",{attr:{style:"font-size:13px;color:var(--text-muted);line-height:1.6;max-width:260px;"}}).textContent=`${e.length} ${v?"\u500B\u7AE0\u7BC0\u5168\u90E8\u5B8C\u6210":"chapters completed"}`,h.length>0){let k=Math.round(h.reduce((E,S)=>E+S,0)/h.length);y.createEl("div",{attr:{style:"font-size:28px;font-weight:800;color:var(--text-normal);margin-top:4px;"}}).textContent=k+"%"}let w=y.createEl("button",{attr:{style:"margin-top:12px;border-radius:99px;padding:14px 32px;font-size:15px;font-weight:700;cursor:pointer;border:none;background:linear-gradient(135deg,#4f46e5,#818cf8);color:#fff;box-shadow:0 4px 16px rgba(79,70,229,0.4);"}});w.textContent=v?"\u95DC\u9589":"Close",w.addEventListener("click",()=>{c.close(),l&&l()})}function f(){let v=e[p],y=ln(o,i);m(),c.contentEl.empty(),c.modalEl.style.cssText="width:min(92vw,820px);max-width:none;max-height:90vh;overflow:hidden;display:flex;flex-direction:column",c.contentEl.style.cssText="padding:0;flex:1;display:flex;flex-direction:column;overflow:hidden;min-height:0",c.contentEl.createEl("div",{attr:{style:"height:6px;background:var(--background-modifier-border);flex-shrink:0"}}).createEl("div",{attr:{style:`height:100%;border-radius:0 4px 4px 0;width:${(p+1)/e.length*100}%;background:linear-gradient(90deg,${g.g1},${g.g2});transition:width .5s`}});let w=c.contentEl.createEl("div",{attr:{style:"padding:28px 32px;overflow-y:auto;flex:1;min-height:0"}});if(v.challenge&&Array.isArray(v.challenge.questions_json)&&v.challenge.questions_json.length>0){let T=w.createEl("div",{attr:{style:"display:flex;gap:16px;align-items:center;margin-bottom:16px;padding:8px 14px;border-radius:8px;background:var(--background-secondary);border:1px solid var(--background-modifier-border);font-size:12px;font-weight:600;color:var(--text-muted)"}});T.createEl("span").innerHTML=me("star")+" "+d.score,T.createEl("span").innerHTML=me("heart")+" "+d.lives,v.challenge.type==="auction"&&(T.createEl("span").innerHTML=me("coin")+" "+(v.challenge.coins||d.coins)),T.createEl("span").innerHTML=me("flame")+" "+d.streak}let E=w.createEl("div",{attr:{style:"display:flex;align-items:flex-start;gap:16px;margin-bottom:16px"}});E.createEl("span",{text:v.emoji||"\u{1F4DD}",attr:{style:"font-size:36px;flex-shrink:0"}});let S=E.createEl("div");S.createEl("h2",{text:v.title||`${y?"\u7AE0\u7BC0":"Chapter"} ${p+1}`,attr:{style:"font-size:22px;font-weight:800;margin:0;line-height:1.3;color:var(--text-normal)"}}),S.createEl("p",{text:`${y?"\u7B2C":"Chapter "}${p+1}${y?" / ":" of "}${e.length}`,attr:{style:"margin:4px 0 0;font-size:12px;color:var(--text-muted)"}}),v.summary&&w.createEl("p",{text:v.summary,attr:{style:"font-size:14px;line-height:1.7;color:var(--text-normal);margin-bottom:24px"}}),Array.isArray(v.points)&&v.points.length>0&&(w.createEl("h3",{text:y?"\u91CD\u9EDE\u6574\u7406":"Key Takeaways",attr:{style:"font-size:11px;font-weight:700;color:var(--text-faint);letter-spacing:.08em;text-transform:uppercase;margin-bottom:16px"}}),v.points.forEach((T,q)=>{let I=w.createEl("div",{attr:{style:"border-radius:12px;border:1px solid var(--background-modifier-border);background:var(--background-secondary);padding:16px 20px;margin-bottom:12px"}}),R=I.createEl("div",{attr:{style:"display:flex;align-items:flex-start;gap:12px;margin-bottom:8px"}});R.createEl("span",{text:String(q+1),attr:{style:"flex-shrink:0;width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;background:var(--interactive-accent);color:white"}}),R.createEl("p",{text:T.title||"",attr:{style:"font-size:15px;font-weight:700;margin:0;color:var(--text-normal)"}}),T.body&&I.createEl("p",{text:T.body,attr:{style:"font-size:14px;line-height:1.65;color:var(--text-normal);margin:0 0 0 36px"}})}));let _=ti(v);_?ai(w,v,(T,q={})=>{var R;let I=sn(v.id,{...q,scorePct:(R=q.scorePct)!=null?R:T?100:0});h.push(I.scorePct),l&&l(v.id,p,I),p<e.length-1?(p+=1,f()):b()},i,t,r,{...o,registerCleanup:x}):v.challenge&&(o.renderQuestChallenge(w,v.challenge,s,(T,q={})=>{var R;let I=sn(v.id,{...q,scorePct:(R=q.scorePct)!=null?R:T?100:0});h.push(I.scorePct),l&&l(v.id,p,I),p<e.length-1?(p+=1,f()):b()},i,t,r,{...o,registerCleanup:x},d),v.challenge.type!=="image-occlusion"&&Dd(w));let C=w.createEl("div",{attr:{style:"display:flex;align-items:center;justify-content:space-between;border-top:1px solid var(--background-modifier-border);padding-top:20px;margin-top:28px"}}),L=C.createEl("button",{text:y?"\u4E0A\u4E00\u7AE0":"Previous",attr:{style:`border-radius:9999px;padding:8px 16px;font-size:14px;font-weight:600;color:var(--text-muted);background:transparent;border:none;cursor:pointer;visibility:${p===0?"hidden":"visible"}`}}),A=C.createEl("button",{text:p===e.length-1?y?"\u5B8C\u6210":"Done":y?"\u4E0B\u4E00\u7AE0":"Next",attr:{style:`border-radius:9999px;padding:8px 16px;font-size:14px;font-weight:700;color:white;border:none;cursor:pointer;background:linear-gradient(135deg,${g.g1},${g.g2})`}});_&&(A.disabled=!0,A.style.opacity="0.35",A.style.cursor="not-allowed",A.textContent=y?"\u5B8C\u6210\u4E92\u52D5\u5F8C\u524D\u9032":"Complete to continue"),L.addEventListener("click",()=>{p>0&&(p-=1,f())}),A.addEventListener("click",()=>{if(!ti(e[p]))if(p<e.length-1){if(!e[p].challenge){let T=sn(e[p].id);h.push(T.scorePct),l&&l(e[p].id,p,T)}p+=1,f()}else{if(!e[p].challenge){let T=sn(e[p].id);h.push(T.scorePct),l&&l(e[p].id,p,T)}b()}})}c.open(),f()}ri.exports={renderQuestHtmlIframe:ai,renderQuestChallenge:ni,openQuestChapterModal:$d}});var di=Y((rg,ci)=>{"use strict";var Ea=require("obsidian"),{t:Fd,getLocale:Qd}=ge(),{giHTML:cn}=be(),{buildQuestRenderState:si,loadQuestState:Bd,markQuestNodeComplete:Ud,saveQuestState:jd,updateQuestBestRun:Gd}=Rt(),{collectExpectedAnswers:Wd,getQuestImageResource:Yd,getQuestTheme:Kd,matchesExpectedAnswer:Vd,openQuestLink:Jd,questDifficultyPresets:Zd,renderClozeSentence:Xd,retriggerShake:ep}=Lt(),{openQuestChapterModal:tp,renderQuestChallenge:np}=va(),wa=Ea.moment;function dn(t){return Qd(t,wa&&wa.locale&&wa.locale())}function ii(){var t,e;return typeof activeDocument!="undefined"&&((e=(t=activeDocument.body)==null?void 0:t.classList)==null?void 0:e.contains("theme-dark"))}function oi(t){return(t==null?void 0:t.summary)||(t==null?void 0:t.scenario)||(t==null?void 0:t.mission_goal)||(t==null?void 0:t.stakes)||""}var ka={easy:[{x:22.9,y:81.9},{x:55.3,y:51.7},{x:71.9,y:25.2}],medium:[{x:24.8,y:77.9},{x:39.8,y:61.2},{x:53.8,y:49},{x:65.9,y:35.6},{x:73.6,y:22.4},{x:84.3,y:11.6}],hard:[{x:22.5,y:86.7},{x:41.1,y:69.8},{x:31.2,y:47.4},{x:55.6,y:57.3},{x:66.7,y:45.9},{x:75.5,y:66.4},{x:57.6,y:26.4},{x:67,y:15.7}]};function ap(t,e){if(e<=0)return[];if(e===1)return[{...t[Math.floor(t.length/2)]}];if(e===t.length)return t.map(s=>({...s}));if(e<t.length)return Array.from({length:e},(s,i)=>{let r=Math.round(i*(t.length-1)/(e-1));return{...t[r]}});let n=[],a=0;for(let s=0;s<t.length-1;s++){let i=t[s+1].x-t[s].x,r=t[s+1].y-t[s].y,o=Math.sqrt(i*i+r*r);n.push(o),a+=o}return Array.from({length:e},(s,i)=>{let r=i/(e-1)*a,o=0;for(let l=0;l<n.length;l++){if(o+n[l]>=r-1e-9){let c=n[l]>0?(r-o)/n[l]:0;return{x:t[l].x+c*(t[l+1].x-t[l].x),y:t[l].y+c*(t[l+1].y-t[l].y)}}o+=n[l]}return{...t[t.length-1]}})}function li(t,e={},n="medium"){if(t.every(i=>Number.isFinite(Number(i.x))&&Number.isFinite(Number(i.y))))return t.map(i=>({x:Number(i.x),y:Number(i.y)}));if(Array.isArray(e.positions)&&e.positions.length>=t.length)return t.map((i,r)=>{let o=e.positions[r]||{};return{x:Number.isFinite(Number(o.x))?Math.max(0,Math.min(100,Number(o.x))):10+r*12,y:Number.isFinite(Number(o.y))?Math.max(0,Math.min(100,Number(o.y))):60}});let s=ka[n]||ka.medium;return ap(s,Math.max(1,t.length))}function rp(t,e,n,a){let s=n.x-e.x,i=n.y-e.y,r=Math.sqrt(s*s+i*i),o=Math.atan2(i,s)*180/Math.PI,l=a?"linear-gradient(90deg,#22c55e,#6366f1)":"linear-gradient(90deg,rgba(255,255,255,.55),rgba(255,255,255,.25))",c=a?"0 0 8px 2px rgba(99,102,241,.45)":"0 2px 6px rgba(0,0,0,.35)";t.createEl("div",{attr:{style:["position:absolute",`left:${e.x}%`,`top:${e.y}%`,`width:${r}%`,"height:5px",`transform:rotate(${o}deg)`,"transform-origin:0 50%",`background:${l}`,"border-radius:999px","z-index:1",`box-shadow:${c}`].join(";")}})}var _a=class extends Ea.Modal{constructor(e,n,a,s){super(e),this.plugin=n,this.quest=a,this.onDone=s,this.state=null,this.renderState=null,this.changed=!1}async onOpen(){this.modalEl.addClass("qm-modal"),this.modalEl.addClass("lh-hub"),ii()&&this.modalEl.addClass("lh-dark"),this.modalEl.style.cssText="width:min(96vw,1040px);max-width:none;height:min(92vh,760px);max-height:none;padding:0;border-radius:18px;overflow:hidden;display:flex;flex-direction:column;",this.contentEl.style.cssText="padding:0;display:flex;flex-direction:column;min-height:0;flex:1;background:var(--background-primary);color:var(--text-normal);",await this.reload(),this.render()}onClose(){this.contentEl.empty(),this.changed&&this.onDone&&this.onDone()}async reload(){this.state=await Bd(this.app.vault.adapter,this.quest.stateKey||this.quest.sourcePath||this.quest.slug),this.renderState=si(this.quest.nodes||[],this.state)}async markComplete(e,n={}){if(!e)return;let a=this.quest.stateKey||this.quest.sourcePath||this.quest.slug;this.state=Ud(this.state,e,n),this.state=Gd(this.state,this.quest.nodes||[]),await jd(this.app.vault.adapter,a,this.state),this.changed=!0,await this.reload(),this.render()}openNode(e){var l,c,p,d,h,u;let a=(this.quest.nodes||[])[e];if(!a)return;let s=!!((d=(p=(c=(l=this.renderState)==null?void 0:l.state)==null?void 0:c.nodes)==null?void 0:p[a.id])!=null&&d.completed),i=(u=(h=this.renderState)==null?void 0:h.activeIndex)!=null?u:0,r=dn(this.plugin.settings)==="zh-tw";if(e>i&&!s){new Ea.Notice(r?"\u5148\u5B8C\u6210\u524D\u9762\u7684\u7BC0\u9EDE\u3002":"Complete earlier nodes first.");return}let o={getQuestTheme:Kd,getLanguage:x=>dn(x),renderQuestChallenge:(x,m,g,b,f,v,y,w,k)=>np(x,m,g,b,f,v,y,{...w,collectExpectedAnswers:Wd,getQuestImageResource:Yd,getLanguage:E=>dn(E),matchesExpectedAnswer:Vd,openQuestLink:Jd,questDifficultyPresets:Zd,renderClozeSentence:Xd,retriggerShake:ep,translateKey:Fd},k)};tp(this.app,[a],0,this.quest.style||"ocean",this.quest.difficulty||"medium",this.plugin.settings,this.quest.sourcePath||this.quest.stateKey||"",o,(x,m,g)=>{x&&this.markComplete(x,g||{scorePct:100}).catch(b=>{console.error("EngramQuest: quest node completion failed",b)})})}render(){let e=this.plugin.settings,n=dn(e)==="zh-tw",a=this.quest.nodes||[],s=this.renderState||si(a,this.state),i=li(a,this.quest.layout,this.quest.difficulty||"medium"),r=a[s.activeIndex]||a[0]||{},o=s.completedCount||0,l=s.progressPct||0,c=window.innerWidth<600;this.contentEl.empty();let p=this.contentEl.createEl("div",{attr:{style:"display:flex;align-items:flex-start;gap:12px;padding:14px 18px;border-bottom:1px solid var(--background-modifier-border);background:linear-gradient(135deg,rgba(99,102,241,.12),rgba(16,185,129,.10));flex-wrap:wrap;"}}),d=p.createEl("div",{attr:{style:"flex:1;min-width:0;"}});d.createEl("div",{text:this.quest.title||this.quest.slug||"Quest Map",attr:{style:"font-size:18px;font-weight:850;color:var(--text-normal);line-height:1.25;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"}}),d.createEl("div",{text:this.quest.description||oi(r)||(n?"\u5728\u4EFB\u52D9\u5730\u5716\u4E2D\u5B8C\u6210\u7BC0\u9EDE\uFF0C\u89E3\u9396\u4E0B\u4E00\u6B65\u3002":"Complete nodes in the mission map to unlock the next step."),attr:{style:"margin-top:4px;font-size:12px;color:var(--text-muted);line-height:1.45;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;"}});let h=p.createEl("div",{attr:{style:"display:flex;align-items:flex-end;flex-direction:column;gap:6px;"}});h.createEl("div",{text:String(this.quest.difficulty||"medium").toUpperCase(),attr:{style:"font-size:11px;font-weight:900;letter-spacing:.08em;color:#4f46e5;"}});let u=c?"100px":"150px";h.createEl("div",{attr:{style:`width:${u};height:8px;border-radius:999px;background:rgba(148,163,184,.22);overflow:hidden;`}}).createEl("div",{attr:{style:`height:100%;width:${l}%;background:linear-gradient(90deg,#10b981,#6366f1);border-radius:inherit;`}}),h.createEl("div",{text:`${o}/${a.length}${s.bestScorePct!=null?` / ${s.bestScorePct}%`:""}`,attr:{style:"font-size:11px;font-weight:750;color:var(--text-muted);"}});let m=ii(),g=this.app.vault.configDir+"/plugins/engram-quest/assets/quest-map/",b=g+"generated/",f={crown:this.app.vault.adapter.getResourcePath(b+"crown-current-node.webp"),nodeFrame:this.app.vault.adapter.getResourcePath(b+"node-number-frame.webp"),bossGate:this.app.vault.adapter.getResourcePath(b+"boss-gate.webp"),bossLock:this.app.vault.adapter.getResourcePath(b+"boss-lock-badge.webp")},v=this.quest.difficulty||"medium",y=this.app.vault.adapter.getResourcePath(g+"quest-map_backgroud_"+v+".png"),w=this.contentEl.createEl("div",{attr:{style:c?"flex:1;min-height:0;display:flex;flex-direction:column;overflow:auto;":"flex:1;min-height:0;display:grid;grid-template-columns:minmax(0,1fr) 280px;gap:0;overflow:hidden;"}}),k=S=>{let _=S.createEl("div",{attr:{style:c?"background:var(--background-secondary);padding:16px;":"border-left:1px solid var(--background-modifier-border);background:var(--background-secondary);padding:18px;overflow:auto;"}});this.renderSidePanel(_,r,s,n)},E=S=>{let _=S.createEl("div",{attr:{style:`min-width:0;overflow:${c?"hidden":"auto"};padding:${c?"12px":"18px"};background:var(--background-primary);`}}),C=["position:relative","border-radius:16px","overflow:hidden","border:1px solid var(--background-modifier-border)","box-shadow:0 10px 30px rgba(15,23,42,.08)"];c||C.push("min-width:760px");let L=_.createEl("div",{attr:{style:C.join(";")}});L.createEl("img",{attr:{src:y,style:"width:100%;display:block;border-radius:14px;"}}),i.slice(0,-1).forEach((A,T)=>{var R,O;let q=i[T+1],I=!!((O=s.state.nodes[(R=a[T])==null?void 0:R.id])!=null&&O.completed);rp(L,A,q,I)}),a.forEach((A,T)=>this.renderNode(L,A,T,i[T],s,g,m,f))};c?(k(w),E(w)):(E(w),k(w))}renderNode(e,n,a,s,i,r,o,l={}){var f,v,y;let c=!!((f=i.state.nodes[n.id])!=null&&f.completed),p=a===i.activeIndex&&!c,d=a>i.activeIndex&&!c,h=!!(n.boss||n.type==="boss"),u=p?"0 0 0 4px rgba(99,102,241,.4),0 0 20px 8px rgba(99,102,241,.45)":h?"0 0 0 3px rgba(245,158,11,.5),0 0 16px 6px rgba(239,68,68,.4)":"",x=h?110:94,m=d?"grayscale(70%) brightness(.7) saturate(.55)":c?"brightness(1.08) saturate(1.08)":"none",g=e.createEl("button",{attr:{style:["position:absolute",`left:${(v=s==null?void 0:s.x)!=null?v:50}%`,`top:${(y=s==null?void 0:s.y)!=null?y:50}%`,"transform:translate(-50%,-50%)","z-index:3","display:flex","flex-direction:column","align-items:center","gap:5px","border:0","background:transparent","padding:0",`cursor:${d?"not-allowed":"pointer"}`,`opacity:${d?".72":"1"}`].join(";")}}),b=g.createEl("div",{attr:{style:["position:relative",`width:${x}px`,`height:${x}px`,"border-radius:50%",u?`box-shadow:${u}`:""].filter(Boolean).join(";")}});p&&l.crown&&b.createEl("img",{attr:{src:l.crown,alt:"",style:["position:absolute","left:50%","top:-36px","width:48px","height:48px","object-fit:contain","transform:translateX(-50%)","z-index:5","filter:drop-shadow(0 8px 10px rgba(0,0,0,.32))","pointer-events:none"].join(";")}}),b.createEl("img",{attr:{src:h?l.bossGate:l.nodeFrame,alt:"",style:[`width:${x}px`,`height:${x}px`,"object-fit:contain","display:block",`filter:${m} drop-shadow(0 12px 16px rgba(0,0,0,.25))`,"pointer-events:none"].join(";")}}),h&&d&&l.bossLock?b.createEl("img",{attr:{src:l.bossLock,alt:"",style:["position:absolute","right:5px","top:8px","width:42px","height:42px","object-fit:contain","filter:drop-shadow(0 8px 10px rgba(0,0,0,.32))","pointer-events:none"].join(";")}}):c&&l.crown?b.createEl("img",{attr:{src:l.crown,alt:"",style:["position:absolute","left:50%","top:50%","width:52px","height:52px","object-fit:contain","transform:translate(-50%,-50%)","filter:drop-shadow(0 4px 8px rgba(255,210,80,.45))","pointer-events:none","z-index:6"].join(";")}}):d?b.createEl("div",{attr:{style:["position:absolute","left:50%","top:50%","transform:translate(-50%,-50%)","font-size:24px","line-height:1","filter:drop-shadow(0 3px 6px rgba(0,0,0,.35))","pointer-events:none"].join(";")}}).innerHTML=cn("lock"):h||b.createEl("div",{text:String(a+1),attr:{style:["position:absolute","left:50%","top:50%","transform:translate(-50%,-50%)","font-size:30px","line-height:1","font-weight:950","color:#fff","text-shadow:0 2px 6px rgba(0,0,0,.65)","pointer-events:none"].join(";")}}),g.createEl("div",{text:n.title||n.id,attr:{style:["max-width:110px","border-radius:999px","padding:3px 9px","background:rgba(10,10,20,.82)","color:#fff","font-size:10px","font-weight:800","line-height:1.4","text-align:center","box-shadow:0 2px 8px rgba(0,0,0,.5)","white-space:nowrap","overflow:hidden","text-overflow:ellipsis","backdrop-filter:blur(3px)"].join(";")}}),g.addEventListener("click",w=>{w.preventDefault(),this.openNode(a)})}renderSidePanel(e,n,a,s){e.createEl("div",{text:s?"\u76EE\u524D\u7BC0\u9EDE":"Current Node",attr:{style:"font-size:11px;font-weight:900;letter-spacing:.08em;text-transform:uppercase;color:var(--text-muted);margin-bottom:8px;"}}),e.createEl("div",{text:n.title||"-",attr:{style:"font-size:18px;font-weight:850;color:var(--text-normal);line-height:1.3;margin-bottom:8px;"}}),e.createEl("div",{text:oi(n)||(s?"\u9EDE\u64CA\u53EF\u7528\u7BC0\u9EDE\u958B\u59CB\u4EFB\u52D9\u3002":"Click an available node to begin."),attr:{style:"font-size:13px;color:var(--text-muted);line-height:1.6;margin-bottom:16px;"}}),e.createEl("button",{text:s?"\u958B\u59CB\u76EE\u524D\u7BC0\u9EDE":"Start Current Node",attr:{style:"width:100%;border:0;border-radius:12px;padding:11px 14px;background:linear-gradient(135deg,#4f46e5,#7c3aed);color:#fff;font-size:13px;font-weight:850;cursor:pointer;box-shadow:0 8px 18px rgba(79,70,229,.24);"}}).addEventListener("click",()=>this.openNode(a.activeIndex||0));let r=e.createEl("div",{attr:{style:"display:flex;flex-direction:column;gap:8px;margin-top:18px;"}});(this.quest.nodes||[]).forEach((o,l)=>{var h;let c=!!((h=a.state.nodes[o.id])!=null&&h.completed),p=l>a.activeIndex&&!c,d=r.createEl("button",{attr:{style:["text-align:left","border-radius:10px","padding:8px 10px","font-size:12px","font-weight:750","border:1px solid var(--background-modifier-border)",`background:${l===a.activeIndex?"rgba(56,189,248,.14)":"var(--background-primary)"}`,"color:var(--text-normal)",`cursor:${p?"not-allowed":"pointer"}`,`opacity:${p?".58":"1"}`].join(";")}});d.textContent=o.title||o.id,d.insertAdjacentHTML("afterbegin",cn(c?"check":p?"lock":"play")+" "),d.addEventListener("click",()=>this.openNode(l))})}};ci.exports={QuestViewerModal:_a,nodePositions:li,DISC_PRESETS:ka}});var hi=Y((sg,pi)=>{"use strict";var pn=require("obsidian"),{t:qe}=ge(),{loadCardSr:sp,updateCardContent:ip,enqueueRegenerate:op}=St(),Sa=class extends pn.Modal{constructor(e,n,a,s){super(e),this.plugin=n,this.card=a,this.onSaved=s}onOpen(){let e=this.plugin.settings,{contentEl:n,modalEl:a}=this;a.style.cssText="width:min(96vw,620px);max-height:90vh;border-radius:16px;",(async()=>{try{await this._render(e,n)}catch(s){console.error("EngramQuest: quadrant edit modal failed to open",s),n.empty(),n.createEl("div",{text:qe(e,"QUADRANT_SAVE_FAILED"),attr:{style:"padding:14px 16px;color:var(--text-error);"}})}})()}async _render(e,n){let a=this.app.vault.adapter,s=await sp(a,this.card.cardId)||this.card;n.empty(),n.createEl("div",{text:qe(e,"QUADRANT_EDIT_TITLE"),attr:{style:"font-size:16px;font-weight:700;color:var(--text-normal);margin-bottom:12px;"}});let i=n.createEl("div",{attr:{class:"lh-edit-form"}}),r=i.createEl("div",{attr:{class:"lh-edit-field"}});r.createEl("label",{text:qe(e,"QUADRANT_EDIT_TITLE_LABEL"),attr:{class:"lh-edit-label"}});let o=r.createEl("input",{attr:{type:"text",class:"lh-edit-textarea"}});o.value=s.title||"";let l=(g,b)=>{let f=i.createEl("div",{attr:{class:"lh-edit-field"}});f.createEl("label",{text:qe(e,g),attr:{class:"lh-edit-label"}});let v=f.createEl("textarea",{attr:{class:"lh-edit-textarea"}});return v.value=b||"",v},c=l("QUADRANT_EDIT_Q1",s.q1),p=l("QUADRANT_EDIT_Q2",s.q2),d=l("QUADRANT_EDIT_Q3",s.q3),h=l("QUADRANT_EDIT_Q4",s.q4);i.createEl("div",{text:qe(e,"QUADRANT_EDIT_HINT"),attr:{style:"font-size:12px;color:var(--text-muted);line-height:1.5;margin:4px 0 12px;"}});let u=i.createEl("div",{attr:{class:"lh-edit-btn-row"}}),x=u.createEl("button",{attr:{class:"lh-edit-save-btn"}});x.textContent=qe(e,"EDIT_SAVE");let m=u.createEl("button",{attr:{class:"lh-edit-cancel-btn"}});m.textContent=qe(e,"EDIT_CANCEL"),m.addEventListener("click",()=>this.close()),x.addEventListener("click",async()=>{let g={title:o.value.trim(),q1:c.value.trim(),q2:p.value.trim(),q3:d.value.trim(),q4:h.value.trim()},b=g.q1!==(s.q1||"")||g.q2!==(s.q2||"")||g.q3!==(s.q3||"")||g.q4!==(s.q4||"");x.disabled=!0;try{let f=await ip(a,this.card.cardId,g);if(!f){new pn.Notice(qe(e,"QUADRANT_CARD_NOT_FOUND")),x.disabled=!1;return}b&&await op(a,{cardId:this.card.cardId,source:f.source,title:g.title,q1:g.q1,q2:g.q2,q3:g.q3,q4:g.q4}),Object.assign(this.card,g),new pn.Notice(qe(e,b?"QUADRANT_REGEN_QUEUED":"QUADRANT_SAVED")),this.onSaved&&this.onSaved(this.card),this.close()}catch(f){console.error("EngramQuest: quadrant edit save failed",f),new pn.Notice(qe(e,"QUADRANT_SAVE_FAILED")),x.disabled=!1}})}onClose(){this.contentEl.empty()}};pi.exports={QuadrantEditModal:Sa}});var mi=Y((ig,gi)=>{"use strict";var We=require("obsidian"),{t:xe,interpolate:ui,getLocale:lp}=ge(),{applyQuadrantRating:cp,deleteQuadrantCard:dp,QUADRANT_DIR:pp}=St(),{QuadrantEditModal:hp}=hi(),{giHTML:It}=be(),up='<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4h6v2"/></svg>',Aa=We.moment;function gp(t){return lp(t,Aa&&Aa.locale&&Aa.locale())}var mp=600,Ta=class extends We.Modal{constructor(e,n,a,s,i){super(e),this.plugin=n,this.card=a,this.onDone=s,this.position=i&&Number(i.position)||1,this.total=i&&Number(i.total)||1,this._handler=null,this._isOpen=!1,this._iframe=null}onOpen(){let e=this.plugin.settings,n=gp(e)==="zh-tw",{contentEl:a,modalEl:s}=this;this._isOpen=!0,s.style.cssText="width:min(96vw,620px);max-height:95vh;padding:0;border-radius:16px;overflow:hidden;display:flex;flex-direction:column;",a.style.cssText="padding:12px;display:flex;flex-direction:column;gap:6px;overflow-y:auto;flex:1;min-height:0;";let i=a.createEl("div",{attr:{style:"display:flex;align-items:center;flex-wrap:wrap;gap:8px;padding-right:36px;"}}),r=this.card.title||this.card.cardId,o=i.createEl("div",{text:r,attr:{style:"flex:1;min-width:0;font-size:15px;font-weight:700;color:var(--text-normal);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;",title:r}}),l="flex-shrink:0;font-size:12px;padding:3px 8px;border-radius:8px;border:1px solid var(--background-modifier-border);background:var(--background-secondary);color:var(--text-muted);cursor:pointer;white-space:nowrap;";if(this.card.source){let f=String(this.card.source).split("/").pop().replace(/\.md$/i,""),v=i.createEl("button",{attr:{style:l+"max-width:110px;overflow:hidden;text-overflow:ellipsis;color:#38bdf8;",title:this.card.source}});v.innerHTML=It("file")+" "+f,v.addEventListener("click",()=>{let y=this.card.source,w=this.app.vault.getAbstractFileByPath(y);if(w)this.app.workspace.openLinkText(w.path,"",!1);else{let E=this.app.metadataCache.getFirstLinkpathDest(f,"");if(E)this.app.workspace.openLinkText(E.path,"",!1);else{new We.Notice(ui(xe(e,"QUADRANT_SOURCE_NOT_FOUND"),{name:f}));return}}let k=v.innerHTML;v.innerHTML=It("check")+" "+xe(e,"QUADRANT_SOURCE_OPENED"),window.setTimeout(()=>{v.innerHTML=k},1200)})}let c=i.createEl("button",{attr:{style:l,title:xe(e,"QUADRANT_COPY"),"aria-label":xe(e,"QUADRANT_COPY")}});c.innerHTML=It("copy"),c.addEventListener("click",()=>{let f=(this.card.q1||"")+`
A: `+(this.card.q2||""),v=(y,w)=>{c.innerHTML=It(y),c.title=w,c.setAttribute("aria-label",w)};navigator.clipboard.writeText(f).then(()=>{v("check",xe(e,"QUADRANT_COPIED")),window.setTimeout(()=>v("copy",xe(e,"QUADRANT_COPY")),1500)}).catch(y=>{console.warn("EngramQuest: quadrant copy failed",y),v("x",xe(e,"QUADRANT_COPY_FAILED")),window.setTimeout(()=>v("copy",xe(e,"QUADRANT_COPY")),1500)})});let p=i.createEl("button",{attr:{style:l,title:xe(e,"QUADRANT_EDIT"),"aria-label":xe(e,"QUADRANT_EDIT")}});p.innerHTML=It("edit"),p.addEventListener("click",()=>{new hp(this.app,this.plugin,this.card,f=>{let v=f.title||f.cardId;o.textContent=v,o.title=v}).open()});let d=i.createEl("button",{attr:{class:"lh-delete-btn",title:xe(e,"DELETE"),"aria-label":xe(e,"DELETE"),style:"opacity:1;flex-shrink:0;"}});d.appendChild(We.sanitizeHTMLToDom(up)),d.addEventListener("click",()=>this._confirmDelete(n));let h=a.createEl("div",{attr:{style:"border:1px solid var(--background-modifier-border);border-radius:14px;overflow:hidden;background:var(--background-secondary)"}}),u=a.createEl("div",{attr:{style:"display:flex;align-items:center;gap:12px;padding:0 2px;"}}),x=u.createEl("span",{attr:{style:"flex:1;min-width:0;font-size:11px;color:var(--text-muted);line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"}});if(this.total>1){let f=u.createEl("div",{attr:{class:"lh-review-prog-wrap",style:"width:90px;flex-shrink:0;"}}),v=Math.round((this.position-1)/this.total*100);f.createEl("div",{attr:{class:"lh-review-prog-bar",style:`width:${v}%`}})}u.createEl("span",{text:`${this.position} / ${this.total}`,attr:{class:"lh-review-badge",style:"flex-shrink:0;"}});let m=f=>{h.empty(),h.createEl("div",{text:f,attr:{style:"padding:14px 16px;color:var(--text-error);background:var(--background-modifier-error);border:1px solid var(--background-modifier-error);border-radius:10px;font-size:13px;line-height:1.5"}})};this._handler=async f=>{if(!this._isOpen)return;let v=this._iframe;if(v&&f.source&&f.source!==v.contentWindow)return;let y=f.data||{};if(!(!y||typeof y!="object")){if(y.type==="engram-quest-resize"&&v){let k=Math.max(360,Math.floor(((typeof activeWindow!="undefined"&&activeWindow||window).innerHeight||900)*.86)),E=Math.max(180,Math.min(k,Math.round(Number(y.height)||520)));v.style.height=E+"px"}if(y.type==="engram-quest-solved"){let w=Math.max(0,Math.min(100,Math.round(Number(y.score)||0)));try{await cp(this.app.vault.adapter,this.card.cardId,w,e)}catch(k){console.error("EngramQuest: quadrant rating failed",k),x.textContent=n?"\u6392\u7A0B\u66F4\u65B0\u5931\u6557\uFF0C\u8ACB\u518D\u8907\u7FD2\u4E00\u6B21\u3002":"Schedule update failed \u2014 please review again.",new We.Notice(n?"\u56DB\u8C61\u9650\u5361\u6392\u7A0B\u66F4\u65B0\u5931\u6557":"Failed to update quadrant card schedule");return}x.textContent=n?`\u5DF2\u66F4\u65B0\u6392\u7A0B\uFF08${w}%\uFF09`:`Schedule updated (${w}%)`,window.setTimeout(()=>{this.close(),this.onDone&&this.onDone()},mp)}}},window.addEventListener("message",this._handler);let g=`${pp}/${this.card.cardId}.html`,b=this.app.vault.adapter;(async()=>{try{if(b.exists&&!await b.exists(g)){m(n?`\u627E\u4E0D\u5230\u5361\u7247\u6A94\u6848\uFF1A${g}`:`Card file not found: ${g}`);return}let f=await b.read(g);if(!this._isOpen)return;let v=h.createEl("iframe",{attr:{sandbox:"allow-scripts",srcdoc:f,style:"display:block;width:100%;height:520px;border:0;background:white;"}});this._iframe=v,x.textContent=n?"\u5B8C\u6210\u81EA\u8A55\u5F8C\u6703\u81EA\u52D5\u66F4\u65B0\u6392\u7A0B\u3002":"Your self-assessment updates the schedule automatically.",v.addEventListener("load",()=>{var y;(y=v.contentWindow)==null||y.postMessage({type:"engram-quest-theme",dark:activeDocument.body.classList.contains("theme-dark")},"*")})}catch(f){m((n?"\u5361\u7247\u8F09\u5165\u5931\u6557\uFF1A":"Failed to load card: ")+String(f&&f.message||f))}})()}_confirmDelete(e){let n=this.plugin.settings,a=new We.Modal(this.app);a.modalEl.style.cssText="width:min(92vw,420px);padding:0;border-radius:16px;overflow:hidden;";let s=a.contentEl;s.style.cssText="padding:24px;display:flex;flex-direction:column;gap:14px;",s.createEl("div",{text:ui(xe(n,"DELETE_CONFIRM_TITLE"),{name:this.card.title||this.card.cardId}),attr:{style:"font-size:16px;font-weight:700;color:var(--text-normal);"}}),s.createEl("div",{text:xe(n,"QUADRANT_DELETE_CARD_BODY"),attr:{style:"font-size:13px;color:var(--text-muted);line-height:1.6;"}});let i=s.createEl("div",{attr:{style:"display:flex;gap:8px;justify-content:flex-end;padding-top:4px;"}});i.createEl("button",{text:xe(n,"DELETE_CANCEL_BTN"),attr:{style:"padding:7px 16px;border-radius:8px;border:1px solid var(--background-modifier-border);background:var(--background-secondary);color:var(--text-normal);cursor:pointer;font-size:13px;"}}).addEventListener("click",()=>a.close());let r=i.createEl("button",{text:xe(n,"DELETE_CONFIRM_BTN"),attr:{style:"padding:7px 16px;border-radius:8px;border:none;background:#ef4444;color:#fff;cursor:pointer;font-size:13px;font-weight:600;"}});r.addEventListener("click",async()=>{if(!r.disabled){r.disabled=!0,a.close();try{await dp(this.app.vault.adapter,this.card.cardId)}catch(o){console.error("EngramQuest: quadrant card delete failed",o),new We.Notice(e?"\u522A\u9664\u5931\u6557\uFF0C\u8ACB\u91CD\u8A66":"Delete failed \u2014 please try again");return}new We.Notice(e?"\u56DB\u8C61\u9650\u5361\u5DF2\u522A\u9664":"Quadrant card deleted"),this.close(),this.onDone&&this.onDone()}}),a.open()}onClose(){this._isOpen=!1,this._handler&&(window.removeEventListener("message",this._handler),this._handler=null),this._iframe=null,this.contentEl.empty()}};gi.exports={QuadrantReviewModal:Ta}});var vi=Y((og,yi)=>{"use strict";var Mt=require("obsidian"),{t:ee,interpolate:Ye}=ge(),{matchFlashcardTagPrefix:fp}=Se(),{layoutStatOrbs:bp}=da(),{scanQuadrantCards:xp,groupQuadrantDecks:yp,isCardMastered:vp,deleteQuadrantCard:wp}=St(),{QuadrantReviewModal:Ep}=mi(),{giHTML:qt}=be(),ot=["folder","book","brain","star","bolt","link","sprout","target","card","wave","bulb","stack"];function kp(t){let e=[];if(!t)return e;if(Array.isArray(t.tags)&&e.push(...t.tags.map(n=>String(n.tag).replace(/^#/,""))),t.frontmatter&&t.frontmatter.tags){let n=t.frontmatter.tags;typeof n=="string"?e.push(...n.split(",").map(a=>a.trim().replace(/^#/,""))):Array.isArray(n)?e.push(...n.map(a=>String(a).replace(/^#/,""))):e.push(String(n).replace(/^#/,""))}return[...new Set(e.filter(Boolean))]}function _p(t){let e=t.plugin.settings;return n=>{if(n.deck)return String(n.deck);let a=n.source;if(a&&typeof a=="string"){let s=t.app.vault.getAbstractFileByPath(a);if(s){let r=t.app.metadataCache.getFileCache(s),o=fp(kp(r),e.flashcardTags);if(o)return o;if(s.parent&&s.parent.path&&s.parent.path!=="/")return s.parent.name}let i=a.split("/");if(i.length>1)return i[i.length-2]}return"Quadrant"}}function hn(t,e,n){let a=e.cards.filter(o=>o._due||o._new);a.length===0&&(a=e.cards.slice());let s=a.length,i=0,r=()=>{if(i>=s){n();return}let o=a[i++];new Ep(t.app,t.plugin,o,r,{position:i,total:s}).open()};r()}var Sp='<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4h6v2"/></svg>';function Ap(t,e,n,a){let s=t.app.vault.adapter,i=new Mt.Modal(t.app);i.modalEl.style.cssText="width:min(92vw,420px);padding:0;border-radius:16px;overflow:hidden;";let r=i.contentEl;r.style.cssText="padding:24px;display:flex;flex-direction:column;gap:14px;",r.createEl("div",{text:Ye(ee(e,"DELETE_CONFIRM_TITLE"),{name:n.name}),attr:{style:"font-size:16px;font-weight:700;color:var(--text-normal);"}}),r.createEl("div",{text:Ye(ee(e,"QUADRANT_DELETE_DECK_BODY"),{count:n.cards.length}),attr:{style:"font-size:13px;color:var(--text-muted);line-height:1.6;"}});let o=r.createEl("div",{attr:{style:"display:flex;gap:8px;justify-content:flex-end;padding-top:4px;"}});o.createEl("button",{text:ee(e,"DELETE_CANCEL_BTN"),attr:{style:"padding:7px 16px;border-radius:8px;border:1px solid var(--background-modifier-border);background:var(--background-secondary);color:var(--text-normal);cursor:pointer;font-size:13px;"}}).addEventListener("click",()=>i.close()),o.createEl("button",{text:ee(e,"DELETE_CONFIRM_BTN"),attr:{style:"padding:7px 16px;border-radius:8px;border:none;background:#ef4444;color:#fff;cursor:pointer;font-size:13px;font-weight:600;"}}).addEventListener("click",async()=>{i.close();let c=0;try{for(let p of n.cards)await wp(s,p.cardId),c++;new Mt.Notice(Ye(ee(e,"QUADRANT_DELETE_DECK_DONE"),{count:c}))}catch(p){console.error("EngramQuest: quadrant deck delete failed",p),new Mt.Notice(String(p&&p.message||p))}a()}),i.open()}function Ca(t,e,n,a,s){let i=t.createEl("button",{attr:{class:"lh-delete-btn",title:ee(n,"DELETE")}});return i.appendChild(Mt.sanitizeHTMLToDom(Sp)),i.addEventListener("click",r=>{r.stopPropagation(),Ap(e,n,a,s)}),i}function fi(t){let e=t.viewModes&&t.viewModes.quadrant;return e==="grid"||e==="tree"?e:"list"}async function Tp(t,e){t.viewModes=t.viewModes||{},t.viewModes.quadrant=e,t.plugin.settings._viewModes=t.viewModes;try{await t.plugin.saveData(t.plugin.settings)}catch(n){console.warn("EngramQuest: save view mode failed",n)}}async function bi(t,e){let n=e.plugin.settings,a=e.app.vault.adapter;t.empty(),e._qSearch=e._qSearch||"",e._qFilter=e._qFilter||"all";let s;try{s=await xp(a)}catch(E){console.error("EngramQuest: quadrant scan failed",E),s={cards:[],due:0,new:0,total:0}}let i=_p(e),r=yp(s.cards,i),o="",l=activeDocument.body.classList.contains("theme-dark"),c=!l&&!activeDocument.body.classList.contains("is-phone");try{o=a.getResourcePath(e.app.vault.configDir+"/plugins/engram-quest/"+(l?"stats_bg_dark.webp":"bg.png"))}catch(E){o=""}let p=t.createEl("div",{attr:{class:"lh-stats-header",style:o?`background-image:url('${o}')`:""}}),d=r.reduce((E,S)=>E+S.due,0),h=r.reduce((E,S)=>E+S.unseen,0),u=r.reduce((E,S)=>E+S.total,0),x=[{label:ee(n,"READY"),val:d+h,cls:"ready",left:"17%",top:"30%",fs:c?"32px":""},{label:ee(n,"FILTER_NEW"),val:h,cls:"new",left:"50%",top:"26%",fs:c?"30px":""},{label:ee(n,"TOTAL"),val:u,cls:"total",left:"79%",top:"30%",fs:c?"30px":""}].map(E=>{let S=p.createEl("div",{attr:{class:"lh-stat-grp",style:`left:${E.left};top:${E.top}`}});return S.createEl("span",{text:E.label,attr:{class:"lh-stat-label"}}),S.createEl("span",{text:String(E.val),attr:{class:`lh-stat-num ${E.cls}`,style:E.fs?`font-size:${E.fs}`:""}}),S});if(c&&bp(p,x),p.createEl("div",{text:ee(n,"READY_HELP"),attr:{class:"lh-stats-helper"}}),s.total===0){let E=t.createEl("div",{attr:{class:"lh-quadrant-empty"}});E.createEl("div",{attr:{style:"font-size:40px;text-align:center;color:#7dd3fc;"}}).innerHTML=qt("cards"),E.createEl("div",{text:ee(n,"QUADRANT_EMPTY_TITLE"),attr:{style:"font-size:15px;font-weight:700;text-align:center;margin-top:8px;color:var(--text-normal);"}}),E.createEl("div",{text:ee(n,"QUADRANT_EMPTY_BODY"),attr:{style:"font-size:13px;line-height:1.6;text-align:center;margin-top:8px;color:var(--text-muted);max-width:420px;margin-left:auto;margin-right:auto;"}});return}let m=()=>{t.isConnected&&bi(t,e)},g=t.createEl("div",{attr:{class:"lh-card"}}),b=g.createEl("div",{attr:{class:"lh-card-header"}});b.createEl("span",{text:ee(n,"TAB_QUADRANT"),attr:{class:"lh-card-title"}});let f=r.flatMap(E=>E.cards).filter(E=>E._due||E._new),v=b.createEl("button",{attr:{class:"lh-study-all"}});v.textContent=`\u25B6 ${ee(n,"STUDY_ALL")}`,v.addEventListener("click",()=>{if(f.length===0){new Mt.Notice(ee(n,"NOTHING_READY"));return}hn(e,{cards:f},m)});let y=g.createEl("div",{attr:{class:"lh-toolbar"}}),w=g.createEl("div",{attr:{style:"flex:1;overflow-y:auto"}}),k=()=>Rp(w,e,n,r,m);Cp(y,e,n,k),k()}function Cp(t,e,n,a){let s=t.createEl("div",{attr:{class:"lh-search"}});s.createEl("span",{text:"\u2315",attr:{class:"lh-search-icon"}});let i=s.createEl("input",{attr:{type:"text",placeholder:ee(n,"SEARCH_QUADRANT"),value:e._qSearch}});i.addEventListener("input",()=>{e._qSearch=i.value,a()});let r=t.createEl("select",{attr:{class:"lh-filter"}});[["all","FILTER_ALL"],["due","FILTER_DUE"],["new","FILTER_NEW"],["done","FILTER_DONE"]].forEach(([d,h])=>{let u=r.createEl("option",{text:ee(n,h),attr:{value:d}});d===e._qFilter&&(u.selected=!0)}),r.addEventListener("change",()=>{e._qFilter=r.value,a()});let o=t.createEl("div",{attr:{class:"lh-view-toggle"}}),l=fi(e),c=[],p=(d,h,u)=>{let x=o.createEl("button",{attr:{class:"lh-view-btn"+(l===h?" active":""),style:u||""}});return x.innerHTML=qt(d),x.addEventListener("click",async()=>{await Tp(e,h),c.forEach(m=>m.classList.remove("active")),x.classList.add("active"),a()}),c.push(x),x};p("list","list"),p("grid","grid"),p("tree","tree","font-size:12px;")}function Lp(t,e){let n=e,a=(t._qSearch||"").toLowerCase();a&&(n=n.filter(i=>i.name.toLowerCase().includes(a)));let s=t._qFilter||"all";return s==="due"?n=n.filter(i=>i.due>0):s==="new"?n=n.filter(i=>i.unseen>0):s==="done"&&(n=n.filter(i=>i.due===0&&i.unseen===0)),n}function Rp(t,e,n,a,s){t.empty();let i=Lp(e,a);if(i.length===0){t.createEl("div",{attr:{class:"lh-empty"}}).textContent=ee(n,"EMPTY_FILTERED_DECKS");return}let r=fi(e);r==="list"?Ip(t,e,n,i,s):r==="grid"?Mp(t,e,n,i,s):qp(t,e,n,i,s)}function Ip(t,e,n,a,s){let i=t.createEl("div",{attr:{class:"lh-deck-list"}});a.forEach((r,o)=>{let l=r.due+r.unseen,c=i.createEl("div",{attr:{class:`lh-deck-row${l===0?" is-emptyready":""}`}});c.createEl("span",{text:">",attr:{class:"lh-deck-chevron"}});let p=c.createEl("div",{attr:{class:"lh-deck-main"}}),d=p.createEl("div",{attr:{class:"lh-deck-row-name",style:"display:flex;align-items:center;gap:8px;"}});d.createEl("span",{attr:{style:"font-size:16px;line-height:1;"}}).innerHTML=qt(ot[o%ot.length]),d.createEl("span",{text:r.name}),p.createEl("div",{text:l>0?`${ee(n,"READY_NOW")}: ${l}`:Ye(ee(n,"ALL_SCHEDULED"),{total:r.total}),attr:{class:"lh-deck-row-sub"}}),xi(c,n,r,l),Ca(c,e,n,r,s),c.title=l===0?Ye(ee(n,"NO_READY_IN_DECK"),{deck:r.name,total:r.total}):"",c.addEventListener("click",()=>hn(e,r,s))})}function xi(t,e,n,a){let s=t.createEl("div",{attr:{class:"lh-deck-metrics"}}),i=s.createEl("div",{attr:{class:`lh-deck-ready${a===0?" is-zero":""}`}});i.createEl("div",{text:ee(e,"READY"),attr:{class:"lh-deck-ready-label"}}),i.createEl("div",{text:String(a),attr:{class:"lh-deck-ready-num"}});let r=s.createEl("div",{attr:{class:"lh-deck-breakdown"}});[{key:"FILTER_DUE",cls:"due",value:n.due},{key:"FILTER_NEW",cls:"new",value:n.unseen},{key:"TOTAL",cls:"total",value:n.total}].forEach(o=>{let l=r.createEl("div",{attr:{class:`lh-deck-metric ${o.cls}`}});l.createEl("div",{text:ee(e,o.key),attr:{class:"lh-deck-metric-label"}}),l.createEl("div",{text:String(o.value),attr:{class:"lh-deck-metric-num"}})})}function Mp(t,e,n,a,s){let i=t.createEl("div",{attr:{class:"lh-deck-grid"}});a.forEach((r,o)=>{let l=r.due+r.unseen,c=r.cards.filter(vp).length,p=r.total>0?Math.round(c/r.total*100):0,d=i.createEl("div",{attr:{class:`lh-deck-gc${r.due>0?" has-due":""}${l===0?" is-emptyready":""}`}});d.createEl("div",{attr:{class:"lh-deck-gc-emoji"}}).innerHTML=qt(ot[o%ot.length]),d.createEl("div",{attr:{class:"lh-deck-gc-name"}}).textContent=r.name,d.createEl("div",{text:l>0?ee(n,"READY_NOW"):ee(n,"NOTHING_READY"),attr:{class:"lh-deck-gc-status"}});let h=d.createEl("div",{attr:{class:"lh-deck-gc-ready"}});h.createEl("div",{text:String(l),attr:{class:"lh-deck-gc-ready-num"}}),h.createEl("div",{text:ee(n,"READY"),attr:{class:"lh-deck-gc-ready-label"}}),d.createEl("div",{attr:{class:"lh-deck-gc-bar-bg"}}).createEl("div",{attr:{class:"lh-deck-gc-bar",style:`width:${p}%`}}),d.createEl("div",{attr:{class:"lh-deck-gc-sub"}}).textContent=ee(n,"MASTERY_RATE",{percent:p});let u=d.createEl("div",{attr:{class:"lh-deck-gc-metrics"}});[{key:"FILTER_DUE",cls:"due",value:r.due},{key:"FILTER_NEW",cls:"new",value:r.unseen},{key:"TOTAL",cls:"total",value:r.total}].forEach(x=>{let m=u.createEl("div",{attr:{class:`lh-deck-gc-metric ${x.cls}`}});m.createEl("div",{text:ee(n,x.key),attr:{class:"lh-deck-gc-metric-label"}}),m.createEl("div",{text:String(x.value),attr:{class:"lh-deck-gc-metric-num"}})}),Ca(d,e,n,r,s),d.title=l===0?Ye(ee(n,"NO_READY_IN_DECK"),{deck:r.name,total:r.total}):"",d.addEventListener("click",()=>hn(e,r,s))})}function qp(t,e,n,a,s){let i=t.createEl("div",{attr:{class:"lh-tree-container"}}),r=e._buildTree(a,l=>l.name.replace(/^#/,"")),o=0;e._renderTreeRecursive(i,r,(l,c)=>{let p=c.due+c.unseen,d=l.createEl("div",{attr:{class:`lh-deck-row lh-tree-item-row${p===0?" is-emptyready":""}`}}),h=d.createEl("div",{attr:{class:"lh-deck-main"}}),u=h.createEl("div",{attr:{class:"lh-deck-row-name",style:"display:flex;align-items:center;gap:8px;"}});u.createEl("span",{attr:{style:"font-size:16px;line-height:1;"}}).innerHTML=qt(ot[o%ot.length]),u.createEl("span",{text:c.name.split("/").pop()}),h.createEl("div",{text:p>0?`${ee(n,"READY_NOW")}: ${p}`:Ye(ee(n,"ALL_SCHEDULED"),{total:c.total}),attr:{class:"lh-deck-row-sub"}}),xi(d,n,c,p),Ca(d,e,n,c,s),d.title=p===0?Ye(ee(n,"NO_READY_IN_DECK"),{deck:c.name,total:c.total}):"",d.addEventListener("click",()=>hn(e,c,s)),o++})}yi.exports={renderQuadrantTab:bi}});var _i=Y((lg,ki)=>{"use strict";var Ei=require("obsidian"),{t:un,interpolate:Np,getLocale:Dp}=ge(),{LESSONS_DIR:Op,markLesson:zp,lessonCompletion:Pp}=xt(),{giHTML:wi}=be(),La=Ei.moment;function Hp(t){return Dp(t,La&&La.locale&&La.locale())}var $p=`
<script>
(function () {
  var NARROW = 600, MIN_COL = 180, touched = [];
  function fix() {
    if ((window.innerWidth || 0) > NARROW) {
      touched.forEach(function (el) { el.style.gridTemplateColumns = ""; });
      touched = [];
      return;
    }
    var all = document.querySelectorAll("*");
    for (var i = 0; i < all.length; i++) {
      var el = all[i], s = getComputedStyle(el);
      if (s.display !== "grid") continue;
      var cols = (s.gridTemplateColumns || "").split(" ").filter(Boolean).length;
      if (cols > 1 && el.clientWidth / cols < MIN_COL) {
        el.style.gridTemplateColumns = "1fr";
        if (touched.indexOf(el) < 0) touched.push(el);
      }
    }
  }
  window.addEventListener("resize", fix);
  if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", fix);
  else fix();
})();
<\/script>`,Ra=class extends Ei.Modal{constructor(e,n,a,s,i,r,o=Op){super(e),this.plugin=n,this.slug=a,this.meta=s,this.lesson=i,this.onDone=r,this.root=o,this._handler=null,this._isOpen=!1,this._iframe=null,this._changed=!1,this._saving=Promise.resolve()}_mark(e){return this._changed=!0,this._saving=this._saving.then(()=>zp(this.app.vault.adapter,this.slug,this.lesson.id,e,this.root)).catch(n=>console.error("EngramQuest: lesson mark failed",n)),this._saving}onOpen(){let e=this.plugin.settings,n=Hp(e)==="zh-tw",{contentEl:a,modalEl:s}=this;this._isOpen=!0,s.style.cssText="width:min(96vw,900px);max-height:96vh;padding:0;border-radius:16px;overflow:hidden;display:flex;flex-direction:column;",a.style.cssText="padding:12px;display:flex;flex-direction:column;gap:8px;overflow-y:auto;flex:1;min-height:0;";let i=a.createEl("div",{attr:{style:"display:flex;align-items:center;gap:8px;padding-right:36px;"}});i.createEl("div",{text:this.lesson.title,attr:{style:"flex:1;min-width:0;font-size:15px;font-weight:700;color:var(--text-normal);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;",title:this.lesson.title}}),i.createEl("span",{text:this.meta.title,attr:{style:"flex-shrink:0;font-size:11px;color:var(--text-muted);"}});let r=a.createEl("div",{attr:{style:"flex:1;min-height:0;border:1px solid var(--background-modifier-border);border-radius:14px;overflow:hidden;background:var(--background-secondary)"}}),o=Pp(this.meta,this.lesson.id),l=a.createEl("div",{attr:{style:"display:flex;align-items:center;gap:8px;padding:0 2px;"}});l.createEl("span",{text:this.meta.title+" \xB7 "+this.lesson.title,attr:{style:"flex:1;min-width:0;font-size:11px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"}});let c="flex-shrink:0;font-size:12px;padding:5px 14px;border-radius:8px;border:1px solid var(--background-modifier-border);background:var(--background-secondary);color:var(--text-normal);cursor:pointer;white-space:nowrap;",p=l.createEl("button",{attr:{style:c}}),d=v=>{p.innerHTML=wi("star")+" "+un(e,"LESSON_MARK_STAR"),p.style.color=v?"#fbbf24":"var(--text-normal)"};d(o.starred);let h=o.starred;p.addEventListener("click",()=>{h=!h,d(h),this._mark({starred:h})});let u=l.createEl("button",{attr:{style:c}}),x=v=>{u.innerHTML=v?wi("check")+" "+un(e,"LESSON_MARKED_DONE"):un(e,"LESSON_MARK_DONE"),u.style.background=v?"#22c55e":"var(--background-secondary)",u.style.color=v?"#fff":"var(--text-normal)",u.style.borderColor=v?"#22c55e":"var(--background-modifier-border)"};x(o.completed);let m=o.completed;u.addEventListener("click",()=>{m=!m,x(m),this._mark({completed:m})});let g=v=>{r.empty(),r.createEl("div",{text:v,attr:{style:"padding:14px 16px;color:var(--text-error);background:var(--background-modifier-error);border:1px solid var(--background-modifier-error);border-radius:10px;font-size:13px;line-height:1.5"}})};this._handler=v=>{if(!this._isOpen)return;let y=this._iframe;if(y&&v.source&&v.source!==y.contentWindow)return;let w=v.data||{};if(w&&w.type==="engram-quest-resize"&&y){let E=Math.max(360,Math.floor(((typeof activeWindow!="undefined"&&activeWindow||window).innerHeight||900)*.86)),S=Math.max(240,Math.min(E,Math.round(Number(w.height)||600)));y.style.height=S+"px"}},this._win=typeof activeWindow!="undefined"&&activeWindow||window,this._win.addEventListener("message",this._handler);let b=`${this.root}/${this.slug}/${this.lesson.file}`,f=this.app.vault.adapter;(async()=>{try{if(f.exists&&!await f.exists(b)){g(Np(un(e,"LESSON_NOT_FOUND"),{file:this.lesson.file}));return}let v=await f.read(b);if(!this._isOpen)return;let w=Math.max(360,Math.floor(((typeof activeWindow!="undefined"&&activeWindow||window).innerHeight||900)*.82)),k=r.createEl("iframe",{attr:{sandbox:"allow-scripts allow-same-origin allow-popups",srcdoc:v+$p,style:`display:block;width:100%;height:${w}px;border:0;background:white;`}});this._iframe=k,k.addEventListener("load",()=>{var E;(E=k.contentWindow)==null||E.postMessage({type:"engram-quest-theme",dark:activeDocument.body.classList.contains("theme-dark")},"*")}),this._mark({viewed:!0})}catch(v){g((n?"\u8AB2\u6642\u8F09\u5165\u5931\u6557\uFF1A":"Failed to load lesson: ")+String(v&&v.message||v))}})()}onClose(){this._isOpen=!1,this._handler&&((this._win||window).removeEventListener("message",this._handler),this._handler=null,this._win=null),this._iframe=null,this.contentEl.empty(),this._changed&&this.onDone&&this.onDone()}};ki.exports={LessonViewerModal:Ra}});var Pi=Y((cg,zi)=>{"use strict";var ke=require("obsidian"),{t:H,interpolate:Ke}=ge(),{listCourses:Fp,lessonCompletion:Si,lessonStatus:Ai,courseProgress:gn,toggleCourseStar:Qp,toggleCourseArchive:Bp,importLesson:Up,deleteLesson:jp,deleteCourse:Gp,createCourse:Wp,addPlannedLesson:Yp,recentLessons:Kp,overallStats:Vp}=xt(),{LessonViewerModal:Ci}=_i(),{attachInfoButton:Jp}=Zt(),{giHTML:ye}=be(),Ma={indigo:{dark:{bg:"#1a1d2e",icon:"#3730a3",bar:"#6366f1",edge:"#6366f1"},light:{bg:"#eef2ff",icon:"#4f46e5",bar:"#6366f1",edge:"#6366f1"}},green:{dark:{bg:"#0d1f18",icon:"#065f46",bar:"#10b981",edge:"#10b981"},light:{bg:"#ecfdf5",icon:"#059669",bar:"#10b981",edge:"#059669"}},amber:{dark:{bg:"#1c1710",icon:"#92400e",bar:"#f59e0b",edge:"#f59e0b"},light:{bg:"#fffbeb",icon:"#d97706",bar:"#f59e0b",edge:"#d97706"}},rose:{dark:{bg:"#1f0d10",icon:"#9f1239",bar:"#f43f5e",edge:"#f43f5e"},light:{bg:"#fff1f2",icon:"#e11d48",bar:"#f43f5e",edge:"#e11d48"}},cyan:{dark:{bg:"#0a1a1f",icon:"#164e63",bar:"#06b6d4",edge:"#06b6d4"},light:{bg:"#ecfeff",icon:"#0891b2",bar:"#06b6d4",edge:"#0891b2"}},violet:{dark:{bg:"#1b1430",icon:"#6d28d9",bar:"#8b5cf6",edge:"#8b5cf6"},light:{bg:"#f5f3ff",icon:"#7c3aed",bar:"#8b5cf6",edge:"#7c3aed"}},blue:{dark:{bg:"#0b1a31",icon:"#1d4ed8",bar:"#3b82f6",edge:"#3b82f6"},light:{bg:"#eff6ff",icon:"#2563eb",bar:"#3b82f6",edge:"#2563eb"}},teal:{dark:{bg:"#08201f",icon:"#0f766e",bar:"#14b8a6",edge:"#14b8a6"},light:{bg:"#f0fdfa",icon:"#0f766e",bar:"#14b8a6",edge:"#0f766e"}},lime:{dark:{bg:"#17200b",icon:"#4d7c0f",bar:"#84cc16",edge:"#84cc16"},light:{bg:"#f7fee7",icon:"#65a30d",bar:"#84cc16",edge:"#65a30d"}},orange:{dark:{bg:"#241209",icon:"#c2410c",bar:"#f97316",edge:"#f97316"},light:{bg:"#fff7ed",icon:"#ea580c",bar:"#f97316",edge:"#ea580c"}},fuchsia:{dark:{bg:"#240f24",icon:"#a21caf",bar:"#d946ef",edge:"#d946ef"},light:{bg:"#fdf4ff",icon:"#c026d3",bar:"#d946ef",edge:"#c026d3"}},slate:{dark:{bg:"#111827",icon:"#334155",bar:"#94a3b8",edge:"#94a3b8"},light:{bg:"#f8fafc",icon:"#475569",bar:"#64748b",edge:"#475569"}}},Ti=Object.keys(Ma);function Li(t){let e=0;for(let n=0;n<t.length;n++)e=e*31+t.charCodeAt(n)|0;return Math.abs(e)}function Zp(t,e,n){let a=e.colorScheme;return Ma[a]||(a=Ti[Li(t)%Ti.length]),Ma[a][n?"dark":"light"]}function Xp(t,e,n){let a=Li(t),s=18+a%64,i=14+(a>>3)%58,r=120+a%90,o=n?"22":"18";return[`radial-gradient(circle at ${s}% ${i}%, ${e.edge}${o} 0, transparent 34%)`,`linear-gradient(${r}deg, ${e.bg} 0%, ${e.bg} 68%, ${e.icon}${n?"38":"20"} 100%)`,e.bg].join(",")}function eh(){return activeDocument.body.classList.contains("theme-dark")}function th(t){return t?{text:"#e2e8f0",muted:"#94a3b8",faint:"#475569",border:"#1e2640",panel:"#111827",row:"#13182a",rowHover:"#1a1d2e",badgeBg:"#1e2640",badgeText:"#94a3b8",pillIdleBg:"#1e2640",pillIdleText:"#94a3b8",track:"#1e2640"}:{text:"#1e293b",muted:"#64748b",faint:"#94a3b8",border:"#e2e8f0",panel:"#ffffff",row:"#ffffff",rowHover:"#f8fafc",badgeBg:"#e2e8f0",badgeText:"#64748b",pillIdleBg:"#f1f5f9",pillIdleText:"#64748b",track:"#e2e8f0"}}function Ri(t,e,n){return e==="planned"?{icon:"card",color:"#94a3b8",label:H(t,"LESSON_STATUS_PLANNED")}:e==="completed"?n?{icon:"star",color:"#fbbf24",label:H(t,"LESSON_STATUS_DONE")}:{icon:"check",color:"#22c55e",label:H(t,"LESSON_STATUS_DONE")}:e==="viewed"?n?{icon:"star",color:"#fbbf24",label:H(t,"LESSON_STATUS_VIEWED")}:{icon:"eye",color:"#94a3b8",label:H(t,"LESSON_STATUS_VIEWED")}:n?{icon:"star",color:"#fbbf24",label:H(t,"LESSON_STATUS_NEW")}:{icon:"target",color:"#64748b",label:H(t,"LESSON_STATUS_NEW")}}function mn(t,e){return t.slug===e.slug&&t.root===e.root}async function Ii(t,e){let n=e.plugin.settings,a=e.app.vault.adapter,s=await Fp(a,n.lessonRoots);if(!e._lessonState){let m=e.plugin.settings._lessonCourseView||{};e._lessonState={slug:null,root:null,filter:"all",courseQuery:"",expanded:!1,courseTag:m.tag||"all",courseProg:m.prog||"all",courseSort:m.sort||"recent",courseScope:m.scope||"active"}}let i=e._lessonState;i.courseScope==="archived"&&!s.some(m=>m.meta.archived)&&(i.courseScope="active");let r=s.filter(m=>qa(m.meta,i.courseScope));r.some(m=>mn(m,i))||(i.slug=r.length?r[0].slug:null,i.root=r.length?r[0].root:null);let o=()=>{t.empty(),Ii(t,e)},l=eh(),c=th(l),p=t.createEl("div",{attr:{style:"flex:1;min-height:0;overflow-y:auto;padding:14px 18px 20px;"}});if(s.length===0){nh(p,n,c);return}let d=p.createEl("div",{attr:{style:"display:flex;gap:16px;align-items:flex-start;flex-wrap:wrap;"}}),h=d.createEl("div",{attr:{style:i.expanded?"width:100%;min-width:0;":"flex:1 1 0;min-width:360px;"}}),u=null;i.expanded||(u=d.createEl("div",{attr:{style:`width:210px;flex-shrink:0;background:${c.panel};border:1px solid ${c.border};border-radius:14px;padding:16px;`}})),ah(h,e,n,c,r,i,o),ih(h,e,n,c,l,s,i,o);let x=r.find(m=>mn(m,i)&&Mi(m.meta,i.courseQuery)&&Ni(m.meta,i.courseTag)&&Di(m.meta,i.courseProg));x&&!i.expanded&&lh(h,e,n,c,x,i,o),u&&hh(u,e,n,c,r,o)}function nh(t,e,n){let a=t.createEl("div",{attr:{style:"display:flex;flex-direction:column;align-items:center;gap:10px;padding:60px 24px;text-align:center;"}});a.createEl("div",{attr:{style:"font-size:40px;color:#7dd3fc;"}}).innerHTML=ye("cap"),a.createEl("div",{text:H(e,"LESSON_NO_COURSES_TITLE"),attr:{style:`font-size:17px;font-weight:700;color:${n.text};`}}),a.createEl("div",{text:H(e,"LESSON_NO_COURSES_BODY"),attr:{style:`font-size:13px;color:${n.muted};line-height:1.7;max-width:440px;`}})}function ah(t,e,n,a,s,i,r){let o=t.createEl("div",{attr:{style:"margin-bottom:14px;"}}),l=o.createEl("div",{attr:{style:"display:flex;align-items:center;gap:10px;flex-wrap:wrap;"}});if(l.createEl("span",{attr:{style:"font-size:22px;color:#7dd3fc;"}}).innerHTML=ye("cap"),l.createEl("span",{text:H(n,"LESSON_ACADEMY_TITLE"),attr:{style:`min-width:0;font-size:19px;font-weight:800;color:${a.text};letter-spacing:0.5px;`}}),e&&e.app&&e.plugin&&Jp(l,e.app,e.plugin,"lesson"),l.createEl("span",{attr:{style:"flex:1;min-width:0;"}}),s.length>=6||i.courseQuery){let p=l.createEl("input",{attr:{type:"search",placeholder:H(n,"LESSON_SEARCH_PH"),value:i.courseQuery||"",style:`flex-shrink:0;width:180px;padding:6px 12px;border-radius:99px;border:1px solid ${a.border};background:${a.panel};color:${a.text};font-size:12px;`}});p.addEventListener("input",()=>{i.courseQuery=p.value,i._searchFocus=!0,r()}),i._searchFocus&&(i._searchFocus=!1,window.setTimeout(()=>{p.focus(),p.setSelectionRange(p.value.length,p.value.length)},0))}l.createEl("button",{text:"\uFF0B "+H(n,"LESSON_NEW_COURSE"),attr:{style:`flex-shrink:0;font-size:12px;padding:6px 14px;border-radius:99px;border:1px dashed ${a.border};background:transparent;color:${a.muted};cursor:pointer;`,title:H(n,"LESSON_NEW_COURSE_HINT")}}).addEventListener("click",()=>oh(e,n,i,r)),s.length>=2&&l.createEl("button",{text:i.expanded?"\u2594 "+H(n,"LESSON_COLLAPSE"):"\u229E "+H(n,"LESSON_EXPAND_ALL"),attr:{style:"flex-shrink:0;font-size:12px;padding:6px 12px;border-radius:99px;cursor:pointer;"+(i.expanded?"border:1px solid #6366f1;background:#6366f1;color:#fff;":`border:1px solid ${a.border};background:transparent;color:${a.muted};`)}}).addEventListener("click",()=>{i.expanded=!i.expanded,r()}),o.createEl("div",{text:H(n,"LESSON_ACADEMY_SUBTITLE"),attr:{style:`font-size:12px;color:${a.muted};margin-top:2px;`}})}function Mi(t,e){if(!e)return!0;let n=e.toLowerCase();return t.title.toLowerCase().includes(n)||t.topic.toLowerCase().includes(n)||t.tags.some(a=>a.toLowerCase().includes(n))}function qi(t){let e="";for(let n of Object.keys(t.completion||{})){let a=t.completion[n]&&t.completion[n].lastViewed;a&&a>e&&(e=a)}return e}function Ni(t,e){return!e||e==="all"?!0:e==="starred"?!!t.starred:t.tags.includes(e)}function rh(t){let e=gn(t);return e.total>0&&e.completed===e.total?"done":e.completed>0||qi(t)?"doing":"new"}function Di(t,e){return!e||e==="all"?!0:rh(t)===e}function qa(t,e){return e==="archived"?!!t.archived:!t.archived}async function Ia(t,e){t.plugin.settings._lessonCourseView={tag:e.courseTag,prog:e.courseProg,sort:e.courseSort,scope:e.courseScope};try{await t.plugin.saveData(t.plugin.settings)}catch(n){console.warn("EngramQuest: save course view failed",n)}}function sh(t,e,n,a,s,i,r){let o=s.filter(({meta:u})=>u.archived).length;i.courseScope==="archived"&&o===0&&(i.courseScope="active",Ia(e,i));let l=s.filter(({meta:u})=>qa(u,i.courseScope)),c=new Map;for(let{meta:u}of l)for(let x of u.tags)c.set(x,(c.get(x)||0)+1);let p=[...c.keys()].sort((u,x)=>c.get(x)-c.get(u));i.courseTag!=="all"&&i.courseTag!=="starred"&&!c.has(i.courseTag)&&(i.courseTag="all",Ia(e,i));let d=t.createEl("div",{attr:{style:"display:flex;gap:6px;flex-wrap:wrap;align-items:center;padding:0 2px 8px;"}}),h=(u,x,m,g)=>{let b=d.createEl("select",{attr:{style:`font-size:11px;padding:4px 8px;border-radius:14px;cursor:pointer;max-width:140px;background:${a.panel};`+(m?`border:1px solid ${a.border};color:${a.muted};`:"border:1px solid #6366f1;color:#6366f1;font-weight:600;")}});for(let f of u)b.createEl("option",{text:f.label,attr:{value:f.id}});b.value=x,b.addEventListener("change",async()=>{g(b.value),await Ia(e,i),r()})};h([{id:"all",label:H(n,"LESSON_CFILTER_TAG_ALL")},{id:"starred",label:H(n,"LESSON_CFILTER_STARRED")},...p.map(u=>({id:u,label:u}))],i.courseTag,i.courseTag==="all",u=>{i.courseTag=u}),h([{id:"all",label:H(n,"LESSON_CFILTER_PROG_ALL")},{id:"new",label:H(n,"LESSON_CFILTER_PROG_NEW")},{id:"doing",label:H(n,"LESSON_CFILTER_PROG_DOING")},{id:"done",label:H(n,"LESSON_CFILTER_PROG_DONE")}],i.courseProg,i.courseProg==="all",u=>{i.courseProg=u}),h([{id:"recent",label:H(n,"LESSON_CSORT_RECENT")},{id:"created",label:H(n,"LESSON_CSORT_CREATED")},{id:"progress",label:H(n,"LESSON_CSORT_PROGRESS")}],i.courseSort,i.courseSort==="recent",u=>{i.courseSort=u}),(o>0||i.courseScope==="archived")&&h([{id:"active",label:H(n,"LESSON_SCOPE_ACTIVE")},{id:"archived",label:H(n,"LESSON_SCOPE_ARCHIVED")}],i.courseScope,i.courseScope==="active",u=>{i.courseScope=u,i.courseTag="all",i.courseProg="all",i.slug=null})}function ih(t,e,n,a,s,i,r,o){let l=i.filter(({meta:m})=>qa(m,r.courseScope));(i.length>=2||r.courseTag!=="all"||r.courseProg!=="all"||i.some(({meta:m})=>m.archived))&&sh(t,e,n,a,i,r,o);let c=t.createEl("div",{attr:{style:r.expanded?"display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px;align-items:stretch;padding:4px 2px 12px;":"display:flex;gap:12px;padding:4px 2px 12px;overflow-x:auto;"}}),p=m=>`${m.root}\0${m.slug}`,d=new Map(l.map(m=>[p(m),qi(m.meta)])),h={recent:(m,g)=>d.get(p(g)).localeCompare(d.get(p(m)))||String(g.meta.createdAt||"").localeCompare(String(m.meta.createdAt||"")),created:(m,g)=>String(g.meta.createdAt||"").localeCompare(String(m.meta.createdAt||"")),progress:(m,g)=>gn(g.meta).pct-gn(m.meta).pct},u=h[r.courseSort]||h.recent,x=l.filter(({meta:m})=>Mi(m,r.courseQuery)&&Ni(m,r.courseTag)&&Di(m,r.courseProg)).sort(u);x.length&&!x.some(m=>mn(m,r))&&(r.slug=x[0].slug,r.root=x[0].root),x.length===0&&c.createEl("div",{text:r.courseScope==="archived"?H(n,"LESSON_ARCHIVE_EMPTY"):"\u2014",attr:{style:`padding:24px;color:${a.faint};font-size:13px;`}});for(let{slug:m,meta:g,root:b}of x){let f=Zp(m,g,s),v=mn({slug:m,root:b},r),y=gn(g),w=c.createEl("div",{attr:{style:`${r.expanded?"min-width:0;":"flex-shrink:0;width:240px;"}border-radius:14px;padding:14px;cursor:pointer;position:relative;overflow:hidden;background:${Xp(m,f,s)};border:1px solid ${v?f.edge:a.border};`+(v?`box-shadow:0 0 0 1px ${f.edge},0 4px 18px ${f.edge}33;`:"")+"display:flex;flex-direction:column;gap:8px;transition:box-shadow .15s;"}});w.addEventListener("click",()=>{r.slug=m,r.root=b,r.expanded&&(r.expanded=!1),o()});let k=w.createEl("div",{attr:{style:"display:flex;align-items:center;gap:10px;position:relative;z-index:1;"}});k.createEl("div",{text:g.icon,attr:{style:`width:42px;height:42px;border-radius:13px;background:${f.icon};display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0;box-shadow:inset 0 0 0 1px rgba(255,255,255,.14),0 8px 18px ${f.edge}22;`}});let E=k.createEl("div",{attr:{style:"flex:1;min-width:0;"}});E.createEl("div",{text:g.title,attr:{style:`min-height:36px;font-size:14px;font-weight:700;line-height:1.25;color:${a.text};overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;word-break:break-word;`,title:g.title}}),E.createEl("div",{text:Ke(H(n,"LESSON_COURSE_DONE"),{completed:y.completed,total:y.total}),attr:{style:`font-size:10px;color:${a.muted};`}});let S=k.createEl("span",{attr:{style:`flex-shrink:0;font-size:15px;cursor:pointer;color:${g.starred?"#fbbf24":a.faint};padding:2px;`}});if(S.innerHTML=ye("star"),S.addEventListener("click",async A=>{A.stopPropagation();try{await Qp(e.app.vault.adapter,m,b),o()}catch(T){console.error("EngramQuest: course star failed",T)}}),g.archived&&w.createEl("span",{text:H(n,"LESSON_ARCHIVED_BADGE"),attr:{style:`align-self:flex-start;font-size:9px;padding:2px 7px;border-radius:10px;background:${a.pillIdleBg};color:${a.pillIdleText};`}}),g.tags.length){let A=w.createEl("div",{attr:{style:"display:flex;gap:4px;flex-wrap:wrap;"}});for(let T of g.tags.slice(0,2))A.createEl("span",{text:T,attr:{style:`font-size:9px;padding:2px 7px;border-radius:10px;background:${a.pillIdleBg};color:${a.pillIdleText};`}})}g.description&&w.createEl("div",{text:g.description,attr:{style:`font-size:11px;color:${a.muted};line-height:1.5;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;`}});let _=w.createEl("div",{attr:{style:"display:flex;align-items:center;gap:8px;margin-top:auto;"}});_.createEl("div",{attr:{style:`flex:1;height:5px;border-radius:3px;background:${a.track};overflow:hidden;`}}).createEl("div",{attr:{style:`height:100%;width:${y.pct}%;border-radius:3px;background:${f.bar};`}});let L=_.createEl("span",{attr:{style:y.pct===100&&y.total>0?"font-size:10px;color:#22c55e;font-weight:700;":`font-size:10px;color:${a.muted};`}});y.pct===100&&y.total>0?L.innerHTML=ye("check")+" 100%":L.textContent=y.pct+"%"}}function oh(t,e,n,a){let s=new ke.Modal(t.app);s.modalEl.style.cssText="width:min(94vw,480px);padding:0;border-radius:16px;overflow:hidden;";let i=s.contentEl;i.style.cssText="padding:24px;display:flex;flex-direction:column;gap:12px;",i.createEl("div",{text:H(e,"LESSON_CREATE_TITLE"),attr:{style:"font-size:16px;font-weight:700;color:var(--text-normal);"}});let r="width:100%;padding:8px 10px;border-radius:8px;border:1px solid var(--background-modifier-border);background:var(--background-primary);color:var(--text-normal);font-size:13px;box-sizing:border-box;",o=u=>i.createEl("div",{text:u,attr:{style:"font-size:12px;color:var(--text-muted);margin-bottom:-6px;"}});o(H(e,"LESSON_CREATE_NAME_LABEL"));let l=i.createEl("input",{attr:{type:"text",style:r}});o(H(e,"LESSON_CREATE_DESC_LABEL"));let c=i.createEl("input",{attr:{type:"text",style:r}});o(H(e,"LESSON_CREATE_OUTLINE_LABEL"));let p=i.createEl("textarea",{attr:{rows:"5",placeholder:H(e,"LESSON_CREATE_OUTLINE_PH"),style:r+"resize:vertical;font-family:inherit;"}}),d=i.createEl("div",{attr:{style:"display:flex;gap:8px;justify-content:flex-end;padding-top:4px;"}});d.createEl("button",{text:H(e,"DELETE_CANCEL_BTN"),attr:{style:"padding:7px 16px;border-radius:8px;border:1px solid var(--background-modifier-border);background:var(--background-secondary);color:var(--text-normal);cursor:pointer;font-size:13px;"}}).addEventListener("click",()=>s.close());let h=d.createEl("button",{text:H(e,"LESSON_CREATE_SAVE"),attr:{style:"padding:7px 16px;border-radius:8px;border:none;background:#6366f1;color:#fff;cursor:pointer;font-size:13px;font-weight:600;"}});h.addEventListener("click",async()=>{let u=l.value.trim();if(!u){new ke.Notice(H(e,"LESSON_CREATE_NAME_REQUIRED"));return}if(!h.disabled){h.disabled=!0;try{let x=e.lessonRoots&&e.lessonRoots[0]||"engram-quest/lessons",{slug:m,root:g}=await Wp(t.app.vault.adapter,{title:u,description:c.value.trim(),outline:p.value.split(`
`),root:x});n.slug=m,n.root=g,s.close(),new ke.Notice(H(e,"LESSON_CREATED")),a()}catch(x){console.error("EngramQuest: course create failed",x),new ke.Notice(H(e,"LESSON_CREATE_FAILED")),h.disabled=!1}}}),s.open(),l.focus()}function lh(t,e,n,a,s,i,r){let{slug:o,meta:l,root:c}=s,p=e.app.vault.adapter,d=t.createEl("div",{attr:{style:"display:flex;align-items:center;gap:8px;margin:6px 0 10px;flex-wrap:wrap;"}});d.createEl("span",{attr:{style:"font-size:15px;color:#7dd3fc;"}}).innerHTML=ye("book"),d.createEl("span",{text:Ke(H(n,"LESSON_CONTENT_OF"),{course:l.title}),attr:{style:`flex:1 1 220px;min-width:220px;font-size:14px;font-weight:700;line-height:1.35;color:${a.text};overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;word-break:break-word;`,title:l.title}});let h=[{id:"all",label:H(n,"LESSON_FILTER_ALL")},{id:"todo",label:H(n,"LESSON_FILTER_TODO")},{id:"starred",icon:"star"},{id:"done",label:H(n,"LESSON_FILTER_DONE")}],u=d.createEl("div",{attr:{style:"display:flex;gap:4px;"}});for(let y of h){let w=i.filter===y.id,k=u.createEl("button",{attr:{style:"font-size:11px;padding:4px 12px;border-radius:14px;border:none;cursor:pointer;"+(w?"background:#38bdf8;color:#04121f;font-weight:600;":`background:${a.pillIdleBg};color:${a.pillIdleText};`)}});y.icon?k.innerHTML=ye(y.icon):k.textContent=y.label,k.addEventListener("click",()=>{i.filter=y.id,r()})}let x=d.createEl("button",{attr:{style:`font-size:11px;padding:4px 10px;border-radius:8px;border:1px solid ${a.border};background:transparent;color:${a.muted};cursor:pointer;`}});x.innerHTML=ye("download")+" "+H(n,"LESSON_IMPORT"),x.addEventListener("click",()=>ch(e,n,o,c,r)),d.createEl("button",{text:l.archived?H(n,"LESSON_UNARCHIVE"):H(n,"LESSON_ARCHIVE"),attr:{style:`font-size:11px;padding:4px 10px;border-radius:8px;border:1px solid ${a.border};background:transparent;color:${a.muted};cursor:pointer;`,title:l.archived?H(n,"LESSON_UNARCHIVE_HINT"):H(n,"LESSON_ARCHIVE_HINT")}}).addEventListener("click",async()=>{try{let y=await Bp(p,o,c);new ke.Notice(y?Ke(H(n,"LESSON_ARCHIVED_NOTICE"),{course:l.title}):Ke(H(n,"LESSON_UNARCHIVED_NOTICE"),{course:l.title}))}catch(y){console.error("EngramQuest: course archive failed",y),new ke.Notice(H(n,"LESSON_ARCHIVE_FAILED"))}r()});let g=d.createEl("button",{attr:{style:`font-size:12px;padding:4px 8px;border-radius:8px;border:1px solid ${a.border};background:transparent;color:${a.muted};cursor:pointer;`,title:H(n,"DELETE")}});g.innerHTML=ye("trash"),g.addEventListener("click",()=>ph(e,n,o,c,l,r));let b=t.createEl("div",{attr:{style:`border:1px solid ${a.border};border-radius:14px;overflow:hidden;background:${a.row};`}}),f=l.lessons.filter(y=>{let w=Ai(l,y.id),k=Si(l,y.id);return i.filter==="todo"?w!=="completed":i.filter==="done"?w==="completed":i.filter==="starred"?k.starred:!0});f.length===0&&b.createEl("div",{text:"\u2014",attr:{style:`padding:20px;text-align:center;color:${a.faint};font-size:13px;`}}),f.forEach((y,w)=>{let k=l.lessons.indexOf(y),E=Ai(l,y.id),S=Si(l,y.id),_=Ri(n,E,S.starred),C=E==="completed",L=C?"rgba(34,197,94,.07)":"transparent",A=b.createEl("div",{attr:{style:`display:flex;align-items:center;gap:12px;padding:11px 14px;cursor:pointer;border-left:3px solid ${C?"#22c55e":"transparent"};background:${L};`+(w>0?`border-top:1px solid ${a.border};`:"")}});A.addEventListener("mouseenter",()=>{A.style.background=a.rowHover}),A.addEventListener("mouseleave",()=>{A.style.background=L}),A.addEventListener("click",()=>{if(!y.file){new ke.Notice(H(n,"LESSON_PLANNED_HINT"));return}new Ci(e.app,e.plugin,o,l,y,r,c).open()});let T=A.createEl("span",{attr:{style:"flex-shrink:0;width:30px;height:30px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-weight:700;"+(C?"background:#22c55e;color:#fff;font-size:14px;":`background:${a.badgeBg};color:${a.badgeText};font-size:11px;`)}});C?T.innerHTML=ye("check"):T.textContent=String(k+1).padStart(2,"0");let q=A.createEl("div",{attr:{style:"flex:1;min-width:0;"}}),I=q.createEl("div",{attr:{style:"display:flex;align-items:center;gap:6px;"}});I.createEl("span",{text:y.title,attr:{style:`flex:1;min-width:0;font-size:13px;font-weight:600;line-height:1.35;color:${a.text};overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;word-break:break-word;`,title:y.title}}),y.source==="import"&&(I.createEl("span",{attr:{style:`flex-shrink:0;font-size:9px;padding:1px 6px;border-radius:8px;background:${a.pillIdleBg};color:${a.pillIdleText};`}}).innerHTML=ye("download")+" "+H(n,"LESSON_SOURCE_IMPORT")),y.file&&q.createEl("div",{text:y.file,attr:{style:`font-size:10px;color:${a.faint};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`}}),C?A.createEl("span",{attr:{style:"flex-shrink:0;font-size:11px;font-weight:700;color:#22c55e;background:rgba(34,197,94,.14);border:1px solid rgba(34,197,94,.45);border-radius:99px;padding:3px 10px;"}}).innerHTML=(S.starred?ye("star"):ye("check"))+" "+_.label:(A.createEl("span",{attr:{style:`flex-shrink:0;font-size:15px;color:${_.color};`}}).innerHTML=ye(_.icon),A.createEl("span",{text:_.label,attr:{style:`flex-shrink:0;font-size:11px;color:${a.muted};width:52px;`}}));let R=A.createEl("button",{attr:{style:`flex-shrink:0;font-size:11px;padding:3px 6px;border-radius:6px;border:none;background:transparent;color:${a.faint};cursor:pointer;`,title:H(n,"DELETE")}});R.innerHTML=ye("trash"),R.addEventListener("click",O=>{O.stopPropagation(),dh(e,n,o,c,y,r)}),A.createEl("span",{text:"\u203A",attr:{style:`flex-shrink:0;font-size:15px;color:${a.faint};`}})});let v=b.createEl("div",{attr:{style:`display:flex;align-items:center;gap:8px;padding:9px 14px;cursor:pointer;color:${a.muted};font-size:12px;border-top:1px solid ${a.border};`}});v.createEl("span",{text:"\uFF0B"}),v.createEl("span",{text:H(n,"LESSON_ADD_OUTLINE")}),v.addEventListener("mouseenter",()=>{v.style.background=a.rowHover}),v.addEventListener("mouseleave",()=>{v.style.background="transparent"}),v.addEventListener("click",()=>{if(v.querySelector("input"))return;v.empty();let y=v.createEl("input",{attr:{type:"text",placeholder:H(n,"LESSON_ADD_OUTLINE_PH"),style:"flex:1;padding:5px 8px;border-radius:6px;border:1px solid var(--background-modifier-border);background:var(--background-primary);color:var(--text-normal);font-size:12px;"}});y.focus();let w=!1,k=async()=>{if(w)return;w=!0;let E=y.value.trim();if(!E){r();return}try{await Yp(p,o,E,c)}catch(S){console.error("EngramQuest: add outline failed",S)}r()};y.addEventListener("keydown",E=>{E.key==="Enter"&&k(),E.key==="Escape"&&(w=!0,r())}),y.addEventListener("blur",k)})}function ch(t,e,n,a,s){let i=activeDocument.createElement("input");i.type="file",i.accept=".html,.htm",i.addEventListener("change",()=>{let r=i.files&&i.files[0];if(!r)return;let o=new FileReader;o.onload=async()=>{try{let l=r.name.replace(/\.html?$/i,""),c=await Up(t.app.vault.adapter,n,String(o.result||""),l,a);c?(new ke.Notice(Ke(H(e,"LESSON_IMPORTED"),{title:c.title})),s()):new ke.Notice(H(e,"LESSON_IMPORT_FAILED"))}catch(l){console.error("EngramQuest: lesson import failed",l),new ke.Notice(H(e,"LESSON_IMPORT_FAILED"))}},o.onerror=()=>new ke.Notice(H(e,"LESSON_IMPORT_FAILED")),o.readAsText(r)}),i.click()}function Oi(t,e,n,a,s){let i=new ke.Modal(t.app);i.modalEl.style.cssText="width:min(92vw,420px);padding:0;border-radius:16px;overflow:hidden;";let r=i.contentEl;r.style.cssText="padding:24px;display:flex;flex-direction:column;gap:14px;",r.createEl("div",{text:n,attr:{style:"font-size:16px;font-weight:700;color:var(--text-normal);"}}),r.createEl("div",{text:a,attr:{style:"font-size:13px;color:var(--text-muted);line-height:1.6;"}});let o=r.createEl("div",{attr:{style:"display:flex;gap:8px;justify-content:flex-end;padding-top:4px;"}});o.createEl("button",{text:H(e,"DELETE_CANCEL_BTN"),attr:{style:"padding:7px 16px;border-radius:8px;border:1px solid var(--background-modifier-border);background:var(--background-secondary);color:var(--text-normal);cursor:pointer;font-size:13px;"}}).addEventListener("click",()=>i.close());let l=o.createEl("button",{text:H(e,"DELETE_CONFIRM_BTN"),attr:{style:"padding:7px 16px;border-radius:8px;border:none;background:#ef4444;color:#fff;cursor:pointer;font-size:13px;font-weight:600;"}});l.addEventListener("click",async()=>{l.disabled||(l.disabled=!0,i.close(),await s())}),i.open()}function dh(t,e,n,a,s,i){Oi(t,e,Ke(H(e,"DELETE_CONFIRM_TITLE"),{name:s.title}),H(e,"LESSON_DELETE_BODY"),async()=>{try{await jp(t.app.vault.adapter,n,s.id,a),new ke.Notice(H(e,"LESSON_DELETED"))}catch(r){console.error("EngramQuest: lesson delete failed",r)}i()})}function ph(t,e,n,a,s,i){Oi(t,e,Ke(H(e,"DELETE_CONFIRM_TITLE"),{name:s.title}),Ke(H(e,"LESSON_DELETE_COURSE_BODY"),{count:s.lessons.length}),async()=>{try{await Gp(t.app.vault.adapter,n,a),new ke.Notice(H(e,"LESSON_COURSE_DELETED"))}catch(r){console.error("EngramQuest: course delete failed",r)}i()})}function hh(t,e,n,a,s,i){let r=Vp(s);t.createEl("div",{text:H(n,"LESSON_PROGRESS_TITLE"),attr:{style:`font-size:13px;font-weight:700;color:${a.text};margin-bottom:12px;`}});let o=44,l=10,c=2*Math.PI*o,p=t.createEl("div",{attr:{style:"position:relative;width:110px;height:110px;margin:0 auto 14px;"}}),d="http://www.w3.org/2000/svg",h=p.ownerDocument.createElementNS(d,"svg");h.setAttribute("width","110"),h.setAttribute("height","110"),h.setAttribute("viewBox","0 0 110 110");let u=(f,v)=>{let y=p.ownerDocument.createElementNS(d,"circle");return y.setAttribute("cx","55"),y.setAttribute("cy","55"),y.setAttribute("r",String(o)),y.setAttribute("fill","none"),y.setAttribute("stroke",f),y.setAttribute("stroke-width",String(l)),y.setAttribute("stroke-linecap","round"),v!=null&&(y.setAttribute("stroke-dasharray",`${v} ${c}`),y.setAttribute("transform","rotate(-90 55 55)")),h.appendChild(y),y};u(a.track,null),u("#6366f1",r.pct/100*c),p.appendChild(h);let x=p.createEl("div",{attr:{style:"position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;"}});x.createEl("div",{text:r.pct+"%",attr:{style:`font-size:20px;font-weight:800;color:${a.text};`}}),x.createEl("div",{text:H(n,"LESSON_PROGRESS_OVERALL"),attr:{style:`font-size:9px;color:${a.muted};`}});let m=t.createEl("div",{attr:{style:"display:flex;gap:6px;margin-bottom:16px;"}}),g=(f,v,y)=>{let w=m.createEl("div",{attr:{style:`flex:1;text-align:center;padding:8px 2px;border-radius:10px;background:${a.badgeBg};`}});w.createEl("div",{attr:{style:"font-size:12px;color:#7dd3fc;"}}).innerHTML=ye(f),w.createEl("div",{text:String(v),attr:{style:`font-size:15px;font-weight:800;color:${a.text};`}}),w.createEl("div",{text:y,attr:{style:`font-size:8px;color:${a.muted};line-height:1.3;`}})};g("book",r.coursesInProgress,H(n,"LESSON_STAT_IN_PROGRESS")),g("star",r.coursesCompleted,H(n,"LESSON_STAT_COMPLETED")),g("clock",r.estimatedHours+"h",H(n,"LESSON_STAT_HOURS")),t.createEl("div",{text:H(n,"LESSON_RECENT_TITLE"),attr:{style:`font-size:13px;font-weight:700;color:${a.text};margin-bottom:8px;`}});let b=Kp(s,5);if(b.length===0){t.createEl("div",{text:H(n,"LESSON_RECENT_EMPTY"),attr:{style:`font-size:11px;color:${a.faint};line-height:1.5;`}});return}for(let f of b){let v=Ri(n,f.status,f.starred),y=s.find(E=>E.slug===f.slug&&E.root===f.root),w=t.createEl("div",{attr:{style:"display:flex;align-items:center;gap:8px;padding:7px 8px;border-radius:10px;cursor:pointer;margin-bottom:2px;"}});w.addEventListener("mouseenter",()=>{w.style.background=a.rowHover}),w.addEventListener("mouseleave",()=>{w.style.background="transparent"}),y&&w.addEventListener("click",()=>{new Ci(e.app,e.plugin,f.slug,y.meta,f.lesson,i,f.root).open()});let k=w.createEl("div",{attr:{style:"flex:1;min-width:0;"}});k.createEl("div",{text:f.lesson.title,attr:{style:`font-size:11px;font-weight:600;color:${a.text};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`}}),k.createEl("div",{text:f.courseTitle,attr:{style:`font-size:9px;color:${a.muted};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`}}),w.createEl("span",{attr:{style:`flex-shrink:0;font-size:12px;color:${v.color};`}}).innerHTML=ye(v.icon)}}zi.exports={renderLessonTab:Ii}});var Pa=Y((dg,Fi)=>{"use strict";var ne=require("obsidian"),{t:M,getLocale:uh,interpolate:gh}=ge(),Na=ne.moment;function mh(t){return uh(t,Na&&Na.locale&&Na.locale())}function Fe(t,e){return gh(String(t||""),e)}var{ReviewSessionModal:fn}=ia(),{HelpModal:fh}=en(),{layoutStatOrbs:bh}=da(),{giHTML:Ce}=be(),{FeatureGuideModal:xh,attachInfoButton:Da,hasFeatureGuide:yh}=Zt(),{scanReviewDecks:vh}=pa(),{getReviewStatus:Xe}=Se(),{renderSynapseProBanner:wh,renderTimeboxRow:Eh}=Ds(),za=Se(),{registerSelectionCopy:kh}=kt(),{deleteDeckCards:_h}=Kn(),{openCreateCardModal:Oa}=Fs(),{parseQuestMap:Sh}=Lt(),{listQuestPackages:Ah,questPackageToHubItem:Th}=ei(),{QuestViewerModal:Hi}=di(),bn=Rt();function Ch(t,e){return vh(t,e,za)}var{renderAchievementTab:Lh}=Wn(),{renderQuadrantTab:Rh}=vi(),{renderLessonTab:Ih}=Pi(),{listCourses:Mh}=xt(),$i="engram-quest-hub-mobile-patch";function qh(){if(activeDocument.getElementById($i))return;let t=activeDocument.createElement("style");t.id=$i,t.textContent=`
body.is-phone .lh-nav { overflow:hidden !important; }
body.is-phone .lh-nav > .lh-tabs {
  flex:1 1 auto;
  min-width:0;
  overflow-x:auto;
  overflow-y:hidden;
  -webkit-overflow-scrolling:touch;
  scrollbar-width:none;
}
body.is-phone .lh-nav > .lh-tabs::-webkit-scrollbar { display:none; }
body.is-phone .lh-nav .lh-tab { flex:0 0 auto; }
body.is-phone .lh-nav > div:last-of-type {
  display:flex !important;
  align-items:center;
  gap:4px;
  flex-shrink:0;
}
body.is-phone .lh-nav .lh-help-btn {
  display:flex !important;
  width:30px !important;
  height:30px !important;
  flex-shrink:0;
}
body.is-phone .lh-nav .lh-close {
  display:none !important;
}
body.is-phone .lh-card-header {
  flex-wrap:wrap;
  gap:8px;
}
body.is-phone .lh-study-all {
  width:100%;
  justify-content:center;
}
body.is-phone .lh-timebox-row { width:100%; justify-content:center; flex-wrap:wrap; }
body.is-phone .lh-timebox-btn { flex:1 1 auto; justify-content:center; }
body.is-phone .lh-tab-list-item {
  align-items:flex-start;
  flex-wrap:wrap;
  gap:8px;
  padding:12px 14px;
}
body.is-phone .lh-tab-list-item > div:last-of-type {
  width:100%;
  margin-left:52px;
}
body.is-phone .lh-mm-open { margin-top:6px; }
body.is-phone .lh-toolbar { flex-wrap:wrap; gap:6px; padding:0 10px 10px; }
body.is-phone .lh-search { flex:1 1 100%; order:-1; }
body.is-phone .lh-filter { flex:1 1 calc(50% - 6px); min-width:0; font-size:11px; padding:7px 8px; }
body.is-phone .lh-view-toggle { flex-shrink:0; margin-left:auto !important; }
body.is-phone .lh-toolbar > div[style*="position:relative"] { flex:1 1 calc(50% - 6px); min-width:0; margin-left:0 !important; }
body.is-phone .lh-mm-grid { grid-template-columns:repeat(2,1fr); gap:10px; padding:0 0 16px; }
body.is-phone .lh-mm-card { padding:12px 10px; gap:5px; border-radius:12px; }
body.is-phone .lh-mm-emoji { font-size:20px; }
body.is-phone .lh-mm-name { font-size:12px; }
body.is-phone .lh-mm-open { font-size:10px; margin-top:2px; }
body.is-phone .lh-deck-gc-metrics {
  gap:4px;
}
body.is-phone .lh-deck-gc-metric {
  min-width:0;
  padding:6px 4px;
}
`,activeDocument.head.appendChild(t)}var Nh=class xn extends ne.Modal{constructor(e,n){super(e),this.plugin=n,this.decks=[],this.quests=[],this.memories=[],this.courses=[],this.activeTab="review",this.viewModes=n.settings._viewModes||{review:"list",memory:"list",quest:"list"},this.viewMode=this.viewModes.review,this.treeExpanded=n.settings._treeExpanded||{},this.filterStatus="all",this.searchQuery="",this.sortQuest="newest",this.sortMemory="newest",this.filterQuestStatus="all"}loadDecks(){return this._decksReady=(async()=>{this.decks=await Ch(this.app,this.plugin.settings)})()}loadLessons(){return this._lessonsReady=(async()=>{this.courses=await Mh(this.app.vault.adapter,this.plugin.settings.lessonRoots)})()}loadLibrary(){return this._libraryReady=this._loadLibrary()}async loadData(){await Promise.all([this.loadDecks(),this.loadLibrary()])}_needsFor(e){return e==="review"?[this._decksReady]:e==="memory"||e==="quest"?[this._libraryReady]:e==="quadrant"||e==="lesson"?[]:e==="achievement"?[this._decksReady,this._libraryReady,this.loadLessons()].filter(Boolean):[this._decksReady,this._libraryReady].filter(Boolean)}async _loadLibrary(){this.quests=[];let e=r=>this.app.vault.cachedRead?this.app.vault.cachedRead(r):this.app.vault.read(r),n=this.app.vault.getMarkdownFiles().filter(r=>{let o=this.app.metadataCache.getFileCache(r),l=!o||!o.frontmatter&&(!o.sections||o.sections.length===0)&&(!o.headings||o.headings.length===0)&&(!o.tags||o.tags.length===0);return r.name.endsWith("-quest.md")||l||o&&o.sections&&o.sections.some(c=>c.type==="code")}),a=(r,o)=>{for(let l of r||[])for(let c of o){let p=l&&l[c];if(p&&String(p).trim())return String(p).trim()}return""},s=await za.mapLimit(n,8,async r=>{try{let o=await e(r);if(!(r.name.endsWith("-quest.md")||/\`\`\`quest-map\s/i.test(o)))return null;let l=(o.match(/difficulty:\s*(\w+)/)||[])[1]||"medium",c=o.match(/style:\s*([a-zA-Z0-9_-]+)/),p=c?c[1]:"sky-island",d=[],h=this.app.metadataCache.getFileCache(r);if(h&&h.tags&&h.tags.forEach(A=>d.push(A.tag.replace(/^#/,"").toLowerCase())),h&&h.frontmatter&&h.frontmatter.tags){let A=h.frontmatter.tags;typeof A=="string"?A.split(",").forEach(T=>d.push(T.trim().replace(/^#/,"").toLowerCase())):Array.isArray(A)&&A.forEach(T=>d.push(String(T).replace(/^#/,"").toLowerCase()))}let u=[...new Set(d.filter(Boolean))].map(A=>"#"+A),x=(o.match(/\`\`\`quest-map\s*\n?([\s\S]*?)\`\`\`/i)||[])[1],m=x?Sh(x):null,g=m&&Array.isArray(m.nodes)?m.nodes:[],b=null,f=null;g.length&&(b=await bn.loadQuestState(this.app.vault.adapter,r.path),b=bn.migrateQuestCompletion(b,g),f=bn.buildQuestRenderState(g,b));let v=!!(f&&g.length&&f.completedCount>=g.length),y=g.findIndex(A=>A&&A.boss),w=y>=0?g[y]:g[g.length-1]||null,k=f&&g.length&&g[f.activeIndex]||null,E=g.filter(A=>A&&A.challenge),S=[...new Set(E.map(A=>A.challenge&&A.challenge.type).filter(Boolean))],_=f?f.progressPct:0,C=f?f.completedCount:g.filter(A=>A.completed).length||0,L=!!(f&&y>=0&&f.activeIndex>=y&&!v);return{sourceType:"markdown",file:r,stateKey:r.path,sourcePath:r.path,title:m&&m.title||r.basename.replace(/-quest$/i,""),description:m&&m.description||"",nodes:g,difficulty:l,tags:u,style:p,ctime:r.stat.ctime,completed:v,nodeCount:g.length,missionCount:E.length,completedCount:C,progressPct:_,bestScorePct:f?f.bestScorePct:null,bossReady:L,bossTitle:w&&w.title?w.title:"Boss Battle",activeTitle:k&&k.title?k.title:"",scenario:a(g,["scenario","mission_goal","stakes"]),missionGoal:a(g,["mission_goal","scenario","summary"]),stakes:a(g,["stakes"]),missionTypes:S}}catch(o){return console.warn("EngramQuest: failed to scan quest",r&&r.path,o),null}});{let r=await Ah(this.app.vault.adapter,this.plugin.settings.questRoots),o=await za.mapLimit(r,8,async l=>{try{let c=await bn.loadQuestState(this.app.vault.adapter,l.stateKey);return Th(l,c)}catch(c){return console.warn("EngramQuest: failed to scan quest package",l&&l.slug,c),null}});this.quests=[...o.filter(Boolean),...s.filter(Boolean)]}this.memories=[];let i=new Map;for(let r of this.app.vault.getMarkdownFiles())i.has(r.basename)||i.set(r.basename,r);for(let r of this.app.vault.getFiles())if(r.name.endsWith("-memory.canvas")){let o=r.name.replace("-memory.canvas",""),l=[],c=i.get(o);if(c){let p=[],d=this.app.metadataCache.getFileCache(c);if(d&&d.tags&&d.tags.forEach(h=>p.push(h.tag.replace(/^#/,"").toLowerCase())),d&&d.frontmatter&&d.frontmatter.tags){let h=d.frontmatter.tags;typeof h=="string"?h.split(",").forEach(u=>p.push(u.trim().replace(/^#/,"").toLowerCase())):Array.isArray(h)&&h.forEach(u=>p.push(String(u).replace(/^#/,"").toLowerCase()))}l=[...new Set(p.filter(h=>h))].map(h=>"#"+h)}this.memories.push({file:r,baseName:o,tags:l,ctime:r.stat.ctime})}}onOpen(){qh();let e=activeDocument.body.classList.contains("theme-dark"),n=e?"#1e1e2e":"#ffffff",a=e?"#252538":"#f3f4f6",s=e?"#e2e8f0":"#1f2937",i=e?"#94a3b8":"#6b7280",r=e?"#3a3a5a":"#e5e7eb",o=e?"#1a1a2e":"#f8faff";this.modalEl.addClass("lh-hub"),e&&this.modalEl.addClass("lh-dark"),kh(this),this.modalEl.style.cssText=`width:min(95vw,900px);max-width:none;height:min(90vh,660px);max-height:none;padding:0;overflow:hidden;border-radius:20px;background:${o}`,this.modalEl.style.setProperty("--background-primary",n,"important"),this.modalEl.style.setProperty("--background-secondary",a,"important"),this.modalEl.style.setProperty("--text-normal",s,"important"),this.modalEl.style.setProperty("--text-muted",i,"important"),this.modalEl.style.setProperty("--background-modifier-border",r,"important"),this.contentEl.style.cssText=`padding:0;height:100%;display:flex;flex-direction:column;overflow:hidden;background:${o};color:${s}`;{let l=this.contentEl.createEl("div",{attr:{style:"flex:1;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:12px;"}});l.createEl("div",{attr:{style:"width:36px;height:36px;border:3px solid #e5e7eb;border-top-color:#6366f1;border-radius:50%;animation:lh-spin 0.7s linear infinite;"}});let c=activeDocument.createElement("style");c.textContent="@keyframes lh-spin{to{transform:rotate(360deg)}}",l.appendChild(c)}this._decksReady||this.loadDecks(),this._libraryReady||this.loadLibrary(),Promise.all(this._needsFor(this.activeTab)).then(()=>this.render()).catch(l=>console.error("EngramQuest: hub load failed",l))}render(){this._hubOpen=!0;let{contentEl:e}=this,n=this.plugin.settings;e.empty();let a=e.createEl("div",{attr:{class:"lh-nav"}});a.createEl("span",{text:M(n,"HUB_TITLE"),attr:{class:"lh-logo"}});let s=[{id:"memory",label:M(n,"CAT_MEMORY"),tabs:["review",...n.licenseValid?["quadrant"]:[],"memory"]},{id:"learn",label:M(n,"CAT_LEARN"),tabs:["lesson","quest"]},{id:"achieve",label:M(n,"CAT_ACHIEVE"),tabs:["achievement"]}],i={review:"TAB_REVIEW",memory:"TAB_MEMORY",quest:"TAB_QUEST",quadrant:"TAB_QUADRANT",lesson:"TAB_LESSON",achievement:"TAB_ACHIEVEMENT"};if(!this.activeCategory||!s.some(m=>m.id===this.activeCategory)){let m=s.find(g=>g.tabs.includes(this.activeTab));this.activeCategory=m?m.id:"memory"}let r=a.createEl("div",{attr:{class:"lh-tabs"}}),o=e.createEl("div",{attr:{class:"lh-subtabs"}}),l=e.createEl("div",{attr:{style:"flex:1;display:flex;flex-direction:column;overflow:hidden"}}),c=async m=>{this.activeTab=m,this.viewMode=this.viewModes[m]||"list",o.querySelectorAll(".lh-subtab").forEach(b=>b.classList.toggle("active",b.dataset.tab===m));let g=this._tabGen=(this._tabGen||0)+1;l.empty(),l.createEl("div",{text:"\u23F3",attr:{style:"flex:1;display:flex;align-items:center;justify-content:center;font-size:20px;color:var(--text-muted,#6b7280);"}}),await Promise.all(this._needsFor(m)),this._tabGen===g&&(l.empty(),m==="review"?this._renderReview(l):m==="memory"?this._renderMemoryTab(l):m==="quest"?this._renderQuestTab(l):m==="quadrant"?this._renderQuadrantTab(l):m==="lesson"?this._renderLessonTab(l):m==="achievement"&&this._renderAchievementTab(l),this._maybeShowGuide(m))},p=()=>{o.empty();let m=s.find(g=>g.id===this.activeCategory)||s[0];o.style.display=m.tabs.length>1?"flex":"none",m.tabs.forEach(g=>{let b=o.createEl("button",{attr:{class:"lh-subtab"+(g===this.activeTab?" active":""),"data-tab":g}});b.textContent=M(n,i[g]),b.addEventListener("click",()=>c(g))})},d=m=>{this.activeCategory=m;let g=s.find(b=>b.id===m)||s[0];r.querySelectorAll(".lh-tab").forEach(b=>b.classList.toggle("active",b.dataset.cat===m)),p(),c(g.tabs.includes(this.activeTab)?this.activeTab:g.tabs[0])};s.forEach(m=>{let g=r.createEl("button",{attr:{class:"lh-tab"+(m.id===this.activeCategory?" active":""),"data-cat":m.id,style:`flex:${m.id==="achieve"?1:2};justify-content:center;`}});g.createEl("span").textContent=m.label,g.addEventListener("click",()=>d(m.id))});let h=a.createEl("div",{attr:{style:"display:flex;gap:8px;align-items:center"}}),u=h.createEl("button",{attr:{class:"lh-help-btn",title:M(n,"HELP_OPEN")}});u.style.cssText="width:32px;height:32px;border-radius:8px;background:transparent;border:none;cursor:pointer;color:#6b7280;display:flex;align-items:center;justify-content:center;transition:all 0.2s;padding:0",u.appendChild(ne.sanitizeHTMLToDom('<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>')),u.addEventListener("mouseenter",()=>{u.style.color="#3b82f6",u.style.background="#eff6ff",u.style.borderRadius="8px"}),u.addEventListener("mouseleave",()=>{u.style.color="#6b7280",u.style.background="transparent"}),u.addEventListener("click",()=>new fh(this.app,this.plugin).open());let x=h.createEl("button",{attr:{class:"lh-help-btn",title:"Minimize"}});x.style.cssText="width:32px;height:32px;border-radius:8px;background:transparent;border:none;cursor:pointer;color:#6b7280;display:flex;align-items:center;justify-content:center;transition:all 0.2s;padding:0;font-size:16px;",x.textContent="\u23EC",x.addEventListener("mouseenter",()=>{x.style.color="#6366f1",x.style.background="#eef2ff"}),x.addEventListener("mouseleave",()=>{x.style.color="#6b7280",x.style.background="transparent"}),x.addEventListener("click",()=>this._minimize()),h.createEl("button",{text:"\xD7",attr:{class:"lh-close",title:M(n,"HUB_CLOSE")}}).addEventListener("click",()=>this.close()),d(this.activeCategory)}_maybeShowGuide(e){try{if(!yh(e))return;let n=this.plugin.settings._guideSeen||(this.plugin.settings._guideSeen={});if(n[e])return;n[e]=!0,this.plugin.saveData(this.plugin.settings),this._guideTimer=window.setTimeout(()=>{this._guideTimer=null,!this._minimized&&this._hubOpen&&new xh(this.app,this.plugin,e).open()},220)}catch(n){console.warn("EngramQuest: feature guide popup failed",n)}}_minimize(){if(this._minimized)return;this._minimized=!0,this.containerEl.style.display="none";try{this.app.keymap.popScope(this.scope)}catch(n){}if(this._fab)return;let e=activeDocument.createElement("div");e.className="engram-fab-hub",e.innerHTML=Ce("home"),e.title="Openhub",e.style.cssText="position:fixed;bottom:calc(env(safe-area-inset-bottom, 0px) + 88px);right:20px;width:48px;height:48px;border-radius:50%;background:linear-gradient(135deg,#4f46e5,#818cf8);color:#fff;display:flex;align-items:center;justify-content:center;font-size:22px;cursor:pointer;z-index:9999;box-shadow:0 4px 16px rgba(79,70,229,0.45);user-select:none;-webkit-tap-highlight-color:transparent;transition:transform 0.15s;",e.addEventListener("mouseenter",()=>{e.style.transform="scale(1.12)"}),e.addEventListener("mouseleave",()=>{e.style.transform="scale(1)"}),e.addEventListener("click",()=>this._restore()),activeDocument.body.appendChild(e),this._fab=e}_restore(){if(this._minimized){this._minimized=!1,this.containerEl.style.display="";try{this.app.keymap.pushScope(this.scope)}catch(e){}this._fab&&(this._fab.remove(),this._fab=null),["quest","memory"].includes(this.activeTab)&&(this._libraryReady=null,this.loadLibrary().then(()=>{this._hubOpen&&this.render()}).catch(e=>console.warn("EngramQuest: restore reload failed",e)))}}onClose(){this._hubOpen=!1,this._guideTimer&&(window.clearTimeout(this._guideTimer),this._guideTimer=null),this._fab&&(this._fab.remove(),this._fab=null),this._minimized=!1,this._tagFilterCleanups&&(this._tagFilterCleanups.forEach(e=>e()),this._tagFilterCleanups=[])}_confirmDelete(e,n){let a=this.plugin.settings,s,i;n==="quest"?(s=e.file.basename.replace(/-quest$/,""),i=[e.file]):n==="quest-package"?(s=e.title||e.slug,i=[]):n==="memory"?(s=e.baseName,i=[e.file]):n==="deck"&&(s=e.name,i=[]);let r=new ne.Modal(this.app);r.modalEl.style.cssText="width:min(92vw,420px);padding:0;border-radius:16px;overflow:hidden;";let o=r.contentEl;o.style.cssText="padding:24px;display:flex;flex-direction:column;gap:14px;",o.createEl("div",{text:M(a,"DELETE_CONFIRM_TITLE").replace("{name}",s),attr:{style:"font-size:16px;font-weight:700;color:var(--text-normal,#111);"}});let l;n==="deck"?l=M(a,"DELETE_CONFIRM_DECK").replace("{count}",String(i.length))+`
`+i.map(h=>"  \u2022 "+(h?h.path:"")).join(`
`):l=M(a,"DELETE_CONFIRM_FILE"),o.createEl("div",{text:l,attr:{style:"font-size:13px;color:var(--text-muted,#6b7280);white-space:pre-line;line-height:1.6;"}});let c=o.createEl("div",{attr:{style:"display:flex;gap:8px;justify-content:flex-end;padding-top:4px;"}}),p=c.createEl("button",{text:M(a,"DELETE_CANCEL_BTN"),attr:{style:"padding:7px 16px;border-radius:8px;border:1px solid #d1d5db;background:#fff;cursor:pointer;font-size:13px;"}}),d=c.createEl("button",{text:M(a,"DELETE_CONFIRM_BTN"),attr:{style:"padding:7px 16px;border-radius:8px;border:none;background:#ef4444;color:#fff;cursor:pointer;font-size:13px;font-weight:600;"}});p.addEventListener("click",()=>r.close()),d.addEventListener("click",async()=>{try{if(r.close(),n==="deck")await _h(this.app,e);else if(n==="quest-package"){let h=this.app.vault.getAbstractFileByPath((e.root||"engram-quest/quests")+"/"+e.slug);h&&await this.app.vault.delete(h,!0);let u=this.app.vault.getAbstractFileByPath("engram-quest/state/"+e.slug+".json");u&&await this.app.vault.delete(u,!1)}else for(let h of i)h&&await this.app.fileManager.trashFile(h);await this.loadData(),this.render()}catch(h){console.error("EngramQuest: delete failed",h),new ne.Notice("Delete failed: "+String(h))}}),r.open()}_renderReview(e){let n=this.plugin.settings,a=(()=>{let b=activeDocument.body.classList.contains("theme-dark");return this.app.vault.adapter.getResourcePath(this.app.vault.configDir+"/plugins/engram-quest/"+(b?"stats_bg_dark.webp":"bg.png"))})(),s=e.createEl("div",{attr:{class:"lh-stats-header",style:`background-image:url('${a}')`}}),i=this.decks.reduce((b,f)=>b+f.due,0),r=this.decks.reduce((b,f)=>b+f.unseen,0),o=this.decks.reduce((b,f)=>b+f.total,0),l=i+r;(()=>{let b=activeDocument.body.classList.contains("theme-dark"),f=!b&&!activeDocument.body.classList.contains("is-phone"),v=[{label:M(n,"READY"),val:l,cls:"ready",left:"17%",top:"30%",fs:f?"32px":""},{label:M(n,"FILTER_NEW"),val:r,cls:"new",left:"50%",top:"26%",fs:f?"30px":""},{label:M(n,"TOTAL"),val:o,cls:"total",left:"79%",top:"30%",fs:f?"30px":""}].map(y=>{let w=s.createEl("div",{attr:{class:"lh-stat-grp",style:`left:${y.left};top:${y.top}`}});return w.createEl("span",{text:y.label,attr:{class:"lh-stat-label"}}),w.createEl("span",{text:String(y.val),attr:{class:`lh-stat-num ${y.cls}`,style:y.fs?`font-size:${y.fs}`:""}}),w});f&&bh(s,v)})(),s.createEl("div",{text:M(n,"READY_HELP"),attr:{class:"lh-stats-helper"}});let c=n._reviewProgress;if(c&&c.deck){let b=e.createEl("div",{attr:{style:"margin:0 16px;padding:10px 16px;background:linear-gradient(135deg,#eef2ff,#e0e7ff);border-radius:12px;display:flex;align-items:center;justify-content:space-between;cursor:pointer;border:1px solid #c7d2fe;"}});b.createEl("span",{text:`\u25B6 ${c.deck} \xB7 ${M(n,"PREVIOUS")} #${c.idx+1}`,attr:{style:"font-size:13px;font-weight:600;color:#4338ca;"}}),b.createEl("span",{text:"\u2192",attr:{style:"font-size:16px;color:#6366f1;"}}),b.addEventListener("click",()=>{let f=this.decks.find(v=>v.name===c.deck);f&&this._startDeckReview(f)})}wh(e,this.plugin,this.app).catch(b=>console.warn("synapse banner failed",b));let p=e.createEl("div",{attr:{class:"lh-card"}}),d=p.createEl("div",{attr:{class:"lh-card-header"}}),h=d.createEl("div",{attr:{style:"display:flex;align-items:center;gap:8px"}});h.createEl("span",{text:M(n,"TAB_REVIEW"),attr:{class:"lh-card-title"}}),Da(h,this.app,this.plugin,"review");let u=async()=>{let b=new xn(this.app,this.plugin);await b.loadData(),b.open()},x=(b,f,v,y)=>{this.close(),new fn(this.app,b,f,this.plugin,v,{timeboxMinutes:y}).open()};Eh(d,this.plugin,this.app,this.decks,x,u);let m=d.createEl("button",{attr:{class:"lh-study-all"}});m.textContent=`\u25B6 ${M(n,"STUDY_ALL")}`,m.addEventListener("click",()=>{let b=this.decks.flatMap(f=>f.cards).filter(f=>{let v=Xe(f.srMeta);return v==="due"||v==="unseen"});if(b.length===0){new ne.Notice(M(n,"NO_CARDS_TO_REVIEW"));return}b.sort((f,v)=>(Xe(f.srMeta)==="due"?0:1)-(Xe(v.srMeta)==="due"?0:1)),this.close(),new fn(this.app,b,M(n,"FILTER_ALL"),this.plugin,async()=>{let f=new xn(this.app,this.plugin);await f.loadData(),f.open()}).open()}),this._renderToolbar(p,()=>this._renderDeckContent(g));let g=p.createEl("div",{attr:{style:"flex:1;overflow-y:auto"}});this._renderDeckContent(g)}_renderToolbar(e,n){let a=e.createEl("div",{attr:{class:"lh-toolbar"}}),s=a.createEl("div",{attr:{class:"lh-search"}});s.createEl("span",{text:"\u2315",attr:{class:"lh-search-icon"}});let i=s.createEl("input",{attr:{type:"text",placeholder:M(this.plugin.settings,"SEARCH_REVIEW"),value:this.searchQuery}});i.addEventListener("input",()=>{this.searchQuery=i.value,n()});let r=a.createEl("select",{attr:{class:"lh-filter"}});[["all",M(this.plugin.settings,"FILTER_ALL")],["due",M(this.plugin.settings,"FILTER_DUE")],["new",M(this.plugin.settings,"FILTER_NEW")],["done",M(this.plugin.settings,"FILTER_DONE")]].forEach(([d,h])=>{let u=r.createEl("option",{text:h,attr:{value:d}});d===this.filterStatus&&(u.selected=!0)}),r.addEventListener("change",()=>{this.filterStatus=r.value,n()});let o=a.createEl("div",{attr:{class:"lh-view-toggle"}}),l=o.createEl("button",{text:"\u2263",attr:{class:"lh-view-btn"+(this.viewMode==="list"?" active":"")}}),c=o.createEl("button",{text:"\u229E",attr:{class:"lh-view-btn"+(this.viewMode==="grid"?" active":"")}}),p=Object.assign(o.createEl("button",{attr:{class:"lh-view-btn"+(this.viewMode==="tree"?" active":""),style:"font-size:12px;"}}),{innerHTML:Ce("tree")});l.addEventListener("click",async()=>{this.viewMode="list",this.viewModes[this.activeTab]="list",this.plugin.settings._viewModes=this.viewModes,await this.plugin.saveData(this.plugin.settings),l.classList.add("active"),c.classList.remove("active"),p.classList.remove("active"),n()}),c.addEventListener("click",async()=>{this.viewMode="grid",this.viewModes[this.activeTab]="grid",this.plugin.settings._viewModes=this.viewModes,await this.plugin.saveData(this.plugin.settings),c.classList.add("active"),l.classList.remove("active"),p.classList.remove("active"),n()}),p.addEventListener("click",async()=>{this.viewMode="tree",this.viewModes[this.activeTab]="tree",this.plugin.settings._viewModes=this.viewModes,await this.plugin.saveData(this.plugin.settings),p.classList.add("active"),l.classList.remove("active"),c.classList.remove("active"),n()})}_renderDeckContent(e){let n=this.plugin.settings,a="ontouchstart"in window||navigator.maxTouchPoints>0;if(e.empty(),this.decks.length===0){e.createEl("div",{attr:{class:"lh-empty"}}).appendChild(ne.sanitizeHTMLToDom(M(n,"EMPTY_DECKS").replace(/\n/g,"<br>")));return}let s=this.decks;if(this.searchQuery&&(s=s.filter(r=>r.name.toLowerCase().includes(this.searchQuery.toLowerCase()))),this.filterStatus==="due"&&(s=s.filter(r=>r.due>0)),this.filterStatus==="new"&&(s=s.filter(r=>r.unseen>0)),this.filterStatus==="done"&&(s=s.filter(r=>r.due===0&&r.unseen===0)),s.length===0){e.createEl("div",{attr:{class:"lh-empty"}}).textContent=M(n,"EMPTY_FILTERED_DECKS");return}let i=["folder","book","brain","star","bolt","link","sprout","target","card","wave","bulb","stack"];if(this.viewMode==="list"){let r=e.createEl("div",{attr:{class:"lh-deck-list"}});s.forEach((o,l)=>{let c=o.due+o.unseen,p=r.createEl("div",{attr:{class:`lh-deck-row${c===0?" is-emptyready":""}`}});p.createEl("span",{text:">",attr:{class:"lh-deck-chevron"}});let d=p.createEl("div",{attr:{class:"lh-deck-main"}}),h=d.createEl("div",{attr:{class:"lh-deck-row-name",style:"display:flex;align-items:center;gap:8px;"}});h.createEl("span",{attr:{style:"font-size:16px;line-height:1;"}}).innerHTML=Ce(i[l%i.length]),h.createEl("span",{text:o.name}),d.createEl("div",{text:c>0?`${M(n,"READY_NOW")}: ${c}`:Fe(M(n,"ALL_SCHEDULED"),{total:o.total}),attr:{class:"lh-deck-row-sub"}});let u=p.createEl("div",{attr:{class:"lh-deck-metrics"}}),x=u.createEl("div",{attr:{class:`lh-deck-ready${c===0?" is-zero":""}`}});x.createEl("div",{text:M(n,"READY"),attr:{class:"lh-deck-ready-label"}}),x.createEl("div",{text:String(c),attr:{class:"lh-deck-ready-num"}});let m=u.createEl("div",{attr:{class:"lh-deck-breakdown"}});[{key:"FILTER_DUE",cls:"due",value:o.due},{key:"FILTER_NEW",cls:"new",value:o.unseen},{key:"TOTAL",cls:"total",value:o.total}].forEach(g=>{let b=m.createEl("div",{attr:{class:`lh-deck-metric ${g.cls}`}});b.createEl("div",{text:M(n,g.key),attr:{class:"lh-deck-metric-label"}}),b.createEl("div",{text:String(g.value),attr:{class:"lh-deck-metric-num"}})}),p.title=c===0?Fe(M(n,"NO_READY_IN_DECK"),{deck:o.name,total:o.total}):"";{let g=p.createEl("button",{attr:{class:"lh-delete-btn",title:M(n,"CREATE_CARD"),style:"color:#6366f1;border-color:#6366f1;"+(a?"opacity:1;":"")}});g.appendChild(ne.sanitizeHTMLToDom('<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>')),g.addEventListener("click",b=>{b.stopPropagation(),Oa(this.app,o,n,async()=>{await this.loadData(),this._renderDeckContent(e)})})}{let g=p.createEl("button",{attr:{class:"lh-delete-btn",title:M(n,"DELETE"),style:a?"opacity:1;":""}});g.appendChild(ne.sanitizeHTMLToDom('<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4h6v2"/></svg>')),g.addEventListener("click",b=>{b.stopPropagation(),this._confirmDelete(o,"deck")})}p.addEventListener("click",()=>this._startDeckReview(o))})}else if(this.viewMode==="tree"){let r=e.createEl("div",{attr:{class:"lh-tree-container"}}),o=this._buildTree(s,l=>l.name.replace(/^#/,""));this._renderReviewTree(r,o,n,i)}else{let r=e.createEl("div",{attr:{class:"lh-deck-grid"}});s.forEach((o,l)=>{let c=o.due+o.unseen,p=o.cards.filter(m=>Xe(m.srMeta)==="mastered").length,d=o.total>0?Math.round(p/o.total*100):0,h=r.createEl("div",{attr:{class:`lh-deck-gc${o.due>0?" has-due":""}${c===0?" is-emptyready":""}`}});h.createEl("div",{attr:{class:"lh-deck-gc-emoji"}}).innerHTML=Ce(i[l%i.length]),h.createEl("div",{attr:{class:"lh-deck-gc-name"}}).textContent=o.name,h.createEl("div",{text:c>0?M(n,"READY_NOW"):M(n,"NOTHING_READY"),attr:{class:"lh-deck-gc-status"}});let u=h.createEl("div",{attr:{class:"lh-deck-gc-ready"}});u.createEl("div",{text:String(c),attr:{class:"lh-deck-gc-ready-num"}}),u.createEl("div",{text:M(n,"READY"),attr:{class:"lh-deck-gc-ready-label"}}),h.createEl("div",{attr:{class:"lh-deck-gc-bar-bg"}}).createEl("div",{attr:{class:"lh-deck-gc-bar",style:`width:${d}%`}}),h.createEl("div",{attr:{class:"lh-deck-gc-sub"}}).textContent=M(n,"MASTERY_RATE",{percent:d});let x=h.createEl("div",{attr:{class:"lh-deck-gc-metrics"}});[{key:"FILTER_DUE",cls:"due",value:o.due},{key:"FILTER_NEW",cls:"new",value:o.unseen},{key:"TOTAL",cls:"total",value:o.total}].forEach(m=>{let g=x.createEl("div",{attr:{class:`lh-deck-gc-metric ${m.cls}`}});g.createEl("div",{text:M(n,m.key),attr:{class:"lh-deck-gc-metric-label"}}),g.createEl("div",{text:String(m.value),attr:{class:"lh-deck-gc-metric-num"}})}),h.title=c===0?Fe(M(n,"NO_READY_IN_DECK"),{deck:o.name,total:o.total}):"";{let m=h.createEl("button",{attr:{class:"lh-delete-btn",title:M(n,"CREATE_CARD"),style:"color:#6366f1;border-color:#6366f1;"+(a?"opacity:1;":"")}});m.appendChild(ne.sanitizeHTMLToDom('<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>')),m.addEventListener("click",g=>{g.stopPropagation(),Oa(this.app,o,n,async()=>{await this.loadData(),this._renderDeckContent(e)})})}{let m=h.createEl("button",{attr:{class:"lh-delete-btn",title:M(n,"DELETE"),style:a?"opacity:1;":""}});m.appendChild(ne.sanitizeHTMLToDom('<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4h6v2"/></svg>')),m.addEventListener("click",g=>{g.stopPropagation(),this._confirmDelete(o,"deck")})}h.addEventListener("click",()=>this._startDeckReview(o))})}}_startDeckReview(e){let n=this.plugin.settings,a=e.cards.filter(l=>{let c=Xe(l.srMeta);return c==="due"||c==="unseen"}),s=async()=>{let l=new xn(this.app,this.plugin);await l.loadData(),l.open()};if(a.length===0){this.close(),new fn(this.app,e.cards,e.name,this.plugin,s,{browseOnly:!0}).open();return}a.sort((l,c)=>(Xe(l.srMeta)==="due"?0:1)-(Xe(c.srMeta)==="due"?0:1));let i=n._reviewProgress,r=0;i&&i.deck===e.name&&i.idx>0&&i.idx<a.length&&(r=i.idx),this.close();let o=new fn(this.app,a,e.name,this.plugin,s);o.idx=r,o.open()}_sortItems(e,n,a){let s=n==="newest"?(i,r)=>r.ctime-i.ctime:n==="oldest"?(i,r)=>i.ctime-r.ctime:(i,r)=>i.file.basename.localeCompare(r.file.basename);return a?e.sort((i,r)=>(i.completed?1:0)-(r.completed?1:0)||s(i,r)):e.sort(s)}_createTagFilter(e,n,a,s){let i=this.plugin.settings,r=e.createEl("div",{attr:{style:"position:relative;margin-left:8px;"}}),o={value:"all"},l=r.createEl("div",{attr:{class:"lh-filter",style:"display:flex;align-items:center;gap:4px;min-width:90px;cursor:pointer;user-select:none;"}});l.textContent=M(i,"FILTER_ALL_TAGS");let c=r.createEl("div",{attr:{style:"display:none;position:absolute;top:100%;left:0;margin-top:4px;min-width:200px;max-height:240px;overflow-y:auto;background:#fff;border:1.5px solid #e5e7eb;border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,0.12);z-index:100;flex-direction:column;"}}),d=c.createEl("div",{attr:{style:"padding:6px 8px;border-bottom:1px solid #f1f5f9;position:sticky;top:0;background:#fff;z-index:1;"}}).createEl("input",{attr:{type:"text",placeholder:M(i,"SEARCH_TAG"),style:"width:100%;padding:6px 8px;border:1.5px solid #e5e7eb;border-radius:6px;font-size:12px;outline:none;background:#f9fafb;color:#374151;box-sizing:border-box;"}}),h=c.createEl("div",{attr:{style:"padding:4px 0;"}});function u(m){h.empty();let g=Array.from(n).sort(),b=m?g.filter(v=>v.toLowerCase().includes(m.toLowerCase())):g;h.createEl("div",{text:M(i,"FILTER_ALL_TAGS"),attr:{style:"padding:6px 12px;font-size:12px;font-weight:600;cursor:pointer;color:"+(o.value==="all"?"#6366f1":"#374151")+";background:"+(o.value==="all"?"#eff6ff":"transparent")+";"}}).addEventListener("click",()=>{o.value="all",l.textContent=M(i,"FILTER_ALL_TAGS"),c.style.display="none",s()}),b.forEach(v=>{let y=h.createEl("div",{text:v,attr:{style:"padding:6px 12px;font-size:12px;cursor:pointer;color:"+(o.value===v?"#6366f1":"#374151")+";background:"+(o.value===v?"#eff6ff":"transparent")+";"}});y.addEventListener("mouseenter",()=>{o.value!==v&&(y.style.background="#f3f4f6")}),y.addEventListener("mouseleave",()=>{y.style.background=o.value===v?"#eff6ff":"transparent"}),y.addEventListener("click",()=>{o.value=v,l.textContent=v,c.style.display="none",s()})})}l.addEventListener("click",m=>{m.stopPropagation();let g=c.style.display==="flex";c.style.display=g?"none":"flex",g||(d.value="",u(""),window.setTimeout(()=>d.focus(),0))}),d.addEventListener("input",()=>u(d.value)),d.addEventListener("click",m=>m.stopPropagation()),c.addEventListener("click",m=>m.stopPropagation());let x=()=>{c.style.display="none"};return activeDocument.addEventListener("click",x),this._tagFilterCleanups=this._tagFilterCleanups||[],this._tagFilterCleanups.push(()=>activeDocument.removeEventListener("click",x)),o}_renderMemoryTab(e){let n=this.plugin.settings,a=e.createEl("div",{attr:{class:"lh-card",style:"margin-top:0;border-radius:0;flex:1;display:flex;flex-direction:column"}}),s=a.createEl("div",{attr:{class:"lh-card-header"}});if(s.createEl("span",{text:M(n,"TAB_MEMORY"),attr:{class:"lh-card-title"}}),Da(s,this.app,this.plugin,"memory"),s.createEl("span",{text:`${this.memories.length} ${M(n,"CARD_COUNT_UNIT")}`,attr:{class:"lh-card-count"}}),this.memories.length===0){a.createEl("div",{attr:{class:"lh-empty"}}).appendChild(ne.sanitizeHTMLToDom(M(n,"EMPTY_MEMORIES").replace(/\n/g,"<br>")));return}let i=a.createEl("div",{attr:{class:"lh-toolbar"}}),r=i.createEl("div",{attr:{class:"lh-search",style:"flex:1"}});r.createEl("span",{text:"\u2315",attr:{class:"lh-search-icon"}});let o=r.createEl("input",{attr:{type:"text",placeholder:M(n,"SEARCH_MEMORY"),value:this.searchQuery}}),l=i.createEl("select",{attr:{class:"lh-filter"}});[["newest",M(n,"SORT_NEWEST")],["oldest",M(n,"SORT_OLDEST")],["name",M(n,"SORT_NAME")]].forEach(([v,y])=>{let w=l.createEl("option",{text:y,attr:{value:v}});v===this.sortMemory&&(w.selected=!0)});let c=new Set;this.memories.forEach(v=>{var y;return(y=v.tags)==null?void 0:y.forEach(w=>c.add(w))});let p=this._createTagFilter(i,c,n,()=>f()),d=i.createEl("div",{attr:{class:"lh-view-toggle",style:"margin-left:8px"}}),h=d.createEl("button",{text:"\u2263",attr:{class:"lh-view-btn"+(this.viewMode==="list"?" active":"")}}),u=d.createEl("button",{text:"\u229E",attr:{class:"lh-view-btn"+(this.viewMode==="grid"?" active":"")}}),x=Object.assign(d.createEl("button",{attr:{class:"lh-view-btn"+(this.viewMode==="tree"?" active":""),style:"font-size:12px;"}}),{innerHTML:Ce("tree")}),m=a.createEl("div",{attr:{style:"flex:1;overflow-y:auto;padding:0 16px 16px"}}),g=['<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>','<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 3a3 3 0 0 0-3 3v12a3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3H6a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3V6a3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3h12a3 3 0 0 0 3-3 3 3 0 0 0-3-3z"/></svg>','<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="2" ry="2"/><rect x="9" y="9" width="6" height="6"/><line x1="9" y1="1" x2="9" y2="4"/><line x1="15" y1="1" x2="15" y2="4"/><line x1="9" y1="20" x2="9" y2="23"/><line x1="15" y1="20" x2="15" y2="23"/><line x1="20" y1="9" x2="23" y2="9"/><line x1="20" y1="14" x2="23" y2="14"/><line x1="1" y1="9" x2="4" y2="9"/><line x1="1" y1="14" x2="4" y2="14"/></svg>','<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>','<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>'],b=["linear-gradient(135deg, #8b5cf6, #c4b5fd)","linear-gradient(135deg, #3b82f6, #93c5fd)","linear-gradient(135deg, #10b981, #6ee7b7)","linear-gradient(135deg, #f59e0b, #fcd34d)","linear-gradient(135deg, #ec4899, #f9a8d4)"],f=()=>{m.empty();let v=o.value.toLowerCase(),y=p.value,w=this.memories.filter(k=>{var E,S;return(y==="all"||k.tags&&k.tags.includes(y))&&(k.baseName.toLowerCase().includes(v)||((S=(E=k.file.parent)==null?void 0:E.path)!=null?S:"").toLowerCase().includes(v))});if(this._sortItems(w,this.sortMemory,!1),w.length===0){m.createEl("div",{attr:{class:"lh-empty"}}).textContent=M(n,"EMPTY_FILTERED_MEMORIES");return}if(this.viewMode==="list"){let k=m.createEl("div",{attr:{class:"lh-tab-list"}});w.forEach((E,S)=>{var _,C;let L=k.createEl("div",{attr:{class:"lh-tab-list-item"}}),A=L.createEl("div",{attr:{class:"lh-qm-icon-wrap"}}),T=S%g.length;A.style.cssText=`width:40px;height:40px;border-radius:12px;background:${b[T].replace("linear-gradient(135deg, ","").split(",")[0]}22;color:${b[T].replace("linear-gradient(135deg, ","").split(",")[0]};display:flex;align-items:center;justify-content:center;flex-shrink:0`,A.appendChild(ne.sanitizeHTMLToDom(g[T]));let q=L.createEl("div",{attr:{style:"flex:1;min-width:0"}});q.createEl("div",{text:E.baseName,attr:{class:"lh-tab-list-title"}});let I=(C=(_=E.file.parent)==null?void 0:_.path)!=null?C:"",R=E.tags&&E.tags.length>0?E.tags.join(" ")+" \xB7 ":"";q.createEl("div",{text:R+I,attr:{class:"lh-tab-list-sub"}}),L.createEl("div",{attr:{class:"lh-mm-open",style:"margin-top:0"}}).appendChild(ne.sanitizeHTMLToDom(`${M(n,"OPEN_MAP")} <span style="font-size:16px">\u2192</span>`));{let $=L.createEl("button",{attr:{class:"lh-delete-btn",title:M(n,"DELETE")}});$.appendChild(ne.sanitizeHTMLToDom('<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4h6v2"/></svg>')),$.addEventListener("click",z=>{z.stopPropagation(),this._confirmDelete(E,"memory")})}L.addEventListener("click",()=>{this._minimize(),this.app.workspace.openLinkText(E.file.path,"",!1)})})}else if(this.viewMode==="tree"){let k=m.createEl("div",{attr:{class:"lh-tree-container"}}),E=this._buildTree(w,S=>{var _;return((_=S.file.parent)==null?void 0:_.path)||"/"});this._renderMemoryTree(k,E,n,g,b)}else{let k=m.createEl("div",{attr:{class:"lh-mm-grid"}});w.forEach((E,S)=>{var _,C;let L=k.createEl("div",{attr:{class:"lh-mm-card premium-card"}}),A=S%g.length,T=L.createEl("div",{attr:{class:"icon-container"}});T.style.background=b[A],T.style.WebkitBackgroundClip="text",T.style.WebkitTextFillColor="transparent",T.appendChild(ne.sanitizeHTMLToDom(g[A])),L.createEl("div",{attr:{class:"lh-mm-name"}}).textContent=E.baseName;let q=(C=(_=E.file.parent)==null?void 0:_.path)!=null?C:"",I=E.tags&&E.tags.length>0?E.tags.join(" ")+" \xB7 ":"";L.createEl("div",{attr:{class:"lh-mm-path truncate"}}).textContent=I+q,L.createEl("div",{attr:{class:"lh-mm-open"}}).appendChild(ne.sanitizeHTMLToDom(`${M(n,"OPEN_MAP")} <span style="font-size:16px; transition:transform 0.2s">\u2192</span>`));{let O=L.createEl("button",{attr:{class:"lh-delete-btn",title:M(n,"DELETE")}});O.appendChild(ne.sanitizeHTMLToDom('<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4h6v2"/></svg>')),O.addEventListener("click",$=>{$.stopPropagation(),this._confirmDelete(E,"memory")})}L.addEventListener("click",()=>{this._minimize(),this.app.workspace.openLinkText(E.file.path,"",!1)})})}};o.addEventListener("input",()=>{this.searchQuery=o.value,f()}),l.addEventListener("change",()=>{this.sortMemory=l.value,f()}),h.addEventListener("click",async()=>{this.viewMode="list",this.viewModes[this.activeTab]="list",this.plugin.settings._viewModes=this.viewModes,await this.plugin.saveData(this.plugin.settings),h.classList.add("active"),u.classList.remove("active"),x.classList.remove("active"),f()}),u.addEventListener("click",async()=>{this.viewMode="grid",this.viewModes[this.activeTab]="grid",this.plugin.settings._viewModes=this.viewModes,await this.plugin.saveData(this.plugin.settings),u.classList.add("active"),h.classList.remove("active"),x.classList.remove("active"),f()}),x.addEventListener("click",async()=>{this.viewMode="tree",this.viewModes[this.activeTab]="tree",this.plugin.settings._viewModes=this.viewModes,await this.plugin.saveData(this.plugin.settings),x.classList.add("active"),u.classList.remove("active"),h.classList.remove("active"),f()}),f()}_renderQuestTab(e){let n=this.plugin.settings,a=mh(n)==="zh-tw",s=activeDocument.body.classList.contains("theme-dark"),i=e.createEl("div",{attr:{class:"lh-card lh-quest-shell",style:"margin-top:0;border-radius:0;flex:1;display:flex;flex-direction:column;overflow:hidden"}}),r=i.createEl("div",{attr:{class:"lh-quest-hero"}}),o=r.createEl("div",{attr:{class:"lh-quest-hero-main"}}),l=o.createEl("div",{attr:{style:"display:flex;align-items:center;gap:8px"}});l.createEl("span",{text:M(n,"QUEST_PRACTICE_TITLE"),attr:{class:"lh-card-title lh-quest-title"}}),Da(l,this.app,this.plugin,"quest"),o.createEl("div",{text:M(n,"QUEST_PRACTICE_DESC"),attr:{class:"lh-quest-desc"}});let c=this.quests.filter(D=>D.completed).length,p=this.quests.filter(D=>D.bossReady).length,d=this.quests.filter(D=>!D.completed&&D.progressPct>0).length,h=r.createEl("div",{attr:{class:"lh-quest-stats"}});if([[String(this.quests.length),M(n,"COUNT_UNIT")],[String(d),M(n,"QUEST_IN_PROGRESS")],[String(p),M(n,"QUEST_BOSS_READY")],[String(c),M(n,"QUEST_CLEARED")]].forEach(([D,N])=>{let G=h.createEl("div",{attr:{class:"lh-quest-stat"}});G.createEl("b",{text:D}),G.createEl("span",{text:N})}),this.quests.length===0){i.createEl("div",{attr:{class:"lh-empty"}}).appendChild(ne.sanitizeHTMLToDom(M(n,"EMPTY_QUESTS").split(`
`).join("<br>")));return}let u=i.createEl("div",{attr:{class:"lh-toolbar lh-quest-toolbar"}}),x=u.createEl("div",{attr:{class:"lh-search",style:"flex:1"}});x.createEl("span",{text:"\u2315",attr:{class:"lh-search-icon"}});let m=x.createEl("input",{attr:{type:"text",placeholder:M(n,"SEARCH_QUEST"),value:this.searchQuery}}),g=u.createEl("select",{attr:{class:"lh-filter"}});[["newest",M(n,"SORT_NEWEST")],["oldest",M(n,"SORT_OLDEST")],["name",M(n,"SORT_NAME")]].forEach(([D,N])=>{let G=g.createEl("option",{text:N,attr:{value:D}});D===this.sortQuest&&(G.selected=!0)});let b=u.createEl("select",{attr:{class:"lh-filter"}});[["all",M(n,"FILTER_ALL_DIFFICULTIES")],["easy",M(n,"DIFF_EASY")],["medium",M(n,"DIFF_MEDIUM")],["hard",M(n,"DIFF_HARD")]].forEach(([D,N])=>b.createEl("option",{text:N,attr:{value:D}}));let f=u.createEl("select",{attr:{class:"lh-filter"}});[["all",M(n,"FILTER_ALL")],["incomplete",M(n,"FILTER_INCOMPLETE")],["boss",M(n,"QUEST_BOSS_READY")],["completed",M(n,"QUEST_CLEARED")]].forEach(([D,N])=>{let G=f.createEl("option",{text:N,attr:{value:D}});D===this.filterQuestStatus&&(G.selected=!0)});let v=new Set;this.quests.forEach(D=>D.tags&&D.tags.forEach(N=>v.add(N)));let y=this._createTagFilter(u,v,n,()=>re()),w=u.createEl("div",{attr:{class:"lh-view-toggle",style:"margin-left:8px"}}),k=Object.assign(w.createEl("button",{attr:{class:"lh-view-btn"+(this.viewMode==="list"?" active":""),title:"List"}}),{innerHTML:Ce("list")}),E=Object.assign(w.createEl("button",{attr:{class:"lh-view-btn"+(this.viewMode==="grid"?" active":""),title:"Grid"}}),{innerHTML:Ce("grid")}),S=Object.assign(w.createEl("button",{attr:{class:"lh-view-btn"+(this.viewMode==="tree"?" active":""),title:"Tree"}}),{innerHTML:Ce("tree")}),_=i.createEl("div",{attr:{class:"lh-quest-content"}}),C={easy:{bg:"#ecfdf5",color:"#059669",label:M(n,"DIFF_EASY")},medium:{bg:"#eef2ff",color:"#4f46e5",label:M(n,"DIFF_MEDIUM")},hard:{bg:"#fef2f2",color:"#dc2626",label:M(n,"DIFF_HARD")}},L={"sky-island":["linear-gradient(135deg,#818cf8,#c4b5fd)","linear-gradient(135deg,#4338ca,#6d28d9)"],ocean:["linear-gradient(135deg,#7dd3fc,#a5b4fc)","linear-gradient(135deg,#1e40af,#4338ca)"],forest:["linear-gradient(135deg,#86efac,#a5b4fc)","linear-gradient(135deg,#166534,#1e3a5f)"],galaxy:["linear-gradient(135deg,#c4b5fd,#f9a8d4)","linear-gradient(135deg,#5b21b6,#9d174d)"],dungeon:["linear-gradient(135deg,#fca5a5,#c4b5fd)","linear-gradient(135deg,#991b1b,#6d28d9)"],space:["linear-gradient(135deg,#a5b4fc,#99f6e4)","linear-gradient(135deg,#3730a3,#115e59)"],cyber:["linear-gradient(135deg,#6ee7b7,#a5b4fc)","linear-gradient(135deg,#065f46,#312e81)"],default:["linear-gradient(135deg,#a5b4fc,#c4b5fd)","linear-gradient(135deg,#4338ca,#7c3aed)"]},A={};for(let D in L){let N={"sky-island":["\u5929\u7A7A\u5CF6","Sky Island"],ocean:["\u6D77\u6D0B","Ocean"],forest:["\u68EE\u6797","Forest"],galaxy:["\u661F\u7CFB","Galaxy"],dungeon:["\u5730\u57CE","Dungeon"],space:["\u592A\u7A7A\u8266\u968A","Space Fleet"],cyber:["\u8CFD\u535A\u7DB2\u8DEF","Cyber Network"],default:["\u4EFB\u52D9","Mission"]},G={"sky-island":"cloud",ocean:"wave",forest:"tree",galaxy:"galaxy",dungeon:"castle",space:"rocket",cyber:"robot",default:"star"};A[D]={icon:G[D],gradient:s?L[D][1]:L[D][0],label:a?N[D][0]:N[D][1]}}let T=D=>D.title||(D.file?D.file.basename.replace(/-quest$/i,""):D.slug||"Quest Map"),q=D=>A[D.style]||A.default,I=D=>D.nodeCount?Fe(M(n,"QUEST_PROGRESS_COUNT"),{done:D.completedCount,total:D.nodeCount}):M(n,"QUEST_NO_PROGRESS"),R=D=>D.scenario||D.missionGoal||D.stakes||M(n,"QUEST_SCENARIO_FALLBACK"),O=D=>D.completed?M(n,"QUEST_CLEARED"):D.bossReady?M(n,"QUEST_BOSS_READY"):D.progressPct>0?M(n,"QUEST_IN_PROGRESS"):M(n,"QUEST_NOT_STARTED"),$=D=>D.completed?M(n,"QUEST_REVIEW_CLEAR"):D.bossReady?M(n,"QUEST_CHALLENGE_BOSS"):D.progressPct>0?M(n,"QUEST_CONTINUE_MISSION"):M(n,"OPEN_QUEST"),z=D=>"lh-quest-open "+(D.completed?"lh-quest-open-cleared":D.bossReady?"lh-quest-open-boss":"lh-quest-open-primary"),B=D=>{new Hi(this.app,this.plugin,D,async()=>{try{this._libraryReady=null,await this.loadLibrary(),this._hubOpen&&this.render()}catch(N){console.warn("EngramQuest: quest refresh failed",N)}}).open()},P=D=>{try{let N=D&&D.bossReady&&!D.completed?"hard":String(D&&D.difficulty||"medium").toLowerCase();["easy","medium","hard"].includes(N)||(N="medium");let G=this.app&&this.app.vault&&this.app.vault.adapter;return!G||typeof G.getResourcePath!="function"?null:G.getResourcePath(ne.normalizePath(`${this.plugin.manifest.dir}/assets/quest-map/card-headers/quest-card-header-${N}.webp`))}catch(N){return console.warn("EngramQuest: quest card header asset unavailable",N),null}},K=(D,N,G)=>{D.style.background=G.gradient;let U=P(N);U&&(D.style.backgroundImage=`linear-gradient(90deg,${s?"rgba(15,23,42,.28)":"rgba(255,255,255,.30)"},rgba(255,255,255,0) 46%),url("${U}")`,D.style.backgroundSize="cover",D.style.backgroundPosition="center",D.style.backgroundRepeat="no-repeat")},Z=(D,N)=>{if(!N.file&&N.sourceType!=="package")return null;let G=D.createEl("button",{attr:{class:"lh-delete-btn",title:M(n,"DELETE")}});return G.appendChild(ne.sanitizeHTMLToDom('<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4h6v2"/></svg>')),G.addEventListener("click",U=>{U.stopPropagation(),this._confirmDelete(N,N.sourceType==="package"?"quest-package":"quest")}),G},W=(D,N)=>{let G=D.createEl("div",{attr:{class:"lh-quest-progress"}}),U=G.createEl("div",{attr:{class:"lh-quest-progress-top"}});U.createEl("span",{text:I(N)}),U.createEl("span",{text:N.bestScorePct!=null?Fe(M(n,"QUEST_BEST_SCORE"),{score:N.bestScorePct}):O(N)}),G.createEl("div",{attr:{class:"lh-quest-progress-bar"}}).createEl("div",{attr:{style:"width:"+Math.max(0,Math.min(100,N.progressPct||0))+"%"}})},V=(D,N)=>{let G=D.createEl("div",{attr:{class:"lh-quest-meta"}}),U=C[N.difficulty]||{bg:"#ede9fe",color:"#6366f1",label:N.difficulty},le=G.createEl("span",{text:U.label});le.style.background=U.bg,le.style.color=U.color,G.createEl("span",{text:Fe(M(n,"QUEST_MISSION_COUNT"),{count:N.missionCount||0})}),N.missionTypes&&N.missionTypes.length&&G.createEl("span",{text:N.missionTypes.slice(0,3).join(" / ")}),N.bossTitle&&G.createEl("span",{text:Fe(M(n,"QUEST_BOSS_LABEL"),{boss:N.bossTitle})})},X=(D,N)=>{let G=q(N),U=D.createEl("div",{attr:{class:"lh-quest-row"+(N.completed?" is-cleared":"")+(N.bossReady?" is-boss-ready":"")}}),le=U.createEl("div",{attr:{class:"lh-quest-row-cover"}});le.style.background=G.gradient,le.createEl("span"),le.lastElementChild.innerHTML=Ce(G.icon);let J=U.createEl("div",{attr:{class:"lh-quest-row-body"}}),se=J.createEl("div",{attr:{class:"lh-quest-row-head"}});se.createEl("div",{text:T(N),attr:{class:"lh-quest-card-name"}}),se.createEl("span",{text:O(N),attr:{class:"lh-quest-status"}}),J.createEl("div",{text:R(N),attr:{class:"lh-quest-scenario"}}),V(J,N),W(J,N);let ce=U.createEl("div",{attr:{class:"lh-quest-row-side"}});ce.createEl("button",{text:$(N),attr:{class:z(N)}}).addEventListener("click",nt=>{nt.stopPropagation(),B(N)}),Z(ce,N),U.addEventListener("click",()=>B(N))},oe=(D,N)=>{var Xa,er;let G=q(N),U=D.createEl("div",{attr:{class:"lh-quest-card"+(N.completed?" is-cleared":"")+(N.bossReady?" is-boss-ready":"")}}),le=U.createEl("div",{attr:{class:"lh-quest-card-cover"}});K(le,N,G);let J=Z(U,N);J&&J.classList.add("lh-quest-card-del");let se=U.createEl("div",{attr:{class:"lh-quest-card-body"}}),ce=(er=(Xa=N.bestScorePct)!=null?Xa:N.progressPct)!=null?er:0,ut=ce>=90?3:ce>=60?2:ce>0?1:0,nt=se.createEl("div",{attr:{class:"lh-quest-stars"}});for(let gt=0;gt<3;gt++){let So=gt<ut?'<svg viewBox="0 0 24 24" width="16" height="16"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26" fill="#f59e0b" stroke="#d97706" stroke-width="0.8"/></svg>':'<svg viewBox="0 0 24 24" width="16" height="16"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26" fill="none" stroke="#d1d5db" stroke-width="1.2"/></svg>';nt.appendChild(ne.sanitizeHTMLToDom(So))}se.createEl("div",{text:T(N),attr:{class:"lh-quest-card-name"}}),se.createEl("div",{text:R(N),attr:{class:"lh-quest-scenario"}}),V(se,N),W(se,N),se.createEl("button",{text:$(N),attr:{class:z(N)}}).addEventListener("click",gt=>{gt.stopPropagation(),B(N)}),U.addEventListener("click",()=>B(N))},re=()=>{_.empty();let D=m.value.toLowerCase(),N=b.value,G=y.value,U=this.quests.filter(J=>{let se=[T(J),J.file&&J.file.parent&&J.file.parent.path,J.slug,J.description,J.scenario,J.missionGoal,J.stakes,(J.tags||[]).join(" ")].join(" ").toLowerCase(),ce=this.filterQuestStatus==="all"||(this.filterQuestStatus==="completed"?J.completed:this.filterQuestStatus==="boss"?J.bossReady:!J.completed);return(N==="all"||J.difficulty===N)&&(G==="all"||J.tags&&J.tags.includes(G))&&ce&&se.includes(D)});if(this._sortItems(U,this.sortQuest,!0),U.length===0){_.createEl("div",{text:M(n,"EMPTY_FILTERED_QUESTS"),attr:{class:"lh-empty"}});return}if(this.viewMode==="tree"){let J=_.createEl("div",{attr:{class:"lh-tree-container lh-quest-tree"}}),se=this._buildTree(U,ce=>ce.file&&ce.file.parent?ce.file.parent.path:ce.sourceType==="package"?"engram-quest/quests/"+ce.slug:"/");this._renderQuestTree(J,se,n,C,A);return}if(this.viewMode==="grid"){let J=_.createEl("div",{attr:{class:"lh-quest-grid"}});U.forEach(se=>oe(J,se));return}let le=_.createEl("div",{attr:{class:"lh-quest-list"}});U.forEach(J=>X(le,J))};m.addEventListener("input",()=>{this.searchQuery=m.value,re()}),g.addEventListener("change",()=>{this.sortQuest=g.value,re()}),b.addEventListener("change",re),f.addEventListener("change",()=>{this.filterQuestStatus=f.value,re()}),k.addEventListener("click",async()=>{this.viewMode="list",this.viewModes[this.activeTab]="list",this.plugin.settings._viewModes=this.viewModes,await this.plugin.saveData(this.plugin.settings),k.classList.add("active"),E.classList.remove("active"),S.classList.remove("active"),re()}),E.addEventListener("click",async()=>{this.viewMode="grid",this.viewModes[this.activeTab]="grid",this.plugin.settings._viewModes=this.viewModes,await this.plugin.saveData(this.plugin.settings),E.classList.add("active"),k.classList.remove("active"),S.classList.remove("active"),re()}),S.addEventListener("click",async()=>{this.viewMode="tree",this.viewModes[this.activeTab]="tree",this.plugin.settings._viewModes=this.viewModes,await this.plugin.saveData(this.plugin.settings),S.classList.add("active"),k.classList.remove("active"),E.classList.remove("active"),re()}),re()}_renderQuadrantTab(e){Rh(e,this)}_renderLessonTab(e){Ih(e,this)}_renderAchievementTab(e){Lh(e,this.plugin,this.decks,this.quests,this.memories,this.courses)}_buildTree(e,n){let a={name:"Root",children:{},items:[]};return e.forEach(s=>{let r=(n(s)||"").split("/").filter(l=>l.trim()),o=a;r.forEach(l=>{o.children[l]||(o.children[l]={name:l,children:{},items:[]}),o=o.children[l]}),o.items.push(s)}),a}_renderTreeRecursive(e,n,a,s="",i=0){if(Object.keys(n.children).sort((o,l)=>o.localeCompare(l)).forEach(o=>{let l=n.children[o],c=s?s+"/"+o:o,p=this.treeExpanded[c]!==void 0?this.treeExpanded[c]:i===0,d=e.createEl("details",{attr:{class:"lh-tree-details"}});p&&d.setAttribute("open",""),d.addEventListener("toggle",async m=>{m.target===d&&(this.treeExpanded[c]=d.open,this.plugin.settings._treeExpanded=this.treeExpanded,await this.plugin.saveData(this.plugin.settings))});let u=d.createEl("summary",{attr:{class:"lh-tree-summary"}}).createEl("div",{attr:{class:"lh-tree-summary-inner"}});u.createEl("span",{attr:{class:"lh-tree-folder-icon"}}).innerHTML=Ce("folder"),u.createEl("span",{text:l.name,attr:{class:"lh-tree-folder-name"}});let x=d.createEl("div",{attr:{class:"lh-tree-content"}});this._renderTreeRecursive(x,l,a,c,i+1)}),n.items.length>0){let o=e.createEl("div",{attr:{class:"lh-tree-items-container"}});n.items.forEach(l=>{a(o,l)})}}_renderReviewTree(e,n,a,s){let i=0,r="ontouchstart"in window||navigator.maxTouchPoints>0;this._renderTreeRecursive(e,n,(o,l)=>{let c=l.due+l.unseen,p=o.createEl("div",{attr:{class:`lh-deck-row lh-tree-item-row${c===0?" is-emptyready":""}`}}),d=p.createEl("div",{attr:{class:"lh-deck-main"}}),h=d.createEl("div",{attr:{class:"lh-deck-row-name",style:"display:flex;align-items:center;gap:8px;"}});h.createEl("span",{text:s[i%s.length],attr:{style:"font-size:16px;line-height:1;"}}),h.createEl("span",{text:l.name.split("/").pop()}),d.createEl("div",{text:c>0?`${M(a,"READY_NOW")}: ${c}`:Fe(M(a,"ALL_SCHEDULED"),{total:l.total}),attr:{class:"lh-deck-row-sub"}});let u=p.createEl("div",{attr:{class:"lh-deck-metrics"}}),x=u.createEl("div",{attr:{class:`lh-deck-ready${c===0?" is-zero":""}`}});x.createEl("div",{text:M(a,"READY"),attr:{class:"lh-deck-ready-label"}}),x.createEl("div",{text:String(c),attr:{class:"lh-deck-ready-num"}});let m=u.createEl("div",{attr:{class:"lh-deck-breakdown"}});[{key:"FILTER_DUE",cls:"due",value:l.due},{key:"FILTER_NEW",cls:"new",value:l.unseen},{key:"TOTAL",cls:"total",value:l.total}].forEach(g=>{let b=m.createEl("div",{attr:{class:`lh-deck-metric ${g.cls}`}});b.createEl("div",{text:M(a,g.key),attr:{class:"lh-deck-metric-label"}}),b.createEl("div",{text:String(g.value),attr:{class:"lh-deck-metric-num"}})}),p.title=c===0?Fe(M(a,"NO_READY_IN_DECK"),{deck:l.name,total:l.total}):"";{let g=p.createEl("button",{attr:{class:"lh-delete-btn",title:M(a,"CREATE_CARD"),style:"color:#6366f1;border-color:#6366f1;"+(r?"opacity:1;":"")}});g.appendChild(ne.sanitizeHTMLToDom('<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>')),g.addEventListener("click",b=>{b.stopPropagation(),Oa(this.app,l,a,async()=>{await this.loadData(),this.render()})})}{let g=p.createEl("button",{attr:{class:"lh-delete-btn",title:M(a,"DELETE"),style:r?"opacity:1;":""}});g.appendChild(ne.sanitizeHTMLToDom('<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4h6v2"/></svg>')),g.addEventListener("click",b=>{b.stopPropagation(),this._confirmDelete(l,"deck")})}p.addEventListener("click",()=>this._startDeckReview(l)),i++})}_renderMemoryTree(e,n,a,s,i){let r=0;this._renderTreeRecursive(e,n,(o,l)=>{let c=o.createEl("div",{attr:{class:"lh-tab-list-item lh-tree-item-row"}}),p=c.createEl("div",{attr:{class:"lh-qm-icon-wrap"}}),d=r%s.length;p.style.cssText=`width:40px;height:40px;border-radius:12px;background:${i[d].replace("linear-gradient(135deg, ","").split(",")[0]}22;color:${i[d].replace("linear-gradient(135deg, ","").split(",")[0]};display:flex;align-items:center;justify-content:center;flex-shrink:0`,p.appendChild(ne.sanitizeHTMLToDom(s[d]));let h=c.createEl("div",{attr:{style:"flex:1;min-width:0"}});h.createEl("div",{text:l.baseName,attr:{class:"lh-tab-list-title"}});let u=l.tags&&l.tags.length>0?l.tags.join(" "):"";h.createEl("div",{text:u,attr:{class:"lh-tab-list-sub"}}),c.createEl("div",{attr:{class:"lh-mm-open",style:"margin-top:0"}}).appendChild(ne.sanitizeHTMLToDom(`${M(a,"OPEN_MAP")} <span style="font-size:16px">\u2192</span>`));{let m=c.createEl("button",{attr:{class:"lh-delete-btn",title:M(a,"DELETE")}});m.appendChild(ne.sanitizeHTMLToDom('<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4h6v2"/></svg>')),m.addEventListener("click",g=>{g.stopPropagation(),this._confirmDelete(l,"memory")})}c.addEventListener("click",()=>{this._minimize(),this.app.workspace.openLinkText(l.file.path,"",!1)}),r++})}_renderQuestTree(e,n,a,s,i){this._renderTreeRecursive(e,n,(r,o)=>{let l=o.title||(o.file?o.file.basename.replace(/-quest$/,""):o.slug||"Quest Map"),c=s[o.difficulty]||{bg:"#e0f2fe",color:"#38bdf8",label:o.difficulty},p=i[o.style]||i["sky-island"],d=(p.gradient.match(/#[a-fA-F0-9]{3,6}/g)||["#3b82f6","#60a5fa"])[0],h=r.createEl("div",{attr:{class:"lh-tab-list-item lh-tree-item-row"+(o.completed?" is-cleared":"")}}),u=h.createEl("div",{attr:{class:"lh-qm-icon-wrap"}});u.style.cssText=`width:40px;height:40px;border-radius:12px;background:${d}22;color:${d};display:flex;align-items:center;justify-content:center;flex-shrink:0;`,u.innerHTML=Ce(p.icon);let x=h.createEl("div",{attr:{style:"flex:1;min-width:0"}}),m=x.createEl("div",{attr:{style:"display:flex;align-items:center;gap:6px;"}});m.createEl("div",{text:l,attr:{class:"lh-tab-list-title"}}),o.completed&&(m.createEl("span",{attr:{style:"font-size:10px;font-weight:700;color:#16a34a;background:#dcfce7;border-radius:99px;padding:2px 8px;white-space:nowrap;"}}).innerHTML=Ce("check")+" "+M(a,"QUEST_CLEARED"));let g=o.tags&&o.tags.length>0?o.tags.join(" "):"";x.createEl("div",{text:g,attr:{class:"lh-tab-list-sub"}});let b=h.createEl("div",{attr:{style:"display:flex; flex-direction:column; align-items:flex-end; white-space:nowrap"}}),f=b.createEl("span",{text:c.label,attr:{class:"lh-qm-diff-badge"}});if(f.style.cssText=`background:${c.bg};color:${c.color};border-radius:99px;padding:4px 12px;font-size:11px;font-weight:700;margin-bottom:6px`,b.createEl("div",{attr:{class:"lh-mm-open",style:"margin-top:0"}}).appendChild(ne.sanitizeHTMLToDom(`${M(a,"OPEN_QUEST")} <span style="font-size:16px">\u2192</span>`)),o.file){let y=b.createEl("button",{attr:{class:"lh-delete-btn",title:M(a,"DELETE")}});y.appendChild(ne.sanitizeHTMLToDom('<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4h6v2"/></svg>')),y.addEventListener("click",w=>{w.stopPropagation(),this._confirmDelete(o,"quest")})}h.addEventListener("click",()=>{new Hi(this.app,this.plugin,o,async()=>{try{this._libraryReady=null,await this.loadLibrary(),this._hubOpen&&this.render()}catch(y){console.warn("EngramQuest: quest refresh failed",y)}}).open()})})}};Fi.exports={HubModal:Nh}});var Bi=Y((pg,Qi)=>{"use strict";var Dh={"lesson-academy/references/html-recipe.md":`# Lesson HTML Recipe \u2014 hard rules & visual guide

Lessons render inside Obsidian in a sandboxed iframe (\`sandbox="allow-scripts"\`, loaded via
\`srcdoc\`). Obsidian is an Electron app with a strict Content-Security-Policy: **any externally
loaded resource silently fails** \u2014 no error, just missing fonts/styles/scripts. These rules
exist because violations look fine in a normal browser but break inside Obsidian.

## Hard rules (CSP compatibility)

1. **One file, fully self-contained.** All CSS in one \`<style>\` block, all JS in one
   \`<script>\` block at the end of \`<body>\`.
2. **No external loads of any kind**:
   - \u274C \`<link rel="stylesheet" href="https://\u2026">\` (no Google Fonts, no CDN CSS)
   - \u274C \`<script src="https://\u2026">\` (no CDN libraries \u2014 write vanilla JS)
   - \u274C \`@import\`, \`url(https://\u2026)\` in CSS
   - \u274C \`<img src="https://\u2026">\` (use emoji, styled divs, or inline SVG instead)
   - \u2705 Raster images are allowed only when self-contained (for example a
     \`data:image/...;base64\` URI) or when the plugin explicitly resolves local vault resources.
     Do not point lesson HTML at Codex temp files or generated-image cache paths.
3. **Citations are fine** \u2014 \`<a href="https://\u2026" target="_blank">\` only *navigates* on click,
   it doesn't load anything. Cite sources freely.
4. **System font stack only**:
   \`font-family: -apple-system, 'Segoe UI', 'Noto Sans TC', sans-serif;\`
   (\`'Noto Sans TC'\` helps zh rendering; monospace: \`'Consolas', 'JetBrains Mono', monospace\`)
5. **No \`eval()\` / \`new Function()\`** \u2014 Electron CSP blocks them.
6. **\`<title>\` is required** \u2014 set it to the lesson title (the plugin's import flow and tab
   tooling read it).
7. Start with \`<!DOCTYPE html>\` and \`<meta charset="UTF-8">\`.

## Raster image assets

Use raster images sparingly and only when they teach better than text or SVG. See
\`visual-teaching.md\` for when to use AI image generation capability versus SVG/HTML diagrams.

When using a generated image:

1. Copy the selected asset into \`engram-quest/lessons/{courseSlug}/assets/\`.
2. Embed it into the lesson as a self-contained data URI unless the plugin has a confirmed local
   resource resolver for lesson iframes.
3. Add meaningful \`alt\` text and a caption that states the learning job of the image.
4. Make it responsive:
   \`\`\`css
   .lesson-image img { display:block; width:100%; height:auto; object-fit:contain; }
   \`\`\`
5. Check file size. If a lesson becomes heavy, reduce image dimensions or split the content.

If exact labels, arrows, or technical flow text must be correct, prefer inline SVG/HTML or place
those exact labels outside/on top of the generated image as a hybrid visual.

## Theme

Lessons may optionally listen for the plugin's theme message:

\`\`\`js
window.addEventListener("message", (e) => {
  if (e.data && e.data.type === "engram-quest-theme") {
    document.body.classList.toggle("light", !e.data.dark);
  }
});
\`\`\`

This is optional \u2014 a well-designed dark theme with sufficient contrast reads fine in both
Obsidian themes. If you skip theme handling, design dark-first (the reference palette below).

## Reference palette (dark)

\`\`\`css
:root {
  --bg: #0f1117;       /* page background */
  --surface: #1a1d27;  /* cards, callouts */
  --surface2: #242736; /* nested surfaces, table headers */
  --border: #2e3248;
  --accent: #6c8fff;   /* h2, links, primary highlights */
  --accent2: #a78bfa;  /* h3, secondary highlights */
  --green: #34d399;  --yellow: #fbbf24;  --red: #f87171;
  --text: #e2e8f0;   --muted: #94a3b8;
  --code-bg: #0d1117;
}
body { max-width: 860px; margin: 0 auto; padding: 48px 24px 80px;
       background: var(--bg); color: var(--text); line-height: 1.7; font-size: 16px; }
\`\`\`

## Responsive / mobile (hard rules)

Lessons are read on phones inside a ~380\u2013500px-wide iframe as often as on desktop. A fixed
multi-column layout at that width squeezes each column to ~120px, and CJK text then wraps
**one character per line** \u2014 unreadable. Every layout must survive a 400px viewport:

1. **Every multi-column grid collapses on narrow screens.** Either add a media query:
   \`\`\`css
   .concept-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
   @media (max-width: 600px) { .concept-cards { grid-template-columns: 1fr; } }
   \`\`\`
   or make it intrinsically responsive (no media query needed):
   \`\`\`css
   grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
   \`\`\`
2. **Fixed-width label columns** (e.g. timeline \`grid-template-columns: 100px 1fr\`) \u2014 stack
   them on mobile (\`@media (max-width:600px){ grid-template-columns: 1fr; }\` puts the label
   above the content).
3. **Horizontal flow diagrams** (pipelines, step arrows built from flex divs): give the
   container \`overflow-x: auto\` and each stage a \`min-width\` \u2014 on phones it scrolls
   horizontally instead of crushing the stages.
4. **Wide tables**: wrap in \`<div style="overflow-x:auto">\` so the page never overflows.
5. Sanity check before finishing: imagine the lesson at 400px wide \u2014 no column should force
   text below ~8 characters per line, and nothing should overflow the viewport.

- **Header**: lesson number tag pill \u2192 \`<h1>\` title \u2192 one-line subtitle in \`--muted\`.
- **Callout boxes** for key points / warnings / pitfalls: left-border colored box with an
  uppercase label (\`KEY INSIGHT\` / \`PITFALL\` / \`WHY THIS MATTERS\`).
- **Tables** for comparisons; **styled div diagrams** for architecture/flows (boxes + arrows
  built from flex divs \u2014 never external images).
- **Code blocks**: \`<pre><code>\` with \`--code-bg\`, syntax-color important tokens manually
  with spans (\`.keyword\`, \`.string\`, \`.fn\`).
- **Folder trees & indented text blocks**: always use \`<pre>\`, never \`<div>\`.
  HTML collapses all whitespace inside \`<div>\` \u2014 the entire tree becomes one line.
  \`<pre>\` preserves newlines and indentation by default; add \`overflow-x: auto\` for long lines.
  \`\`\`html
  <!-- \u2705 correct -->
  <pre class="folder-tree">src/
  \u251C\u2500\u2500 Domain/
  \u2502   \u2514\u2500\u2500 Order.cs
  \u2514\u2500\u2500 App/</pre>

  <!-- \u274C wrong \u2014 collapses to one line in Obsidian iframe -->
  <div class="folder-tree">src/
  \u251C\u2500\u2500 Domain/
  \u2502   \u2514\u2500\u2500 Order.cs</div>
  \`\`\`
- **Footer**: citation links + "ask your AI teacher follow-up questions" reminder.

## Interactive quiz (end of every lesson)

3\u20135 questions, vanilla JS, immediate visual feedback. Minimal working pattern:

**CRITICAL UX rule**: wrong answers must allow retry \u2014 only lock the question after a correct answer.
Do NOT add \`pointer-events: none\` or \`cursor: default\` to \`.wrong\` \u2014 users must be able to click again.

\`\`\`html
<div class="quiz">
  <h3>\u2705 \u81EA\u6211\u6AA2\u6E2C</h3>
  <p class="quiz-q">1. Which layer does X belong to?</p>
  <div class="quiz-options" data-answer="1">
    <div class="quiz-opt">Option A</div>
    <div class="quiz-opt">Option B (correct)</div>
  </div>
  <div class="quiz-feedback"></div>
</div>
<style>
/* IMPORTANT: .wrong must NOT have pointer-events:none \u2014 retry must stay available */
.quiz-opt { cursor: pointer; border: 1px solid var(--border); border-radius: 8px;
            padding: 10px 14px; margin: 6px 0; transition: background .15s; }
.quiz-opt:hover { background: var(--surface2); }
.quiz-opt.correct { background: rgba(52,211,153,.15); border-color: var(--green); pointer-events: none; cursor: default; }
.quiz-opt.wrong   { background: rgba(248,113,113,.12); border-color: var(--red); /* no pointer-events:none */ }
.quiz-feedback { min-height: 1.4em; margin-top: 6px; font-size: .9em; }
.quiz-feedback.correct { color: var(--green); }
.quiz-feedback.wrong   { color: var(--red); }
</style>
<script>
document.querySelectorAll(".quiz-options").forEach(group => {
  const fb = group.parentElement.querySelector(".quiz-feedback");
  const opts = group.querySelectorAll(".quiz-opt");
  let solved = false;
  opts.forEach((opt, i) => {
    opt.addEventListener("click", () => {
      if (solved) return;                          // locked only after correct
      const ok = i === Number(group.dataset.answer);
      opts.forEach(o => o.classList.remove("wrong")); // clear previous wrong marks
      if (ok) {
        solved = true;
        opt.classList.add("correct");
        fb.textContent = "\u2713 \u6B63\u78BA\uFF01<short why>";
        fb.className = "quiz-feedback show correct";
      } else {
        opt.classList.add("wrong");
        fb.textContent = "\u2717 \u518D\u60F3\u60F3 \u2014 <hint>";
        fb.className = "quiz-feedback show wrong";
      }
    });
  });
});
<\/script>
\`\`\`

Always explain *why* in the feedback text \u2014 the explanation is where the learning happens.

## Length & density

A lesson is 10\u201320 minutes of reading + the quiz. As HTML that's typically 300\u2013600 lines.
If a topic needs more, that's a signal to split it into two lessons in the outline.
`,"lesson-academy/references/lesson-format.md":'# Lesson Format \u2014 directory layout & meta.json contract\n\nThis file defines the exact on-disk format the EngramQuest plugin reads. The plugin\'s data\nlayer (`src/lessons/data.js`) parses these files; deviating from this shape makes courses\ninvisible or breaks progress tracking.\n\n## Directory layout\n\n```\nengram-quest/lessons/\n\u2514\u2500\u2500 {courseSlug}/                      \u2190 short kebab-case ASCII, one folder per course\n    \u251C\u2500\u2500 meta.json                      \u2190 course contract (below)\n    \u251C\u2500\u2500 lsn-1717977600000-types.html   \u2190 one self-contained HTML per lesson\n    \u2514\u2500\u2500 lsn-1717977700000-linq.html\n```\n\n`courseSlug` rules: lowercase ASCII letters, digits, hyphens. No spaces, no CJK (the slug is a\nfolder name; the human-readable title lives in meta.json).\n\n## meta.json schema\n\n```json\n{\n  "title": "dotnet \u5165\u9580",\n  "topic": "dotnet",\n  "icon": "\u{1F537}",\n  "description": "\u5F9E\u578B\u5225\u7CFB\u7D71\u5230 LINQ\uFF0C\u7D66\u5F8C\u7AEF\u65B0\u624B\u7684 .NET \u57FA\u790E\u8AB2\u3002",\n  "tags": ["programming", ".NET"],\n  "colorScheme": "indigo",\n  "starred": false,\n  "createdAt": "2026-06-10",\n  "lessons": [\n    {\n      "id": "lsn-1717977600000",\n      "title": "\u578B\u5225\u7CFB\u7D71\u57FA\u790E",\n      "file": "lsn-1717977600000-types.html",\n      "source": "skill"\n    }\n  ],\n  "completion": {\n    "lsn-1717977600000": { "viewed": false, "completed": false, "starred": false }\n  }\n}\n```\n\n### Field reference\n\n| Field | Type | Notes |\n|---|---|---|\n| `title` | string | Human-readable course name, user\'s language |\n| `topic` | string | Short topic keyword (used for matching later requests to this course) |\n| `icon` | string | One emoji, shown on the course card |\n| `description` | string | One sentence for the course card (2-line clamp in UI) |\n| `tags` | string[] | 2\u20133 short tags, user\'s language |\n| `colorScheme` | string | Exactly one of: `indigo` `green` `amber` `rose` `cyan` |\n| `starred` | bool | Course-level star \u2014 plugin-owned, initialize `false`, never reset on update |\n| `createdAt` | string | `YYYY-MM-DD`, set once at course creation |\n| `lessons` | array | **Array order = display order.** No order field exists. |\n| `lessons[].id` | string | `lsn-<unix-ms>`. Unique forever; never reuse or renumber |\n| `lessons[].title` | string | Lesson title, user\'s language |\n| `lessons[].file` | string \\| null | Filename (not path) of the HTML inside the course folder. **`null` = planned** (outline entry without content yet \u2014 see below) |\n| `lessons[].source` | string | `"skill"` for generated lessons. (`"import"` is written by the plugin\'s import button \u2014 never set it yourself) |\n| `completion` | object | Keyed by lesson id \u2014 **plugin-owned progress state** |\n\n### Ownership rules (read carefully)\n\nThe skill and the plugin share this file, so writes must merge, not replace:\n\n- **Skill owns**: `title`, `topic`, `icon`, `description`, `tags`, `colorScheme`,\n  `createdAt`, and `lessons[]` entries with `source:"skill"`.\n- **Plugin owns**: `starred`, the entire `completion` map, and `lessons[]` entries with\n  `source:"import"` (user-imported HTML).\n\nWhen updating an existing course:\n1. Read the current meta.json.\n2. Apply your changes (append lessons, fix a title, \u2026).\n3. Preserve everything else byte-for-byte semantically \u2014 especially `completion`,\n   `starred`, and any `source:"import"` lesson entries.\n4. For each lesson you add, also add\n   `completion[id] = { "viewed": false, "completed": false, "starred": false }`.\n\nWhen regenerating one lesson\'s HTML (user asked for a redo): keep the same `id` and `file`,\noverwrite only the HTML file. The completion state then carries over automatically.\n\n## Planned lessons (outline entries, `file: null`)\n\nThe plugin\'s "Create course" form (and the skill itself, when the user only wants an outline)\ncan write lesson entries with `"file": null` \u2014 a title-only placeholder shown as "\u5F85\u751F\u6210 /\nPlanned" in the UI:\n\n```json\n{ "id": "lsn-1718000000000", "title": "REST API \u8A2D\u8A08", "file": null, "source": "skill" }\n```\n\nWhen the user asks to "generate my course" / "\u628A\u8AB2\u7DB1\u751F\u6210\u5167\u5BB9" / "fill in the lessons":\n1. Read meta.json, find every entry with `file: null`.\n2. Generate HTML for each, **keeping the entry\'s existing `id`** (progress and order depend\n   on it). Filename = `<existing-id>-<short-ascii-slug>.html`.\n3. Set the entry\'s `file` to the new filename. Touch nothing else in the entry.\n\nThe skill may also create outline-only courses (all `file: null`) when the user wants to\nreview/edit the plan in the plugin UI before committing to generation.\n\n## Completion map (plugin-written, for your reference)\n\nThe plugin writes these as the user studies \u2014 you may read them to decide what to teach next:\n\n```json\n"completion": {\n  "lsn-1717977600000": {\n    "viewed": true,            \u2190 opened at least once\n    "completed": true,         \u2190 user pressed "mark done"\n    "starred": false,          \u2190 user starred this lesson\n    "lastViewed": "2026-06-10T10:00:00.000Z"\n  }\n}\n```\n\n"Continue my course" = generate/point to the first lesson whose `completed` is false.\n',"lesson-academy/references/obsidian-cli.md":`# Obsidian CLI Reference

## What it is

Obsidian CLI is the **official** Obsidian command line interface (v1.12+).
It uses Obsidian's built-in index \u2014 the same index the GUI uses.
Results are fast, accurate, and support full Obsidian search syntax.

**Requirement**: Obsidian app must be running. Enable via Settings \u2192 General \u2192 Enable Command line interface.

## Search syntax

\`\`\`bash
# Full-text search
obsidian search query="keyword"

# Tag search
obsidian search query="tag:#flashcards/azure" limit=20

# Path filter
obsidian search query="path:Study keyword"

# JSON output (use this for programmatic processing)
obsidian search query="keyword" format=json

# Target a specific vault
obsidian vault="Obsidian_Note" search query="keyword"
\`\`\`

## Query operators

\`\`\`
keyword                  full-text
tag:#flashcards/azure    tag search
path:Study               path filter
file:azure               filename filter
"exact phrase"           exact match
kw1 kw2                  AND (space)
kw1 OR kw2               OR
\`\`\`

## Read / write notes

\`\`\`bash
obsidian read path="folder/note.md"
obsidian read file="Note Name"
obsidian create name="New Note" content="# Hello" silent
obsidian append file="My Note" content="new line"
obsidian property:set name="status" value="done" file="My Note"
\`\`\`

## Known silent failures (v1.12)

| Wrong | Problem | Correct |
|---|---|---|
| \`tasks todo\` | scoped to active file only, returns empty | \`tasks all todo\` |
| \`tags counts\` | returns empty | \`tags all counts\` |
| \`create name="x" content="y"\` | opens GUI | add \`silent\` flag |

## Fallback when CLI is unavailable

If \`obsidian search\` fails (CLI not installed or Obsidian not running), fall back to:

\`\`\`bash
bash scripts/search_vault.sh "<topic>" 30
\`\`\`

\`search_vault.sh\` is grep-based \u2014 slower, no index, no Obsidian syntax. Use only as fallback for note discovery, or for L2 hint personal context search (its only other permitted use).
`,"lesson-academy/references/visual-teaching.md":`# Visual Teaching Guide

Use this when a Lesson Academy lesson would be easier to understand with a visual. A visual is
optional, not a requirement. Add one only when it performs a learning job that text alone does
poorly.

## Contents

1. When to add a visual
2. Choose the visual type
3. Choose the production method
4. Prompt patterns
5. Quality gate
6. HTML integration

## 1. When to add a visual

Add a visual when at least one is true:

- The learner must understand spatial structure, hierarchy, or layout.
- The learner must track steps, branches, or multiple actors.
- The topic is abstract and needs a concrete scene or analogy.
- The lesson compares similar ideas that are easy to confuse.
- The learner needs a decision aid for choosing a method.

Skip visuals when the concept is already simple in prose, when the image would only decorate the
page, or when a short table or code snippet is clearer.

Default visual budget:

- 0 visuals: simple lessons.
- 1 visual: normal target for hard concepts.
- 2 visuals: only when one visual gives real-world context and another explains the mechanism.
- More than 2: split the lesson unless the user explicitly asks for visual experimentation.

## 2. Choose the visual type

Pick by the shape of the knowledge:

Always classify the knowledge shape before choosing the production method. Complex concepts with
multiple actors, layers, or steps are strong candidates for a teaching image \u2014 a well-crafted
visual makes an abstract mechanism immediately graspable. The goal is an image that explains
itself: a learner should be able to look at it and understand the key idea without reading a
paragraph first. Avoid generic atmospheric scenes that look good but teach nothing.

| Knowledge shape | Best visual type | Use for |
|---|---|---|
| Concepts and relationships | Concept map | Terms, dependencies, mental models |
| Steps and branches | Flowchart | Procedures, debugging, conditional logic |
| Actor-to-actor messages | Sequence or swimlane | OAuth, API calls, frontend/backend flows |
| System parts and data movement | System diagram | Architecture, modules, infrastructure |
| Similar ideas and tradeoffs | Comparison | Tool choice, confusing pairs |
| Choosing what to do | Decision tree | Selection rules, troubleshooting |
| Change over time | Timeline | Lifecycles, history, release flow |
| Feedback dynamics | Causal loop | Reinforcing loops, product/system behavior |

Mind maps are good for brainstorming or course planning, but less precise for final teaching than
concept maps or decision trees.

## 3. Choose the production method

Use **AI image generation capability** when the learning value comes from realism, immersion, or a
polished teaching image that compresses a complex concept into one memorable visual:

- Product-like screens, real-world UI states, login pages, dashboards, broken layouts.
- Physical scenes, real workplace contexts, everyday analogies.
- A single focused moment that helps the learner feel the problem.
- Educational infographics for complex mechanisms \u2014 e.g. a numbered step-by-step flow with
  clearly labelled actors and arrows, a layered system diagram, or an authentication sequence.
  The image must show the mechanism, not just the setting. If a learner can glance at it and
  understand the key idea, it passes; if it only sets a mood, it fails.

Actively consider using a teaching image for any lesson where the concept involves multiple
moving parts, layers, or actors. A great teaching image is often more effective than three
paragraphs of prose.

Use **SVG/HTML diagrams** when the learning value comes from precision:

- Exact text labels, arrows, numbered steps, tables, decision trees.
- Flows that must be correct.
- Interactive controls, overlays, or mobile-responsive diagrams.
- Repairing or supplementing an AI-generated image whose labels, arrows, or details are not exact
  enough.

Use **hybrid** when both matter:

- Generate the scene as a bitmap, then add exact HTML/SVG labels, arrows, legends, or captions.
- Prefer hybrid when generated text may be unreliable but the scene itself is valuable.

Do not interpret "generate an image" as "ask for a PNG filename in the prompt." It means use AI
image generation capability when that medium is appropriate, then save/embed the resulting raster
asset.

## 4. Prompt patterns

Keep each generated image focused on one teaching point. Ask for readable text only when needed;
if exact text is critical, use SVG/HTML or hybrid overlays instead.

### Immersive real-world case

Use for: "why should I care?" openings.

\`\`\`text
Create a high-quality Traditional Chinese educational image for <topic>.
Show a realistic product/work scenario where <problem> is visible.
The image should teach one point: <single learning point>.
Include clear title text: "<short title>".
Show before/after or problem/cause/result if useful.
Style: polished online course slide, realistic UI/product context, clean hierarchy.
Avoid: decorative-only imagery, clutter, tiny text, fake glyphs, logos, watermarks.
\`\`\`

### What / Why / How explainer

Use for: concept summaries.

\`\`\`text
Create a Traditional Chinese educational infographic for <topic> using three columns:
WHAT: <what it is>
WHY: <why it matters>
HOW: <how to use or reason about it>
Use clear arrows, simple examples, and one-sentence takeaway.
Style: clean course slide, high information clarity.
Avoid tiny text and garbled labels.
\`\`\`

### Top-down mental model

Use for: hierarchy from big picture to details.

\`\`\`text
Create a Traditional Chinese top-down mental model diagram for <topic>.
Show levels from high-level goal to low-level mechanism:
1. <level>
2. <level>
3. <level>
Include a side example that decomposes one concrete case through those levels.
Style: precise educational infographic, clear hierarchy, readable labels.
\`\`\`

### Flow / sequence

Use for: processes and actor interactions.

\`\`\`text
Create a Traditional Chinese educational flow diagram for <topic>.
Actors: <actor list>.
Steps: <numbered steps>.
Visually distinguish browser/user actions from backend/server actions.
Emphasize the key boundary: <boundary>.
Style: clean technical infographic, clear arrows, readable labels.
\`\`\`

### Comparison

Use for: confusing pairs and tradeoffs.

\`\`\`text
Create a Traditional Chinese comparison infographic: <A> vs <B>.
Compare by these dimensions: <dimension list>.
End with a clear rule of thumb: <decision rule>.
Style: two-column course slide, crisp labels, strong contrast.
\`\`\`

### Decision tree

Use for: turning knowledge into action.

\`\`\`text
Create a Traditional Chinese decision tree for choosing <method/tool>.
Start question: <question>.
Branches: <conditions>.
Leaves: <recommendations>.
Style: clean teaching diagram, minimal text, clear decision path.
\`\`\`

## 5. Quality gate

Before embedding a visual, check:

- It has one clear teaching focus.
- It helps explain something that prose alone would make harder.
- Text in the image is readable, accurate, and not garbled.
- The visual is understandable at lesson width; captions can carry detail, not rescue a bad image.
- A generated image with unreliable text is either regenerated or converted to hybrid/SVG.
- The image does not contain irrelevant logos, fake UI noise, watermarks, or hallucinated claims.
- The lesson still works without the visual if the learner is on a small screen.

If a visual fails the gate, do not keep it just because it looks good.

## 6. HTML integration

Lesson HTML is loaded through a sandboxed \`srcdoc\` iframe. Keep resources self-contained.

For generated raster assets:

1. Copy the selected generated image into the course folder, usually
   \`engram-quest/lessons/{courseSlug}/assets/\`.
2. Embed it as a data URI in the HTML, or use another plugin-supported local resource mechanism
   if one exists.
3. Add \`alt\` text and a caption explaining the learning job of the image.
4. Keep image dimensions responsive: \`width:100%; height:auto; object-fit:contain\`.
5. Watch file size. If one lesson becomes heavy, reduce image dimensions or split the lesson.

For SVG/HTML diagrams:

- Inline the SVG or build the diagram from styled HTML.
- Keep text selectable/readable where possible.
- Use system fonts only.
- Avoid external assets, scripts, fonts, and CSS imports.

For hybrid visuals:

- Use the generated raster image for the scene.
- Add exact labels, arrows, or callouts in HTML/SVG adjacent to or over the image.
- Keep overlays responsive and test narrow widths.
`,"lesson-academy/skills.md":`---
name: engram-lesson-academy
description: >
  Build interactive HTML lesson courses for the EngramQuest plugin's Lesson Academy
  (the "Lessons" / "\u6559\u6750" tab). Given any topic \u2014 programming, marketing, medicine, yoga,
  anything \u2014 this skill designs a course matched to the user's background and goal, then
  generates beautiful self-contained HTML lessons the user studies inside Obsidian.
  Trigger this skill whenever the user says they want to learn something, asks to be taught
  a topic, mentions "teach", "\u6559\u6211", "\u6211\u60F3\u5B78", "\u8AB2\u7A0B", "\u6559\u6750", "lesson", "course",
  or asks to add / extend / regenerate lessons in an existing course \u2014 even if they
  don't name the skill. Also trigger when the user wants study material generated from
  their own vault notes.
---

# Lesson Academy Skill

Create **courses** of interactive HTML **lessons** for the EngramQuest plugin. A course is a
folder holding a \`meta.json\` (course info + lesson list + completion tracking) and one
self-contained HTML file per lesson. The plugin's **Learn \u2192 Lessons** tab renders courses as
cards with progress bars; the user opens each lesson in an in-app viewer and marks it done.

**Which root folder to use.** The plugin scans every folder listed in its "Lesson storage
folders" setting (default: just \`engram-quest/lessons/\`) \u2014 see \`Lesson storage folders\` under
Settings \u2192 EngramQuest \u2192 Lesson Academy in the app. Pick the root **before** Step 1:
- If this course is part of an active curriculum \u2014 i.e. \`8.TRACK/Learning/<domain>/curriculum-<domain>.md\`
  exists for the topic being taught \u2014 write the course under
  \`8.TRACK/Learning/<domain>/lessons/{courseSlug}/\`, alongside that domain's curriculum,
  progress tracking, and diagrams, so the whole domain's study material stays in one place.
- Otherwise (a standalone course with no curriculum context), use the default
  \`engram-quest/lessons/{courseSlug}/\`.
- If unsure which applies, ask the user rather than guessing.

Everywhere below, \`{lessonsRoot}\` means whichever of the two you picked for this course.

Your lessons are the user's actual study material \u2014 quality matters more than quantity.
One excellent, tightly-scoped lesson beats three shallow ones.

## Output language

Lesson content and course metadata (title, description, tags) match the language of the user's
prompt and source notes. If the user writes in Traditional Chinese, teach in Traditional Chinese.

## Core principles

1. **One lesson teaches ONE thing.** Completable in 10\u201320 minutes, ending with a tangible win.
2. **Ground in the user's mission.** Knowing *why* they want to learn shapes every lesson.
   If the goal is unclear, ask before designing the course.
3. **Zone of proximal development.** Each lesson should challenge "just enough" \u2014 build on what
   the previous lessons established, never re-explain what's already mastered.
4. **Never trust parametric knowledge alone for fast-moving topics.** For current
   frameworks/tools/APIs, verify key claims with web search and cite sources in the lesson.
5. **Knowledge first, then practice.** Teach the concept, then end the lesson with an
   interactive quiz or exercise so the user gets an immediate feedback loop.
6. **Use visuals only when they materially improve understanding.** Lessons do not need visuals
   by default. Add a visual when the topic is spatial, procedural, comparative, abstract, or
   easier to understand through a realistic scene. Prefer one primary visual; use two only when
   one sets context and the other explains the mechanism.

### Visual teaching decision

Before writing lesson HTML, decide whether a visual would clarify the one thing this lesson
teaches. If yes, choose the medium by the learning job:

- **First classify the knowledge shape** (see \`references/visual-teaching.md\`). Complex concepts
  \u2014 multi-actor flows, layered architectures, abstract mechanisms \u2014 are strong candidates for a
  teaching image. A well-crafted visual compresses what would take three paragraphs into one
  memorable picture.
- Use **AI image generation capability** for polished educational infographics and teaching
  images that make a complex concept click \u2014 e.g. a step-by-step flow diagram with numbered
  actors and arrows, a layered architecture overview, a before/after comparison. Prompt for a
  *clean technical teaching image*, not a generic decorative scene. A good teaching image is
  immediately informative on its own; a bad one is atmospheric but explains nothing.
- Use **SVG/HTML diagrams** for exact labels, arrows, comparisons, flows, decision trees,
  overlays, or interactive controls where precision matters, or when an AI-generated image fails
  the quality gate.
- Use a **hybrid** when a generated image teaches the big picture but needs exact explanatory
  labels: generated image for the teaching visual, HTML/SVG for labels, arrows, captions, checks,
  or interactive overlays.

If the lesson needs visual strategy, read \`references/visual-teaching.md\` before generating the
HTML. Do not add decorative images.

---

## Step 1: Understand the request

Parse what the user wants:

| Request | Action |
|---|---|
| "I want to learn X" (new topic) | New course \u2192 Step 2 |
| "Add a lesson about Y to my Z course" | Extend existing course \u2192 Step 4 (read meta.json first) |
| "Continue my course" / "next lesson" | Read meta.json + completion, generate the next lesson in the outline |
| "Generate my course" / "\u628A\u8AB2\u7DB1\u751F\u6210\u5167\u5BB9" | Course has planned (\`file: null\`) lessons \u2014 generate HTML for each, keeping their ids (see \`references/lesson-format.md\` \xA7 Planned lessons) |
| "Just build me an outline first" | Create the course with all lessons as \`file: null\` so the user can edit the plan in the plugin UI before generation |
| "Make lessons from my notes on X" | Search vault for source notes, then Step 2 |

The user can also create courses and outline entries directly in the plugin ("\u5EFA\u7ACB\u65B0\u8AB2\u7A0B" /
"\u65B0\u589E\u8AB2\u7DB1") \u2014 those appear as lessons with \`"file": null\`. Generating their content is the
most common follow-up request this skill receives.

Before asking the user anything, check what already exists \u2014 across every configured root, not
just the default one:

\`\`\`bash
ls engram-quest/lessons/ 2>/dev/null
ls 8.TRACK/Learning/*/lessons/ 2>/dev/null
\`\`\`

If a matching course exists (in either location), read its \`meta.json\` to see the outline and
which lessons are completed \u2014 extend it in place, at the root it already lives in, rather than
starting over or duplicating it under a different root.

When the request builds on the user's own notes, find them with Obsidian CLI
(see \`references/obsidian-cli.md\` for syntax):

\`\`\`bash
obsidian search query="<topic>" format=json
\`\`\`

## Step 2: Establish the mission (new course only)

Ask the user (briefly, 2\u20133 questions max, skip what's already clear from the prompt):

1. **Why** do they want to learn this? (project, exam, career, curiosity)
2. **Current level** \u2014 total beginner, some exposure, or experienced in an adjacent area?
3. **Depth** \u2014 quick overview (3\u20134 lessons) or thorough path (6\u201310 lessons)?

The answers shape the outline. A .NET backend developer learning React needs a completely
different course than a designer learning React.

## Step 3: Design the course outline

Produce an outline of N lessons, each one line:

\`\`\`
1. <lesson title> \u2014 <the ONE thing it teaches>
2. ...
\`\`\`

Order matters: each lesson should rely only on earlier lessons. Show the outline to the user
for approval before generating (generation is expensive; outline changes are cheap).

Pick the course metadata now:
- \`courseSlug\` \u2014 short kebab-case ASCII (e.g. \`dotnet-basics\`, \`marketing-seo\`)
- \`icon\` \u2014 one emoji that captures the topic (\u{1F537} \u{1F48A} \u{1F4C8} \u{1F9D8} \u2026)
- \`colorScheme\` \u2014 one of \`indigo | green | amber | rose | cyan\` (pick what fits the topic mood)
- \`tags\` \u2014 **reuse existing tags first; only add a new one if nothing fits:**
  1. Scan the existing tag pool before deciding:
     \`\`\`bash
     python3 -c "
     import json, glob
     tags = set()
     for f in glob.glob('engram-quest/lessons/*/meta.json') + glob.glob('8.TRACK/Learning/*/lessons/*/meta.json'):
         tags.update(json.load(open(f, encoding='utf-8')).get('tags', []))
     print(sorted(tags))
     "
     \`\`\`
  2. Pick 1\u20132 tags from the pool that best match this course's topic.
  3. Only add a brand-new tag when the pool has nothing suitable. Maximum **2 tags total**.
  4. Exception: if the user explicitly specifies tags, use those exactly.
- \`description\` \u2014 one sentence, shown on the course card (\u2264 40 chars in zh, \u2264 80 in en)

## Step 4: Generate lessons

For each lesson, write a single self-contained HTML file following the hard rules in
\`references/html-recipe.md\` (read it before generating the first lesson \u2014 Obsidian's CSP
silently breaks externally-loaded resources, so violations look fine to you but broken to
the user).

When a visual would materially improve learning, also read \`references/visual-teaching.md\` and
choose the visual type and production method before writing the lesson. Keep the visual count
tight: usually one, rarely two.

Lesson ID and filename:
- \`id\` = \`lsn-<current unix ms timestamp>\` (e.g. \`lsn-1717977600000\`); when generating several
  lessons in one run, make each timestamp distinct (increment by 1 if needed)
- filename = \`<id>-<short-ascii-slug>.html\` (e.g. \`lsn-1717977600000-seo-basics.html\`)

Write the file to:

\`\`\`
{lessonsRoot}/{courseSlug}/{filename}
\`\`\`

## Step 5: Write / update meta.json

\`{lessonsRoot}/{courseSlug}/meta.json\` is the contract with the plugin \u2014 its exact
shape is specified in \`references/lesson-format.md\` (read it the first time you write one).

Critical rules:
- **Never overwrite an existing meta.json blindly.** Read \u2192 merge \u2192 write. Existing \`lessons\`
  entries and the whole \`completion\` map must survive (they hold the user's progress).
- New lessons append to the \`lessons\` array with \`"source": "skill"\`.
- Initialize \`completion[id] = { "viewed": false, "completed": false, "starred": false }\`
  for each new lesson.

## Step 6: Tell the user

After generating, summarize:

\`\`\`
\u2705 Course "{title}" \u2014 {N} lessons created

Open EngramQuest \u2192 \u5B78\u7FD2 (Learn) \u2192 \u6559\u6750 (Lessons) to start.
Lessons:
1. <title>
2. ...
\`\`\`

Remind them they can ask you follow-up questions about any lesson, ask for the next lesson,
or ask to regenerate one that didn't land.

---

## Lesson HTML quality bar

A lesson is a **beautiful, readable document** the user will revisit. Follow the visual and
structural guidance in \`references/html-recipe.md\`, and:

- Open with what the lesson teaches and why it matters for the user's mission.
- Readable on a phone: multi-column layouts must collapse at narrow widths \u2014 see the
  responsive hard rules in \`references/html-recipe.md\`.
- Use concrete examples over abstract definitions. Code topics get runnable-looking snippets;
  physical skills get step sequences; concept topics get diagrams built from styled divs.
- Litter factual claims with citations (\`<a href>\` links to sources) when the topic is
  technical or fast-moving.
- End with a short interactive quiz (3\u20135 questions, inline JS, immediate feedback) plus a
  "next steps" pointer.
- Close with a reminder that the user can ask their AI teacher (you) follow-up questions.

## Converting lessons to other EngramQuest formats

When the user finishes a course (or asks), offer to:
- **Generate review-deck flashcards** from the lessons' key facts \u2014 create an
  \`engram-review/ai-cards/{course-title}.md\` per the review-deck skill's card format
  (\`question :: answer\` lines, if that skill is installed).
- **Build a quest map** from the course \u2014 hand off to the quest-map skill if installed.

Don't auto-generate these; lessons alone are the deliverable unless asked.

## What NOT to do

- Don't write into the user's own notes \u2014 only the configured lesson roots (\`engram-quest/lessons/\`
  and, when a course belongs to a curriculum, that domain's \`8.TRACK/Learning/<domain>/lessons/\`)
  are yours.
- Don't delete or rename existing lesson files; the plugin tracks them by \`meta.json\`.
- Don't generate the whole course's HTML before the outline is approved.
- Don't pad lessons to look longer; density and clarity beat length.
`,"memory-map/references/create.md":`# Create a Memory Map

## Goal

Build a new \`-memory.canvas\` file that helps the user understand and remember a concept.

## Minimum Structure

- one central concept
- one novelty or contrast node
- one or more association nodes from real vault context when available
- one analogy or elaboration node
- several chunked support nodes
- one file node linking back to the source note (use \`type: "file"\` with the vault-relative path in the \`file\` field, e.g. \`"file": "Study/OS\u6982\u8AD6.md"\`)

## Design Rules

- Prefer 3 to 6 major branches
- Keep each node short and useful
- Use layout to make the core contrast obvious
- Favor meaningful memory structure over decorative complexity

### Layout patterns by content type

Choose the layout that matches the underlying structure of the content. **Flow clarity always wins over grid compactness.**

- **Sequential flow** (Step 1 \u2192 2 \u2192 3 \u2192 4, cause\u2192effect chains, hierarchical levels like L1\u2192L2\u2192L3): use a **single vertical column**, arrows top-to-bottom. Do NOT compress into a 2\xD72 grid \u2014 mixed horizontal/vertical arrows will cross.
- **Contrast / comparison** (A vs B, before/after, problem/solution): two parallel columns side by side, central concept above or between them.
- **Radial / hub-and-spoke** (one core concept with branches): central node surrounded by groups. **Cap edges from the central node at 6**; if more branches exist, introduce intermediate grouping nodes to avoid spider-web visuals.
- **Cluster / chunked** (independent themed groups, no strong order): grid of groups, each group internally laid out per its own structure.

When the content does not match any pattern cleanly, prefer vertical sequential flow as the default \u2014 it scales better than grids for memory maps.

## Output

### Determine save location

Before creating the canvas file:

1. Read \`.memory-map/config.json\` if it exists.
2. If \`memoryMapFolder\` is set (non-empty string), save the file there:
   \`<memoryMapFolder>/<source-note-name>-memory.canvas\`
   Create the folder if it does not exist.
3. If the config file does not exist, or \`memoryMapFolder\` is empty or missing,
   save next to the source note:
   \`<source-note-folder>/<source-note-name>-memory.canvas\`

### AI workflow preference

For EngramQuest runtime discovery, folder placement matters:

- Preferred: save the map next to the source note
- If the map is synthesized across several related notes, save it in the most representative shared topic folder
- Only use a dedicated \`memoryMapFolder\` when the user or config explicitly requires it

The Review Deck runtime searches same-folder candidates before weaker relation signals, so keeping AI-generated maps in the source/topic folder maximizes automatic linkage success.

### Canvas node sizing rules

**Text node height estimation:**
- \`## heading\` line: ~42px each
- Regular text line (including **bold**, list items): ~30px each
- Node internal padding: 32px total (16px top + 16px bottom)
- Formula: \`height = (heading_lines \xD7 42) + (body_lines \xD7 30) + 32\`
- When uncertain, **overestimate generously** \u2014 too large is fine, too small breaks layout

**Node width:**
- Latin / English content: 220px for short nodes, 280px for longer content. Never go below 180px.
- **CJK content (Chinese / Japanese / Korean)**: glyphs are wider and denser than Latin \u2014 the English defaults will overflow. Use:
  - Single-column nodes: **320\u2013460px** (use the upper range when a node has 4+ lines or contains \`**bold**\` runs)
  - Two-column / side-by-side nodes: **200\u2013230px each** (only acceptable when each column is 1\u20132 short lines; if a column is multi-line, promote it to single-column 320px+)
- When mixing CJK and Latin in the same node, follow the CJK rule.

**Minimum spacing between nodes (CRITICAL \u2014 prevents overlap):**
- Horizontal gap between adjacent nodes in the same row: **at least 60px**
- Vertical gap between nodes in the same column: **at least 50px**
- Before finalizing coordinates, verify: for every pair of nodes that share a group or are visually adjacent, confirm \`node_B.x >= node_A.x + node_A.width + 60\` (horizontal) or \`node_B.y >= node_A.y + node_A.height + 50\` (vertical).
- File nodes (type \`file\`) are typically taller than text nodes \u2014 treat their height as **at least 160px** when computing spacing for neighbors.
- When in doubt, **add more space**. A sparse map is always better than an overlapping one.

**Minimum spacing between groups (CRITICAL \u2014 groups need MORE breathing room than nodes):**
- Horizontal gap between adjacent groups: **at least 100px** (measured between the right edge of one group and the left edge of the next)
- Vertical gap between stacked groups: **at least 200px** (group labels and visual weight need clear separation)
- Do NOT reuse the 60/50px node-spacing values for groups \u2014 adjacent groups will look glued together.
- Verification: for every pair of adjacent groups, confirm \`group_B.x >= group_A.x + group_A.width + 100\` or \`group_B.y >= group_A.y + group_A.height + 200\`.

**Group bounding box:**
- Add 60px padding on all four sides around child nodes
- \`group_x = min(nodes.x) - 60\`
- \`group_y = min(nodes.y) - 60\`
- \`group_width = (max(nodes.x + nodes.width) - min(nodes.x)) + 120\`
- \`group_height = (max(nodes.y + nodes.height) - min(nodes.y)) + 120\`

**Group containment validation (CRITICAL \u2014 Obsidian will silently reposition orphan child nodes):**

After computing all group bounding boxes, run this check for every group and every child node assigned to it. Any failure means the child has escaped its parent and will be auto-corrected by Obsidian's linter \u2014 usually to the wrong place.

For each child node \`C\` inside group \`G\`:
- \`C.x >= G.x + 60\`
- \`C.x + C.width <= G.x + G.width - 60\`
- \`C.y >= G.y + 60\`
- \`C.y + C.height <= G.y + G.height - 60\`

If any inequality fails, expand the group bounding box (or move the child) until all four hold. Do this **before** writing the canvas file, not after \u2014 once Obsidian rewrites coordinates, the layout intent is lost.

### Save the file

**CRITICAL MANDATE: Filename Requirement**

You **MUST** save the file using the exact format:
\`<source-note-name>-memory.canvas\`

**NEVER** use descriptive or contextual names (e.g., \`Azure_Full_Ecosystem_Map.canvas\`). The EngramQuest plugin detects Memory Maps by the \`-memory.canvas\` suffix. Files without this exact suffix are invisible to the plugin and the user.
`,"memory-map/references/engram-data-layout.md":`# EngramQuest Data Layout \u2014 where the plugin's own content lives

EngramQuest generates and stores its own study artifacts in fixed folders at the **vault root**
(the directory your commands run from). These are a first-class source of material \u2014 when the
user refers to "my course", "the deck", "the quest I made", they mean content in these folders,
**not** a raw vault note. Searching the vault notes for it (\`obsidian search\`) will find nothing
and you will wrongly conclude it does not exist.

So: before falling back to vault-note discovery, check whether the user is pointing at an
EngramQuest artifact and read it directly from the folder below.

## Vocabulary \u2192 location

| The user says\u2026 | They mean | Read from |
|---|---|---|
| "course", "\u8AB2\u7A0B", "\u6559\u6750", "lesson", "the X course" | A Lesson Academy course | \`engram-quest/lessons/{courseSlug}/\` |
| "deck", "review deck", "\u5361\u7247", "flashcards", "\u8907\u7FD2\u5361" | A Review Deck | \`engram-review/\` |
| "quest", "quest map", "\u95DC\u5361", "the quest I made" | A Quest Map | a \`*-quest.md\` note **or** \`engram-quest/state/\` for progress |
| "quadrant card", "\u56DB\u8C61\u9650\u5361" | A Quadrant Card (Pro) | \`engram-quest/quadrant/\` |

## Folder reference

\`\`\`
engram-quest/
\u251C\u2500\u2500 lessons/{courseSlug}/
\u2502   \u251C\u2500\u2500 meta.json          \u2190 course info + lessons array + completion (the index)
\u2502   \u2514\u2500\u2500 lsn-*.html         \u2190 one self-contained HTML per lesson (the actual content)
\u251C\u2500\u2500 state/{quest}.json     \u2190 Quest Map progress / scores (not source content)
\u2514\u2500\u2500 quadrant/{cardId}.html \u2190 four-quadrant cards (Pro)

engram-review/
\u251C\u2500\u2500 ai-cards/{note}.md     \u2190 AI-generated flashcards (\`question :: answer\` lines)
\u2514\u2500\u2500 hints/{note}.json      \u2190 L1/L2/L3 hints
\`\`\`

## Reading a Lesson Academy course as source material

This is the common case (a user asks to build a quest/deck/map from a course they already made):

1. **List courses** \u2014 every course is one folder:
   \`\`\`bash
   ls engram-quest/lessons/
   \`\`\`
2. **Match by title, not folder name.** The folder (\`courseSlug\`) is ASCII kebab-case
   (\`event-storming\`), but the human title the user typed ("AI \u6642\u4EE3 Event Storming") lives
   inside \`meta.json\` as \`title\` / \`topic\`. Read each candidate \`meta.json\` and match the
   user's phrase against \`title\` and \`topic\`. Do not assume the folder name contains the title.
   \`\`\`bash
   cat engram-quest/lessons/*/meta.json
   \`\`\`
3. **Read the lesson content.** \`meta.json\`'s \`lessons[]\` array lists each lesson's \`title\`
   and \`file\`. The teaching content is in those HTML files \u2014 read them for the concepts to
   build from:
   \`\`\`bash
   cat "engram-quest/lessons/{courseSlug}/{file}.html"
   \`\`\`
   A lesson with \`"file": null\` is a planned outline entry with no content yet \u2014 use its title
   only, and tell the user that lesson hasn't been generated.

Once you have the course's lesson content, treat it as your source material and proceed with
the normal generation flow. You do **not** need a matching vault note \u2014 the course content
itself is enough.

## When nothing matches

If \`ls engram-quest/lessons/\` shows no folder whose \`meta.json\` title/topic matches what the
user described, say so plainly and offer to either (a) search the vault notes instead, or
(b) point them to the Lesson Academy tab to confirm the course name. Do not silently invent
content or quest from a topic the user did not actually have.
`,"memory-map/references/explain.md":`# Explain a Memory Map

## Goal

Explain what the existing memory map is doing and why the structure helps memory.

## What to Cover

- the central concept
- why the novelty or contrast matters
- how association nodes connect the idea to other notes
- how analogy or elaboration makes the concept easier to recall
- how the chunks reduce cognitive load

## Tone

- concise
- practical
- focused on memory benefit, not just visual layout
`,"memory-map/references/obsidian-cli.md":`# Obsidian CLI Reference

## What it is

Obsidian CLI is the **official** Obsidian command line interface (v1.12+).
It uses Obsidian's built-in index \u2014 the same index the GUI uses.
Results are fast, accurate, and support full Obsidian search syntax.

**Requirement**: Obsidian app must be running. Enable via Settings \u2192 General \u2192 Enable Command line interface.

## Search syntax

\`\`\`bash
# Full-text search
obsidian search query="keyword"

# Tag search
obsidian search query="tag:#flashcards/azure" limit=20

# Path filter
obsidian search query="path:Study keyword"

# JSON output (use this for programmatic processing)
obsidian search query="keyword" format=json

# Target a specific vault
obsidian vault="Obsidian_Note" search query="keyword"
\`\`\`

## Query operators

\`\`\`
keyword                  full-text
tag:#flashcards/azure    tag search
path:Study               path filter
file:azure               filename filter
"exact phrase"           exact match
kw1 kw2                  AND (space)
kw1 OR kw2               OR
\`\`\`

## Read / write notes

\`\`\`bash
obsidian read path="folder/note.md"
obsidian read file="Note Name"
obsidian create name="New Note" content="# Hello" silent
obsidian append file="My Note" content="new line"
obsidian property:set name="status" value="done" file="My Note"
\`\`\`

## Known silent failures (v1.12)

| Wrong | Problem | Correct |
|---|---|---|
| \`tasks todo\` | scoped to active file only, returns empty | \`tasks all todo\` |
| \`tags counts\` | returns empty | \`tags all counts\` |
| \`create name="x" content="y"\` | opens GUI | add \`silent\` flag |

## Fallback when CLI is unavailable

If \`obsidian search\` fails (CLI not installed or Obsidian not running), fall back to:

\`\`\`bash
bash scripts/search_vault.sh "<topic>" 30
\`\`\`

\`search_vault.sh\` is grep-based \u2014 slower, no index, no Obsidian syntax. Use only as fallback for note discovery, or for L2 hint personal context search (its only other permitted use).
`,"memory-map/references/update.md":`# Update a Memory Map

## Goal

Revise an existing memory map without losing useful structure.

## Rules

- Preserve good nodes and links when still relevant
- Keep node ids stable when practical
- Add missing novelty, analogy, or association only where they improve recall
- Remove redundant nodes if they add clutter but no memory value

## Typical Reasons to Update

- the source note changed
- the user learned a better analogy
- more vault context is now available
- the map feels too dense or too shallow
`,"memory-map/references/user-guide.md":`# Memory Map User Guide\r
\r
Memory Map is the visual understanding module in EngramQuest.\r
\r
It creates an Obsidian Canvas file that helps users remember difficult concepts through:\r
\r
- contrast\r
- association\r
- analogy\r
- contextual anchoring\r
- chunking\r
\r
## Example prompts\r
\r
- \`Create a memory-map for [note].md\`\r
- \`Update the memory-map for [note].md\`\r
- \`Explain this memory-map\`\r
\r
## Output\r
\r
The generated file should be saved as \`<source-note-name>-memory.canvas\`.\r
`,"memory-map/scripts/search_vault.sh":`#!/bin/bash
# search_vault.sh \u2014 Search vault markdown files for personal context
# Usage: bash scripts/search_vault.sh "keyword1 keyword2" [limit]
# Multiple keywords are matched with OR logic.
#
# Windows note: always invoke as \`bash scripts/search_vault.sh ...\`
# Running the .sh file directly on Windows will open it in an editor instead of executing it.

# Self-re-exec guard: if not running under bash, re-invoke with bash
if [ -z "$BASH_VERSION" ]; then
  exec bash "$0" "$@"
fi

QUERY="\${1:?Error: query required. Usage: bash search_vault.sh \\"keyword\\" [limit]}"
LIMIT="\${2:-20}"

# Convert space-separated keywords to escaped grep OR pattern
PATTERN=$(echo "$QUERY" | tr ' ' '\\n' | sed 's/[.[\\*^$()+?{}|\\\\]/\\\\&/g' | paste -sd '|')

# Find vault root (4 levels up from scripts/)
VAULT_ROOT="$(cd "$(dirname "$0")/../../../.." && pwd)"

grep -ri --include="*.md" \\
  --exclude-dir=".obsidian" \\
  --exclude-dir=".git" \\
  --exclude-dir="node_modules" \\
  --exclude-dir="engram-review" \\
  -E "$PATTERN" \\
  -B 2 -A 2 \\
  "$VAULT_ROOT" \\
  | head -n $((LIMIT * 6))
`,"memory-map/skills.md":`---
name: engram-memory-map
description: 
  Generate or update memory-map Canvas files for the EngramQuest plugin.
  Trigger when the user asks to create, update, or explain a memory map, or asks how memory-map works.
  Use this skill whenever the user mentions memory maps, visual knowledge maps, or wants to build a Canvas map for retention \u2014 even if they do not use the exact term "memory-map".
---

# Memory Map Skill

## Output Language Rule

Important: generate user-facing output in the language that best matches the user's prompt or the source note.

- If the prompt explicitly asks for a language, follow the prompt.
- Otherwise, match the source note language.
- Keep JSON structure and machine-facing fields in English.

This rule applies to:
node text, explanations, analogies, labels, and any generated contextual content inside the map.

## Reference Routing

Choose the right reference before writing:

| Goal | Reference |
|---|---|
| Create a new map | \`references/create.md\` |
| Update an existing map | \`references/update.md\` |
| Explain an existing map | \`references/explain.md\` |
| Answer user-facing questions | \`references/user-guide.md\` |

## Core Memory Principles

- **Novelty**: emphasize what is surprising or counterintuitive
- **Association**: connect to real notes already in the vault
- **Contextual anchoring**: use real learning situations when available
- **Elaborative encoding**: use analogy, imagery, and explanation
- **Chunking**: keep the map broken into small useful groups

## Workflow

0. Check for a pre-existing knowledge index or graph in the vault (e.g. \`graphify-out/graph.json\`, \`GRAPH_REPORT.md\`). If found, use its concept relationships as association edges and community structure as chunk groupings \u2014 this replaces or reduces the vault search in step 2.
1. Read the source note. **First decide what kind of source the user named.** If they refer to
   "a course", "\u8AB2\u7A0B", "\u6559\u6750", "a lesson", or "the deck" \u2014 that is EngramQuest's own generated
   content, stored in the plugin's data folders, not a vault note. Read it directly from there
   (see \`references/engram-data-layout.md\` for the vocabulary\u2192location map and how to read a
   Lesson Academy course). A vault search for a course title finds nothing and looks like it
   does not exist.
2. If the source is a vault note, search the vault for related notes or context using this priority.
   IMPORTANT: When vault search is needed, use Obsidian CLI (\`obsidian search\`). For full syntax, query operators, and fallback rules, see \`references/obsidian-cli.md\`.
   a. Step 0 already found a graph index \u2192 use it directly. Skip this step.
   b. No index: \`obsidian search query="<key concepts from source note>" format=json\`
   c. Obsidian CLI not available: \`bash scripts/search_vault.sh "<key concepts>" 30\`
   d. Source note is self-contained and no associations are needed \u2192 skip this step.
3. Choose the best reference flow.
4. Read \`.memory-map/config.json\` \u2014 this is a **config file only**, not an output folder. Extract the \`memoryMapFolder\` value, then follow \`references/create.md\` \u2192 Output section for the exact save location. Never save canvas files inside \`.memory-map/\`.
5. Keep the canvas readable and stable. Ensure no two nodes overlap \u2014 maintain at least 60px horizontal and 50px vertical gap between every pair of adjacent nodes. Adjacent **groups** need MORE space: at least 100px horizontal and 200px vertical between groups (do not reuse the node-spacing values for groups). File nodes are tall; treat them as at least 160px high when spacing neighbors. CJK content needs wider nodes \u2014 see \`references/create.md\` for sizing rules.

## Discovery Contract

The plugin now discovers Memory Maps for Review Deck cards using this priority:
1. same folder as the related source/topic notes
2. same topic / deck context
3. canvas file-node relations

Therefore:
- Prefer saving AI-generated maps in the source note folder
- If the map genuinely spans multiple notes, save it in the most representative shared topic folder
- Continue adding file nodes to related notes, but treat them as support for discovery rather than the only linkage mechanism

## Output Rules

- Use valid JSON Canvas format.
- Prefer one clear central idea with supporting branches.
- Keep node text concise.
- Use real associations when possible.
- Do not invent fake personal memories or fake vault references.

## If Context Is Missing

- Use analogy, chunking, and concept structure anyway.
- Leave contextual anchoring lighter rather than fabricating evidence.
- Prefer honest gaps over fake confidence.
`,"quadrant/assets/recipe-A-flywheel.html":`<!--
  \u6578\u64DA\u98DB\u8F2A \xD7 A4 \u56DB\u8C61\u9650\u8A18\u61B6\u6CD5\uFF08\u6E2C\u8A66 1 / 2\uFF09
  \u539F\u5361\uFF1A\u300C\u6578\u64DA\u98DB\u8F2A\u300D\u5982\u4F55\u8B93 AI \u539F\u751F\u5E73\u53F0\u7522\u751F\u7AF6\u722D\u58C1\u58D8\uFF1F
  Q4 \u8996\u89BA\u7B56\u7565\uFF1ACSS \u65CB\u8F49\u98DB\u8F2A + emoji \u7BC0\u9EDE\uFF08\u52D5\u614B motion graphic\uFF09
-->
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>\u6578\u64DA\u98DB\u8F2A \xB7 \u56DB\u8C61\u9650\u8A18\u61B6\u6CD5</title>
<style>
  * { box-sizing: border-box; margin: 0; padding: 0; }
  :root {
    --bg: #f3f6fb; --paper: #ffffff; --paper2: #fafcff;
    --ink: #1a2332; --muted: #5a6478; --accent: #0078d4;
    --accent-soft: #e6f1fb; --border: #d6dde6; --crease: #c2cad6;
    --success: #107c10; --warn: #d97706; --danger: #d13438;
    --flywheel: #6366f1; --flywheel-soft: #eef2ff;
    --bad: #ef4444; --bad-soft: #fef2f2;
    --good: #16a34a; --good-soft: #f0fdf4;
    --warn-soft: #fef3c7; --warn-text: #78350f;
  }
  body.dark {
    --bg: #181c22; --paper: #242a32; --paper2: #2b323b;
    --ink: #ecf0f5; --muted: #9aa3ad; --accent: #4cc2ff;
    --accent-soft: #1d3a52; --border: #3a414b; --crease: #4a5260;
    --flywheel: #818cf8; --flywheel-soft: #1e1b4b;
    --bad: #f87171; --bad-soft: #3a1717;
    --good: #4ade80; --good-soft: #14331f;
    --warn: #fbbf24; --warn-soft: #3a2b0a; --warn-text: #fde68a;
  }
  body {
    font-family: -apple-system, "Segoe UI", "PingFang TC", "Microsoft JhengHei", sans-serif;
    background: var(--bg); color: var(--ink); padding: 12px; min-height: 100vh;
  }
  .header { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; }
  .badge { background: var(--accent); color: #fff; padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: 600; }
  .phase { color: var(--muted); font-size: 13px; font-weight: 500; flex: 1; }

  .paper { background: var(--paper); border: 1px solid var(--border); border-radius: 10px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.06); display: grid;
    grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr;
    position: relative; overflow: hidden; min-height: 500px; }
  .paper::before, .paper::after { content: ""; position: absolute; background: var(--crease); opacity: 0.5; pointer-events: none; z-index: 1; }
  .paper::before { left: 0; right: 0; top: 50%; height: 1px; }
  .paper::after { top: 0; bottom: 0; left: 50%; width: 1px; }

  .quadrant { padding: 10px 12px; display: flex; flex-direction: column; position: relative; }
  .qLabel { font-size: 11px; font-weight: 700; color: var(--muted); letter-spacing: 1px; margin-bottom: 6px; }
  .qLabel .num { color: var(--accent); }

  .cover {
    flex: 1; border: 2px dashed var(--border); border-radius: 8px;
    background: repeating-linear-gradient(45deg, var(--paper2), var(--paper2) 10px, var(--paper) 10px, var(--paper) 20px);
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    cursor: pointer; color: var(--muted); font-size: 13px; gap: 6px; transition: all 0.2s ease;
  }
  .cover:hover { border-color: var(--accent); color: var(--accent); transform: scale(1.02); }
  .cover .lock { font-size: 22px; opacity: 0.6; }
  .quadrant.locked .content { display: none; }
  .quadrant.opened .cover { display: none; }
  .content { flex: 1; display: flex; flex-direction: column; animation: pop 0.3s ease; }
  @keyframes pop { from { opacity: 0; transform: scale(0.92); } to { opacity: 1; transform: scale(1); } }

  /* Q1 */
  .q1-question { flex: 1; display: flex; align-items: center; justify-content: center; text-align: center;
    font-size: 16px; font-weight: 700; line-height: 1.5; padding: 4px; }
  .q1-question .small { font-size: 11px; color: var(--muted); font-weight: 500; display: block; margin-top: 6px; }

  /* Q2 */
  .q2-answer { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px; }
  .q2-flow { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 700; color: var(--ink); flex-wrap: wrap; justify-content: center; }
  .q2-node { background: var(--flywheel-soft); color: var(--flywheel); padding: 4px 8px; border-radius: 6px; border: 1px solid var(--flywheel); font-size: 12px; }
  .q2-arrow { color: var(--muted); font-size: 14px; }
  .q2-note { margin-top: 6px; font-size: 11px; color: var(--muted); font-style: italic; text-align: center; }

  /* Q3 */
  .q3-text { flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 8px; padding: 4px; }
  .q3-quote { font-size: 14px; font-weight: 800; color: var(--flywheel); text-align: center; padding: 8px;
    background: var(--flywheel-soft); border-left: 3px solid var(--flywheel); border-radius: 4px; line-height: 1.4; }
  .q3-quote .em { color: var(--danger); }
  .q3-rule { font-size: 11px; color: var(--muted); text-align: center; line-height: 1.5; }
  .q3-rule code { background: var(--paper2); padding: 1px 5px; border-radius: 3px; font-size: 11px; color: var(--ink); border: 1px solid var(--border); }

  /* Q4 \u2014 CSS \u65CB\u8F49\u98DB\u8F2A */
  .q4-image { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; padding: 4px; }
  .flywheel-stage {
    position: relative; width: 200px; height: 200px;
  }
  .flywheel-ring {
    position: absolute; inset: 0;
    animation: spin 14s linear infinite;
  }
  @keyframes spin { to { transform: rotate(360deg); } }
  .flywheel-ring svg { width: 100%; height: 100%; display: block; }
  .flywheel-node {
    position: absolute;
    width: 56px; height: 56px;
    border-radius: 50%;
    background: var(--paper);
    border: 2.5px solid var(--flywheel);
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    font-size: 11px; font-weight: 700; color: var(--flywheel);
    box-shadow: 0 2px 6px rgba(99, 102, 241, 0.25);
  }
  .flywheel-node .em { font-size: 22px; line-height: 1; margin-bottom: 2px; }
  /* 4 \u500B\u65B9\u4F4D \u2014 \u4E0D\u65CB\u8F49\uFF0C\u4FDD\u6301\u6587\u5B57\u6B63\u5411 */
  .n-top    { top: -4px; left: 50%; transform: translateX(-50%); }
  .n-right  { right: -4px; top: 50%; transform: translateY(-50%); }
  .n-bottom { bottom: -4px; left: 50%; transform: translateX(-50%); }
  .n-left   { left: -4px; top: 50%; transform: translateY(-50%); }
  .flywheel-center {
    position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: 50px; height: 50px; border-radius: 50%;
    background: var(--flywheel); color: white;
    display: flex; align-items: center; justify-content: center;
    font-size: 24px; box-shadow: 0 0 18px rgba(99, 102, 241, 0.6);
    animation: pulse 2s ease-in-out infinite;
  }
  @keyframes pulse { 0%, 100% { transform: translate(-50%, -50%) scale(1); } 50% { transform: translate(-50%, -50%) scale(1.08); } }
  .q4-cap { font-size: 10px; color: var(--muted); font-style: italic; text-align: center; }

  /* Focus / zoom overlay \u2014 tap a quadrant's \u2922 to blow it up full-screen for content-heavy cards */
  .zoomBtn { position: absolute; top: 6px; right: 6px; z-index: 3; width: 24px; height: 24px;
    border: none; border-radius: 6px; background: var(--accent-soft); color: var(--accent);
    font-size: 14px; line-height: 1; cursor: pointer; display: none; padding: 0;
    align-items: center; justify-content: center; }
  .quadrant.opened .zoomBtn { display: flex; }
  .zoomBtn:hover { filter: brightness(0.95); }
  body.focusing { overflow: hidden; }
  .quadrant.focused { position: fixed; inset: 0; z-index: 50; background: var(--paper);
    padding: 18px 16px; overflow: auto; }
  .quadrant.focused .qLabel { font-size: 13px; }
  .quadrant.focused .content { zoom: 1.45; } /* zoom (not transform:scale) reflows the scaled content into the fixed overlay's scroll box */
  .quadrant.focused .zoomBtn { display: flex; top: 12px; right: 12px; width: 30px; height: 30px;
    font-size: 16px; background: var(--accent); color: #fff; z-index: 60; }

  .controls { margin-top: 12px; display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
  .btn { background: var(--accent); color: white; border: none; border-radius: 8px; padding: 10px 18px;
    font-size: 13px; font-weight: 700; cursor: pointer; font-family: inherit; transition: all 0.15s ease; }
  .btn:hover { filter: brightness(1.1); transform: translateY(-1px); }
  .btn:disabled { opacity: 0.4; cursor: not-allowed; }
  .btn.ghost { background: transparent; color: var(--accent); border: 1.5px solid var(--accent); }
  .btn.success { background: var(--success); }
  .btn.warn { background: var(--warn); }
  .btn.danger { background: var(--danger); }

  .tip { margin-top: 10px; padding: 10px 14px; background: var(--accent-soft); border-left: 3px solid var(--accent);
    border-radius: 6px; font-size: 12px; line-height: 1.5; }
  .tip strong { color: var(--accent); }

  .result { margin-top: 12px; padding: 14px; background: var(--paper); border: 1px solid var(--border);
    border-radius: 10px; text-align: center; }
  .result .verdict { font-size: 18px; font-weight: 800; margin-bottom: 6px; }
  .result .verdict.good { color: var(--success); }
  .result .verdict.mid { color: var(--warn); }
  .result .verdict.bad { color: var(--danger); }
  .result .prescription { font-size: 12px; color: var(--muted); line-height: 1.5; }

  /* Phones / narrow viewport: stack the 2\xD72 into one full-width column. At ~390px a half-cell is
     too narrow \u2014 content overflowed and got clipped by .paper{overflow:hidden}. One column per
     quadrant (Q1\u2192Q2\u2192Q3\u2192Q4) is readable and matches the learn order; the \u2922 zoom still blows up
     any single cell. Desktop modal (iframe ~588px) stays 2\xD72; only phones cross this breakpoint. */
  @media (max-width: 520px) {
    body { padding: 8px; }
    .paper { grid-template-columns: 1fr; grid-template-rows: none; min-height: 0; }
    .paper::before, .paper::after { display: none; }
    .quadrant { min-height: 148px; }
    .quadrant + .quadrant { border-top: 1px solid var(--crease); }
    .zoomBtn { top: 8px; right: 8px; }
  }
</style>
</head>
<body>

<div class="header">
  <div class="badge">Quadrant Card \xB7 \u5361 1/2</div>
  <div class="phase" id="phase">\u{1F4D6} Q1 \u5DF2\u986F\u793A \xB7 \u60F3\u4E00\u60F3\u7B54\u6848\uFF0C\u6216\u9EDE\u958B Q2 \u2192 Q3 \u2192 Q4</div>
</div>

<div class="controls" id="controls">
  <button class="btn" id="revealAll">\u4E00\u6B21\u5168\u90E8\u5C55\u958B \u2192 \u81EA\u8A55</button>
</div>

<div class="paper" id="paper">
  <!-- Q1 -->
  <div class="quadrant opened" data-q="1" id="q1">
    <div class="qLabel"><span class="num">Q1</span> \xB7 \u554F\u984C</div>
    <button class="zoomBtn" data-zoom="1" title="\u653E\u5927 / Expand" aria-label="\u653E\u5927 Expand">\u2922</button>
    <div class="cover" data-open="1">
      <div class="lock">\u{1F512}</div><div>\u9EDE\u64CA\u63ED\u66C9\u554F\u984C</div>
    </div>
    <div class="content">
      <div class="q1-question">
        \u300C\u6578\u64DA\u98DB\u8F2A\u300D<br>\u5982\u4F55\u5F62\u6210 AI \u5E73\u53F0\u7684<br>\u7AF6\u722D\u58C1\u58D8\uFF1F
        <span class="small">\uFF084 \u500B\u74B0\u7BC0\uFF0C\u81EA\u6211\u5F37\u5316\uFF09</span>
      </div>
    </div>
  </div>

  <!-- Q2 -->
  <div class="quadrant locked" data-q="2" id="q2">
    <div class="qLabel"><span class="num">Q2</span> \xB7 \u7B54\u6848</div>
    <button class="zoomBtn" data-zoom="2" title="\u653E\u5927 / Expand" aria-label="\u653E\u5927 Expand">\u2922</button>
    <div class="cover" data-open="2">
      <div class="lock">\u{1F512}</div><div>\u9EDE\u64CA\u63ED\u66C9\u7B54\u6848</div>
    </div>
    <div class="content">
      <div class="q2-answer">
        <div class="q2-flow">
          <span class="q2-node">\u7528\u6236</span>
          <span class="q2-arrow">\u2192</span>
          <span class="q2-node">\u6578\u64DA</span>
        </div>
        <div class="q2-flow">
          <span class="q2-arrow">\u2191</span>
          <span style="width:60px;display:inline-block"></span>
          <span class="q2-arrow">\u2193</span>
        </div>
        <div class="q2-flow">
          <span class="q2-node">\u9AD4\u9A57</span>
          <span class="q2-arrow">\u2190</span>
          <span class="q2-node">AI \u6A21\u578B</span>
        </div>
        <div class="q2-note">\u8F49\u5F97\u8D8A\u5FEB\u3001\u58C1\u58D8\u8D8A\u6DF1</div>
      </div>
    </div>
  </div>

  <!-- Q3 -->
  <div class="quadrant locked" data-q="3" id="q3">
    <div class="qLabel"><span class="num">Q3</span> \xB7 \u6587\u5B57 IP \u5316\uFF08\u53E3\u8A23\uFF09</div>
    <button class="zoomBtn" data-zoom="3" title="\u653E\u5927 / Expand" aria-label="\u653E\u5927 Expand">\u2922</button>
    <div class="cover" data-open="3">
      <div class="lock">\u{1F512}</div><div>\u9EDE\u64CA\u63ED\u66C9\u53E3\u8A23</div>
    </div>
    <div class="content">
      <div class="q3-text">
        <div class="q3-quote">
          \u300C<span class="em">\u4EBA</span>\u7D66<span class="em">\u6599</span>\u3001<span class="em">\u6599</span>\u990A<span class="em">\u8166</span>\u3001<span class="em">\u8166</span>\u9084<span class="em">\u723D</span>\u3001<span class="em">\u723D</span>\u5F15<span class="em">\u4EBA</span>\u300D
        </div>
        <div class="q3-rule">
          <code>\u4EBA</code> \u7528\u6236 \xB7 <code>\u6599</code> \u6578\u64DA \xB7 <code>\u8166</code> AI \u6A21\u578B \xB7 <code>\u723D</code> \u9AD4\u9A57<br>
          <strong>\u56DB\u5B57\u4E00\u5FAA\u74B0</strong>\uFF0C\u8A18\u4F4F\u300C\u4EBA\u2192\u6599\u2192\u8166\u2192\u723D\u2192\u4EBA\u300D\u5C31\u505C\u4E0D\u4E0B\u4F86
        </div>
      </div>
    </div>
  </div>

  <!-- Q4 \u2014 \u65CB\u8F49\u98DB\u8F2A -->
  <div class="quadrant locked" data-q="4" id="q4">
    <div class="qLabel"><span class="num">Q4</span> \xB7 \u5716\u50CF IP \u5316\uFF08\u52D5\u614B\u98DB\u8F2A\uFF09</div>
    <button class="zoomBtn" data-zoom="4" title="\u653E\u5927 / Expand" aria-label="\u653E\u5927 Expand">\u2922</button>
    <div class="cover" data-open="4">
      <div class="lock">\u{1F512}</div><div>\u9EDE\u64CA\u63ED\u66C9\u5716\u50CF</div>
    </div>
    <div class="content">
      <div class="q4-image">
        <div class="flywheel-stage">
          <!-- \u65CB\u8F49\u7684\u74B0\uFF08\u8ECC\u9053\u7BAD\u982D\uFF09 -->
          <div class="flywheel-ring">
            <svg viewBox="0 0 200 200">
              <defs>
                <marker id="arr" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto">
                  <path d="M 0 0 L 8 5 L 0 10 z" fill="#6366f1"/>
                </marker>
              </defs>
              <!-- \u9806\u6642\u91DD\u7684\u5F27\u7DDA\u7BAD\u982D (4 \u6BB5) -->
              <path d="M 100 14 A 86 86 0 0 1 186 100" stroke="#6366f1" stroke-width="3" fill="none" stroke-dasharray="5,3" marker-end="url(#arr)"/>
              <path d="M 186 100 A 86 86 0 0 1 100 186" stroke="#6366f1" stroke-width="3" fill="none" stroke-dasharray="5,3" marker-end="url(#arr)"/>
              <path d="M 100 186 A 86 86 0 0 1 14 100" stroke="#6366f1" stroke-width="3" fill="none" stroke-dasharray="5,3" marker-end="url(#arr)"/>
              <path d="M 14 100 A 86 86 0 0 1 100 14" stroke="#6366f1" stroke-width="3" fill="none" stroke-dasharray="5,3" marker-end="url(#arr)"/>
            </svg>
          </div>
          <!-- \u56FA\u5B9A\u7684 4 \u7BC0\u9EDE -->
          <div class="flywheel-node n-top">
            <div class="em">\u{1F464}</div><div>\u4EBA</div>
          </div>
          <div class="flywheel-node n-right">
            <div class="em">\u{1F4CA}</div><div>\u6599</div>
          </div>
          <div class="flywheel-node n-bottom">
            <div class="em">\u{1F916}</div><div>\u8166</div>
          </div>
          <div class="flywheel-node n-left">
            <div class="em">\u2728</div><div>\u723D</div>
          </div>
          <!-- \u4E2D\u5FC3 -->
          <div class="flywheel-center">\u{1F300}</div>
        </div>
        <div class="q4-cap">\u98DB\u8F2A\u6703\u81EA\u5DF1\u8F49 \u2014 \u8D8A\u591A\u4EBA\u7528\uFF0C\u8F49\u8D8A\u5FEB</div>
      </div>
    </div>
  </div>
</div>

<div class="tip" id="tipBar" style="display:none"></div>

<div id="resultBox"></div>

<script>
  window.addEventListener("message", (e) => {
    if (e.data && e.data.type === "engram-quest-theme") {
      document.body.classList.toggle("dark", !!e.data.dark);
    }
  });
  function reportHeight() {
    window.parent.postMessage({ type: "engram-quest-resize", height: document.documentElement.scrollHeight + 8 }, "*");
  }
  window.addEventListener("load", () => setTimeout(reportHeight, 80));

  let phase = "learn"; let opened = new Set([1]);
  const $ = id => document.getElementById(id);

  function openQ(n) {
    const q = $("q" + n);
    if (!q.classList.contains("locked")) return;
    q.classList.remove("locked"); q.classList.add("opened");
    if (phase === "learn") opened.add(n);
    reportHeight();
  }
  document.querySelectorAll(".cover").forEach(c => c.addEventListener("click", () => openQ(parseInt(c.dataset.open))));
  let focusedQ = null;
  function toggleFocus(n) {
    const q = $("q" + n);
    if (q.classList.contains("locked") && !q.classList.contains("focused")) return;
    const btn = q.querySelector(".zoomBtn");
    if (focusedQ === n) {
      q.classList.remove("focused"); document.body.classList.remove("focusing");
      if (btn) btn.textContent = "\u2922"; focusedQ = null; reportHeight();
    } else {
      if (focusedQ != null) {
        const prev = $("q" + focusedQ); prev.classList.remove("focused");
        const pb = prev.querySelector(".zoomBtn"); if (pb) pb.textContent = "\u2922";
      }
      q.classList.add("focused"); document.body.classList.add("focusing");
      if (btn) btn.textContent = "\u2715"; focusedQ = n;
      window.parent.postMessage({ type: "engram-quest-resize", height: 99999 }, "*");
    }
  }
  document.querySelectorAll(".zoomBtn").forEach(b => b.addEventListener("click", (e) => { e.stopPropagation(); toggleFocus(parseInt(b.dataset.zoom)); }));
  document.addEventListener("keydown", (e) => { if (e.key === "Escape" && focusedQ != null) toggleFocus(focusedQ); });

  // Single review entry: "Reveal all \u2192 self-rating". Individual cover clicks remain a learning peek
  // and never trigger self-rating (so users can study one quadrant at a time without committing).
  $("revealAll").addEventListener("click", () => {
    if (focusedQ != null) {
      const fq = $("q" + focusedQ); fq.classList.remove("focused");
      const fb = fq.querySelector(".zoomBtn"); if (fb) fb.textContent = "\u2922";
      document.body.classList.remove("focusing"); focusedQ = null;
    }
    [1,2,3,4].forEach(n => { const q = $("q" + n); q.classList.remove("locked"); q.classList.add("opened"); });
    phase = "review-revealed";
    $("phase").textContent = "\u270D\uFE0F \u81EA\u8A55\u968E\u6BB5";
    const _tip = $("tipBar"); _tip.innerHTML = "\u270D\uFE0F <strong>\u8AA0\u5BE6\u81EA\u8A55</strong>\uFF1A\u525B\u624D\u8166\u4E2D\u6D6E\u73FE\u7684 4 \u500B\u74B0\u7BC0\u8DDF\u4E0B\u9762\u543B\u5408\u55CE\uFF1F\u53E3\u8A23\u8A18\u5F97\u55CE\uFF1F\u98DB\u8F2A\u756B\u9762\u6D6E\u73FE\u4E86\u55CE\uFF1F"; _tip.style.display = "";
    $("controls").innerHTML = \`
      <button class="btn success" data-g="correct">\u2705 \u7B54\u5C0D\u4E86</button>
      <button class="btn warn" data-g="wrong">\u274C \u7B54\u932F\u4E86</button>
      <button class="btn danger" data-g="blank">\u{1F635} \u5B8C\u5168\u60F3\u4E0D\u8D77\u4F86</button>
    \`;
    $("controls").querySelectorAll("[data-g]").forEach(b => b.addEventListener("click", () => submitGrade(b.dataset.g)));
    reportHeight();
  });

  const RX = {
    correct: { score: 100, cls: "good", title: "\u{1F389} \u8A18\u4F4F\u4E86\uFF01", body: "\u4E0B\u6B21\u8907\u7FD2\u5EF6\u5F8C\uFF08\u9694\u9031 \u2192 \u9694\u5169\u9031 \u2192 \u9694\u56DB\u9031\uFF09\u3002" },
    wrong:   { score: 60,  cls: "mid",  title: "\u{1F501} \u5370\u8C61\u6709\u4F46\u4E0D\u5920\u7CBE\u6E96", body: "\u9694\u4E00\u6BB5\u6642\u9593\u518D\u4F86\uFF0CQ3 \u53E3\u8A23\u53EF\u518D\u52A0\u5F37\u3002" },
    blank:   { score: 25,  cls: "bad",  title: "\u{1F4DD} \u5370\u8C61\u592A\u6DE1", body: "\u56DE\u53BB\u91CD\u756B Q3\u3001Q4\uFF0C\u8B93\u53E3\u8A23\u66F4\u8352\u8B2C\u3001\u98DB\u8F2A\u756B\u9762\u66F4\u9BAE\u660E\u3002" }
  };
  function submitGrade(g) {
    phase = "done";
    const p = RX[g];
    $("phase").textContent = "\u{1F3C1} \u5B8C\u6210 \xB7 " + p.title;
    $("controls").innerHTML = ""; $("tipBar").style.display = "none";
    $("resultBox").innerHTML = \`<div class="result"><div class="verdict \${p.cls}">\${p.title}</div><div class="prescription">\${p.body}</div></div>\`;
    window.parent.postMessage({ type: "engram-quest-solved", score: p.score }, "*");
    setTimeout(reportHeight, 80);
  }
<\/script>
</body>
</html>
`,"quadrant/assets/recipe-B-comic.html":`<!--
  Bedrock vs SageMaker \xD7 A4 \u56DB\u8C61\u9650\u8A18\u61B6\u6CD5\uFF08\u6E2C\u8A66 2 / 2\uFF09
  \u539F\u5361\uFF1AASP.NET \u6574\u5408 Claude\uFF0CAWS \u9078 Bedrock \u9084\u662F SageMaker\uFF1F\u9078\u932F\u8E29\u4EC0\u9EBC\u5751\uFF1F
  Q4 \u8996\u89BA\u7B56\u7565\uFF1A\u5169\u683C\u5C0D\u6BD4\u6F2B\u756B\uFF08big emoji + \u6A19\u7C64 + \u6975\u7C21 SVG \u88DD\u98FE\u7DDA\uFF09
-->
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bedrock vs SageMaker \xB7 \u56DB\u8C61\u9650\u8A18\u61B6\u6CD5</title>
<style>
  * { box-sizing: border-box; margin: 0; padding: 0; }
  :root {
    --bg: #f3f6fb; --paper: #ffffff; --paper2: #fafcff;
    --ink: #1a2332; --muted: #5a6478; --accent: #0078d4;
    --accent-soft: #e6f1fb; --border: #d6dde6; --crease: #c2cad6;
    --success: #107c10; --warn: #d97706; --danger: #d13438;
    --bad: #ef4444; --bad-soft: #fef2f2;
    --good: #16a34a; --good-soft: #f0fdf4;
    --warn-soft: #fef3c7; --warn-text: #78350f;
  }
  body.dark {
    --bg: #181c22; --paper: #242a32; --paper2: #2b323b;
    --ink: #ecf0f5; --muted: #9aa3ad; --accent: #4cc2ff;
    --accent-soft: #1d3a52; --border: #3a414b; --crease: #4a5260;
    --bad: #f87171; --bad-soft: #3a1717;
    --good: #4ade80; --good-soft: #14331f;
    --warn: #fbbf24; --warn-soft: #3a2b0a; --warn-text: #fde68a;
  }
  body { font-family: -apple-system, "Segoe UI", "PingFang TC", "Microsoft JhengHei", sans-serif;
    background: var(--bg); color: var(--ink); padding: 12px; min-height: 100vh; }
  .header { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; }
  .badge { background: var(--accent); color: #fff; padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: 600; }
  .phase { color: var(--muted); font-size: 13px; font-weight: 500; flex: 1; }

  .paper { background: var(--paper); border: 1px solid var(--border); border-radius: 10px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.06); display: grid;
    grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr;
    position: relative; overflow: hidden; min-height: 520px; }
  .paper::before, .paper::after { content: ""; position: absolute; background: var(--crease); opacity: 0.5; pointer-events: none; z-index: 1; }
  .paper::before { left: 0; right: 0; top: 50%; height: 1px; }
  .paper::after { top: 0; bottom: 0; left: 50%; width: 1px; }

  .quadrant { padding: 10px 12px; display: flex; flex-direction: column; position: relative; }
  .qLabel { font-size: 11px; font-weight: 700; color: var(--muted); letter-spacing: 1px; margin-bottom: 6px; }
  .qLabel .num { color: var(--accent); }

  .cover { flex: 1; border: 2px dashed var(--border); border-radius: 8px;
    background: repeating-linear-gradient(45deg, var(--paper2), var(--paper2) 10px, var(--paper) 10px, var(--paper) 20px);
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    cursor: pointer; color: var(--muted); font-size: 13px; gap: 6px; transition: all 0.2s ease; }
  .cover:hover { border-color: var(--accent); color: var(--accent); transform: scale(1.02); }
  .cover .lock { font-size: 22px; opacity: 0.6; }
  .quadrant.locked .content { display: none; }
  .quadrant.opened .cover { display: none; }
  .content { flex: 1; display: flex; flex-direction: column; animation: pop 0.3s ease; }
  @keyframes pop { from { opacity: 0; transform: scale(0.92); } to { opacity: 1; transform: scale(1); } }

  /* Q1 */
  .q1-question { flex: 1; display: flex; align-items: center; justify-content: center; text-align: center;
    font-size: 14px; font-weight: 700; line-height: 1.5; padding: 4px; }
  .q1-question .vs { display: inline-block; background: var(--accent); color: white; padding: 1px 8px;
    border-radius: 10px; font-size: 12px; margin: 0 4px; }
  .q1-question .small { font-size: 11px; color: var(--muted); font-weight: 500; display: block; margin-top: 6px; }

  /* Q2 */
  .q2-answer { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; padding: 4px; }
  .q2-pick { font-size: 16px; font-weight: 800; color: var(--good); }
  .q2-pick .check { background: var(--good); color: white; padding: 2px 8px; border-radius: 6px; margin-right: 4px; }
  .q2-why { font-size: 11px; color: var(--ink); line-height: 1.5; text-align: center; }
  .q2-trap { margin-top: 4px; padding: 6px 10px; background: var(--bad-soft); border-left: 3px solid var(--bad);
    border-radius: 4px; font-size: 11px; color: var(--ink); line-height: 1.4; }
  .q2-trap strong { color: var(--bad); }

  /* Q3 \u2014 \u5C0D\u6BD4\u53E3\u8A23 */
  .q3-text { flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 6px; padding: 2px; }
  .q3-pair { display: flex; gap: 6px; align-items: stretch; }
  .q3-side { flex: 1; padding: 6px 8px; border-radius: 6px; font-size: 11px; line-height: 1.4; }
  .q3-side.bad { background: var(--bad-soft); border-left: 3px solid var(--bad); }
  .q3-side.good { background: var(--good-soft); border-left: 3px solid var(--good); }
  .q3-side .tag { font-size: 11px; font-weight: 800; margin-bottom: 3px; }
  .q3-side.bad .tag { color: var(--bad); }
  .q3-side.good .tag { color: var(--good); }
  .q3-vs { display: flex; align-items: center; font-size: 10px; font-weight: 800; color: var(--muted); }
  .q3-formula { margin-top: 4px; padding: 6px; background: var(--paper2); border-radius: 6px;
    text-align: center; font-size: 12px; font-weight: 700; color: var(--accent); border: 1px dashed var(--accent); }

  /* Q4 \u2014 \u5169\u683C\u6F2B\u756B */
  .q4-image { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; padding: 2px; }
  .comic { display: flex; gap: 8px; width: 100%; max-width: 320px; }
  .panel { flex: 1; border-radius: 10px; padding: 8px 6px; display: flex; flex-direction: column;
    align-items: center; justify-content: space-between; gap: 4px; position: relative; min-height: 160px; }
  .panel.bad { background: var(--bad-soft); border: 2px solid var(--bad); }
  .panel.good { background: var(--good-soft); border: 2px solid var(--good); }
  .panel-title { font-size: 11px; font-weight: 800; }
  .panel.bad .panel-title { color: var(--bad); }
  .panel.good .panel-title { color: var(--good); }
  .panel-scene { font-size: 42px; line-height: 1; position: relative; padding: 4px 0; }
  .panel-scene .tiny {
    font-size: 14px; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -45%);
  }
  .panel-scene .corner-tag {
    position: absolute; top: -4px; right: -10px; font-size: 16px;
  }
  .panel-caption { font-size: 10px; color: var(--ink); text-align: center; line-height: 1.3; }
  .panel-cost { font-size: 11px; font-weight: 700; padding: 2px 6px; border-radius: 4px; }
  .panel.bad .panel-cost { color: var(--bad); background: rgba(239, 68, 68, 0.1); }
  .panel.good .panel-cost { color: var(--good); background: rgba(22, 163, 74, 0.1); }
  .comic-vs {
    position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
    background: var(--accent); color: white; width: 22px; height: 22px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 800;
    z-index: 5; box-shadow: 0 2px 4px rgba(0,0,0,0.2);
  }
  .q4-cap { font-size: 10px; color: var(--muted); font-style: italic; text-align: center; }

  /* Focus / zoom overlay \u2014 tap a quadrant's \u2922 to blow it up full-screen for content-heavy cards */
  .zoomBtn { position: absolute; top: 6px; right: 6px; z-index: 3; width: 24px; height: 24px;
    border: none; border-radius: 6px; background: var(--accent-soft); color: var(--accent);
    font-size: 14px; line-height: 1; cursor: pointer; display: none; padding: 0;
    align-items: center; justify-content: center; }
  .quadrant.opened .zoomBtn { display: flex; }
  .zoomBtn:hover { filter: brightness(0.95); }
  body.focusing { overflow: hidden; }
  .quadrant.focused { position: fixed; inset: 0; z-index: 50; background: var(--paper);
    padding: 18px 16px; overflow: auto; }
  .quadrant.focused .qLabel { font-size: 13px; }
  .quadrant.focused .content { zoom: 1.45; } /* zoom (not transform:scale) reflows the scaled content into the fixed overlay's scroll box */
  .quadrant.focused .zoomBtn { display: flex; top: 12px; right: 12px; width: 30px; height: 30px;
    font-size: 16px; background: var(--accent); color: #fff; z-index: 60; }

  .controls { margin-top: 12px; display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
  .btn { background: var(--accent); color: white; border: none; border-radius: 8px; padding: 10px 18px;
    font-size: 13px; font-weight: 700; cursor: pointer; font-family: inherit; transition: all 0.15s ease; }
  .btn:hover { filter: brightness(1.1); transform: translateY(-1px); }
  .btn:disabled { opacity: 0.4; cursor: not-allowed; }
  .btn.ghost { background: transparent; color: var(--accent); border: 1.5px solid var(--accent); }
  .btn.success { background: var(--success); }
  .btn.warn { background: var(--warn); }
  .btn.danger { background: var(--danger); }

  .tip { margin-top: 10px; padding: 10px 14px; background: var(--accent-soft); border-left: 3px solid var(--accent);
    border-radius: 6px; font-size: 12px; line-height: 1.5; }
  .tip strong { color: var(--accent); }
  .result { margin-top: 12px; padding: 14px; background: var(--paper); border: 1px solid var(--border);
    border-radius: 10px; text-align: center; }
  .result .verdict { font-size: 18px; font-weight: 800; margin-bottom: 6px; }
  .result .verdict.good { color: var(--success); }
  .result .verdict.mid { color: var(--warn); }
  .result .verdict.bad { color: var(--danger); }
  .result .prescription { font-size: 12px; color: var(--muted); line-height: 1.5; }

  /* Phones / narrow viewport: stack the 2\xD72 into one full-width column. At ~390px a half-cell is
     too narrow \u2014 content overflowed and got clipped by .paper{overflow:hidden}. One column per
     quadrant (Q1\u2192Q2\u2192Q3\u2192Q4) is readable and matches the learn order; the \u2922 zoom still blows up
     any single cell. Desktop modal (iframe ~588px) stays 2\xD72; only phones cross this breakpoint. */
  @media (max-width: 520px) {
    body { padding: 8px; }
    .paper { grid-template-columns: 1fr; grid-template-rows: none; min-height: 0; }
    .paper::before, .paper::after { display: none; }
    .quadrant { min-height: 148px; }
    .quadrant + .quadrant { border-top: 1px solid var(--crease); }
    .zoomBtn { top: 8px; right: 8px; }
  }
</style>
</head>
<body>

<div class="header">
  <div class="badge">Quadrant Card \xB7 \u5361 2/2</div>
  <div class="phase" id="phase">\u{1F4D6} Q1 \u5DF2\u986F\u793A \xB7 \u60F3\u4E00\u60F3\u7B54\u6848\uFF0C\u6216\u9EDE\u958B Q2 \u2192 Q3 \u2192 Q4</div>
</div>

<div class="controls" id="controls">
  <button class="btn" id="revealAll">\u4E00\u6B21\u5168\u90E8\u5C55\u958B \u2192 \u81EA\u8A55</button>
</div>

<div class="paper" id="paper">
  <!-- Q1 -->
  <div class="quadrant opened" data-q="1" id="q1">
    <div class="qLabel"><span class="num">Q1</span> \xB7 \u554F\u984C</div>
    <button class="zoomBtn" data-zoom="1" title="\u653E\u5927 / Expand" aria-label="\u653E\u5927 Expand">\u2922</button>
    <div class="cover" data-open="1">
      <div class="lock">\u{1F512}</div><div>\u9EDE\u64CA\u63ED\u66C9\u554F\u984C</div>
    </div>
    <div class="content">
      <div class="q1-question">
        ASP.NET \u6574\u5408 Claude \u6A21\u578B<br>
        AWS \u4E0A\u8981\u9078<br>
        <span class="vs">Bedrock</span> \u9084\u662F <span class="vs">SageMaker</span>\uFF1F
        <span class="small">\uFF08\u9078\u932F\u6703\u8E29\u4EC0\u9EBC\u5751\uFF1F\uFF09</span>
      </div>
    </div>
  </div>

  <!-- Q2 -->
  <div class="quadrant locked" data-q="2" id="q2">
    <div class="qLabel"><span class="num">Q2</span> \xB7 \u7B54\u6848</div>
    <button class="zoomBtn" data-zoom="2" title="\u653E\u5927 / Expand" aria-label="\u653E\u5927 Expand">\u2922</button>
    <div class="cover" data-open="2">
      <div class="lock">\u{1F512}</div><div>\u9EDE\u64CA\u63ED\u66C9\u7B54\u6848</div>
    </div>
    <div class="content">
      <div class="q2-answer">
        <div class="q2-pick"><span class="check">\u2713</span>Bedrock</div>
        <div class="q2-why">Serverless API \xB7 \u6309\u7528\u91CF\u4ED8\u8CBB \xB7 \u76F4\u63A5\u547C\u53EB Foundation Model</div>
        <div class="q2-trap">
          <strong>\u9078\u932F SageMaker \u7684\u5751\uFF1A</strong>Endpoint \u662F\u5E38\u99D0 EC2 instance\uFF0C\u6C92 request \u4E5F\u7167\u7B97\u8CBB\uFF1BClaude \u9019\u7A2E\u5927\u6A21\u578B\u9700\u8981 GPU instance\uFF0C\u8D85\u8CB4\u3002
        </div>
      </div>
    </div>
  </div>

  <!-- Q3 -->
  <div class="quadrant locked" data-q="3" id="q3">
    <div class="qLabel"><span class="num">Q3</span> \xB7 \u6587\u5B57 IP \u5316\uFF08\u5C0D\u6BD4\u53E3\u8A23\uFF09</div>
    <button class="zoomBtn" data-zoom="3" title="\u653E\u5927 / Expand" aria-label="\u653E\u5927 Expand">\u2922</button>
    <div class="cover" data-open="3">
      <div class="lock">\u{1F512}</div><div>\u9EDE\u64CA\u63ED\u66C9\u53E3\u8A23</div>
    </div>
    <div class="content">
      <div class="q3-text">
        <div class="q3-pair">
          <div class="q3-side bad">
            <div class="tag">\u274C SageMaker</div>
            \u5305\u6574\u68DF\u8FA6\u516C\u5BA4<br>
            <span style="color:var(--muted)">\u4E0D\u7BA1\u6709\u6C92\u4EBA\u90FD\u4ED8\u79DF\u91D1</span>
          </div>
          <div class="q3-side good">
            <div class="tag">\u2713 Bedrock</div>
            \u6253\u4E00\u901A\u96FB\u8A71<br>
            <span style="color:var(--muted)">\u6309\u901A\u8A71\u6642\u9593\u4ED8\u8CBB</span>
          </div>
        </div>
        <div class="q3-formula">\u7528 SageMaker \u6574\u5408\u73FE\u6210\u6A21\u578B = \u{1F69A} \u5361\u8ECA\u9001 \u{1F4E8} \u4E00\u5C01\u4FE1</div>
      </div>
    </div>
  </div>

  <!-- Q4 \u2014 \u5169\u683C\u5C0D\u6BD4\u6F2B\u756B -->
  <div class="quadrant locked" data-q="4" id="q4">
    <div class="qLabel"><span class="num">Q4</span> \xB7 \u5716\u50CF IP \u5316\uFF08\u5C0D\u6BD4\u6F2B\u756B\uFF09</div>
    <button class="zoomBtn" data-zoom="4" title="\u653E\u5927 / Expand" aria-label="\u653E\u5927 Expand">\u2922</button>
    <div class="cover" data-open="4">
      <div class="lock">\u{1F512}</div><div>\u9EDE\u64CA\u63ED\u66C9\u5716\u50CF</div>
    </div>
    <div class="content">
      <div class="q4-image">
        <div class="comic" style="position:relative">
          <!-- \u5DE6 \xB7 SageMaker\uFF1A\u5361\u8ECA\u9001\u4E00\u5C01\u4FE1 -->
          <div class="panel bad">
            <div class="panel-title">SageMaker</div>
            <div class="panel-scene">
              \u{1F69A}<span class="corner-tag">\u{1F4B8}</span>
            </div>
            <div class="panel-caption">\u7528\u5361\u8ECA\u9001\u4E00\u5C01\u4FE1</div>
            <div class="panel-cost">$$$ \u5E38\u99D0\u71D2\u9322</div>
          </div>

          <!-- \u4E2D\u592E VS -->
          <div class="comic-vs">VS</div>

          <!-- \u53F3 \xB7 Bedrock\uFF1A\u6253\u4E00\u901A\u96FB\u8A71 -->
          <div class="panel good">
            <div class="panel-title">Bedrock</div>
            <div class="panel-scene">
              \u{1F4DE}<span class="corner-tag">\u26A1</span>
            </div>
            <div class="panel-caption">\u6253\u4E00\u901A\u96FB\u8A71</div>
            <div class="panel-cost">$ \u6309\u901A\u8A71\u6536\u8CBB</div>
          </div>
        </div>
        <div class="q4-cap">\u300C\u6BBA\u96DE\u7528\u725B\u5200\u300Dvs\u300C\u5C0D\u75C7\u4E0B\u85E5\u300D</div>
      </div>
    </div>
  </div>
</div>

<div class="tip" id="tipBar" style="display:none"></div>

<div id="resultBox"></div>

<script>
  window.addEventListener("message", (e) => {
    if (e.data && e.data.type === "engram-quest-theme") {
      document.body.classList.toggle("dark", !!e.data.dark);
    }
  });
  function reportHeight() {
    window.parent.postMessage({ type: "engram-quest-resize", height: document.documentElement.scrollHeight + 8 }, "*");
  }
  window.addEventListener("load", () => setTimeout(reportHeight, 80));

  let phase = "learn"; let opened = new Set([1]);
  const $ = id => document.getElementById(id);

  function openQ(n) {
    const q = $("q" + n);
    if (!q.classList.contains("locked")) return;
    q.classList.remove("locked"); q.classList.add("opened");
    if (phase === "learn") opened.add(n);
    reportHeight();
  }
  document.querySelectorAll(".cover").forEach(c => c.addEventListener("click", () => openQ(parseInt(c.dataset.open))));
  let focusedQ = null;
  function toggleFocus(n) {
    const q = $("q" + n);
    if (q.classList.contains("locked") && !q.classList.contains("focused")) return;
    const btn = q.querySelector(".zoomBtn");
    if (focusedQ === n) {
      q.classList.remove("focused"); document.body.classList.remove("focusing");
      if (btn) btn.textContent = "\u2922"; focusedQ = null; reportHeight();
    } else {
      if (focusedQ != null) {
        const prev = $("q" + focusedQ); prev.classList.remove("focused");
        const pb = prev.querySelector(".zoomBtn"); if (pb) pb.textContent = "\u2922";
      }
      q.classList.add("focused"); document.body.classList.add("focusing");
      if (btn) btn.textContent = "\u2715"; focusedQ = n;
      window.parent.postMessage({ type: "engram-quest-resize", height: 99999 }, "*");
    }
  }
  document.querySelectorAll(".zoomBtn").forEach(b => b.addEventListener("click", (e) => { e.stopPropagation(); toggleFocus(parseInt(b.dataset.zoom)); }));
  document.addEventListener("keydown", (e) => { if (e.key === "Escape" && focusedQ != null) toggleFocus(focusedQ); });

  // Single review entry: "Reveal all \u2192 self-rating". Individual cover clicks remain a learning peek
  // and never trigger self-rating (so users can study one quadrant at a time without committing).
  $("revealAll").addEventListener("click", () => {
    if (focusedQ != null) {
      const fq = $("q" + focusedQ); fq.classList.remove("focused");
      const fb = fq.querySelector(".zoomBtn"); if (fb) fb.textContent = "\u2922";
      document.body.classList.remove("focusing"); focusedQ = null;
    }
    [1,2,3,4].forEach(n => { const q = $("q" + n); q.classList.remove("locked"); q.classList.add("opened"); });
    phase = "review-revealed";
    $("phase").textContent = "\u270D\uFE0F \u81EA\u8A55\u968E\u6BB5";
    const _tip = $("tipBar"); _tip.innerHTML = "\u270D\uFE0F <strong>\u8AA0\u5BE6\u81EA\u8A55</strong>\uFF1A\u525B\u624D\u8166\u4E2D\u662F\u5426\u6D6E\u73FE\u300C\u9078 Bedrock + serverless + \u5361\u8ECA\u9001\u4FE1\u6BD4\u55BB\u300D\uFF1F"; _tip.style.display = "";
    $("controls").innerHTML = \`
      <button class="btn success" data-g="correct">\u2705 \u7B54\u5C0D\u4E86</button>
      <button class="btn warn" data-g="wrong">\u274C \u7B54\u932F\u4E86</button>
      <button class="btn danger" data-g="blank">\u{1F635} \u5B8C\u5168\u60F3\u4E0D\u8D77\u4F86</button>
    \`;
    $("controls").querySelectorAll("[data-g]").forEach(b => b.addEventListener("click", () => submitGrade(b.dataset.g)));
    reportHeight();
  });

  const RX = {
    correct: { score: 100, cls: "good", title: "\u{1F389} \u8A18\u4F4F\u4E86\uFF01", body: "\u4E0B\u6B21\u8907\u7FD2\u5EF6\u5F8C\uFF08\u9694\u9031 \u2192 \u9694\u5169\u9031 \u2192 \u9694\u56DB\u9031\uFF09\u3002" },
    wrong:   { score: 60,  cls: "mid",  title: "\u{1F501} \u5370\u8C61\u6709\u4F46\u4E0D\u5920\u7CBE\u6E96", body: "\u53EF\u80FD\u8A18\u5F97\u9078 Bedrock \u4F46\u5FD8\u4E86\u70BA\u4EC0\u9EBC\u3002Q3 \u7684\u300C\u8FA6\u516C\u5BA4 vs \u96FB\u8A71\u300D\u6BD4\u55BB\u53EF\u518D\u52A0\u5F37\u3002" },
    blank:   { score: 25,  cls: "bad",  title: "\u{1F4DD} \u5370\u8C61\u592A\u6DE1", body: "\u56DE\u53BB\u91CD\u756B Q3\u3001Q4\uFF0C\u628A\u300C\u5361\u8ECA\u9001\u4E00\u5C01\u4FE1\u300D\u9019\u500B\u8352\u8B2C\u756B\u9762\u653E\u5927\u3002" }
  };
  function submitGrade(g) {
    phase = "done";
    const p = RX[g];
    $("phase").textContent = "\u{1F3C1} \u5B8C\u6210 \xB7 " + p.title;
    $("controls").innerHTML = ""; $("tipBar").style.display = "none";
    $("resultBox").innerHTML = \`<div class="result"><div class="verdict \${p.cls}">\${p.title}</div><div class="prescription">\${p.body}</div></div>\`;
    window.parent.postMessage({ type: "engram-quest-solved", score: p.score }, "*");
    setTimeout(reportHeight, 80);
  }
<\/script>
</body>
</html>
`,"quadrant/assets/recipe-C-emoji.html":`<!--
  Recipe C demo: \u55AE\u4E00\u5927 emoji \u8B6C\u55BB\u7269
  \u5167\u5BB9\u7528\u66F8\u4E2D\u7D93\u5178\u7BC4\u4F8B\u300Ctremendous = \u76EE\u77AA\u53E3\u5446\u7684\u5C0F\u9CE5\u300D
  \u95DC\u9375\u793A\u7BC4\uFF1AQ4 \u4E0D\u756B\u4EFB\u4F55\u5E7E\u4F55\u5F62\u72C0\uFF0C\u7528\u4E00\u500B\u5927 emoji + \u52D5\u756B
-->
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>tremendous \xB7 \u56DB\u8C61\u9650\u8A18\u61B6\u6CD5\uFF08Recipe C demo\uFF09</title>
<style>
  * { box-sizing: border-box; margin: 0; padding: 0; }
  :root {
    --bg: #f3f6fb; --paper: #ffffff; --paper2: #fafcff;
    --ink: #1a2332; --muted: #5a6478; --accent: #0078d4;
    --accent-soft: #e6f1fb; --border: #d6dde6; --crease: #c2cad6;
    --success: #107c10; --warn: #d97706; --danger: #d13438;
    --bird: #f59e0b; --bird-soft: #fffbeb;
    --bad: #ef4444; --bad-soft: #fef2f2;
    --good: #16a34a; --good-soft: #f0fdf4;
    --warn-soft: #fef3c7; --warn-text: #78350f;
  }
  body.dark {
    --bg: #181c22; --paper: #242a32; --paper2: #2b323b;
    --ink: #ecf0f5; --muted: #9aa3ad; --accent: #4cc2ff;
    --accent-soft: #1d3a52; --border: #3a414b; --crease: #4a5260;
    --bird: #fbbf24; --bird-soft: #3a2b0a;
    --bad: #f87171; --bad-soft: #3a1717;
    --good: #4ade80; --good-soft: #14331f;
    --warn: #fbbf24; --warn-soft: #3a2b0a; --warn-text: #fde68a;
  }
  body { font-family: -apple-system, "Segoe UI", "PingFang TC", sans-serif;
    background: var(--bg); color: var(--ink); padding: 12px; min-height: 100vh; }
  .header { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; }
  .badge { background: var(--accent); color: #fff; padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: 600; }
  .phase { color: var(--muted); font-size: 13px; font-weight: 500; flex: 1; }

  .paper { background: var(--paper); border: 1px solid var(--border); border-radius: 10px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.06); display: grid;
    grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr;
    position: relative; overflow: hidden; min-height: 460px; }
  .paper::before, .paper::after { content: ""; position: absolute; background: var(--crease); opacity: 0.5; pointer-events: none; z-index: 1; }
  .paper::before { left: 0; right: 0; top: 50%; height: 1px; }
  .paper::after { top: 0; bottom: 0; left: 50%; width: 1px; }

  .quadrant { padding: 10px 12px; display: flex; flex-direction: column; position: relative; }
  .qLabel { font-size: 11px; font-weight: 700; color: var(--muted); letter-spacing: 1px; margin-bottom: 6px; }
  .qLabel .num { color: var(--accent); }

  .cover { flex: 1; border: 2px dashed var(--border); border-radius: 8px;
    background: repeating-linear-gradient(45deg, var(--paper2), var(--paper2) 10px, var(--paper) 10px, var(--paper) 20px);
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    cursor: pointer; color: var(--muted); font-size: 13px; gap: 6px; transition: all 0.2s ease; }
  .cover:hover { border-color: var(--accent); color: var(--accent); transform: scale(1.02); }
  .cover .lock { font-size: 22px; opacity: 0.6; }
  .quadrant.locked .content { display: none; }
  .quadrant.opened .cover { display: none; }
  .content { flex: 1; display: flex; flex-direction: column; animation: pop 0.3s ease; }
  @keyframes pop { from { opacity: 0; transform: scale(0.92); } to { opacity: 1; transform: scale(1); } }

  /* Q1 */
  .q1-question { flex: 1; display: flex; align-items: center; justify-content: center; text-align: center;
    font-size: 22px; font-weight: 700; padding: 4px; }
  .q1-question .word { color: var(--accent); font-style: italic; font-family: Georgia, serif; }
  .q1-question .small { font-size: 11px; color: var(--muted); font-weight: 500; display: block; margin-top: 8px; }

  /* Q2 */
  .q2-answer { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; padding: 4px; text-align: center; }
  .q2-main { font-size: 18px; font-weight: 800; color: var(--ink); line-height: 1.4; }
  .q2-sub { font-size: 11px; color: var(--muted); font-style: italic; }

  /* Q3 \u2014 \u8B6C\u55BB\u62C6\u89E3\uFF08\u66F8\u4E2D\u7BC4\u4F8B\u7684\u8AE7\u97F3\u662F tre+men+dous \u2192 \u9CE5+\u76EE+\u51FA\uFF09 */
  .q3-text { flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 10px; padding: 4px; }
  .q3-row { display: flex; align-items: center; gap: 6px; justify-content: center; font-size: 14px; }
  .q3-piece { background: var(--bird-soft); color: var(--bird); border: 1.5px solid var(--bird);
    padding: 4px 10px; border-radius: 6px; font-weight: 700; font-family: Georgia, serif; font-style: italic; }
  .q3-arrow { color: var(--muted); font-size: 16px; font-weight: 800; }
  .q3-result { background: var(--accent); color: white; padding: 4px 10px; border-radius: 6px; font-weight: 700; }
  .q3-final { margin-top: 4px; text-align: center; font-size: 13px; font-weight: 700;
    color: var(--accent); padding: 8px; background: var(--accent-soft); border-radius: 6px; border: 1px dashed var(--accent); }

  /* Q4 \u2014 Recipe C \u6838\u5FC3\uFF1A\u55AE\u4E00\u5927 emoji + \u6A19\u7C64 + bob \u52D5\u756B */
  .q4-image { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; padding: 4px; }
  .metaphor-stage {
    display: flex; flex-direction: column; align-items: center; gap: 8px;
    padding: 12px; position: relative;
  }
  .metaphor-emoji {
    font-size: 88px; line-height: 1;
    animation: bob 3s ease-in-out infinite;
    filter: drop-shadow(0 4px 8px rgba(245, 158, 11, 0.3));
  }
  @keyframes bob {
    0%, 100% { transform: translateY(0) rotate(-2deg); }
    50% { transform: translateY(-8px) rotate(2deg); }
  }
  .metaphor-label { font-size: 16px; font-weight: 800; color: var(--bird); text-align: center; }
  .metaphor-sub { font-size: 11px; color: var(--muted); font-style: italic; text-align: center; }
  /* \u4E09\u6735\u9A5A\u8A1D\u7B26\u865F\u570D\u7E5E emoji\uFF0C\u52A0\u5F37\u300C\u76EE\u77AA\u53E3\u5446\u300D\u611F */
  .puff { position: absolute; font-size: 18px; opacity: 0.7; animation: puff 2s ease-in-out infinite; }
  .puff.p1 { top: 6px; left: 18%; animation-delay: 0s; }
  .puff.p2 { top: 14px; right: 18%; animation-delay: 0.6s; }
  .puff.p3 { top: 40px; left: 8%;  animation-delay: 1.2s; }
  @keyframes puff {
    0%, 100% { transform: translateY(0) scale(1); opacity: 0.7; }
    50% { transform: translateY(-4px) scale(1.15); opacity: 1; }
  }

  /* Focus / zoom overlay \u2014 tap a quadrant's \u2922 to blow it up full-screen for content-heavy cards */
  .zoomBtn { position: absolute; top: 6px; right: 6px; z-index: 3; width: 24px; height: 24px;
    border: none; border-radius: 6px; background: var(--accent-soft); color: var(--accent);
    font-size: 14px; line-height: 1; cursor: pointer; display: none; padding: 0;
    align-items: center; justify-content: center; }
  .quadrant.opened .zoomBtn { display: flex; }
  .zoomBtn:hover { filter: brightness(0.95); }
  body.focusing { overflow: hidden; }
  .quadrant.focused { position: fixed; inset: 0; z-index: 50; background: var(--paper);
    padding: 18px 16px; overflow: auto; }
  .quadrant.focused .qLabel { font-size: 13px; }
  .quadrant.focused .content { zoom: 1.45; } /* zoom (not transform:scale) reflows the scaled content into the fixed overlay's scroll box */
  .quadrant.focused .zoomBtn { display: flex; top: 12px; right: 12px; width: 30px; height: 30px;
    font-size: 16px; background: var(--accent); color: #fff; z-index: 60; }

  .controls { margin-top: 12px; display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
  .btn { background: var(--accent); color: white; border: none; border-radius: 8px; padding: 10px 18px;
    font-size: 13px; font-weight: 700; cursor: pointer; font-family: inherit; transition: all 0.15s ease; }
  .btn:hover { filter: brightness(1.1); transform: translateY(-1px); }
  .btn:disabled { opacity: 0.4; cursor: not-allowed; }
  .btn.ghost { background: transparent; color: var(--accent); border: 1.5px solid var(--accent); }
  .btn.success { background: var(--success); }
  .btn.warn { background: var(--warn); }
  .btn.danger { background: var(--danger); }

  .tip { margin-top: 10px; padding: 10px 14px; background: var(--accent-soft); border-left: 3px solid var(--accent);
    border-radius: 6px; font-size: 12px; line-height: 1.5; }
  .tip strong { color: var(--accent); }
  .result { margin-top: 12px; padding: 14px; background: var(--paper); border: 1px solid var(--border);
    border-radius: 10px; text-align: center; }
  .result .verdict { font-size: 18px; font-weight: 800; margin-bottom: 6px; }
  .result .verdict.good { color: var(--success); }
  .result .verdict.mid { color: var(--warn); }
  .result .verdict.bad { color: var(--danger); }
  .result .prescription { font-size: 12px; color: var(--muted); line-height: 1.5; }

  /* Phones / narrow viewport: stack the 2\xD72 into one full-width column. At ~390px a half-cell is
     too narrow \u2014 content overflowed and got clipped by .paper{overflow:hidden}. One column per
     quadrant (Q1\u2192Q2\u2192Q3\u2192Q4) is readable and matches the learn order; the \u2922 zoom still blows up
     any single cell. Desktop modal (iframe ~588px) stays 2\xD72; only phones cross this breakpoint. */
  @media (max-width: 520px) {
    body { padding: 8px; }
    .paper { grid-template-columns: 1fr; grid-template-rows: none; min-height: 0; }
    .paper::before, .paper::after { display: none; }
    .quadrant { min-height: 148px; }
    .quadrant + .quadrant { border-top: 1px solid var(--crease); }
    .zoomBtn { top: 8px; right: 8px; }
  }
</style>
</head>
<body>

<div class="header">
  <div class="badge">Quadrant Card \xB7 Recipe C demo</div>
  <div class="phase" id="phase">\u{1F4D6} Q1 \u5DF2\u986F\u793A \xB7 \u60F3\u4E00\u60F3\u7B54\u6848\uFF0C\u6216\u9EDE\u958B Q2 \u2192 Q3 \u2192 Q4</div>
</div>

<div class="controls" id="controls">
  <button class="btn" id="revealAll">\u4E00\u6B21\u5168\u90E8\u5C55\u958B \u2192 \u81EA\u8A55</button>
</div>

<div class="paper" id="paper">
  <!-- Q1 -->
  <div class="quadrant opened" data-q="1" id="q1">
    <div class="qLabel"><span class="num">Q1</span> \xB7 \u554F\u984C</div>
    <button class="zoomBtn" data-zoom="1" title="\u653E\u5927 / Expand" aria-label="\u653E\u5927 Expand">\u2922</button>
    <div class="cover" data-open="1">
      <div class="lock">\u{1F512}</div><div>\u9EDE\u64CA\u63ED\u66C9\u554F\u984C</div>
    </div>
    <div class="content">
      <div class="q1-question">
        <span class="word">tremendous</span><br>\u7684\u610F\u601D\u662F\uFF1F
        <span class="small">\uFF08\u66F8\u4E2D\u7D93\u5178\u7BC4\u4F8B\uFF09</span>
      </div>
    </div>
  </div>

  <!-- Q2 -->
  <div class="quadrant locked" data-q="2" id="q2">
    <div class="qLabel"><span class="num">Q2</span> \xB7 \u7B54\u6848</div>
    <button class="zoomBtn" data-zoom="2" title="\u653E\u5927 / Expand" aria-label="\u653E\u5927 Expand">\u2922</button>
    <div class="cover" data-open="2">
      <div class="lock">\u{1F512}</div><div>\u9EDE\u64CA\u63ED\u66C9\u7B54\u6848</div>
    </div>
    <div class="content">
      <div class="q2-answer">
        <div class="q2-main">\u6975\u597D\u7684\u3001<br>\u5DE8\u5927\u7684</div>
        <div class="q2-sub">(\u5F62\u5BB9\u8A5E \xB7 \u7A0B\u5EA6\u526F\u8A5E)</div>
      </div>
    </div>
  </div>

  <!-- Q3 \u2014 \u66F8\u4E2D\u7684\u8AE7\u97F3\u8B6C\u55BB\u62C6\u89E3 -->
  <div class="quadrant locked" data-q="3" id="q3">
    <div class="qLabel"><span class="num">Q3</span> \xB7 \u6587\u5B57 IP \u5316\uFF08\u8B6C\u55BB \xB7 \u8AE7\u97F3\u62C6\u89E3\uFF09</div>
    <button class="zoomBtn" data-zoom="3" title="\u653E\u5927 / Expand" aria-label="\u653E\u5927 Expand">\u2922</button>
    <div class="cover" data-open="3">
      <div class="lock">\u{1F512}</div><div>\u9EDE\u64CA\u63ED\u66C9\u8B6C\u55BB</div>
    </div>
    <div class="content">
      <div class="q3-text">
        <div class="q3-row">
          <span class="q3-piece">tre</span>
          <span class="q3-arrow">\u2248</span>
          <span class="q3-result">\u9CE5 (tori)</span>
        </div>
        <div class="q3-row">
          <span class="q3-piece">men</span>
          <span class="q3-arrow">\u2248</span>
          <span class="q3-result">\u76EE (me)</span>
        </div>
        <div class="q3-row">
          <span class="q3-piece">dous</span>
          <span class="q3-arrow">\u2248</span>
          <span class="q3-result">\u51FA (dasu)</span>
        </div>
        <div class="q3-final">\u300C\u8B93\u5C0F\u9CE5\u90FD\u76EE\u77AA\u53E3\u5446\u300D</div>
      </div>
    </div>
  </div>

  <!-- Q4 \u2014 Recipe C \u6838\u5FC3\u5C55\u793A\uFF1A\u55AE\u4E00\u5927 emoji + bob \u52D5\u756B + \u5468\u570D\u9A5A\u8A1D\u7B26\u865F -->
  <div class="quadrant locked" data-q="4" id="q4">
    <div class="qLabel"><span class="num">Q4</span> \xB7 \u5716\u50CF IP \u5316\uFF08\u55AE\u4E00\u5927 emoji\uFF09</div>
    <button class="zoomBtn" data-zoom="4" title="\u653E\u5927 / Expand" aria-label="\u653E\u5927 Expand">\u2922</button>
    <div class="cover" data-open="4">
      <div class="lock">\u{1F512}</div><div>\u9EDE\u64CA\u63ED\u66C9\u5716\u50CF</div>
    </div>
    <div class="content">
      <div class="q4-image">
        <div class="metaphor-stage">
          <span class="puff p1">\u{1F4AB}</span>
          <span class="puff p2">\u2728</span>
          <span class="puff p3">\u2757</span>
          <div class="metaphor-emoji">\u{1F426}</div>
          <div class="metaphor-label">\u76EE\u77AA\u53E3\u5446\u7684\u5C0F\u9CE5</div>
          <div class="metaphor-sub">tre + men + dous \xB7 \u9CE5 + \u76EE + \u51FA</div>
        </div>
      </div>
    </div>
  </div>
</div>

<div class="tip" id="tipBar" style="display:none"></div>

<div id="resultBox"></div>

<script>
  window.addEventListener("message", (e) => {
    if (e.data && e.data.type === "engram-quest-theme") {
      document.body.classList.toggle("dark", !!e.data.dark);
    }
  });
  function reportHeight() {
    window.parent.postMessage({ type: "engram-quest-resize", height: document.documentElement.scrollHeight + 8 }, "*");
  }
  window.addEventListener("load", () => setTimeout(reportHeight, 80));

  let phase = "learn"; let opened = new Set([1]);
  const $ = id => document.getElementById(id);

  function openQ(n) {
    const q = $("q" + n);
    if (!q.classList.contains("locked")) return;
    q.classList.remove("locked"); q.classList.add("opened");
    if (phase === "learn") opened.add(n);
    reportHeight();
  }
  document.querySelectorAll(".cover").forEach(c => c.addEventListener("click", () => openQ(parseInt(c.dataset.open))));
  let focusedQ = null;
  function toggleFocus(n) {
    const q = $("q" + n);
    if (q.classList.contains("locked") && !q.classList.contains("focused")) return;
    const btn = q.querySelector(".zoomBtn");
    if (focusedQ === n) {
      q.classList.remove("focused"); document.body.classList.remove("focusing");
      if (btn) btn.textContent = "\u2922"; focusedQ = null; reportHeight();
    } else {
      if (focusedQ != null) {
        const prev = $("q" + focusedQ); prev.classList.remove("focused");
        const pb = prev.querySelector(".zoomBtn"); if (pb) pb.textContent = "\u2922";
      }
      q.classList.add("focused"); document.body.classList.add("focusing");
      if (btn) btn.textContent = "\u2715"; focusedQ = n;
      window.parent.postMessage({ type: "engram-quest-resize", height: 99999 }, "*");
    }
  }
  document.querySelectorAll(".zoomBtn").forEach(b => b.addEventListener("click", (e) => { e.stopPropagation(); toggleFocus(parseInt(b.dataset.zoom)); }));
  document.addEventListener("keydown", (e) => { if (e.key === "Escape" && focusedQ != null) toggleFocus(focusedQ); });

  // Single review entry: "Reveal all \u2192 self-rating". Individual cover clicks remain a learning peek
  // and never trigger self-rating (so users can study one quadrant at a time without committing).
  $("revealAll").addEventListener("click", () => {
    if (focusedQ != null) {
      const fq = $("q" + focusedQ); fq.classList.remove("focused");
      const fb = fq.querySelector(".zoomBtn"); if (fb) fb.textContent = "\u2922";
      document.body.classList.remove("focusing"); focusedQ = null;
    }
    [1,2,3,4].forEach(n => { const q = $("q" + n); q.classList.remove("locked"); q.classList.add("opened"); });
    phase = "review-revealed";
    $("phase").textContent = "\u270D\uFE0F \u81EA\u8A55\u968E\u6BB5";
    const _tip = $("tipBar"); _tip.innerHTML = "\u270D\uFE0F <strong>\u8AA0\u5BE6\u81EA\u8A55</strong>\uFF1A\u525B\u624D\u8166\u4E2D\u6D6E\u73FE\u300C\u5C0F\u9CE5\u300D\u756B\u9762\u4E86\u55CE\uFF1F\u6709\u756B\u9762 = Recipe C \u5C0D\u4F60\u6709\u6548\u3002"; _tip.style.display = "";
    $("controls").innerHTML = \`
      <button class="btn success" data-g="correct">\u2705 \u7B54\u5C0D\u4E86</button>
      <button class="btn warn" data-g="wrong">\u274C \u7B54\u932F\u4E86</button>
      <button class="btn danger" data-g="blank">\u{1F635} \u5B8C\u5168\u60F3\u4E0D\u8D77\u4F86</button>
    \`;
    $("controls").querySelectorAll("[data-g]").forEach(b => b.addEventListener("click", () => submitGrade(b.dataset.g)));
    reportHeight();
  });

  const RX = {
    correct: { score: 100, cls: "good", title: "\u{1F389} \u8A18\u4F4F\u4E86\uFF01", body: "\u4E0B\u6B21\u8907\u7FD2\u5EF6\u5F8C\uFF08\u9694\u9031 \u2192 \u9694\u5169\u9031 \u2192 \u9694\u56DB\u9031\uFF09\u3002" },
    wrong:   { score: 60,  cls: "mid",  title: "\u{1F501} \u5370\u8C61\u6709\u4F46\u4E0D\u5920\u7CBE\u6E96", body: "\u9694\u4E00\u6BB5\u6642\u9593\u518D\u4F86\uFF0C\u5C0F\u9CE5\u756B\u9762\u53EF\u518D\u52A0\u5F37\u3002" },
    blank:   { score: 25,  cls: "bad",  title: "\u{1F4DD} \u5370\u8C61\u592A\u6DE1", body: "\u56DE\u53BB\u628A Q4 \u7684 emoji \u63DB\u6210\u66F4\u8352\u8B2C\u7684\uFF08\u4F8B\u5982 \u{1F9A4} \u6E21\u6E21\u9CE5 + \u66F4\u591A\u9A5A\u8A1D\u7B26\u865F\uFF09\u3002" }
  };
  function submitGrade(g) {
    phase = "done";
    const p = RX[g];
    $("phase").textContent = "\u{1F3C1} \u5B8C\u6210 \xB7 " + p.title;
    $("controls").innerHTML = ""; $("tipBar").style.display = "none";
    $("resultBox").innerHTML = \`<div class="result"><div class="verdict \${p.cls}">\${p.title}</div><div class="prescription">\${p.body}</div></div>\`;
    window.parent.postMessage({ type: "engram-quest-solved", score: p.score }, "*");
    setTimeout(reportHeight, 80);
  }
<\/script>
</body>
</html>
`,"quadrant/references/layout-contract.md":`# A4 Layout & iframe Contract

The recipe templates in \`assets/\` already implement everything in this file. You normally only
edit content fields. This guide explains the framework so you understand what must **not** change
and how the card talks to the plugin.

## Visual skeleton

\`\`\`
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
\u2502 [badge card X]  [phase text]              \u2502  Header
\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
\u2502 [Reveal all \u2192 \u81EA\u8A55]                       \u2502  Single control (top \u2014 reachable without scrolling)
\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
\u2502 Q1 \xB7 Question   \u2922 \u2502 Q2 \xB7 Answer       \u2922 \u2502
\u2502 (shown by default) \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510    \u2502
\u2502                    \u2502 \u2502 \u{1F512} reveal     \u2502    \u2502  Q1 open; Q2/Q3/Q4 covered (locked)
\u2502                    \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518    \u2502
\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
\u2502 Q3 \xB7 Verbal IP  \u2922 \u2502 Q4 \xB7 Visual IP    \u2922 \u2502
\u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510  \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510    \u2502
\u2502 \u2502 \u{1F512} reveal     \u2502  \u2502 \u2502 \u{1F512} reveal     \u2502    \u2502
\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518  \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518    \u2502
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
[\u{1F4A1} tip bar]
\`\`\`

The \`\u2922\` in each opened quadrant's top-right is the **focus / zoom** button (see "Focus / zoom"
below). It surfaces only after a quadrant is opened, so it never lets the learner peek at a
covered answer.

Q1 (the question) is the entry point, so it starts **open** \u2014 like a flashcard's front. The
single "Reveal all \u2192 \u81EA\u8A55" control sits **above** the page so it's reachable without scrolling a
tall A4 card. You can recall straight from Q1 then hit it to grade yourself; opening Q2\u2013Q4 covers
one at a time first is an optional study peek that **does not** trigger self-rating \u2014 only the
"Reveal all" button does.

The cross fold is two 1px lines via \`.paper::before\` / \`.paper::after\` (opacity 0.5). Each
quadrant has a \`.cover\` (dashed, 45\xB0 striped, \u{1F512}) hiding its \`.content\` until clicked.

**Responsive (mobile).** Below a 520px viewport the template auto-collapses the 2\xD72 into a single
full-width column (Q1\u2192Q2\u2192Q3\u2192Q4) and hides the cross fold \u2014 at phone widths a half-cell is only
~160px, too narrow to hold the content without it overflowing and being clipped. You don't author
this; it's a \`@media\` rule in every recipe. But it does shape how you write: the four-at-a-glance
comparison won't be visible at once on a phone, so each cell must stand on its own as a terse
hook, and any side-by-side contrast (e.g. the comic's two panels) should live *inside one
quadrant* rather than relying on Q-to-Q adjacency.

## Filling the quadrant text (especially Q1)

Q1 should read as one natural question \u2014 the way you'd say it aloud. Two habits keep it clean:

- **The \`.vs\` highlight pill is for the term the question hinges on, not an arbitrary phrase.** In
  the two-panel recipe that is usually the compared options
  (\`<span class="vs">Bedrock</span> \u9084\u662F <span class="vs">SageMaker</span>\uFF1F\`) or the single
  subject under scrutiny (\`<span class="vs">Serverless</span> \u70BA\u4F55\u2026\uFF1F\`). Wrapping a stray verb
  phrase like "\u767D\u767D\u71D2\u9322" in a pill drops a coloured box into the middle of the sentence and reads
  as noise \u2014 highlight the noun the learner is being asked about instead.
- **Let the text wrap; use \`<br>\` only at a real clause boundary.** The Q1 cell is centered and
  fairly narrow, so forcing a break every few words collapses it into a cramped vertical stack.
  One deliberate break at a natural pause is plenty; the parenthetical hint goes in \`.small\`,
  which already renders on its own line.

Same spirit for Q2\u2013Q4: highlight the one thing that matters and keep the rest as readable prose.

## Focus / zoom \u2014 when a quadrant needs more room

Each opened quadrant has a \`\u2922\` button (\`.zoomBtn\`). Tapping it promotes that quadrant to a
full-screen overlay (\`.quadrant.focused\` \u2192 \`position:fixed; inset:0\`) with its content scaled up
(\`zoom: 1.45\`); the button turns into \`\u2715\`, and Esc or tapping it again returns to the 2\xD72 grid.
On entering focus the card posts \`{ type:"engram-quest-resize", height: 99999 }\` so the iframe
grows to its height cap (~78vh) and the overlay fills the visible area; on exit it reports the
real \`scrollHeight\` again. This is purely presentational \u2014 phase, scoring, and the postMessage
contract are untouched \u2014 so all of it already lives in the recipe templates and you don't author
it per card.

**This is a relief valve, not a license to write more.** The whole power of the four-quadrant
card is *one A4 page, four cells seen at a glance* \u2014 a memory palace. The grid is the home base;
focus is a temporary detail view, most useful for a genuinely dense quadrant (often Q4's visual
or a layered diagram). So keep authoring each cell as a terse, vivid **memory hook**, sized to
read fine in its half-cell. Focus should make an already-good cell *more comfortable to study*,
never rescue a cell you crammed full of prose.

If a topic honestly carries too much to fit \u2014 e.g. a whole system architecture \u2014 the right move
is **not** to stuff one card and lean on zoom. Either split it into 2\u20133 quadrant cards (each a
clean sub-question), or let the quadrant hold the hook and keep the full detail in the source
note. A card that only makes sense zoomed in is a card that lost the method.

## Three phases (state machine)

The card has one review entry \u2014 clicking **"Reveal all \u2192 \u81EA\u8A55"** jumps straight from \`learn\` into
\`review-revealed\`. There is no intermediate "re-lock to force recall" phase: recall happens in
the learner's head while looking at Q1, then they click the button and grade themselves. Removing
that middle phase keeps the flow short \u2014 the user complained the prior re-lock step felt cumbersome.

| Phase | Shows | Interaction |
|---|---|---|
| \`learn\` | Q1 open; Q2/Q3/Q4 covered "\u{1F512} reveal" | Click a covered quadrant for an optional peek (stays in \`learn\`, no self-rating). When ready to grade yourself, click **"Reveal all \u2192 \u81EA\u8A55"** |
| \`review-revealed\` | All 4 open | Shows 3 self-assessment buttons (\u2705 correct / \u274C wrong / \u{1F635} blank) |
| \`done\` | Shows the matching prescription | Posts the score back to the parent |

## postMessage contract (DO NOT CHANGE)

The plugin embeds the HTML in a sandboxed iframe and communicates only through these messages.
Changing them breaks scheduling.

**Incoming** (plugin \u2192 card):
\`\`\`js
window.addEventListener("message", (e) => {
  if (e.data && e.data.type === "engram-quest-theme") {
    document.body.classList.toggle("dark", !!e.data.dark);   // dark-mode sync
  }
});
\`\`\`

**Outgoing** (card \u2192 plugin):
\`\`\`js
// after any layout change, report height so the iframe can resize
window.parent.postMessage({ type: "engram-quest-resize", height: document.documentElement.scrollHeight + 8 }, "*");

// on self-assessment, report the score (0\u2013100)
window.parent.postMessage({ type: "engram-quest-solved", score: p.score }, "*");
\`\`\`

The plugin clamps the score to 0\u2013100 and maps it to an FSRS rating, then schedules the next
review. The card itself does no scheduling.

The target origin is \`"*"\` on purpose: the iframe is sandboxed with \`allow-scripts\` only (no
\`allow-same-origin\`), so it has a null origin and literally cannot name a specific \`targetOrigin\`.
Don't "tighten" it to a fixed origin \u2014 that silently breaks resize and scoring.

## Scoring \u2192 prescription \u2192 FSRS rating

| Self-assessment | score | Book prescription | Plugin FSRS rating |
|---|---|---|---|
| \u2705 correct | 100 | Remembered; push next review further out (1wk \u2192 2wk \u2192 4wk) | Good |
| \u274C wrong | 60 | Review again soon; strengthen Q3 | Hard |
| \u{1F635} blank | 25 | Redraw Q3/Q4 more absurd/vivid | Again |

Keep these three score values exactly (100 / 60 / 25) \u2014 the plugin relies on them to derive the
rating. The book's base cadence is next-day \u2192 1 week \u2192 2 weeks \u2192 4 weeks; FSRS adapts from there.

## Dark mode

Every recipe defines a \`body.dark { --var: ... }\` palette and toggles it on the
\`engram-quest-theme\` message. When you add new colored elements, define both light and dark
values as CSS variables \u2014 never hardcode a single color that only works in one theme.

### Three color pairs only

Each recipe ships **three** semantic color pairs, all with \`:root\` and \`body.dark\` values defined:

| Semantic | Background | Border / accent | Text on background |
|---|---|---|---|
| \`bad\` (error / wrong answer / pitfall) | \`var(--bad-soft)\` | \`var(--bad)\` | \`var(--ink)\` |
| \`good\` (correct / permanent fix) | \`var(--good-soft)\` | \`var(--good)\` | \`var(--ink)\` |
| \`warn\` (temporary fix / caveat / amber notice) | \`var(--warn-soft)\` | \`var(--warn)\` | \`var(--warn-text)\` |

\`--warn\` uses a dedicated \`--warn-text\` (deep brown in light, pale amber in dark) instead of
\`--ink\`, because \`--warn-soft\` is light amber in light mode and dark amber in dark \u2014 \`--ink\`
flips the wrong way against it.

**Hard rule for any colored box:**

- Use one of these three pairs. Don't write \`background:#fef3c7\` or any other hardcoded hex on a
  \`.q*-trap\` / \`.q*-side\` / similar element \u2014 hardcoded hex does **not** flip with \`body.dark\`,
  so the text inside (which uses a \`var(--\u2026)\` that *does* flip) ends up invisible in one theme.
  This is the most common dark-mode bug in AI-generated quadrant cards.
- Don't put \`color: var(--muted)\` inside a colored-pair box. \`--muted\` is tuned for the *paper*
  background, not for \`--*-soft\`. Use the pair's matching text color (or \`var(--ink)\` for
  bad/good, \`var(--warn-text)\` for warn).
- If a recipe genuinely needs a fourth accent (e.g. Recipe C's \`--bird\` for the bird metaphor),
  define **both** \`:root\` and \`body.dark\` values for it in that recipe's \`<style>\`. Never write
  the accent inline on an element.
`,"quadrant/references/obsidian-cli.md":`# Obsidian CLI Reference

## What it is

Obsidian CLI is the **official** Obsidian command line interface (v1.12+).
It uses Obsidian's built-in index \u2014 the same index the GUI uses.
Results are fast, accurate, and support full Obsidian search syntax.

**Requirement**: Obsidian app must be running. Enable via Settings \u2192 General \u2192 Enable Command line interface.

## Search syntax

\`\`\`bash
# Full-text search
obsidian search query="keyword"

# Tag search
obsidian search query="tag:#flashcards/azure" limit=20

# Path filter
obsidian search query="path:Study keyword"

# JSON output (use this for programmatic processing)
obsidian search query="keyword" format=json

# Target a specific vault
obsidian vault="Obsidian_Note" search query="keyword"
\`\`\`

## Query operators

\`\`\`
keyword                  full-text
tag:#flashcards/azure    tag search
path:Study               path filter
file:azure               filename filter
"exact phrase"           exact match
kw1 kw2                  AND (space)
kw1 OR kw2               OR
\`\`\`

## Read / write notes

\`\`\`bash
obsidian read path="folder/note.md"
obsidian read file="Note Name"
obsidian create name="New Note" content="# Hello" silent
obsidian append file="My Note" content="new line"
obsidian property:set name="status" value="done" file="My Note"
\`\`\`

## Known silent failures (v1.12)

| Wrong | Problem | Correct |
|---|---|---|
| \`tasks todo\` | scoped to active file only, returns empty | \`tasks all todo\` |
| \`tags counts\` | returns empty | \`tags all counts\` |
| \`create name="x" content="y"\` | opens GUI | add \`silent\` flag |

## Fallback when CLI is unavailable

If \`obsidian search\` fails (CLI not installed or Obsidian not running), fall back to:

\`\`\`bash
bash scripts/search_vault.sh "<topic>" 30
\`\`\`

\`search_vault.sh\` is grep-based \u2014 slower, no index, no Obsidian syntax. Use only as fallback for note discovery, or for L2 hint personal context search (its only other permitted use).
`,"quadrant/references/q3-q4-quality.md":`# Q3 & Q4 Quality Guide

Q3 (verbal IP) and Q4 (visual IP) are where Quadrant Cards live or die. Q1/Q2 are mechanical;
these two carry the memory. Read this before writing them.

## Q3 \u2014 Verbal IP (the metaphor)

**Goal:** turn Q1/Q2 into a vivid mental picture so abstract content becomes concrete. Q3 is not
a shorter Q2 \u2014 it is a *different memory interface* (from logic to imagery).

Techniques, in priority order:
1. **Metaphor** \u2014 "Bedrock = a phone call, SageMaker = renting a whole office building". Strongest.
2. **Analogy** \u2014 "like sending one letter by truck".
3. **Contrast** \u2014 "X is like A vs Y is like B" (for decisions / binaries).
4. **Homophone/wordplay** \u2014 only when a human supplies it. AI-forced homophones are almost always bad.

**Forbidden (real pitfalls from testing):**
- \u274C AI-forced keyword-chain mnemonics, e.g. compressing Q2's four words into single characters
  with no metaphor, no story, no picture. That is not memory, it is noise.
- \u274C Acronym mnemonics (don't work well in Chinese; weak in English too).
- \u274C Re-shortening the Q2 answer. Q3 must change the *interface*, not the length.

**The pass test (use this every time):** close your eyes; when the Q3 text surfaces, can you
*directly see a picture*? See one \u2192 pass. See nothing \u2192 rewrite.

Good Q3 examples:
- "Office building vs phone call" (Bedrock decision)
- "The flywheel spins faster the more people use it" (one image, not a word-chain)

Bad Q3 example:
- "\u4EBA\u2192\u6599\u2192\u8166\u2192\u723D" \u2014 four single characters forced from Q2's terms. No metaphor, no picture. Rewrite
  into one image like the flywheel line above.

## Q4 \u2014 Visual IP (the image)

**Goal:** carry Q3's metaphor into one concrete image so visual memory takes over. The book draws
by hand \u2014 the point is **vivid, absurd, memorable**, not "well drawn". Electronically, the safe
path is emoji, not AI geometry.

Visual strategy by content type:

| Content type | Visual method | Recipe |
|---|---|---|
| Cycle / process / flywheel | CSS spin animation + emoji nodes + dashed SVG arrows | A |
| Binary / decision / contrast | Two-panel comic, big emoji + red/green + VS badge | B |
| Single metaphor object | One big emoji + label | C |
| Genuine static structure / architecture | Hand-drawn SVG (high risk) | D |
| Comparison table / parallel relations | Don't draw \u2014 use text contrast instead | \u2014 |

## Q4 quality checklist (tick all before reporting done)

| # | Check | Standard |
|---|---|---|
| 1 | Is the main visual an emoji or AI-drawn geometry? | Must be emoji. AI geometry \u2192 fail |
| 2 | Color count | \u2264 3 main colors (emoji's own colors don't count) |
| 3 | Label readability | \u2265 10px at ~480px width |
| 4 | Does it carry Q3's metaphor? | Must map to it. Unrelated image \u2192 fail |
| 5 | Eye-close test | Look 5s, close eyes, can you reproduce it? No \u2192 too complex, redo |
| 6 | Relationship to Q2 | Image must reinforce Q2, never contradict it |

## Failure cases (do not repeat)

| Failure | Why it's bad | Fix |
|---|---|---|
| Azure robot (brain head + dictionary hands + shield body) stacked from SVG primitives | AI geometry \u2192 broken proportions, stiff lines | Three big emoji (\u{1F9E0} \u{1F4D6} \u{1F6E1}) side by side + labels |
| "\u4EBA\u2192\u6599\u2192\u8166\u2192\u723D" character-chain mnemonic | Forced, no metaphor, no picture | One metaphor sentence ("the flywheel spins itself") |
| One image stuffed with 6 small emoji + 4 lines + 3 labels | Visual density too high, unmemorable | Split into two images, or cut to 3 elements |
| SVG with gradients + shadows to look "polished" | Weak AI color taste \u2192 looks amateur | Flat fill + thin border only |

## Why these judgments

- AI (including Claude) makes weak pure-geometry SVG \u2014 verified by the Azure-robot failure.
- Emoji are a reliable visual primitive \u2014 system-font rendered, immune to AI taste.
- CSS animation clearly helps "process" content \u2014 a spinning vs static flywheel feels very different.
- Forced mnemonics are worse than none \u2014 AI can't produce natural Chinese homophones; forced ones
  interfere with memory.
- Metaphor is what AI is good at \u2014 "Bedrock = phone call, SageMaker = whole office" is well within reach.
`,"quadrant/references/recipes.md":`# Q4 Visual Recipes

Q4 turns Q3's metaphor into one concrete image. The reliable strategy is **emoji as the visual
subject** \u2014 system emoji are professionally drawn, render consistently, and are immune to the
AI's weak geometry instincts. Recipes A/B/C ship as ready templates in \`assets/\`; copy the match
and change only the listed content fields. Recipe D is a last resort with strict guardrails.

**Color rule for any colored box inside Q2/Q3/Q4** (warnings, contrast halves, pitfall boxes,
correct-answer boxes): only the three semantic pairs \`bad\` / \`good\` / \`warn\` defined in each
recipe's \`<style>\` are safe in both light and dark mode. Never inline \`background:#xxx\` on a
colored box \u2014 see \`layout-contract.md\` \xA7 "Three color pairs only".

## Recipe selection (recap)

| Q3 metaphor shape | Recipe | Template |
|---|---|---|
| Cycle / process / self-reinforcing flywheel / PDCA | **A** spinning flywheel | \`assets/recipe-A-flywheel.html\` |
| Binary / either-or / before-after / pitfall-vs-fix | **B** two-panel comic | \`assets/recipe-B-comic.html\` |
| One strong single metaphor object | **C** one big emoji | \`assets/recipe-C-emoji.html\` |
| Genuine spatial structure, no emoji fits | **D** hand-drawn SVG (high risk) | none \u2014 follow rules below |
| Pure node-edge structure | (Mermaid) | not recommended \u2014 CDN conflict with iframe sandbox |

---

## Recipe A \u2014 CSS spinning flywheel (cycle / process)

For 4-step cycles, self-reinforcing flywheels, loops. The whole ring rotates; the four nodes
stay upright. **Change only:** 4 node emoji + 4 node labels + center emoji (5 values), plus the
Q1/Q2/Q3 text. Everything else (rotation, arc arrows, dark mode) is already correct.

Node HTML (inside the rotating ring is the SVG arc; the four nodes sit outside it so text stays
upright):
\`\`\`html
<div class="flywheel-node n-top"><div class="em">\u{1F464}</div><div>User</div></div>
<div class="flywheel-node n-right"><div class="em">\u{1F4CA}</div><div>Data</div></div>
<div class="flywheel-node n-bottom"><div class="em">\u{1F916}</div><div>Model</div></div>
<div class="flywheel-node n-left"><div class="em">\u2728</div><div>Experience</div></div>
<div class="flywheel-center">\u{1F300}</div>
\`\`\`
Key CSS already in template: \`.flywheel-ring { animation: spin 14s linear infinite; }\`, the four
\`.n-*\` position rules, and \`.flywheel-center { animation: pulse 2s ...; }\`. Do not touch them.

---

## Recipe B \u2014 Two-panel contrast comic (binary / decision)

For A-vs-B choices, before/after, pitfall vs fix. Two panels with a big emoji each, red (bad) /
green (good), and a central **VS** badge. **Change only:** 2 titles, 2 main emoji, 2 corner-tags,
2 captions, 2 cost lines (10 values), plus Q1/Q2/Q3.

\`\`\`html
<div class="comic" style="position:relative">
  <div class="panel bad">
    <div class="panel-title">SageMaker</div>
    <div class="panel-scene">\u{1F69A}<span class="corner-tag">\u{1F4B8}</span></div>
    <div class="panel-caption">Truck to deliver one letter</div>
    <div class="panel-cost">$$$ always-on</div>
  </div>
  <div class="comic-vs">VS</div>
  <div class="panel good">
    <div class="panel-title">Bedrock</div>
    <div class="panel-scene">\u{1F4DE}<span class="corner-tag">\u26A1</span></div>
    <div class="panel-caption">Just make a phone call</div>
    <div class="panel-cost">$ pay per call</div>
  </div>
</div>
\`\`\`
\`.panel-scene\` is the big emoji (the visual subject). The red/green + VS framing carries the
decision; pick emoji that dramatize each side.

---

## Recipe C \u2014 One big emoji (single metaphor object)

The most underrated option. When the metaphor object itself is memorable (the book's "stunned
little bird" for *tremendous*), one large emoji + a label is enough.

\`\`\`html
<div class="metaphor-stage">
  <div class="metaphor-emoji">\u{1F426}\u200D\u2B1B</div>
  <div class="metaphor-label">A stunned little bird</div>
  <div class="metaphor-sub">tre(bird) + men(eyes) + dous(out)</div>
</div>
\`\`\`
Why it wins: no geometry to draw (AI can't draw it badly), system emoji guarantee visual quality,
an 80\u2013100px emoji + one label already looks like a book illustration, the template's \`bob\`
animation makes it feel alive. **Choosing the emoji:** prefer expressive faces (\u{1F92F} \u{1F975} \u{1F635}\u200D\u{1F4AB} over
\u2699\uFE0F \u{1F527}), animals tend to stick better than objects (\u{1F422} \u{1F985} \u{1F994}), and two combined emoji are fine
(\u{1F69A}\u{1F4E8}, \u{1F916}\u{1F9E0}).

---

## Recipe D \u2014 Hand-drawn SVG (last resort, AI easily fails this)

Use only when **all three** hold: the content has genuine spatial structure, no emoji can express
it, and neither CSS animation nor the contrast comic fits. The Azure-robot failure case (AI
stacking \`<rect>\` + \`<circle>\` + \`<line>\` into a figure) came from ignoring this.

Anti-pitfall rules if you must draw:

| Rule | Why |
|---|---|
| \u274C Don't stack \`<rect>\`/\`<circle>\`/\`<line>\` to build a figure/object | Proportions always come out wrong |
| \u2705 Use a single \`<path>\` for a meaningful shape | Path curves look more organic than primitives |
| \u2705 Use only 2\u20133 colors total | More colors read as amateur |
| \u2705 Label every element with text | If the drawing fails, the label still reads |
| \u2705 Use emoji as the "subject", SVG only for connectors/lines | Outsource the hard part to emoji |
| \u274C No shadows / gradients / highlights | AI color taste is weak; they make it worse |

Embed emoji as nodes via \`<foreignObject>\` so the AI only handles deterministic line positions:
\`\`\`html
<svg viewBox="0 0 280 180" xmlns="http://www.w3.org/2000/svg">
  <line x1="140" y1="40" x2="60"  y2="120" stroke="#6366f1" stroke-width="2" stroke-dasharray="4,2"/>
  <line x1="140" y1="40" x2="140" y2="120" stroke="#6366f1" stroke-width="2" stroke-dasharray="4,2"/>
  <line x1="140" y1="40" x2="220" y2="120" stroke="#6366f1" stroke-width="2" stroke-dasharray="4,2"/>
  <foreignObject x="115" y="15" width="50" height="40">
    <div xmlns="http://www.w3.org/1999/xhtml" style="font-size:30px;text-align:center">\u{1F333}</div>
  </foreignObject>
  <!-- child nodes as more foreignObject emoji at the line endpoints -->
</svg>
\`\`\`
When even Recipe D feels forced, the real problem is Q3 \u2014 go back and make the metaphor concrete.
`,"quadrant/skills.md":`---
name: engram-quadrant
description: >
  Generate "Quadrant Card" A4 super-memory cards for the EngramQuest plugin (Pro feature).
  A Quadrant Card turns a single Q/A flashcard into a four-quadrant A4 memory sheet
  (Q1 question, Q2 answer, Q3 verbal IP / metaphor, Q4 visual IP / image) rendered as an
  interactive HTML iframe with its own spaced-repetition schedule.
  Trigger this skill whenever the user names a quadrant card by any of its aliases \u2014
  "Quadrant Card", "quadrant", "\u56DB\u8C61\u9650\u5361", "\u56DB\u683C\u5361", "\u56DB\u8C61\u9650", or "\u8D85\u8A18\u61B6\u5361" \u2014 or wants to
  "upgrade" / "\u5347\u7D1A" a flashcard or note Q/A into one, asks to build an A4 super-memory card,
  or wants a single concept turned into a memorable visual memory sheet. Trigger even when the
  user uses only the short name (e.g. just "\u56DB\u683C\u5361", "\u8D85\u8A18\u61B6\u5361", or "\u505A\u4E00\u5F35 quadrant") without
  saying "upgrade", and also when the user has marked cards for upgrade in the plugin and asks
  to process them.
---

# Quadrant Card Skill (Pro)

Generate **Quadrant Cards** \u2014 single-concept A4 "super memory" sheets built on a
four-quadrant layout. Each card is a self-contained interactive HTML iframe with four
quadrants and its own review lifecycle inside the EngramQuest plugin.

## What a Quadrant Card is

Fold one A4 sheet into a cross of four quadrants, each with a strict role:

\`\`\`
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
\u2502  Q1 Question \u2502  Q2 Answer  \u2502
\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
\u2502 Q3 Verbal IP \u2502 Q4 Visual IP\u2502
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
\`\`\`

The core principle is **"IP-ification"**: each quadrant holds only keywords + imagery, never
prose notes. The whole sheet is memorized as one *impression unit* \u2014 recalling the look of the
sheet pulls all four quadrants back together. This is for content the user wants to remember for
a long time, not one-off lookups.

A Quadrant Card is **not** a quest map (no nodes, no map). It is a single reviewable card that
happens to render as an A4 iframe. Do not write it as a \`*-quest.md\` file.

## Output Language Rule

Generate all user-facing card text in the language that best matches the source note or the
user's prompt. If the prompt names a language, follow it; otherwise match the source. Keep JSON
keys and structural fields in English.

## The two modes

### Mode A \u2014 Create a new card
Trigger: "make a quadrant card about X", "/quadrant <note>", "build an A4 memory card for this".

1. Read the source note or topic.
2. Decompose into Q1\u2013Q4 (see "The four quadrants").
3. Pick a recipe for Q4 (see decision tree \u2192 \`references/recipes.md\`).
4. Run the quality gate (\`references/q3-q4-quality.md\`).
5. Write the card artifacts (see "Output contract"). \`source\` = the note path, or \`null\` for a
   topic with no backing note.

### Mode B \u2014 Batch upgrade from the queue
Trigger: "upgrade", "\u5347\u7D1A", "process the cards I marked", "turn the marked flashcards into quadrant cards".

The plugin lets the user mark flashcards for upgrade from its UI. Each mark appends an entry to
\`engram-quest/quadrant/upgrade-queue.json\`. This mode drains that queue:

1. Read \`engram-quest/quadrant/upgrade-queue.json\` (Obsidian CLI: \`obsidian read\`, or read the
   file directly). If it is missing or has no \`pending\` entries, tell the user there is nothing
   to upgrade and offer Mode A instead.
2. For **each** entry whose \`status\` is \`"pending"\`:
   - **If the entry has \`regenerate: true\`** (a card the user edited in the plugin), it already
     carries a \`cardId\` plus edited \`q1\`\u2013\`q4\` and \`title\`. Treat the edited \`q1\`\u2013\`q4\` as the
     authoritative content (the user changed the text on purpose): rebuild Q3/Q4 visuals to match
     them and keep the **same \`cardId\`**. **First read the existing \`sr/{cardId}.json\`**, then write
     it back with only \`title\`/\`q1\`\u2013\`q4\` (and \`recipe\`, if you changed the visual) updated \u2014 never
     start from a blank object. The \`fsrs\`, \`created\`, and \`source\` fields **must** survive
     untouched, or the user loses their review schedule. Overwrite \`{cardId}.html\` to match. Then
     set the entry's \`status\` to \`"done"\`.
   - **Otherwise** (a fresh upgrade): use the entry's \`source\`, \`q\`, and \`a\`. Read the \`source\`
     note when you need more context to write a good Q3 metaphor / Q4 visual. Decompose into
     Q1\u2013Q4, pick a recipe, run the quality gate. Write the card artifacts and set \`source\` to the
     entry's \`source\`. Update the entry in place: set \`status\` to \`"done"\` and fill \`cardId\`.
3. Write the updated queue back. Report how many cards were upgraded (and how many regenerated).

A user may also upgrade a single specific flashcard directly ("upgrade this card") without the
queue \u2014 handle it like one Mode B entry, then there is no queue to update.

## The four quadrants

Brief definitions here; the hard parts (Q3 metaphor and Q4 visual) have a dedicated guide.

- **Q1 \u2014 Question** (most simplified): one clear, single question. A word, a formula, or one
  sentence. 1\u20132 lines, \u2264 ~30 chars. Not a paragraph, not a broad topic.
- **Q2 \u2014 Answer** (heavily simplified): the answer with all filler stripped \u2014 keywords only, or a
  small structured diagram (e.g. \`A \u2192 B \u2192 C\`). \u2264 3 lines, scannable at a glance.
- **Q3 \u2014 Verbal IP** (metaphor / analogy, **NOT a forced mnemonic**): turn Q1/Q2 into a vivid
  mental picture. This is the quadrant the AI most often does badly. **Read
  \`references/q3-q4-quality.md\` before writing Q3.** The test: when the Q3 text surfaces in your
  mind, you must *see a concrete picture*. If not, rewrite it.
- **Q4 \u2014 Visual IP** (draw Q3's metaphor): carry Q3's metaphor into one concrete image. Prefer
  emoji over AI-drawn geometry. Pick the recipe from the decision tree below.

## Q4 recipe decision tree

\`\`\`
What is Q3's metaphor shaped like?
  \u251C\u2500 a cycle / process / flywheel        \u2192 Recipe A (CSS spinning flywheel)
  \u251C\u2500 a binary / either-or / decision     \u2192 Recipe B (two-panel contrast comic)
  \u251C\u2500 one strong single metaphor object   \u2192 Recipe C (one big emoji)
  \u2514\u2500 none of these \u2192
        has genuine spatial structure (architecture / hierarchy)?
          \u251C\u2500 yes \u2192 Recipe D (hand-drawn SVG \u2014 high risk, last resort)
          \u2514\u2500 no  \u2192 rewrite Q3 (the metaphor is not concrete enough)
\`\`\`

Recipes A/B/C are proven and ship as copy-and-edit templates in \`assets/\`. **Copy the matching
template and change only the content fields \u2014 never rewrite the framework** (A4 layout, phase
switching, postMessage, dark mode, self-assessment are all already correct). Full HTML/CSS for
each recipe, the fields to change, and Recipe D's anti-pitfall rules are in
\`references/recipes.md\`.

## Output contract

Each card produces two files plus a queue update (Mode B only).

### 1. The iframe HTML \u2192 \`engram-quest/quadrant/{cardId}.html\`
Start from \`assets/recipe-{A,B,C}-*.html\` and edit only the content. The file already implements
the A4 layout, the four phases (learn \u2192 review-thinking \u2192 review-revealed \u2192 done), dark-mode
theming, and the postMessage contract. **Do not change the postMessage protocol** \u2014 the plugin
depends on it. Full layout + phase + postMessage contract: \`references/layout-contract.md\`.

### 2. The schedule + metadata \u2192 \`engram-quest/quadrant/sr/{cardId}.json\`
\`\`\`jsonc
{
  "cardId": "<slug>-<timestamp>",
  "title": "<short human title for the card list>",
  "source": "<source note path, or null>",
  "deck": "<deck name>",         // group the card under this deck in the Hub tab (see below)
  "recipe": "A",                 // A | B | C | D
  "q1": "...", "q2": "...", "q3": "...", "q4": "...",   // plaintext, for list preview / search
  "created": "YYYY-MM-DD",
  "fsrs": null                   // leave null \u2014 the plugin initializes FSRS on first review
}
\`\`\`
Set \`deck\` so the Hub tab can group cards by tag, exactly like Review Deck. Resolve it in this
order: the source note's flashcard tag's deck segment (e.g. tag \`flashcards/azure\` \u2192 deck \`azure\`),
else the source note's folder name, else a short topic label for an AI-created card. If you genuinely
cannot determine one, omit \`deck\` \u2014 the plugin falls back to the source note's tag/folder at render
time. Leave \`fsrs\` as \`null\`. The plugin owns scheduling; it initializes the FSRS state when the card
is first reviewed and maps the card's self-assessment (correct / wrong / blank) to a rating.

### \`cardId\` rules
- Stable, filesystem-safe slug of the source/title plus a timestamp suffix, e.g.
  \`bedrock-vs-sagemaker-1748252400\`. Used to key both the \`.html\` and the \`sr/*.json\`.
- On re-upgrade of the same source, reuse the existing \`cardId\` so review progress carries over.

## Generation flow

1. Determine the mode (A create / B upgrade-queue) from the user's request.
2. Gather content: read the source note; use the cheapest discovery path. When vault search is
   needed use Obsidian CLI \u2014 see \`references/obsidian-cli.md\`.
3. Decompose into Q1\u2013Q4. Q1/Q2 are usually quick; spend the effort on Q3 (metaphor) and Q4.
4. Read \`references/q3-q4-quality.md\` and write Q3 + Q4 to pass its checklist.
5. Pick the recipe; copy the template from \`assets/\`; fill the content fields only.
6. Write \`{cardId}.html\` and \`sr/{cardId}.json\` under \`engram-quest/quadrant/\`.
7. Mode B: flip the queue entry to \`done\` with its \`cardId\`, then write the queue back.
8. Run the quality gate one final time before reporting done.

## Suitability \u2014 when to make a Quadrant Card

Good fits: a single clear Q/A, a concrete answer, room for a metaphor, content worth remembering
long-term. Poor fits: multi-part notes (split first), code/config snippets (no metaphor room),
pure enumeration lists (use review-deck instead), answers that change over time (metaphors expire).
If asked to upgrade a poor fit, say so honestly and suggest the better tool.

## References

- \`references/recipes.md\` \u2014 full HTML/CSS for Recipe A/B/C/D, fields to change, anti-pitfalls
- \`references/q3-q4-quality.md\` \u2014 Q3 metaphor rules, Q4 visual strategy, quality checklist, failure cases
- \`references/layout-contract.md\` \u2014 A4 skeleton, four phases, postMessage contract, scoring
- \`references/obsidian-cli.md\` \u2014 vault search syntax and fallback rules
`,"quest-map/assets/examples/boss-cascade.html":`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Boss Cascade Example</title>
<style>
:root{--bg:#f8fafc;--card:#fff;--text:#0f172a;--muted:#64748b;--border:#dbe3ef;--accent:#4f46e5;--good:#16a34a;--warn:#d97706;--bad:#dc2626}
body.dark{--bg:#0f172a;--card:#172033;--text:#e2e8f0;--muted:#94a3b8;--border:#334155;--accent:#818cf8}
*{box-sizing:border-box}body{margin:0;padding:22px 18px 72px;background:var(--bg);color:var(--text);font-family:-apple-system,"Segoe UI","Noto Sans TC",system-ui,sans-serif;line-height:1.6}.wrap{max-width:820px;margin:0 auto}.metric{display:inline-block;background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 14px;margin:4px}.warn{display:none;border-left:4px solid var(--warn);background:rgba(217,119,6,.12);padding:10px 12px;border-radius:8px;margin:10px 0}.warn.show{display:block}.panel{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px;margin:12px 0}.choice{display:block;width:100%;text-align:left;border:2px solid var(--border);background:var(--card);color:var(--text);border-radius:10px;padding:12px;margin:8px 0;cursor:pointer}
</style>
</head>
<body>
<main class="wrap">
  <h1>Boss: Cascading Recovery</h1>
  <div><span class="metric">Latency: <b id="lat">620 ms</b></span><span class="metric">Queue: <b id="queue">8k</b></span></div>
  <div id="warn" class="warn"></div>
  <section class="panel" id="stage"></section>
</main>
<script>
const state={score:0,poorFirst:false,stage:0};
const stages=[
  {q:"Stage 1: What do you check first?",opts:[["Consumer logs and queue age",50],["Database scale settings",15]]},
  {q:"Stage 2: What mitigation is safest now?",opts:[["Pause producer burst and add consumers",50],["Restart the database",10]]}
];
function resize(){window.parent.postMessage({type:"engram-quest-resize",height:document.body.scrollHeight+24},"*")}
function solved(){window.parent.postMessage({type:"engram-quest-solved",score:state.score},"*")}
function render(){const s=stages[state.stage],el=document.getElementById("stage");el.innerHTML=\`<h2>\${s.q}</h2>\`;s.opts.forEach(([label,score],i)=>{const b=document.createElement("button");b.className="choice";b.textContent=label;b.onclick=()=>pick(score,i);el.appendChild(b)});resize()}
function pick(score,index){state.score+=score;if(state.stage===0&&index===1){state.poorFirst=true;document.getElementById("lat").textContent="910 ms";document.getElementById("queue").textContent="14k";const w=document.getElementById("warn");w.textContent="Your first move delayed queue diagnosis. The backlog is worse in stage 2.";w.classList.add("show")}state.stage++;if(state.stage>=stages.length){document.getElementById("stage").innerHTML=\`<h2>Recovery complete</h2><p>Score: \${state.score}</p><button class="choice" id="finish">Finish boss</button>\`;document.getElementById("finish").onclick=solved}else render();resize()}
window.addEventListener("message",e=>{if(e.data&&e.data.type==="engram-quest-theme"){document.body.classList.toggle("dark",!!e.data.dark);resize()}});
render();
<\/script>
</body>
</html>
`,"quest-map/assets/examples/mission-basic.html":`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mission Example</title>
<style>
:root{--bg:#f8fafc;--card:#fff;--text:#0f172a;--muted:#64748b;--border:#dbe3ef;--accent:#2563eb;--good:#16a34a;--bad:#dc2626}
body.dark{--bg:#0f172a;--card:#172033;--text:#e2e8f0;--muted:#94a3b8;--border:#334155;--accent:#60a5fa}
*{box-sizing:border-box}body{margin:0;padding:22px 18px 72px;background:var(--bg);color:var(--text);font-family:-apple-system,"Segoe UI","Noto Sans TC",system-ui,sans-serif;line-height:1.6}.wrap{max-width:760px;margin:0 auto}.card{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px;margin:12px 0}.option{display:block;width:100%;text-align:left;border:2px solid var(--border);background:var(--card);color:var(--text);border-radius:10px;padding:12px;margin:8px 0;cursor:pointer}.option:hover:not(:disabled){border-color:var(--accent)}.option.ok{border-color:var(--good)}.option.bad{border-color:var(--bad)}.feedback{min-height:1.5em;color:var(--muted);margin-top:8px}
#finish-btn{display:none;margin-top:16px;padding:10px 24px;background:var(--accent);color:#fff;border:none;border-radius:10px;cursor:pointer;font-size:1rem;font:inherit}
#finish-btn:hover{opacity:.88}
</style>
</head>
<body>
<main class="wrap">
  <h1>Mission: Pick the safer first move</h1>
  <div class="card">
    <p><strong>Situation.</strong> A new release doubled latency. Error rate is stable, but queue depth is rising.</p>
    <button class="option" data-score="100">Inspect queue consumers before rolling back.</button>
    <button class="option" data-score="45">Rollback immediately without checking queue state.</button>
    <button class="option" data-score="20">Scale the database first.</button>
    <p class="feedback" id="feedback"></p>
    <!-- Finish button: always show after feedback so the learner reads it before advancing -->
    <button id="finish-btn">\u5B8C\u6210\u95DC\u5361</button>
  </div>
</main>
<script>
function resize(){window.parent.postMessage({type:"engram-quest-resize",height:document.body.scrollHeight+24},"*")}
function solved(score){window.parent.postMessage({type:"engram-quest-solved",score},"*")}
window.addEventListener("message",e=>{if(e.data&&e.data.type==="engram-quest-theme"){document.body.classList.toggle("dark",!!e.data.dark);resize()}})

let finalScore=0;
document.querySelectorAll(".option").forEach(btn=>btn.addEventListener("click",()=>{
  finalScore=Number(btn.dataset.score);
  document.querySelectorAll(".option").forEach(b=>b.disabled=true);
  btn.classList.add(finalScore>=80?"ok":"bad");
  document.getElementById("feedback").textContent=finalScore>=80
    ?"\u2713 Good: the symptom points at consumer throughput \u2014 inspect before touching anything else."
    :"\u2717 Risky: rolling back or scaling DB hides the real bottleneck. Check consumers first.";
  // Show Finish button \u2014 do NOT call solved() here; let the learner read the feedback first.
  document.getElementById("finish-btn").style.display="inline-block";
  resize();
}));

document.getElementById("finish-btn").addEventListener("click",()=>solved(finalScore));
resize();
<\/script>
</body>
</html>
`,"quest-map/references/boss-design.md":`# Boss Design

A Boss is the final integrated challenge for the quest difficulty. It is not just a harder quiz.

## Boss requirements

Every boss must:
- combine at least two earlier concepts
- present a realistic high-stakes situation
- require multi-step reasoning
- include plausible competing choices
- show consequences across stages
- emit a numeric score from 0 to 100

## Cascade rule

The learner's earlier decisions should change later context.

Good cascade effects:
- a metric worsens
- available options narrow
- time pressure increases
- a stakeholder constraint appears
- a later explanation references the earlier decision

Do not use cascade as random punishment. The consequence must have clear cause and effect.

Example:

\`\`\`js
if (state.misdiagnosedCache) {
  metrics.latency = 920;
  warning.textContent = "Your cache-first rollback delayed DB diagnosis. Latency is now 920 ms and the deploy window is shorter.";
  warning.classList.add("show");
}
\`\`\`

## Plausible options

Avoid choices where only one option is serious.

Weak:
- Restart the database
- Delete logs
- Ignore the alert
- Inspect request traces

Strong:
- Roll back the function app first
- Disable the new queue trigger first
- Scale out the premium plan first
- Inspect correlation traces before mitigation

All four can be reasonable in some incidents; the scenario determines the best answer.

## Scoring

Use transparent scoring:
- best decision: full or high points
- acceptable but incomplete decision: partial points
- risky decision: low points with explanation

Boss score can be:
- average of stage scores
- weighted score where the final synthesis is heavier
- minimum threshold plus bonus for first-try calculation or diagnosis

Post only one final solved message:

\`\`\`js
window.parent.postMessage({ type: "engram-quest-solved", score: total }, "*");
\`\`\`

## Visual tone

Pick tone by domain:
- software/SRE: terminal, traces, metrics, architecture cards
- business/management: board brief, KPI dashboard, stakeholder notes
- medicine: chart, vitals, differential diagnosis, red flags
- math/science: formula panel, simulation, graph, experiment log
- language/history: source excerpt, timeline, evidence board

Do not force every boss into a dark emergency UI. Match the subject.

`,"quest-map/references/challenge-formats.md":`# Challenge Formats Reference

This document defines every legacy v1 challenge type supported by the quest-map parser and renderer.
Read this only when updating an existing v1 quest or when the user explicitly requests lightweight YAML challenges.

For new quests, create plugin-native packages under \`engram-quest/quests/{questSlug}/\`. See \`html-first-contract.md\`.

---

## Quick Reference Table

| Type | Interaction | Best for | Difficulty fit |
|---|---|---|---|
| \`quiz\` | Pick one option | General knowledge | easy\u2013medium |
| \`truefalse\` | True / False | Factual statements | easy |
| \`order\` | Click items in sequence | Step-by-step processes | easy\u2013medium |
| \`match\` | Pair left \u2194 right | Concept\u2013definition pairing | medium |
| \`input\` | Free text entry | Precise term recall | medium\u2013hard |
| \`cloze\` | Fill the blank | Key term memorization | easy\u2013hard |
| \`countdown\` | Pick one option under timer | Fluency / speed drill | easy\u2013hard |
| \`snapshot\` | Memorize grid \u2192 quiz | Dense info, visual memory | medium |
| \`auction\` | Pick + bet coins | Confidence calibration | medium\u2013hard |
| \`timeline\` | Place events in time slots | Historical evolution | medium\u2013hard |
| \`chain\` | Click nodes in order under timer | Causal flow under pressure | medium\u2013hard |
| \`memory-palace\` | Memorize knowledge map \u2192 recall | Architecture, component relationships | medium\u2013hard |
| \`image-quiz\` | Look at image \u2192 pick/type answer | Diagram comprehension | easy\u2013hard |
| \`image-occlusion\` | Guess occluded region (Gemini only) | Label memorization | medium\u2013hard |
| \`iframe\` | Interact with local HTML | Dynamic simulations and timed systems | medium-hard |

---

## Multi-Question Rounds (\`questions_json\`)

Challenge types that benefit from multiple questions should use \`questions_json\` \u2014 an inline JSON array. The renderer loops through questions, tracks score/lives/coins, and shows a round summary at the end. Score counts first-try correct answers; revealed or recovered answers are shown as review items, not fake perfect scores.

### Syntax

\`\`\`yaml
challenge:
  type: auction
  coins: 100
  questions_json: [{"q":"Question 1","opts":["A","B","C","D"],"ans":1,"explanation":"Why B is correct."},{"q":"Question 2","opts":["X","Y"],"ans":0,"explanation":"Why X is correct."}]
\`\`\`

### Per-question type override

Each item in \`questions_json\` may include an optional \`type\` to override the round's outer \`challenge.type\` for that single question:

\`\`\`yaml
challenge:
  type: auction
  coins: 100
  questions_json: [
    {"q":"Which is fastest?","opts":["Edge","Regional","Origin"],"ans":0},
    {"type":"cloze","sentence":"Vercel deploys to the {{c1::edge}}.","answers":["edge"]},
    {"type":"input","q":"Define ISR in one phrase.","keywords":["incremental","static","regeneration"]}
  ]
\`\`\`

Supported overrides: \`quiz\`, \`truefalse\`, \`cloze\`, \`input\`, \`auction\`, \`countdown\`. Items without \`type\` use the round's outer \`challenge.type\`. Inherently single-question mechanics (\`order\`, \`match\`, \`chain\`, \`timeline\`, \`image-quiz\`, \`image-occlusion\`, \`memory-palace\`, \`snapshot\`, \`iframe\`) are NOT valid as item overrides \u2014 they must be their own challenge node.

### Round UX behavior

Every multi-question round displays:
1. A **game rules banner** at the top explaining the mechanic (auto-generated, not in YAML)
2. **Progress pips** showing current question / total
3. **Stats bar** showing lives (countdown) or coins (auction)
4. A **round summary** at the end with first-try correct count and a review panel explaining missed or revealed answers

Type-specific behavior:
- \`countdown\`: each question has its own timer. Wrong = lose a life. \u2665 0 = round ends with failure.
- \`auction\`: coins persist across questions. Wrong = lose bet. \u25C8 0 = round ends with failure.
- \`snapshot\`: shows items grid for N seconds with a countdown bar and "\u{1F4D6} Study Phase" tag. After hiding, switches to "\u{1F3AF} Quiz Phase" and presents questions as standard quiz.
- \`memory-palace\`: shows knowledge map for N seconds with "\u{1F4D6} Study Phase" tag. After hiding, switches to "\u{1F3AF} Quiz Phase" and presents questions as standard quiz. The hidden map stays visible as a blurred placeholder.

### JSON object fields per question

| Field | Used by | Description |
|---|---|---|
| \`q\` | all | Question text |
| \`opts\` | quiz/countdown/auction/snapshot | Options array |
| \`ans\` | quiz/countdown/auction/snapshot | Correct answer (zero-based index) |
| \`sentence\` | cloze | Cloze sentence with \`{{c1::...}}\` |
| \`answers\` | cloze/input | Acceptable answers array |
| \`keywords\` | input | Acceptable keywords array |
| \`statement\` | truefalse | Statement text (ans = true/false) |
| \`explanation\` | all | Why the correct answer is correct; shown in round review |
| \`explain\` | all | Compact alias for \`explanation\` |

### Examples by type

**Auction round (3 questions, coins persist):**
\`\`\`yaml
challenge:
  type: auction
  coins: 100
  questions_json: [{"q":"Which hosting plan for budget + unpredictable traffic?","opts":["Consumption","App Service","Premium"],"ans":0},{"q":"Which eliminates cold start?","opts":["Consumption","App Service","Premium"],"ans":2},{"q":"Which charges even when idle?","opts":["Consumption","App Service","Premium"],"ans":1}]
\`\`\`

**Countdown round (4 questions, lives deplete):**
\`\`\`yaml
challenge:
  type: countdown
  timer: 12
  questions_json: [{"q":"[FromBody] reads from?","opts":["Query String","Request Body","Header","Cookie"],"ans":1},{"q":"201 status means?","opts":["OK","Created","No Content","Bad Request"],"ans":1},{"q":"Correct middleware order?","opts":["Auth\u2192Route\u2192Authz","Route\u2192Auth\u2192Authz","Authz\u2192Auth\u2192Route","Route\u2192Authz\u2192Auth"],"ans":1},{"q":"[ApiController] auto-returns on validation fail?","opts":["500","404","400","200"],"ans":2}]
\`\`\`

**Snapshot round (memorize once, answer 2 questions):**
\`\`\`yaml
challenge:
  type: snapshot
  snapshot_items: [UseExceptionHandler, UseHttpsRedirection, UseAuthentication, UseAuthorization]
  snapshot_labels: [Layer 1, Layer 2, Layer 3, Layer 4]
  snapshot_time: 5
  questions_json: [{"q":"Which middleware is at Layer 3?","opts":["UseRouting","UseAuthentication","UseAuthorization","UseHttpsRedirection"],"ans":1},{"q":"Which is at Layer 1?","opts":["UseHttpsRedirection","UseAuthorization","UseExceptionHandler","UseAuthentication"],"ans":2}]
\`\`\`

**Memory-palace round (memorize map, answer 3 questions):**
\`\`\`yaml
challenge:
  type: memory-palace
  palace_items: [Pipeline, Controller, Service, DbContext, Middleware]
  palace_descs: [HTTP flow, Handles requests, Business logic, ORM layer, Auth and logging]
  palace_time: 15
  questions_json: [{"q":"Which handles ORM?","opts":["Pipeline","Controller","Service","DbContext","Middleware"],"ans":3},{"q":"Which handles auth and logging?","opts":["Pipeline","Controller","Service","DbContext","Middleware"],"ans":4},{"q":"Which is decoupled via DI?","opts":["Pipeline","Controller","Service","DbContext","Middleware"],"ans":2}]
\`\`\`

### Types that do NOT use \`questions_json\`

\`order\`, \`match\`, \`chain\`, \`timeline\`, \`image-quiz\`, \`image-occlusion\` \u2014 these are already multi-step within one question. Use them as single-question challenges.

---

## Boss Design

Boss challenges should test integrated judgment through deterministic interactions inside a case.

- Good boss tasks: diagnose a failure, pick the safest tradeoff, pair symptoms with causes, order a repair sequence, operate a simulation, or recall one precise decision rule in context.
- Bad boss tasks: five cloze blanks in a row, open essay questions, or generic "what would you do and why?" prompts that the runtime cannot grade.
- A boss can use one rich mechanic (\`match\`, \`chain\`, \`auction\`, scenario \`quiz\`) or one multi-question round. Within \`questions_json\`, items may declare an individual \`type\` to override the outer \`challenge.type\` \u2014 combining e.g. \`auction\` recognition with \`cloze\` / \`input\` recall in the same round. Do NOT mix in inherently single-question mechanics (\`match\`, \`chain\`, \`order\`, \`timeline\`, \`image-quiz\`, \`image-occlusion\`, \`memory-palace\`, \`snapshot\`, \`iframe\`) \u2014 they own their whole node.
- Include boss metadata when possible:
  - \`scenario\`: the final incident, case, or operating situation
  - \`mission_goal\`: what the learner must accomplish
  - \`stakes\`: what goes wrong if they choose poorly
- Prefer \`chain\`, \`match\`, \`auction\`, \`countdown\`, \`image-quiz\`, or \`iframe\` for bosses. Use \`quiz\` only when the question is scenario-based and the distractors are plausible applied decisions.
- Include \`explanation\` / \`explain\` for every boss question.

Example deterministic boss question:

\`\`\`yaml
challenge:
  type: quiz
  question: API + DB + background job tests use transaction rollback, but data leaks between tests. What is the most likely cause?
  options: [The test did not wait long enough, App code opened a DB connection outside the test transaction, The HTTP status assertion is wrong, Testcontainers starts too slowly]
  answer: 1
  explanation: Transaction rollback only reverts writes inside the same connection and transaction scope. App requests and background jobs commonly use separate connections.
\`\`\`

---

## Format Details

### iframe

Embed a local HTML challenge through an iframe. Use this only when the learner must manipulate a dynamic system or observe time-based behavior that native YAML mechanics cannot express.

\`\`\`yaml
challenge:
  type: iframe
  html: engram-quest/html/token-bucket/sim1.html
  height: 520
\`\`\`

Required fields:
- \`type: iframe\`
- \`html\`: vault-relative path to an HTML file generated by the skill

Optional fields:
- \`height\`: initial iframe height in pixels. The runtime clamps extreme values.

HTML contract:
- The file must be self-contained: inline CSS and JavaScript only.
- Do not use external \`fetch\`, CDN scripts, remote fonts, popups, \`alert()\`, or top-level navigation.
- When solved, call \`window.parent.postMessage({ type: "engram-quest-solved", score: 0-100 }, "*")\`.
- To resize, call \`window.parent.postMessage({ type: "engram-quest-resize", height: 520 }, "*")\`.
- Listen for \`engram-quest-theme\` messages and adapt colors when \`event.data.dark\` is true.
- The HTML file should live at \`engram-quest/html/{quest-base}/{nodeId}.html\`; keep \`{nodeId}\` stable when updating unchanged challenges.

### quiz

Standard multiple-choice. User picks one option.

\`\`\`yaml
challenge:
  type: quiz
  question: Question text
  options: [A, B, C, D]
  answer: 1
  hint: Optional hint
\`\`\`

Fields:
- \`question\` (required): the question text.
- \`options\` (required): inline array of 2\u20134 choices.
- \`answer\` (required): zero-based index of the correct option.
- \`hint\` (optional): shown after first wrong attempt on easy difficulty.

Design guidance:
- Use when the question has one clear correct answer and plausible distractors.
- Avoid when all options are obviously wrong except one \u2014 that's trivia, not learning.

---

### truefalse

A single statement the user judges as true or false.

\`\`\`yaml
challenge:
  type: truefalse
  statement: One statement
  answer: true
  hint: Optional hint
\`\`\`

Fields:
- \`statement\` (required): a declarative sentence.
- \`answer\` (required): \`true\` or \`false\`.
- \`hint\` (optional).

Design guidance:
- The statement must be unambiguous. Avoid "sometimes true" situations.
- Best for easy difficulty \u2014 it's a 50/50 guess, so pair with a good hint or explanation.

Multi-question form (\`questions_json\`):
- Each item uses \`q\` for the statement text (same convention as other types) and \`ans\` for the boolean.
- \`{"q":"Vercel runs at the edge.","ans":true}\` is correct; do not put the text in \`statement\` inside an item.

---

### order

User clicks items in the correct sequence. Each click is validated immediately \u2014 correct locks green, wrong shakes.

\`\`\`yaml
challenge:
  type: order
  question: Put these in order
  items: [Item A, Item B, Item C]
  answer: [1, 0, 2]
\`\`\`

Fields:
- \`question\` (optional): instruction text.
- \`items\` (required): inline array of items to order.
- \`answer\` (required): array of indices representing the correct sequence. \`answer[step]\` = index of the item that should be clicked at that step.

Design guidance:
- Use for processes, pipelines, or causal chains where order matters.
- 3\u20135 items is ideal. More than 6 becomes tedious.

---

### match

User pairs left-side concepts with right-side descriptions by clicking left then right.

\`\`\`yaml
challenge:
  type: match
  pairs:
    - [Concept A, Description A]
    - [Concept B, Description B]
\`\`\`

Fields:
- \`pairs\` (required): each pair is \`[left, right]\`. Right side is shuffled in the UI.

Design guidance:
- Use for concept \u2194 definition, term \u2194 example, or cause \u2194 effect pairing.
- 3\u20135 pairs is ideal.
- No separate \`answer\` field needed \u2014 pairs define the mapping.

---

### input

Free text input. User types the answer.

\`\`\`yaml
challenge:
  type: input
  question: Fill in the missing term
  keywords: [keyword1, keyword2]
  explanation: Why this term is the correct recall target
  hint: Optional hint
  link: relative/path/to/source.md
\`\`\`

Fields:
- \`question\` (required): the prompt.
- \`keywords\` (required): inline array of acceptable answers. Matching is case-insensitive; include common synonyms explicitly.
- \`explanation\` / \`explain\` (recommended): why the answer is correct.
- \`hint\` (optional).
- \`link\` (optional): source note for self-verification.

Design guidance:
- Use when the answer is a specific term the learner must recall without options.
- Include multiple acceptable forms in \`keywords\` (e.g., \`[RBAC, role-based access control]\`).

---

### cloze

Fill-in-the-blank within a sentence.

\`\`\`yaml
challenge:
  type: cloze
  question: What is the key memory target?
  sentence: Azure data is never used for {{c1::training}}
  answers: [training]
  explanation: Why this blank matters
  reveal_answer: true
  hint: Optional hint
  link: relative/path/to/source.md
\`\`\`

Fields:
- \`sentence\` (required): text with exactly ONE \`{{c1::answer}}\` blank.
- \`answers\` (required): inline array of acceptable inputs.
- \`question\` (recommended): context shown before the cloze sentence.
- \`explanation\` / \`explain\` (recommended): why the blank matters.
- \`reveal_answer\` (optional, default true): show a "reveal" button after wrong attempts.
- \`hint\`, \`link\` (optional).

CRITICAL constraints:
- **ONE blank per challenge.** Multiple \`{{c1::}}\` + \`{{c2::}}\` are NOT supported \u2014 the UI reveals all blanks simultaneously.
- The sentence must read naturally with \`____\` replacing the blank.
- Only blank high-value terms, not numbers or trivial words.
- Do not create cloze merely by deleting a word from a source sentence. Use it only when the blank strengthens a useful memory object.

---

### countdown

A quiz with a countdown timer. Tests speed and fluency.

\`\`\`yaml
challenge:
  type: countdown
  timer: 15
  question: Question text
  options: [A, B, C, D]
  answer: 1
  hint: Optional hint
\`\`\`

Fields:
- \`timer\` (optional, default 15): seconds. Use 10\u201320.
- All other fields same as \`quiz\`.

Behavior:
- Timer bar counts down visually. Color shifts green \u2192 amber \u2192 red.
- On timeout: correct answer is revealed, challenge auto-advances after 1.2s. User is never stuck.
- On correct click: timer stops, solved immediately.

Design guidance:
- Use for fluency testing \u2014 questions the learner should answer quickly if they know the material.
- Easy difficulty: generous timer (20s). Hard difficulty: tight timer (10s).
- Avoid for questions requiring deep thought \u2014 use \`quiz\` or \`auction\` instead.

---

### snapshot

Flash information for a few seconds, hide it, then quiz from memory.

\`\`\`yaml
challenge:
  type: snapshot
  snapshot_items: [UseExceptionHandler, UseHttpsRedirection, UseAuthentication, UseAuthorization]
  snapshot_labels: [Layer 1, Layer 2, Layer 3, Layer 4]
  snapshot_time: 4
  question: Which middleware is at Layer 3?
  options: [UseRouting, UseAuthentication, UseAuthorization, UseHttpsRedirection]
  answer: 1
  hint: Optional hint
\`\`\`

Fields:
- \`snapshot_items\` (required): inline array of items to display in a grid.
- \`snapshot_labels\` (optional): inline array of labels for each item. Defaults to 1, 2, 3...
- \`snapshot_time\` (optional, default 4): seconds to display. Use 3\u20136.
- \`question\`, \`options\`, \`answer\`, \`hint\`: same as \`quiz\` \u2014 shown after items are hidden.

Behavior:
- Items shown in a 2-column grid with labels for \`snapshot_time\` seconds.
- Grid is replaced with a "?" overlay, then quiz options appear.

Design guidance:
- Use for dense structured info: middleware layers, status code tables, config mappings.
- The question must test a detail that requires having memorized the grid \u2014 not something guessable.
- Keep items to 3\u20136. More than 6 is too much to memorize in a few seconds.

---

### auction

Confidence-based betting. User picks an answer and wagers coins.

\`\`\`yaml
challenge:
  type: auction
  coins: 100
  question: Question text
  options: [A, B, C, D]
  answer: 1
\`\`\`

Fields:
- \`coins\` (optional, default 100): starting coin balance.
- \`question\`, \`options\`, \`answer\`: same as \`quiz\`.
- No \`hint\` \u2014 the bet mechanic itself forces confidence calibration.

Behavior:
- User clicks an option to select it, adjusts bet with a slider (5\u201380 coins), then confirms.
- Correct: gain bet amount. Wrong: lose bet amount. Balance updates visually.
- Auto-advances after 1.2s.

Design guidance:
- Use for easily confused concepts where the learner might be "pretty sure but not certain."
- The bet mechanic trains metacognition \u2014 "how confident am I really?"
- Avoid for trivial questions (no point betting on something obvious).

---

### timeline

Place events into time slots. Tests chronological understanding.

\`\`\`yaml
challenge:
  type: timeline
  question: Place these events in the correct era
  slots: [2002, 2009, 2016, 2021]
  events: [ASP.NET 1.0, ASP.NET MVC 1.0, ASP.NET Core 1.0, .NET 6 Minimal API]
  answer: [0, 1, 2, 3]
\`\`\`

Fields:
- \`question\` (optional): instruction text.
- \`slots\` (required): inline array of time labels (left column).
- \`events\` (required): inline array of events (shuffled as clickable chips).
- \`answer\` (required): array where \`answer[i]\` = index of the event that belongs in \`slots[i]\`.

Behavior:
- User clicks an event chip to select it, then clicks a time slot to place it.
- Check button reveals correct (green) / wrong (red) with correct answers shown.
- Auto-advances after check.

Design guidance:
- Use for historical evolution, version history, technology timelines.
- 4\u20137 slots is ideal. Fewer is too easy, more is tedious.
- Events should be distinct enough to be placeable \u2014 avoid vague labels.

---

### chain

Click nodes in the correct sequence under time pressure.

\`\`\`yaml
challenge:
  type: chain
  timer: 20
  question: Click the steps in the correct order
  chain_items: [Create project, Configure services, Build controllers, Map endpoints, Run app]
  answer: [0, 1, 2, 3, 4]
\`\`\`

Fields:
- \`timer\` (optional, default 20): seconds.
- \`question\` (optional): instruction text.
- \`chain_items\` (required): inline array of nodes (displayed shuffled).
- \`answer\` (required): correct click order as indices into \`chain_items\`.

Behavior:
- Nodes displayed in random order with a timer bar.
- Correct click: node locks green with step number. Wrong click: shake + red flash.
- 3 wrong clicks or timeout = fail. Correct order is revealed in amber, then auto-advances.

Design guidance:
- Use for step-by-step processes where order is critical and speed matters.
- Difference from \`order\`: chain has time pressure and a mistake limit. Use \`order\` for relaxed sequencing, \`chain\` for pressure testing.
- 4\u20136 items is ideal.

---

### memory-palace

Memorize a knowledge map, then recall from it.

\`\`\`yaml
challenge:
  type: memory-palace
  palace_items: [Request Pipeline, Controller, Service Layer, DbContext, Middleware]
  palace_descs: [HTTP request flows through layers, Receives requests and returns results, Business logic decoupled via DI, ORM abstraction for database, Handles auth and logging]
  palace_time: 15
  question: Which component handles ORM database operations?
  answer: DbContext
\`\`\`

Fields:
- \`palace_items\` (required): inline array of node titles (3\u20135 items).
- \`palace_descs\` (optional but recommended): inline array of descriptions for each node.
- \`palace_time\` (optional, default 15): seconds to study. Use 10\u201320.
- \`question\` (required): recall question shown after hiding.
- \`answer\` (required): string matching one of \`palace_items\` (case-insensitive).

Behavior:
- Nodes displayed as a vertical list with letter icons (A, B, C...) + title + description.
- After \`palace_time\` seconds, list is hidden and replaced with a brain emoji.
- User picks the correct title from a shuffled 2-column grid.
- Wrong pick: red highlight + correct answer revealed, auto-advances.

Design guidance:
- Use for architecture overviews, component relationships, system structures.
- The question should test understanding of what a component does, not just its name.
- Descriptions are key \u2014 they give the memorization meaning. Without them it's just a name list.

---

### image-quiz

Show an image and ask a question about it. **This is the recommended type for any note that contains diagrams, architecture images, or visual content \u2014 use it aggressively.**

\`type: image-quiz\` is supported by all AI models (Claude, Gemini, Cursor, Codex). Prefer it over text-only quiz whenever the source note has images.

Three interaction modes \u2014 the renderer auto-selects based on which fields are present:

**Mode A \u2014 options (quiz style, recommended)**
\`\`\`yaml
challenge:
  type: image-quiz
  image: path/to/image.png
  question: What role does the highlighted component play in this architecture?
  options: [Load balancer, CDN, API gateway, Database proxy]
  answer: 1
  hint: Optional hint
  link: relative/path/to/source.md
\`\`\`

**Mode B \u2014 keywords (input style)**
\`\`\`yaml
challenge:
  type: image-quiz
  image: path/to/image.png
  question: What valve sits between the left atrium and left ventricle?
  keywords: [Mitral valve, mitral]
  hint: Optional hint
  link: relative/path/to/source.md
\`\`\`

**Mode C \u2014 click-region (legacy, use \`type: image\`)**
\`\`\`yaml
challenge:
  type: image
  image: path/to/image.png
  question: Click the correct region
  region_x: 0.3
  region_y: 0.2
  region_width: 0.4
  region_height: 0.3
\`\`\`

Fields:
- \`image\` (required): vault-relative or note-relative path. No OS absolute paths.
- \`question\` (required): must require understanding the image.
- \`options\` + \`answer\` \u2192 Mode A (quiz style). \`answer\` is zero-based index.
- \`keywords\` \u2192 Mode B (input style). Matching is case-insensitive.
- \`hint\`, \`link\` (optional).

Design guidance:
- **All AI models can and should generate this type** whenever the source note has images.
- The question MUST require seeing the image. If answerable without the image, use \`quiz\` instead.
- Use for architecture diagrams, biological diagrams, flowcharts, UI screenshots, data flow diagrams.
- Mode A (options) is preferred \u2014 it gives the learner clear choices and is easier to answer on mobile.
- Mode B (keywords) is better for precise term recall (e.g., anatomy labels, protocol names).
- When in doubt between \`image-quiz\` and \`quiz\`: if the note has an image that illustrates the concept, use \`image-quiz\`. Visual context strengthens memory encoding.

SVG guidance:
- \`image\` may point to a \`.svg\` file. The renderer displays it through an \`<img>\` tag, so static SVG works well.
- Create a new SVG only when the picture adds learning value beyond text: architecture structure, flow branching, color/status systems, missing-layer diagnosis, or geometric tradeoffs.
- Do not use SVG for plain text lists, formulas, simple definitions, or comparison tables. Those are usually better as \`quiz\`, \`cloze\`, \`match\`, or \`auction\`.
- Keep SVG files static and self-contained: no JavaScript, animation, external URLs, remote fonts, or external resources.
- Use a readable canvas size around 460-500px by 260-330px and built-in fonts such as \`sans-serif\` or \`monospace\`.
- Store generated SVGs near the quest/source note or under \`assets/svg-quiz/\`, then reference them with a vault-relative path.

---

### image-occlusion (Gemini only)

Mask a region of an image and ask the user to identify what's hidden.

\`\`\`yaml
challenge:
  type: image-occlusion
  image: path/to/image.png
  mode: hide_all_guess_one
  prompt: What is the responsibility of this component?
  answer: CloudFront CDN
  answers: [CloudFront CDN, CDN]
  reveal_answer: true
  region_left_pct: 20
  region_top_pct: 22
  region_width_pct: 32
  region_height_pct: 28
  hint: Optional hint
  link: relative/path/to/source.md
\`\`\`

Fields:
- \`image\` (required): same path rules as image-quiz.
- \`mode\`: \`hide_all_guess_one\` for v1.
- \`prompt\` (required): what to guess about the hidden region.
- \`answer\` (required): canonical label. \`answers\`: acceptable user inputs.
- \`reveal_answer\` (optional, default true).
- Coordinates: use \`region_left_pct\`, \`region_top_pct\`, \`region_width_pct\`, \`region_height_pct\` (0\u2013100). Legacy pixel coords (\`region_x\`, \`region_y\`, \`region_width\`, \`region_height\`) also supported.
- \`hint\`, \`link\` (optional).

CRITICAL constraints:
- **Only Gemini should generate this type** \u2014 it has native bounding-box detection.
- Claude, Cursor, and other models MUST use \`image-quiz\` instead.
- **Coordinate workflow**: run \`scripts/occlusion_measure.py <image_path>\` for OCR-detected bbox. If unavailable, Gemini may use native detection; all others fall back to \`image-quiz\`.
- The bbox must cover the meaningful target, not the whole slide.
`,"quest-map/references/domain-patterns.md":`# Domain Patterns

Use these patterns to select the right HTML mission shape. The source content decides; do not force a template.

## Software / systems

Good mission forms:
- bug hunt with highlighted code and repair choice
- incident triage with metrics and logs
- architecture routing decision
- state machine step-through
- latency/cost tradeoff dashboard

Learner actions:
- identify root cause
- choose mitigation order
- tune a threshold
- match symptom to layer
- sequence deployment or rollback steps

## Business / marketing

Good mission forms:
- A/B test decision from conversion data
- pricing or retention dashboard
- stakeholder tradeoff memo
- funnel diagnosis
- campaign budget allocation

Learner actions:
- calculate a metric first
- choose the safest next experiment
- identify confounding variables
- rank risks by business impact

## Medicine / health

Good mission forms:
- differential diagnosis
- triage from vitals and history
- treatment contraindication check
- anatomy or physiology mechanism map

Learner actions:
- eliminate diagnoses
- identify red flags
- pick next test
- explain why a tempting option is unsafe

Always avoid implying real medical advice. Frame as study simulation.

## Math / statistics

Good mission forms:
- scenario calculation before strategy selection
- graph interpretation
- probability simulation
- proof-step ordering
- error-spotting in a derivation

Learner actions:
- compute an intermediate value
- compare formulas
- select assumptions
- reason from a graph or table

## Science / engineering

Good mission forms:
- concept matching with mechanism explanations
- experiment design
- causal chain repair
- force/flow/energy diagram
- lab result interpretation

Learner actions:
- match components to functions
- predict outcome after parameter change
- diagnose failed experiment setup
- order causal steps

## Humanities / language

Good mission forms:
- evidence board
- timeline reconstruction
- rhetoric or grammar edit
- source comparison
- translation choice with nuance feedback

Learner actions:
- classify evidence
- choose the best interpretation
- identify implication
- revise text under constraints

`,"quest-map/references/engram-data-layout.md":`# EngramQuest Data Layout \u2014 where the plugin's own content lives

EngramQuest generates and stores its own study artifacts in fixed folders at the **vault root**
(the directory your commands run from). These are a first-class source of material \u2014 when the
user refers to "my course", "the deck", "the quest I made", they mean content in these folders,
**not** a raw vault note. Searching the vault notes for it (\`obsidian search\`) will find nothing
and you will wrongly conclude it does not exist.

So: before falling back to vault-note discovery, check whether the user is pointing at an
EngramQuest artifact and read it directly from the folder below.

## Vocabulary \u2192 location

| The user says\u2026 | They mean | Read from |
|---|---|---|
| "course", "\u8AB2\u7A0B", "\u6559\u6750", "lesson", "the X course" | A Lesson Academy course | \`engram-quest/lessons/{courseSlug}/\` |
| "deck", "review deck", "\u5361\u7247", "flashcards", "\u8907\u7FD2\u5361" | A Review Deck | \`engram-review/\` |
| "quest", "quest map", "\u95DC\u5361", "the quest I made" | A Quest Map | a \`*-quest.md\` note **or** \`engram-quest/state/\` for progress |
| "quadrant card", "\u56DB\u8C61\u9650\u5361" | A Quadrant Card (Pro) | \`engram-quest/quadrant/\` |

## Folder reference

\`\`\`
engram-quest/
\u251C\u2500\u2500 lessons/{courseSlug}/
\u2502   \u251C\u2500\u2500 meta.json          \u2190 course info + lessons array + completion (the index)
\u2502   \u2514\u2500\u2500 lsn-*.html         \u2190 one self-contained HTML per lesson (the actual content)
\u251C\u2500\u2500 state/{quest}.json     \u2190 Quest Map progress / scores (not source content)
\u2514\u2500\u2500 quadrant/{cardId}.html \u2190 four-quadrant cards (Pro)

engram-review/
\u251C\u2500\u2500 ai-cards/{note}.md     \u2190 AI-generated flashcards (\`question :: answer\` lines)
\u2514\u2500\u2500 hints/{note}.json      \u2190 L1/L2/L3 hints
\`\`\`

## Reading a Lesson Academy course as source material

This is the common case (a user asks to build a quest/deck/map from a course they already made):

1. **List courses** \u2014 every course is one folder:
   \`\`\`bash
   ls engram-quest/lessons/
   \`\`\`
2. **Match by title, not folder name.** The folder (\`courseSlug\`) is ASCII kebab-case
   (\`event-storming\`), but the human title the user typed ("AI \u6642\u4EE3 Event Storming") lives
   inside \`meta.json\` as \`title\` / \`topic\`. Read each candidate \`meta.json\` and match the
   user's phrase against \`title\` and \`topic\`. Do not assume the folder name contains the title.
   \`\`\`bash
   cat engram-quest/lessons/*/meta.json
   \`\`\`
3. **Read the lesson content.** \`meta.json\`'s \`lessons[]\` array lists each lesson's \`title\`
   and \`file\`. The teaching content is in those HTML files \u2014 use the **Read tool** (not \`cat\`)
   to open them. \`cat\` has Windows encoding failures on CJK content; Read handles UTF-8 natively.

   \`\`\`
   Read: engram-quest/lessons/{courseSlug}/{file}
   \`\`\`

   **Sampling strategy for large courses (5+ lessons):**
   - Read the first 3\u20134 core concept lessons in full.
   - For remaining lessons, use their titles only. Lessons whose title contains\u300C\u5BE6\u6230\u300D\u300C\u6848\u4F8B\u300D
     \u300CCase\u300D\u300CPractice\u300Drepeat earlier concepts \u2014 use the title as context, not the HTML.
   - Stop reading when you can list 6+ concrete practice opportunities. You have enough.

   A lesson with \`"file": null\` is a planned outline entry with no content yet \u2014 use its title
   only, and tell the user that lesson hasn't been generated.

Once you have the course's lesson content, treat it as your source material and proceed with
the normal generation flow. You do **not** need a matching vault note \u2014 the course content
itself is enough.

## When nothing matches

If \`ls engram-quest/lessons/\` shows no folder whose \`meta.json\` title/topic matches what the
user described, say so plainly and offer to either (a) search the vault notes instead, or
(b) point them to the Lesson Academy tab to confirm the course name. Do not silently invent
content or quest from a topic the user did not actually have.
`,"quest-map/references/html-first-contract.md":`# HTML-First Quest Package Contract

Read this before creating or updating a plugin-native Quest Map.

## File layout

New Quest Maps are plugin packages, not markdown-first maps:

\`\`\`text
engram-quest/quests/{questSlug}/
\u251C\u2500\u2500 meta.json
\u2514\u2500\u2500 nodes/
    \u251C\u2500\u2500 ch1.html
    \u251C\u2500\u2500 ch2.html
    \u2514\u2500\u2500 boss.html
\`\`\`

\`questSlug\` must be ASCII kebab-case. If the source title is not ASCII, use a compact English slug such as \`procurement-system-dev\`.

The Hub scans \`engram-quest/quests/\`, reads \`meta.json\`, and opens the quest inside the plugin viewer. Do not make the markdown note renderer the primary surface.

## meta.json shape

\`\`\`json
{
  "version": 2,
  "title": "Procurement System Dev Quest",
  "description": "Practice procurement data modeling and monthly purchasing logic through applied missions.",
  "difficulty": "medium",
  "tags": ["procurement", "system-design"],
  "createdAt": "2026-06-20",
  "layout": {
    "mode": "dynamic"
  },
  "nodes": [
    {
      "id": "briefing-domain",
      "title": "Domain Briefing",
      "type": "briefing",
      "summary": "Read the procurement scenario and identify the core entities before modeling.",
      "points": [
        { "title": "Core Entity: Purchase Order", "body": "A PO links a vendor, a set of line items, and a delivery schedule. It is the unit of record for procurement tracking." },
        { "title": "Monthly Budget Cycle", "body": "Each department submits a budget ceiling by the 25th. Procurement compares against rolling 3-month actuals before approving." },
        { "title": "Inventory vs. Direct Purchase", "body": "Inventory items are stocked and consumed over time; direct purchases go straight to cost centers without entering stock." }
      ]
    },
    {
      "id": "ch1-inventory",
      "title": "Inventory Units",
      "type": "mission",
      "html": "nodes/ch1-inventory.html",
      "height": 760
    },
    {
      "id": "boss-monthly-procurement",
      "title": "Monthly Procurement Boss",
      "type": "boss",
      "html": "nodes/boss-monthly-procurement.html",
      "height": 900
    }
  ]
}
\`\`\`

Rules:
- \`version\` is \`2\`.
- \`difficulty\` is \`easy\`, \`medium\`, or \`hard\`.
- \`type\` is \`briefing\`, \`mission\`, or \`boss\`.
- \`mission\` and \`boss\` nodes need \`html\` or \`file\`.
- **\`briefing\` nodes MUST include a \`points\` array with 3\u20135 entries. Each entry: \`{ "title": "...", "body": "..." }\`. A briefing with only \`summary\` and no \`points\` is unacceptable \u2014 the plugin renders it as a single line of text.**
- HTML paths are relative to the quest package folder unless they start with \`engram-quest/\`.
- Optional \`x\` and \`y\` fields are percentages from 0 to 100. Use them only when you intentionally design a fixed map route.
- Prefer \`layout.mode: "dynamic"\` unless you have a tested coordinate plan. Do not place nodes on top of a painted background path unless coordinates are known.

## Runtime contract

The plugin:
- renders the map and node lock state inside the Hub/plugin viewer
- loads node HTML in a sandboxed iframe using \`srcdoc\`
- sends theme information
- stores completion and score in \`engram-quest/state/\`

The HTML file:
- renders the interaction
- grades the learner deterministically
- posts completion and score
- posts resize messages after content changes

## postMessage API

HTML to plugin:

\`\`\`js
window.parent.postMessage({ type: "engram-quest-resize", height: document.body.scrollHeight + 24 }, "*");
window.parent.postMessage({ type: "engram-quest-solved", score: 86 }, "*");
\`\`\`

Plugin to HTML:

\`\`\`js
window.addEventListener("message", (event) => {
  if (event.data && event.data.type === "engram-quest-theme") {
    document.body.classList.toggle("dark", Boolean(event.data.dark));
  }
});
\`\`\`

Score rules:
- \`score\` must be a number from 0 to 100.
- Use first-try correctness, decision quality, or weighted stage scoring.
- Do not post solved before the learner has made the required decisions.

## Legacy markdown

Legacy markdown quest-map blocks remain supported for old content and explicit embed requests. They are not the default output for new quests.

When updating an existing legacy markdown quest:
- preserve node IDs
- preserve progress by not writing \`completed:\`
- prefer moving to a package only if the user asks for migration
`,"quest-map/references/html-quality.md":`# HTML Mission Quality

Use this checklist for every v2 mission and boss HTML file.

## Hard CSP rules

The HTML runs inside an Obsidian sandboxed iframe.

Allowed:
- one \`<!DOCTYPE html>\` document
- inline \`<style>\`
- inline \`<script>\` at the end of \`<body>\`
- vanilla JavaScript
- inline SVG
- data URI images when truly useful

Forbidden:
- external CSS or fonts
- CDN scripts
- remote images
- \`fetch\`
- \`eval\` or \`new Function\`
- \`alert\`, \`confirm\`, or \`prompt\`
- top-level navigation

## Required skeleton

\`\`\`html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mission Title</title>
<style>
:root {
  --bg: #f8fafc;
  --card: #ffffff;
  --text: #0f172a;
  --muted: #64748b;
  --border: #dbe3ef;
  --accent: #2563eb;
  --good: #16a34a;
  --warn: #d97706;
  --bad: #dc2626;
}
body.dark {
  --bg: #0f172a;
  --card: #172033;
  --text: #e2e8f0;
  --muted: #94a3b8;
  --border: #334155;
  --accent: #60a5fa;
}
* { box-sizing: border-box; }
body {
  margin: 0;
  padding: 22px 18px 72px;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, "Segoe UI", "Noto Sans TC", system-ui, sans-serif;
  line-height: 1.6;
}
.wrap { max-width: 900px; margin: 0 auto; }
button { font: inherit; }
@media (max-width: 600px) {
  body { padding: 16px 12px 64px; }
}
</style>
</head>
<body>
<main class="wrap">
  <!-- mission UI -->
</main>
<script>
function resize() {
  window.parent.postMessage({ type: "engram-quest-resize", height: document.body.scrollHeight + 24 }, "*");
}
function solved(score) {
  window.parent.postMessage({ type: "engram-quest-solved", score }, "*");
}
window.addEventListener("message", (event) => {
  if (event.data && event.data.type === "engram-quest-theme") {
    document.body.classList.toggle("dark", Boolean(event.data.dark));
    resize();
  }
});
resize();
<\/script>
</body>
</html>
\`\`\`

## Interaction quality

Every mission should include:
- a clear situation, not only a question
- visible state: metrics, code, diagram, patient chart, campaign table, equation, timeline, or inventory
- learner action: choose, arrange, calculate, inspect, match, debug, triage, or tune
- immediate feedback explaining why the answer is good or risky
- a **"\u5B8C\u6210\u95DC\u5361 / Finish" button** that the learner must click before \`solved()\` is called \u2014 this gives
  learners time to read per-item feedback explanations before the plugin advances the stage

**Never use \`setTimeout(solved, N)\`** for multi-item missions. No value of N is long enough
because readers go at different speeds. The only permitted exception is a single-choice
single-question mission where the feedback is one sentence and there is nothing else to read.

Avoid:
- one-screen trivia quizzes
- decorative graphics with no learning job
- options where one answer is obviously absurd
- locking the learner after one wrong click unless that is the explicit mechanic

## Nielsen Usability Checklist (required before writing any mission HTML)

### 1. Visibility of system status
- Every interactive element must show clearly different visual states for selected / completed /
  correct / wrong \u2014 color **plus** a text label, not only a CSS outline or opacity change.

### 2. Affordance \u2014 interactive elements must look interactive
- Clickable buttons: must have \`border\` + \`hover\` effect + \`cursor:pointer\`
- Input fields: must look like input fields
- Never design an area that looks like a display panel but is actually clickable
- Any interactive region that is not obviously a button must have a visible label or placeholder

### 3. Classification tasks \u2014 per-item inline buttons
- When the task is "assign N items to M buckets": give each item inline buttons for each bucket,
  one click completes the assignment, no hidden state required.
- Do **not** use drag-to-zone or two-step select-then-click: sandboxed iframes have no reliable
  drag feedback, and two-step flows depend on the learner remembering implicit selection state.

### 4. User controls reading pace
- After revealing final feedback, show a "\u5B8C\u6210\u95DC\u5361 / Finish" button \u2014 never auto-advance with
  \`setTimeout\`. The learner must click before the plugin shows the score overlay.

### 5. Error feedback quality
- Every wrong answer must show: \u2717 what the learner chose + \u2713 the correct answer + why it is correct
- Feedback text must remain visible until the learner clicks "\u5B8C\u6210\u95DC\u5361 / Finish"

## Theme and responsive rules

- Support both light and dark themes through CSS variables and the \`engram-quest-theme\` message.
- Use \`auto-fit\` grids or mobile media queries so CJK text does not collapse into one-character columns.
- Wrap wide tables in \`overflow-x:auto\`.
- Keep code blocks scrollable.
- Avoid fixed heights except for controlled panels; let the iframe resize.

## Example assets

Use \`assets/examples/mission-basic.html\` and \`assets/examples/boss-cascade.html\` as implementation references. They are intentionally small; real output should be more domain-specific.

`,"quest-map/references/obsidian-cli.md":`# Obsidian CLI Reference

## What it is

Obsidian CLI is the **official** Obsidian command line interface (v1.12+).
It uses Obsidian's built-in index \u2014 the same index the GUI uses.
Results are fast, accurate, and support full Obsidian search syntax.

**Requirement**: Obsidian app must be running. Enable via Settings \u2192 General \u2192 Enable Command line interface.

## Search syntax

\`\`\`bash
# Full-text search
obsidian search query="keyword"

# Tag search
obsidian search query="tag:#flashcards/azure" limit=20

# Path filter
obsidian search query="path:Study keyword"

# JSON output (use this for programmatic processing)
obsidian search query="keyword" format=json

# Target a specific vault
obsidian vault="Obsidian_Note" search query="keyword"
\`\`\`

## Query operators

\`\`\`
keyword                  full-text
tag:#flashcards/azure    tag search
path:Study               path filter
file:azure               filename filter
"exact phrase"           exact match
kw1 kw2                  AND (space)
kw1 OR kw2               OR
\`\`\`

## Read / write notes

\`\`\`bash
obsidian read path="folder/note.md"
obsidian read file="Note Name"
obsidian create name="New Note" content="# Hello" silent
obsidian append file="My Note" content="new line"
obsidian property:set name="status" value="done" file="My Note"
\`\`\`

## Known silent failures (v1.12)

| Wrong | Problem | Correct |
|---|---|---|
| \`tasks todo\` | scoped to active file only, returns empty | \`tasks all todo\` |
| \`tags counts\` | returns empty | \`tags all counts\` |
| \`create name="x" content="y"\` | opens GUI | add \`silent\` flag |

## Fallback when CLI is unavailable

If \`obsidian search\` fails (CLI not installed or Obsidian not running), fall back to:

\`\`\`bash
bash scripts/search_vault.sh "<topic>" 30
\`\`\`

\`search_vault.sh\` is grep-based \u2014 slower, no index, no Obsidian syntax. Use only as fallback for note discovery, or for L2 hint personal context search (its only other permitted use).
`,"quest-map/references/parser-constraints.md":'\uFEFF# Parser Constraints\r\n\r\nThe plugin still supports a lightweight line-oriented YAML parser for legacy markdown quest-map blocks. These rules apply only to legacy content. New Quest Maps should use the package contract in `html-first-contract.md`, not parser YAML.\r\n\r\nPlugin-native quest files live under:\r\n\r\n```text\r\nengram-quest/quests/{questSlug}/meta.json\r\nengram-quest/quests/{questSlug}/nodes/{nodeId}.html\r\n```\r\n\r\nThe constraints below apply to legacy `challenge:` YAML.\r\n\r\n## Arrays must be inline\r\n\r\nCorrect:\r\n```yaml\r\noptions: [A, B, C, D]\r\nanswers: [CloudFront CDN, CDN]\r\n```\r\n\r\nWrong:\r\n```yaml\r\noptions:\r\n  - A\r\n  - B\r\n```\r\n\r\n## Avoid ASCII commas inside array values\r\n\r\nThe parser splits on commas. Rephrase option text or accepted answers to avoid accidental splits.\r\n\r\n## Cloze: one blank per challenge\r\n\r\nEach cloze challenge **must** contain exactly ONE `{{c1::...}}` blank. Multi-blank (`{{c1::}}` + `{{c2::}}`) is not supported \u2014 the UI reveals all blanks together, which destroys the recall test.\r\n\r\nWrong:\r\n```yaml\r\nsentence: "{{c1::Azure}} uses {{c2::RBAC}} for access control"\r\n```\r\n\r\nCorrect:\r\n```yaml\r\nsentence: "Azure uses {{c1::RBAC}} for access control"\r\n```\r\n\r\n## Flat fields for image-occlusion bbox\r\n\r\nCanonical: percentage-based coordinates with `region_left_pct`, `region_top_pct`, `region_width_pct`, `region_height_pct` (0\u2013100, relative to image width/height). Pixel coords (`region_x`, `region_y`, `region_width`, `region_height`) are legacy-compatible.\r\n\r\nOnly Gemini should produce these coordinates (it has native bounding-box detection). Other models should use `image-quiz` instead.\r\n\r\nCorrect:\r\n```yaml\r\nregion_left_pct: 20\r\nregion_top_pct: 22\r\nregion_width_pct: 32\r\nregion_height_pct: 28\r\n```\r\n\r\nLegacy-compatible:\r\n```yaml\r\nregion_x: 295\r\nregion_y: 292\r\nregion_width: 640\r\nregion_height: 86\r\n```\r\n\r\nWrong:\r\n```yaml\r\nregion:\r\n  x: 295\r\n```\r\n',"quest-map/references/user-guide.md":`# Quest Map User Guide\r
\r
Quest Map turns a note into a staged learning journey.\r
Each chapter contains:\r
\r
- a short summary\r
- a few key points\r
- one challenge\r
\r
## How users create one\r
\r
Ask an AI tool:\r
\r
- \`Turn [note].md into a medium quest-map\`\r
- \`Make a hard quest-map from [topic]\`\r
\r
The result should be saved as \`<source-note-name>-quest.md\`.\r
\r
## Difficulty\r
\r
- \`easy\`: more guided, usually with hints\r
- \`medium\`: balanced difficulty\r
- \`hard\`: less guidance, stronger recall required\r
\r
## Opening the result\r
\r
Users can open the generated file directly or view it from the EngramQuest / Quest Map tab.\r
`,"quest-map/references/visual-challenges.md":'# Visual & Interactive Challenge Authoring\n\nThis file is legacy v1 guidance for `image-quiz`, `image-occlusion`, and `iframe` challenge types.\nFor new HTML-first quests, use `html-quality.md`, `domain-patterns.md`, and `boss-design.md` instead.\n\nRead this when SKILL.md\'s Challenge Type Selection routes you to `image-quiz`, `image-occlusion`, or `iframe`. The Type Selection table already decides **which** to use; this file is **how** to author each well.\n\n## Default expectation\n\nEvery medium or hard quest should include at least one visual or interactive challenge \u2014 `image-quiz`, `image-occlusion`, or `iframe`. The source note having no existing image or HTML is **not** a reason to skip; the spatial or temporal structure described in text is what you make visible. Skip visual only when the content is genuinely text-only (vocabulary, plain definitions, factual statements).\n\n## image-quiz vs image-occlusion: model gate\n\n- Gemini \u2192 may use `image-occlusion` or `image-quiz`.\n- Claude / Cursor / other models \u2192 **must** use `image-quiz`. Do not generate `image-occlusion` (the bbox coordinates require native vision capabilities to be accurate).\n\n## Image quality gate \u2014 when an existing image deserves a challenge\n\nWhen the source note already contains an image, run this three-step gate before turning it into a challenge. If it fails, use a text mechanic for that chapter \u2014 never force an image challenge just because an image exists.\n\n**Step 1 \u2014 Memory value**: is this a target the learner would be tested on?\n- PASS: specific architecture node names, organ labels, protocol names, algorithm names.\n- FAIL: generic labels (Input / Output / Model), arrows, connectors, decorative icons, anything obvious from context.\n\n**Step 2 \u2014 Recall**: with the image shown, does the question still demand retrieval from memory?\n- If the answer is visible in the image without prior study \u2192 FAIL.\n- PASS only if the question creates genuine retrieval demand.\n\n**Step 3 \u2014 Question framing**: test understanding, not label recognition.\n- Good: "What is the responsibility of this component?" / "Why is X used here instead of Y?" / "What is the output of this step?"\n- Bad: "What is this?" (the image carries the answer).\n\nIf no target in the image passes Step 1 AND Step 2, fall back to `cloze` or `quiz` for that chapter.\n\n## Self-authored SVG for `image-quiz`\n\nWhen the source note has no existing image and the content has spatial structure, author a static SVG yourself.\n\nUse SVG when the diagram adds information text cannot express well:\n\n| Content trait | Why SVG helps | Typical examples |\n|---|---|---|\n| Flow or architecture structure | Spatial relationships are the recall target | Fan-out/Fan-in, layered defense stacks |\n| Color or status systems | The color/status mapping is the memory target | Event Storming sticky colors, OFFLINE defense layers |\n| Gap or error diagnosis | Learner inspects the whole diagram to find what is missing or broken | `???` missing layer, warning-marked failure point |\n| Parallel vs sequential contrast | Branching is clearer visually than in prose | Chaining vs Fan-out |\n| Geometric tradeoffs | Position represents meaning | CAP triangle, risk matrix, spectrum placement |\n\nDo not draw an SVG when text would be just as clear:\n\n| Content trait | Use instead |\n|---|---|\n| Math formulas | `cloze` |\n| Plain definitions or text lists | `quiz` / `truefalse` |\n| Simple linear steps with no branching | `order` / `chain` |\n| Comparison tables | `match` / `auction` |\n| Abstract concepts with no spatial relationship | `cloze` / `quiz` |\n\nDecision test: **does the picture add information text alone does not?** If yes, draw it. If no, use a text mechanic.\n\nSVG requirements:\n\n1. Store under `assets/svg-quiz/` (or alongside the source note).\n2. Reference with a vault-relative path: `image: assets/svg-quiz/topic-diagram.svg`.\n3. Canvas ~460\u2013500px wide \xD7 260\u2013330px tall \u2014 reads well in the modal.\n4. Static SVG only \u2014 no `<script>`, animation, external URLs, remote fonts, or external resources.\n5. Use built-in fonts: `font-family="sans-serif"` or `"monospace"`.\n6. UTF-8 (including Chinese) is fine as long as labels stay readable at modal size.\n7. Optional small caption like `Q: Which defense layer is offline?` only when it helps the image stand alone.\n\nExample:\n\n```yaml\nchallenge:\n  type: image-quiz\n  image: assets/svg-quiz/chatbot-defense-audit.svg\n  question: According to this security architecture audit, why can indirect prompt injection reach the LLM?\n  options: [The CDN DDoS rule is disabled, The model guardrail is offline so injected instructions enter the LLM, The API gateway rate limit is too high, The token budget is missing]\n  answer: 1\n  explanation: The diagram marks the model guardrail layer as offline, which leaves the LLM exposed to injected instructions.\n```\n\n## Self-authored HTML for `iframe`\n\nWhen the content describes time-based or rate-controlled behavior, author the HTML yourself. The learner needs to manipulate the system to internalize it.\n\nUse `iframe` when the learner must operate something over time:\n\n| Content trait | Why `iframe` helps | Typical examples |\n|---|---|---|\n| Rate / throttle / burst behavior | Slider + live counter shows what rules allow / reject | Token bucket, leaky bucket, sliding window |\n| Timed lifecycle / TTL | Learner watches keys expire on a clock they control | Redis TTL, session timeout, cache eviction |\n| Step-driven state machine | Each click advances; missing one breaks understanding | SAGA compensation, two-phase commit, transactions |\n| Algorithm step-through | Learner watches the algorithm progress over data | Binary search, BFS/DFS, scheduling |\n| Multi-stage request flow with timing | Send a request, watch each hop\'s latency | CDN \u2192 Gateway \u2192 Lambda \u2192 DB, retry/backoff |\n\nDo not use `iframe` for content better served by text or a static SVG:\n\n| Content trait | Use instead |\n|---|---|\n| Plain definitions or factual lists | `quiz` / `truefalse` / `cloze` |\n| Static architecture diagram (no time dimension) | `image-quiz` with self-authored SVG |\n| Memorization of fixed labels | `image-quiz` or `image-occlusion` |\n\nDecision test: **must the learner manipulate the system over time to understand it?** If yes, `iframe`. If structure is static, `image-quiz` with SVG. If neither, text.\n\nHTML requirements (also see `references/challenge-formats.md` \u2192 `iframe`):\n\n1. Store at `engram-quest/html/{quest-base}/{nodeId}.html`. Keep `{nodeId}` stable across quest updates so progress survives the next regeneration.\n2. Reference from YAML: `html: engram-quest/html/{quest-base}/{nodeId}.html` plus initial `height:` in pixels.\n3. Fully self-contained \u2014 inline CSS and JS only. No external `fetch`, CDN scripts, remote fonts, `alert()` / `confirm()` / `prompt()`, popups, or top-level navigation.\n4. On completion, call `window.parent.postMessage({ type: "engram-quest-solved", score: 0\u2013100 }, "*")`.\n5. To resize, call `window.parent.postMessage({ type: "engram-quest-resize", height: <px> }, "*")`.\n6. Listen for `engram-quest-theme` messages and adapt colors when `event.data.dark` is true. Do not hardcode backgrounds that break dark mode.\n7. Initial canvas ~480\u2013520px tall; the resize message can grow it up to ~900px.\n\nExample reference:\n\n```yaml\nchallenge:\n  type: iframe\n  html: engram-quest/html/token-bucket-quest/sim-burst.html\n  height: 480\n```\n',"quest-map/references/yaml-template.md":`# Quest Package Template

Use this package template for new Quest Maps. Legacy markdown examples live in \`challenge-formats.md\`.

## Folder

\`\`\`text
engram-quest/quests/{questSlug}/
\u251C\u2500\u2500 meta.json
\u2514\u2500\u2500 nodes/
    \u251C\u2500\u2500 ch1-domain.html
    \u251C\u2500\u2500 ch2-calculation.html
    \u2514\u2500\u2500 boss-cascade.html
\`\`\`

## meta.json

\`\`\`json
{
  "version": 2,
  "title": "Procurement System Dev Quest",
  "description": "Practice procurement workflow modeling through applied missions.",
  "difficulty": "medium",
  "tags": ["procurement", "software-engineering"],
  "createdAt": "2026-06-20",
  "layout": {
    "mode": "dynamic"
  },
  "nodes": [
    {
      "id": "briefing-domain",
      "title": "Domain Briefing",
      "type": "briefing",
      "summary": "Understand purchase requests, inventory units, suppliers, and monthly procurement constraints."
    },
    {
      "id": "ch1-domain-model",
      "title": "Domain Model",
      "type": "mission",
      "html": "nodes/ch1-domain-model.html",
      "height": 760
    },
    {
      "id": "ch2-monthly-calculation",
      "title": "Monthly Calculation",
      "type": "mission",
      "html": "nodes/ch2-monthly-calculation.html",
      "height": 760
    },
    {
      "id": "boss-procurement-cascade",
      "title": "Procurement Cascade Boss",
      "type": "boss",
      "html": "nodes/boss-procurement-cascade.html",
      "height": 900
    }
  ]
}
\`\`\`

## Notes

- Keep user-facing text in the source/request language.
- Keep keys in English.
- Keep node IDs stable across updates.
- Do not include completion state in \`meta.json\`.
- Do not use decorative map background coordinates unless every node has tested \`x\` and \`y\` percentages.
`,"quest-map/scripts/list_quest_icons.sh":`#!/bin/bash
# List all available AI-generated generic gamified icons
VAULT_ROOT="$(cd "$(dirname "$0")/../../../.." && pwd)"
ICON_DIR="$VAULT_ROOT/.obsidian/plugins/engram-quest/assets/quest-map/icons"
if [ -d "$ICON_DIR" ]; then
    ls -1 "$ICON_DIR/" 2>/dev/null
else
    echo "Files not found. Use fallback emoji."
fi
`,"quest-map/scripts/occlusion_measure.py":`"""
occlusion_measure.py \u2014 OCR-based text coordinate detection for image-occlusion.

Usage:
  python occlusion_measure.py <image_path> [--annotate]

Output:
  - Image dimensions (naturalWidth x naturalHeight)
  - Detected text labels with pixel bbox + percentage coords
  - With --annotate: saves <image>_annotated.png for visual confirmation

Dependencies:
  pip install pillow pytesseract
  Plus Tesseract OCR engine:
    Windows: https://github.com/UB-Mannheim/tesseract/wiki
    macOS:   brew install tesseract
    Linux:   sudo apt install tesseract-ocr
"""

import sys
from pathlib import Path

def _check_deps():
    missing = []
    try:
        from PIL import Image
    except ImportError:
        missing.append("pillow")
    try:
        import pytesseract
    except ImportError:
        missing.append("pytesseract")
    if missing:
        print(f"ERROR: Missing packages: {', '.join(missing)}")
        print(f"  pip install {' '.join(missing)}")
        print()
        print("Also install Tesseract OCR engine:")
        print("  Windows: https://github.com/UB-Mannheim/tesseract/wiki")
        print("  macOS:   brew install tesseract")
        print("  Linux:   sudo apt install tesseract-ocr")
        sys.exit(1)

_check_deps()

from PIL import Image, ImageDraw
import pytesseract


def detect_elements(path: str, min_conf: int = 30) -> list[dict]:
    """Use pytesseract to detect text bounding boxes, grouping words per line."""
    img = Image.open(path)
    W, H = img.width, img.height
    data = pytesseract.image_to_data(img, output_type=pytesseract.Output.DICT)

    raw = []
    for i, text in enumerate(data['text']):
        text = text.strip()
        if not text or data['conf'][i] < min_conf:
            continue
        raw.append({
            'x': data['left'][i], 'y': data['top'][i],
            'w': data['width'][i], 'h': data['height'][i],
            'text': text, 'conf': data['conf'][i],
            'block': data['block_num'][i], 'line': data['line_num'][i],
        })

    # Group words on same line into single labels
    lines = {}
    for r in raw:
        key = (r['block'], r['line'])
        lines.setdefault(key, []).append(r)

    results = []
    for words in lines.values():
        x0 = min(w['x'] for w in words)
        y0 = min(w['y'] for w in words)
        x1 = max(w['x'] + w['w'] for w in words)
        y1 = max(w['y'] + w['h'] for w in words)
        label = ' '.join(w['text'] for w in words)
        avg_conf = sum(w['conf'] for w in words) // len(words)
        results.append({
            'x': x0, 'y': y0, 'w': x1 - x0, 'h': y1 - y0,
            'x_pct': round(x0 * 100 / W), 'y_pct': round(y0 * 100 / H),
            'w_pct': round((x1 - x0) * 100 / W), 'h_pct': round((y1 - y0) * 100 / H),
            'type': 'text_label', 'label': label, 'conf': avg_conf,
        })

    results.sort(key=lambda e: (e['y'], e['x']))
    return results


def annotate_image(path: str, elements: list[dict], out_path: str | None = None):
    img = Image.open(path).convert('RGBA')
    overlay = Image.new('RGBA', img.size, (0, 0, 0, 0))
    draw = ImageDraw.Draw(overlay)

    colors = [(255, 80, 80), (80, 200, 80), (80, 80, 255),
              (255, 180, 0), (180, 0, 255), (0, 200, 200)]
    for i, el in enumerate(elements):
        c = colors[i % len(colors)]
        x, y, w, h = el['x'], el['y'], el['w'], el['h']
        draw.rectangle([x, y, x + w, y + h], fill=c + (60,), outline=c + (220,), width=3)
        tag = f"[{i}] \\"{el['label']}\\" x={x} y={y}"
        draw.rectangle([x, max(0, y - 22), x + len(tag) * 7, y], fill=(0, 0, 0, 180))
        draw.text((x + 2, max(0, y - 20)), tag, fill=(255, 255, 255, 255))

    combined = Image.alpha_composite(img, overlay).convert('RGB')
    if out_path is None:
        p = Path(path)
        out_path = str(p.parent / (p.stem + '_annotated.png'))
    combined.save(out_path)
    return out_path


if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python occlusion_measure.py <image_path> [--annotate]")
        sys.exit(1)

    path = sys.argv[1]
    annotate = '--annotate' in sys.argv

    img = Image.open(path)
    W, H = img.width, img.height
    print(f"\\nImage: {path}")
    print(f"naturalWidth={W}  naturalHeight={H}\\n")

    elements = detect_elements(path)
    print(f"Detected {len(elements)} text labels:\\n")
    for i, el in enumerate(elements):
        print(f"  [{i}] \\"{el['label']}\\"  (conf={el['conf']})")
        print(f"       pixel:  x={el['x']} y={el['y']} w={el['w']} h={el['h']}")
        print(f"       pct:    left={el['x_pct']}% top={el['y_pct']}% w={el['w_pct']}% h={el['h_pct']}%")
        print()

    if annotate:
        out = annotate_image(path, elements)
        print(f"Annotated image saved: {out}")
`,"quest-map/skills.md":`---
name: engram-quest-map
description:
  Generate HTML-first Quest Maps for the EngramQuest plugin from notes, topics, Lesson Academy courses, or other EngramQuest artifacts.
  Trigger when the user asks to create, update, redesign, or explain a quest map; turn study material into practice missions; build easy/medium/hard quests; or create Boss Battle style applied challenges.
---

# Quest Map Skill

## Default Output

Generate plugin-native HTML-first Quest packages by default.

A plugin-native quest has:
- one folder under \`engram-quest/quests/{questSlug}/\`
- one \`meta.json\` file with map metadata and node pointers
- one self-contained HTML file per mission or boss node in the same quest folder
- runtime progress stored by the plugin in \`engram-quest/state/\`, never in the source note

Use legacy markdown quest-map blocks only when updating existing legacy content or when the user explicitly asks for an embeddable note block. For legacy syntax, read \`references/challenge-formats.md\`.

## Output Language Rule

Generate user-facing quest titles, node text, HTML copy, hints, feedback, and explanations in the language that best matches the source note or user prompt. Keep structural keys in English.

Skills documents and references are execution instructions and must stay in English.

## Hard Runtime Rules

- Do not modify source notes unless the user explicitly asks to embed a quest block in that note.
- Node \`id\` is the progress contract. Preserve existing IDs when updating equivalent content.
- New quest packages go under \`engram-quest/quests/{questSlug}/\`.
- New HTML files stay inside the quest package folder, commonly \`nodes/{nodeId}.html\` or \`{nodeId}.html\`.
- Quest HTML must be self-contained: inline CSS and vanilla JS only, no external scripts, fonts, images, fetches, popups, or top-level navigation.
- HTML completes by posting \`engram-quest-solved\`; it resizes by posting \`engram-quest-resize\`.
- Every mission must be deterministic enough to emit a numeric score from 0 to 100.

## Required References

Read these before generating files:

- \`references/html-first-contract.md\` for package shape, path rules, and postMessage contract.
- \`references/html-quality.md\` before writing any HTML.
- \`references/boss-design.md\` before writing a boss node.
- \`references/domain-patterns.md\` when choosing a mission format for the source domain.

Read conditionally:

- \`references/engram-data-layout.md\` when the user names an EngramQuest course, lesson, review deck, or generated artifact.
- \`references/obsidian-cli.md\` when vault search or note discovery is needed.
- \`references/parser-constraints.md\` and \`references/challenge-formats.md\` only for legacy v1 quests.

Example assets:
- \`assets/examples/mission-basic.html\` demonstrates a compact normal mission.
- \`assets/examples/boss-cascade.html\` demonstrates a small cascade boss. Use these as structural examples, not copy-paste templates.

## Generation Flow

1. Resolve the source **and** read all required references in parallel (no dependencies between them).
   Start these simultaneously:
   - \`references/html-first-contract.md\`
   - \`references/html-quality.md\`
   - \`references/boss-design.md\`
   - \`references/domain-patterns.md\`
   - Source course \`meta.json\` if applicable (read \`references/engram-data-layout.md\` first if needed)

   Other source resolution rules:
   - If the user names an EngramQuest artifact, read the plugin data folder directly before vault search.
   - If vault discovery is needed, use Obsidian CLI (\`obsidian search\`), not grep scripts.
   - Use \`scripts/list_quest_icons.sh\` only when a quest icon filename is needed.
2. Analyze the source for applied practice opportunities.
   - Extract realistic cases, operating constraints, failure modes, calculations, tradeoffs, and common confusions.
   - Do not turn the source into another flashcard deck.
3. Design the v2 quest.
   - Small source: 3-4 nodes including boss.
   - Medium source: 4-6 nodes including boss.
   - Large source: 6-8 nodes including boss.
   - Prefer fewer rich missions over many thin nodes.
   - Use \`difficulty: easy | medium | hard\`; runtime maps this to the correct background.
   - Every briefing node MUST have a \`points\` array (3\u20135 entries, each \`{ title, body }\`). Without \`points\`, the runtime shows only one line of text. See \`references/html-first-contract.md\` for the format.
4. Write the quest package.
   - Create \`engram-quest/quests/{questSlug}/meta.json\`.
   - Use node \`type: briefing | mission | boss\`.
   - Use \`html:\` or \`file:\` on mission and boss nodes.
   - Keep HTML paths relative to the package folder, such as \`nodes/{nodeId}.html\`.
5. Write every HTML mission file.
   - Match the domain and language.
   - Include clear feedback and a completion action.
   - Send score with \`window.parent.postMessage({ type: "engram-quest-solved", score }, "*")\`.
6. Update mode.
   - Read existing \`meta.json\` and existing HTML before editing.
   - Preserve node IDs and filenames for unchanged learning objectives.
   - Replace only the HTML files whose mission content materially changed.
   - Never write progress into \`meta.json\`.

## Quality Bar

The quest should feel like applied practice:
- Briefing frames context or teaches the minimum needed concept.
- Mission asks the learner to operate, diagnose, calculate, sequence, compare, or decide.
- Boss integrates multiple earlier concepts with visible consequences.
- Distractors are plausible and teach why the best answer is best.
- Visuals are inspectable and content-specific, not decorative.
`,"review-deck/references/engram-data-layout.md":`# EngramQuest Data Layout \u2014 where the plugin's own content lives

EngramQuest generates and stores its own study artifacts in fixed folders at the **vault root**
(the directory your commands run from). These are a first-class source of material \u2014 when the
user refers to "my course", "the deck", "the quest I made", they mean content in these folders,
**not** a raw vault note. Searching the vault notes for it (\`obsidian search\`) will find nothing
and you will wrongly conclude it does not exist.

So: before falling back to vault-note discovery, check whether the user is pointing at an
EngramQuest artifact and read it directly from the folder below.

## Vocabulary \u2192 location

| The user says\u2026 | They mean | Read from |
|---|---|---|
| "course", "\u8AB2\u7A0B", "\u6559\u6750", "lesson", "the X course" | A Lesson Academy course | \`engram-quest/lessons/{courseSlug}/\` |
| "deck", "review deck", "\u5361\u7247", "flashcards", "\u8907\u7FD2\u5361" | A Review Deck | \`engram-review/\` |
| "quest", "quest map", "\u95DC\u5361", "the quest I made" | A Quest Map | a \`*-quest.md\` note **or** \`engram-quest/state/\` for progress |
| "quadrant card", "\u56DB\u8C61\u9650\u5361" | A Quadrant Card (Pro) | \`engram-quest/quadrant/\` |

## Folder reference

\`\`\`
engram-quest/
\u251C\u2500\u2500 lessons/{courseSlug}/
\u2502   \u251C\u2500\u2500 meta.json          \u2190 course info + lessons array + completion (the index)
\u2502   \u2514\u2500\u2500 lsn-*.html         \u2190 one self-contained HTML per lesson (the actual content)
\u251C\u2500\u2500 state/{quest}.json     \u2190 Quest Map progress / scores (not source content)
\u2514\u2500\u2500 quadrant/{cardId}.html \u2190 four-quadrant cards (Pro)

engram-review/
\u251C\u2500\u2500 ai-cards/{note}.md     \u2190 AI-generated flashcards (\`question :: answer\` lines)
\u2514\u2500\u2500 hints/{note}.json      \u2190 L1/L2/L3 hints
\`\`\`

## Reading a Lesson Academy course as source material

This is the common case (a user asks to build a quest/deck/map from a course they already made):

1. **List courses** \u2014 every course is one folder:
   \`\`\`bash
   ls engram-quest/lessons/
   \`\`\`
2. **Match by title, not folder name.** The folder (\`courseSlug\`) is ASCII kebab-case
   (\`event-storming\`), but the human title the user typed ("AI \u6642\u4EE3 Event Storming") lives
   inside \`meta.json\` as \`title\` / \`topic\`. Read each candidate \`meta.json\` and match the
   user's phrase against \`title\` and \`topic\`. Do not assume the folder name contains the title.
   \`\`\`bash
   cat engram-quest/lessons/*/meta.json
   \`\`\`
3. **Read the lesson content.** \`meta.json\`'s \`lessons[]\` array lists each lesson's \`title\`
   and \`file\`. The teaching content is in those HTML files \u2014 read them for the concepts to
   build from:
   \`\`\`bash
   cat "engram-quest/lessons/{courseSlug}/{file}.html"
   \`\`\`
   A lesson with \`"file": null\` is a planned outline entry with no content yet \u2014 use its title
   only, and tell the user that lesson hasn't been generated.

Once you have the course's lesson content, treat it as your source material and proceed with
the normal generation flow. You do **not** need a matching vault note \u2014 the course content
itself is enough.

## When nothing matches

If \`ls engram-quest/lessons/\` shows no folder whose \`meta.json\` title/topic matches what the
user described, say so plainly and offer to either (a) search the vault notes instead, or
(b) point them to the Lesson Academy tab to confirm the course name. Do not silently invent
content or quest from a topic the user did not actually have.
`,"review-deck/references/image-cards.md":`# Image Cards \u2014 AI Pedagogy

Read this when generating cards for a source note that contains images,
or when the user explicitly requests image-based cards.

## 1. Mandatory image-reading step (do not skip)

Before designing any question for an image, USE the Read tool with the
image's vault path to bring the image into context as visual content.

Why: vision-capable models can describe spatial relationships, labels,
and arrows only after seeing the image. Without this step, questions
degenerate to "what is this diagram called?" \u2014 trivia, not learning.

What to extract from each image:

- Visible labels, headings, axis names, legend text
- Spatial layout (what is top/bottom/left/right, what arrows point where)
- Distinctive tokens the answer can quote (so the user can self-verify)
- The pedagogical purpose (does the image show a trade-off? a flow?
  a comparison? a structure?)

Examples of how this changes question quality:

WITHOUT looking at the image:
> "What does this cache architecture diagram show?"
> \u2192 user can answer "it shows cache architecture" and feel correct.

AFTER looking at the image:
> "From the diagram, the bottom row labelled 'cheap & safe' sits below
> the row labelled 'fast but risky'. What constraint does this stacking
> encode about layer-add ordering?"
> \u2192 forces actual recall about the concept the diagram teaches.

If the runtime model is not vision-capable and cannot read the image,
stop and tell the user \u2014 do NOT fabricate image content.

## 2. When to make a card from an image

- Visual structures: architecture diagrams, flowcharts, infographics,
  relation graphs.
- Default: 1 image = 1 card (avoid diluting active recall).
- Skip: decorative images, screenshots that text alone could fully
  describe, branding/UI screenshots without conceptual content.
- Multi-image exception: only when two images MUST be cross-referenced
  to answer a single question (rare).

## 3. Format choice \u2014 always use \`%%card%%\`

Use \`%%card%%\`. Do NOT use \`::\` single-line. Do NOT use \`Q:/A:\` two-blank-line.

Reasons:

- \`::\` makes the question line uncomfortably long once \`![[...]]\` is
  embedded.
- \`Q:/A:\` two-blank-line termination is fragile when answers contain
  extra blank lines around image embeds.
- \`%%card%%\` only ends on the closing \`%%card%%\` marker \u2014 safest for
  any multi-line content (verified at \`src/review/helpers.js:133-160\`).

Two valid embedding placements:

**Image on its own line under Q:** (visually clear, but front becomes
multi-line)

\`\`\`\`
%%card%%
Q: From the diagram, what trade-off is being made?
![[Study/cache-layers.png]]
A:
The diagram shows ...
%%card%%
\`\`\`\`

**Image inline on Q: line** (shorter front, easier to copy as JSON key)

\`\`\`\`
%%card%%
Q: ![[Study/cache-layers.png]] What trade-off does this diagram show?
A:
The diagram shows ...
%%card%%
\`\`\`\`

Pick one style per ai-cards file for consistency.

## 4. Embed syntax & path rules

- Prefer \`![[full/vault/path/file.png]]\` wikilink (cross-folder safe).
- Chinese characters, full-width colons, spaces \u2192 DO NOT URL-encode.
  Obsidian wikilinks accept them raw.
- Avoid \`![](relative-path.png)\` \u2014 breaks if the ai-cards file lives in
  a different folder from the image.

## 5. Question & answer design

### Question \u2014 anti-cheat patterns

PROHIBITED (user can answer by glancing at the image):

- "What is this image called?"
- "What does L1 stand for in this diagram?"
- "Name the components shown."

REQUIRED \u2014 pick one of three patterns per question:

- **Cross-reference**: "From X, Y, and Z markings on the diagram, infer
  the underlying constraint."
- **Reasoning**: "Why is the order on the right ranked above the left?
  What trade-off does this encode?"
- **Comparison**: "The diagram splits into two outcomes. From this
  diagram alone, answer (a)(b)(c)."

### Answer \u2014 must quote image elements for verifiability

REQUIRED:

- Quote a recognizable element from the image so the user can
  self-verify:
  > "The top line of the diagram says: 'cheap & safe layers first' ..."
  > "Bottom-right has the MVP-start arrow pointing to ..."

PROHIBITED:

- Pure paraphrase that does not reference any visible image token \u2014
  the user cannot tell if they really got it right.

## 6. Hints \u2014 image-card-only L2 trick & key-matching trap

### L1 / L2 / L3 design adapted for image cards

- **L1**: standard active-recall trigger. Do NOT mention any image
  element (would defeat recall).
- **L2**: image-card unique technique \u2014 L2 may anchor to an in-image
  position (e.g., "look at the bottom-right corner of the diagram, the
  line labelled 'MVP start'"). This is the one place L2 may reference
  image content directly, because the user is allowed to look at the
  image; the recall target is still the answer.
- **L3**: same as text cards \u2014 structural narrowing keyword, no spoiler.

### Hint-key matching trap (CRITICAL \u2014 silent failure if violated)

The plugin matches hints by exact string against the parsed \`front\`
field (\`src/review/helpers.js:435\`, \`hintsDict[card.front]\`). When the
front is multi-line (image on a separate line under Q:), the JSON key
MUST contain both the question line AND the \`![[...]]\` line, joined
by \`\\n\`.

Safest workflow after writing cards:

1. Open the ai-cards \`.md\` file.
2. For each card, copy the EXACT text between the \`Q:\` line content
   and the \`A:\` line (preserving newlines and the full \`![[path]]\`).
3. Paste as the JSON key \u2014 DO NOT retype, DO NOT URL-encode, DO NOT
   remove the wikilink.

Common silent failures:

- Retyping the wikilink and missing one character \u2192 hint never displays.
- Replacing \`![[path]]\` with \`![](path)\` in the JSON key only \u2192 mismatch.
- Trimming trailing whitespace differently from the parser \u2192 mismatch.
  The parser trims trailing blank lines but not trailing spaces on
  content lines.
`,"review-deck/references/obsidian-cli.md":`# Obsidian CLI Reference

## What it is

Obsidian CLI is the **official** Obsidian command line interface (v1.12+).
It uses Obsidian's built-in index \u2014 the same index the GUI uses.
Results are fast, accurate, and support full Obsidian search syntax.

**Requirement**: Obsidian app must be running. Enable via Settings \u2192 General \u2192 Enable Command line interface.

## Search syntax

\`\`\`bash
# Full-text search
obsidian search query="keyword"

# Tag search
obsidian search query="tag:#flashcards/azure" limit=20

# Path filter
obsidian search query="path:Study keyword"

# JSON output (use this for programmatic processing)
obsidian search query="keyword" format=json

# Target a specific vault
obsidian vault="Obsidian_Note" search query="keyword"
\`\`\`

## Query operators

\`\`\`
keyword                  full-text
tag:#flashcards/azure    tag search
path:Study               path filter
file:azure               filename filter
"exact phrase"           exact match
kw1 kw2                  AND (space)
kw1 OR kw2               OR
\`\`\`

## Read / write notes

\`\`\`bash
obsidian read path="folder/note.md"
obsidian read file="Note Name"
obsidian create name="New Note" content="# Hello" silent
obsidian append file="My Note" content="new line"
obsidian property:set name="status" value="done" file="My Note"
\`\`\`

## Known silent failures (v1.12)

| Wrong | Problem | Correct |
|---|---|---|
| \`tasks todo\` | scoped to active file only, returns empty | \`tasks all todo\` |
| \`tags counts\` | returns empty | \`tags all counts\` |
| \`create name="x" content="y"\` | opens GUI | add \`silent\` flag |

## Fallback when CLI is unavailable

If \`obsidian search\` fails (CLI not installed or Obsidian not running), fall back to:

\`\`\`bash
bash scripts/search_vault.sh "<topic>" 30
\`\`\`

\`search_vault.sh\` is grep-based \u2014 slower, no index, no Obsidian syntax. Use only as fallback for note discovery, or for L2 hint personal context search (its only other permitted use).
`,"review-deck/references/plugin-architecture.md":"# Plugin Architecture \u2014 Review Deck\n\nRead this file when you need to understand how the plugin scans and displays cards.\nThis is reference material; the execution steps are in `skills.md`.\n\n## Terminology\n\n- **source note**: the user's original note in the vault (read-only for AI)\n- **ai-cards file**: a card file AI creates at `engram-review/ai-cards/{note-name}.md`\n\n## How the Plugin Scans Cards\n\nThe plugin calls `scanReviewDecks()` which iterates over **every** `.md` file in the vault (including `engram-review/ai-cards/`). For each file it:\n\n1. Parses supported card formats (`question :: answer`, `Q:/A:`, `---` fenced Q/A, `%%card%%`, and Cloze) \u2192 cards\n2. Reads the file's tags (YAML frontmatter + inline `#tags`)\n3. Checks if any tag matches the user-configured `flashcardTags` prefix (default `flashcards`)\n4. If no tag matches **and** legacy `enableSRScan` is off \u2192 file is skipped entirely\n\nBoth conditions must be true for a file to appear as a deck:\n- The file contains at least one supported card\n- The file has a tag matching the configured prefix (e.g. `#flashcards/math`)\n\n## Two Card Flows\n\n| Flow | Who writes cards | Where cards live | Who must set the tag |\n|---|---|---|---|\n| User-written | User | source note | User (in the source note) |\n| AI-generated | AI | `engram-review/ai-cards/{note-name}.md` | AI (in the ai-cards file frontmatter) |\n\nIn both flows the plugin scans the **file that contains the cards**. The tag must be on that same file.\n\n### User-written cards flow\nThe user writes supported card formats directly in their source note and adds a `#flashcards/...` tag. `Q:/A:` remains the everyday multi-line format; `%%card%%` is only for long pasted answers where `---` must stay inside the answer. The plugin scans the source note.\n\nCards support embedded images using `![[image.png]]` (wiki-link) or `![](path/to/image.png)` (standard markdown). The plugin post-processes `MarkdownRenderer` output to resolve `.internal-embed` elements into actual `<img>` tags via `vault.getResourcePath()`.\n\n### AI-generated cards flow\nAI reads the source note for content, then creates a separate file under `engram-review/ai-cards/`. That ai-cards file must contain:\n- YAML frontmatter with a `tags:` field matching the configured prefix\n- supported card formats\n\nThe source note is never modified. The plugin scans the ai-cards file, not the source note.\n\nWhen the plugin detects a card from `engram-review/ai-cards/`, it builds a related-note context from the corresponding hints JSON:\n- `cards[front].source` is the preferred per-card relation signal\n- file-level `note` is the fallback primary source signal\n\nMemory Map discovery for review cards is folder-first:\n1. same folder as the related note(s)\n2. same topic / deck context\n3. canvas file-node relations\n\nThis means aggregated AI decks remain supported as long as the hints and deck naming preserve useful topic/source context.\n\n## Data Locations\n\n| Data | Path |\n|---|---|\n| Plugin settings | `.obsidian/plugins/engram-quest/data.json` |\n| Plugin config | `engram-review/config.json` |\n| Hints | `engram-review/hints/{note-name}.json` |\n| Scan record | `engram-review/scan-record.json` |\n| User-written cards | source notes (any `.md` with supported cards + matching tag) |\n| AI-generated cards | `engram-review/ai-cards/{note-name}.md` |\n| SR schedules | `engram-review/sr/{srFileName}.json` |\n\n### SR file naming\n\n`srFileName(notePath)`: replace `/` with `__`, strip `.md`.\n- `Study/Azure Notes.md` \u2192 `Study__Azure Notes.json`\n- Prevents collision between same-name notes in different folders.\n- `loadSrData` checks new format first, falls back to legacy `{noteName}.json`.\n\n## Tag Prefix Matching\n\n`matchFlashcardTagPrefix(tags, flashcardTags)`:\n- Splits `flashcardTags` setting by whitespace/comma\n- For each tag on the file, checks if it equals or starts with any prefix + `/`\n- Returns the first sub-topic segment as the deck name (e.g. `flashcards/math` \u2192 `math`)\n- Returns `null` if no match\n\n## Legacy Mode\n\nWhen `enableSRScan` is on, files with `question :: answer` but no matching tag are also included. The deck name falls back to the parent folder name. This is a migration feature, not the default.\n","review-deck/references/user-guide.md":`# Review Deck User Guide

Review Deck is the flashcard and spaced-repetition module in EngramQuest.

## Basic setup

1. Ask an AI tool to build a review deck for a topic, or create notes manually.
2. Cards use \`question :: answer\`.
3. By default, the plugin scans notes tagged like \`flashcards/topic\`.
4. If you enable legacy \`::\` scanning in plugin settings, untagged flashcard notes can also be included for migration.
5. Ask an AI tool to generate review-deck hints.

## Card formats

Core formats are supported and freely mixable in one note. \u{1F7E2} <mark><code>Q:/A:</code></mark> remains the everyday multi-line format; \u{1F7E1} <mark><code>%%card%%</code></mark> is only for occasional long pasted AI answers that may contain their own \`---\` separators.

| Format | Best for | Syntax |
|---|---|---|
| \u{1F7E2} <mark><code>Q:/A:</code> Q&A</mark> | Everyday multi-line or bullet answers | \`Q: question\` \u2192 \`A:\` (two blank lines end the card) |
| \u{1F7E1} <mark><code>%%card%%</code> long answer</mark> | Safe mode for long pasted AI answers that may include \`---\` | Wrap one card between two \`%%card%%\` lines; everything after \`A:\` stays in the answer until the closing marker |
| \u26AA <mark><code>::</code> Q&A</mark> | Short answers, one line | \`question :: answer\` |
| \u{1F9E9} <mark><code>{{c1::}}</code> Cloze</mark> | Fill-in-the-blank, Anki-compatible | \`{{c1::answer}}\` or \`{{c1::answer::hint}}\` |

Example:

\`\`\`md
%%card%%
Q: When should I use %%card%%?
A:
Use it only for long pasted AI answers that may contain their own Markdown separators.

---

This separator stays inside the answer.
%%card%%
\`\`\`

## How do I turn a long AI answer into a Review Deck card?

Use normal \u{1F7E2} <mark><code>Q:/A:</code></mark> for everyday cards. Use \u{1F7E1} <mark><code>%%card%%</code></mark> when pasting a long AI answer that may contain its own \`---\` separators, headings, tables, code blocks, or many blank lines.

\`\`\`\`md
#flashcards/ai

%%card%%
Q: How should I explain agentic testing?
A:
Agentic testing checks whether an AI system can complete a task reliably, not just whether one function returns the right value.

---

Example:
- Give the agent a realistic task
- Verify the final artifact
- Check logs, tool calls, and failure recovery

\`\`\`js
expect(result.completed).toBe(true)
\`\`\`
%%card%%
\`\`\`\`

**Why this is safe:** everything after \`A:\` stays in the answer until the closing <mark><code>%%card%%</code></mark>. The \`---\` line above is preserved as answer content, so the card will not be cut early. Existing \`---\` fenced cards still work and do not need to be rebuilt.

## Embedding images in cards

Use Obsidian's wiki-link or standard markdown image syntax inside any card format:

\`\`\`
Q: What does this diagram show?
![[diagram.png]]
A: The architecture of a microservice system.

Cell structure :: ![[cell-diagram.png]]

{{c1::Mitochondria}} is shown here: ![[cell.png]]
\`\`\`

Both \`![[image.png]]\` (wiki-link) and \`![](path/to/image.png)\` (standard markdown) are supported. Images display directly in the review card during study sessions.

**Best practice when authoring image cards yourself:**

- Use \`%%card%%\` so the answer can include extra blank lines around the image without ending the card early.
- Default to 1 image per card. Avoid "what is this image called?" style questions \u2014 they can be answered by glancing. Prefer questions that force cross-reference or reasoning across multiple parts of the image.
- Have the answer quote a recognizable element from the image (e.g., a label or arrow) so you can verify whether you got it right.

## Hint levels

- \`L1\`: active recall trigger
- \`L2\`: real contextual anchor from the vault
- \`L3\`: narrowing hint

## Key capabilities

- **FSRS-5 scheduling:** The latest spaced-repetition algorithm \u2014 intervals adapt to your actual recall performance, not fixed multipliers.
- **Notes stay clean:** Cards and scheduling data live in \`engram-review/\`. Your original markdown is never modified \u2014 no scheduling comments injected into your notes.
- **Multi-line Q&A:** Both question and answer support multiple lines, embedded images, and code blocks. A single blank line within a card is fine; two blank lines end the card.
- **Long pasted answers:** \`%%card%%\` keeps \`---\`, blank lines, tables, and code blocks inside the answer. It is an extra safe mode, not a replacement for normal \`Q:/A:\`.
- **Image support:** Embed \`![[image.png]]\` or \`![](path)\` in any card format \u2014 images render directly in the review session.
- **Card formats:** \`::\` one-liner, \`Q:/A:\` multi-line, \`%%card%%\` long answer, and \`{{c1::}}\` Cloze \u2014 freely mixable in one note.
- **Triple-level Hints:** L1 (active recall), L2 (contextual anchor from your vault), L3 (narrowing hint). L2 links new knowledge to things you already know.
- **AI Skills integration:** Generate cards, hints, and quest challenges from your notes using Claude Code, Gemini CLI, or Cursor.
- **Source note link:** Every card connects back to its origin note \u2014 tap to read context, then resume where you left off.
- **Anki compatible:** AI-generated cards use \`question :: answer\` format with \`TARGET DECK\` frontmatter \u2014 natively compatible with Obsidian_to_Anki.

## Example prompts

- \`Build a review deck for azure\`
- \`Update review-deck hints for database notes\`
- \`Generate flashcards from [note].md\`
- \`Fix the card "What is X" in my azure note\`
- \`Delete the card "What is X" from my azure note\`

## Storage

- AI-generated cards: \`engram-review/ai-cards/\`
- Hints: \`engram-review/hints/\`
- Review progress (SR schedules): \`engram-review/sr/\`

## Editing and deleting cards

You can ask an AI tool to fix or remove cards at any time:

- To fix a card: "Fix the card [front text] in [note name]"
- To delete a card: "Delete the card [front text] from [note name]"
- To remove all hints for a note: "Remove hints for [note name]"

The AI will update the source note, the hints JSON in \`engram-review/hints/\`, and (if the card's front text changed) the SR schedule key in \`engram-review/sr/\`.
`,"review-deck/scripts/get_mtime.sh":`#!/bin/bash
# get_mtime.sh \u2014 Get file mtime in milliseconds (cross-platform)
# Usage: bash scripts/get_mtime.sh "path/to/note.md"

set -eu

FILE="\${1:?Error: file path required. Usage: bash get_mtime.sh \\"path/to/note.md\\"}"

if [[ ! -f "$FILE" ]]; then
    echo "Error: file not found: $FILE" >&2
    exit 1
fi

if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || "$OS" == "Windows_NT" ]]; then
    # PowerShell single-quoted string: ' must be doubled (' \u2192 '')
    ESCAPED="\${FILE//\\'/\\'\\'}"
    powershell -NoProfile -Command "[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; ([DateTimeOffset](Get-Item '$ESCAPED' -ErrorAction Stop).LastWriteTimeUtc).ToUnixTimeMilliseconds()"
elif [[ "$OSTYPE" == "darwin"* ]]; then
    SECS=$(stat -f %m "$FILE")
    echo $((SECS * 1000))
else
    SECS=$(stat -c %Y "$FILE")
    echo $((SECS * 1000))
fi
`,"review-deck/scripts/search_vault.sh":`#!/bin/bash
# search_vault.sh \u2014 Search vault markdown files for personal context
# Usage: bash scripts/search_vault.sh "keyword1 keyword2" [limit]
# Multiple keywords are matched with OR logic.
#
# Windows note: always invoke as \`bash scripts/search_vault.sh ...\`
# Running the .sh file directly on Windows will open it in an editor instead of executing it.

# Self-re-exec guard: if not running under bash, re-invoke with bash
if [ -z "$BASH_VERSION" ]; then
  exec bash "$0" "$@"
fi

QUERY="\${1:?Error: query required. Usage: bash search_vault.sh \\"keyword\\" [limit]}"
LIMIT="\${2:-20}"

# Convert space-separated keywords to escaped grep OR pattern
PATTERN=$(echo "$QUERY" | tr ' ' '\\n' | sed 's/[.[\\*^$()+?{}|\\\\]/\\\\&/g' | paste -sd '|')

# Find vault root (4 levels up from scripts/)
VAULT_ROOT="$(cd "$(dirname "$0")/../../../.." && pwd)"

grep -ri --include="*.md" \\
  --exclude-dir=".obsidian" \\
  --exclude-dir=".git" \\
  --exclude-dir="node_modules" \\
  --exclude-dir="engram-review" \\
  -E "$PATTERN" \\
  -B 2 -A 2 \\
  "$VAULT_ROOT" \\
  | head -n $((LIMIT * 6))
`,"review-deck/skills.md":`---
name: engram-review-deck
description: 
  Manage review-deck data for the EngramQuest plugin.
  Trigger when the user asks to create, update, explain, edit, or delete a review deck,
  flashcard hints, or review-deck setup, or asks how review-deck works.
  Use this skill whenever the user wants to build or manage flashcard decks in EngramQuest \u2014 even if they do not use the exact term "review-deck".
---

# Review Deck Skill

## Reference Routing

| Goal | Action |
|---|---|
| User asks how review-deck works | Read \`references/user-guide.md\` and answer |
| All other tasks | Follow the flows below |

## Terminology

- **source note**: the user's original note in the vault \u2014 content source, read-only for AI
- **ai-cards file**: a card file AI creates at \`engram-review/ai-cards/{note-name}.md\`

For full plugin architecture details (scanning logic, tag matching, data paths), read \`references/plugin-architecture.md\`.

## Output Language Rule

CRITICAL: Generate user-facing output in the language that best matches the user's prompt or the source note.

- If the prompt explicitly asks for a language, follow the prompt.
- Otherwise, match the source note language.
- Keep JSON keys and structural fields in English.

This rule applies to:
\`l1\`, \`l2\`, \`l3\`, generated card text, explanations, and any inline deck titles or labels.

## How the Plugin Works

The plugin scans every \`.md\` file in the vault (including \`engram-review/ai-cards/\`).
A file becomes a deck when it has **both**: \`question :: answer\` lines **and** a tag matching the configured prefix.
For full scanning logic, see \`references/plugin-architecture.md\`.

## Data Locations

| Data | Path |
|---|---|
| Plugin settings | \`.obsidian/plugins/engram-quest/data.json\` |
| Hints | \`engram-review/hints/{note-name}.json\` |
| Scan record | \`engram-review/scan-record.json\` |
| AI-generated cards | \`engram-review/ai-cards/{note-name}.md\` |
| SR schedules | \`engram-review/sr/{note-name}.json\` |

## Output Scope

CRITICAL: When completing any Setup Flow or Single Note Flow, strictly limit your output to:
- Generating AI card files at \`engram-review/ai-cards/{note-name}.md\`
- Generating hint JSON files at \`engram-review/hints/{note-name}.json\`

PROHIBITED \u2014 do not do any of the following:
- Modifying source notes to insert card content
- Adding flashcard tags to source notes without user confirmation
- Writing <!--SR:!...--> comments anywhere
- Creating navigation pages, dashboard files, or index files

Code block examples in this document are syntax references only. Do not treat them as deliverables to create.

Task is complete when AI card files and JSON hints are generated. Stop there.

## Trigger Mapping

| User asks | Action |
|---|---|
| Build a review deck for a topic | Setup Flow |
| Build image-based cards from a note's diagrams | Image Card Flow |
| "Generate image questions from X" / "make cards from these diagrams" | Image Card Flow |
| Update hints for a topic | Update Flow |
| Add cards to a topic | Edit note content + update hints |
| Make a review-deck from a note | Single Note Flow |
| Fix / correct a card | Edit Flow |
| Change a card's question or answer | Edit Flow |
| Delete a card | Delete Flow |
| Remove hints for a note | Delete Flow |

## Scan Record

The file \`engram-review/scan-record.json\` tracks which source notes have already been read by AI to avoid duplicate card creation.

Schema:
\`\`\`json
{
  "lastScan": "2026-04-10T10:00:00.000Z",
  "notes": {
    "Folder/Note.md": {
      "processedAt": "2026-04-10T10:00:00.000Z",
      "mtime": 1712750400000,
      "cards": 12
    }
  }
}
\`\`\`

- \`lastScan\`: ISO 8601 timestamp of last completed AI processing session
- \`notes[path].mtime\`: file mtime in milliseconds at time of processing
- \`notes[path].cards\`: number of cards found in that note at time of processing

To get a file's current mtime in milliseconds: \`bash scripts/get_mtime.sh "path/to/note.md"\`

## Setup Flow

CRITICAL: Follow these steps in order. Do not skip any step.

0. Load scan record: read \`engram-review/scan-record.json\` if it exists. If missing, treat as \`{ "lastScan": null, "notes": {} }\`.
1. Read \`.obsidian/plugins/engram-quest/data.json\` using:
   \`obsidian read path=".obsidian/plugins/engram-quest/data.json"\`
   Extract the \`flashcardTags\` field. This is the user's configured tag prefix (e.g., \`mycard\`, \`flashcards\`, or multiple space-separated values). If the file does not exist or the field is empty, default to \`flashcards\`. Use this value \u2014 not a hardcoded string \u2014 for all tag operations in this session.
2. Ensure \`engram-review/config.json\` exists.
3. Check for a pre-existing knowledge index or graph in the vault (e.g. \`graphify-out/GRAPH_REPORT.md\`, \`graph.json\`). If found, use its key concepts and community structure to prioritize which notes to process first and to identify high-value card candidates. This supplements \u2014 not replaces \u2014 the tag-based note discovery below.
4. Determine scope and choose the appropriate discovery path:
   IMPORTANT: When vault search is needed, use Obsidian CLI (\`obsidian search\`). For full syntax, query operators, and fallback rules, see \`references/obsidian-cli.md\`.
   - If the user points at EngramQuest's own content \u2014 "make cards from my X course / \u8AB2\u7A0B / lesson" \u2014 the source is in the plugin's data folders, not a vault note. Read it directly from \`engram-quest/lessons/\` (see \`references/engram-data-layout.md\`) and treat the lesson content as the source. A vault search for a course title finds nothing.
   - If the user specifies a single note \u2192 read it directly. Skip discovery entirely.
   - Otherwise:
     a. Check for a graph index (\`graphify-out/GRAPH_REPORT.md\` or \`graph.json\`). If found, read it \u2014 use its note paths and key concepts directly. No further search needed.
     b. If no graph index \u2192 \`obsidian search query="path:<topic> OR tag:#<topic>" format=json\`
     c. If \`obsidian search\` fails (CLI not available) \u2192 \`bash scripts/search_vault.sh "<topic>" 30\`
        WARNING (Windows): always invoke as \`bash scripts/search_vault.sh ...\`. Running the \`.sh\` file directly on Windows opens it in an editor instead of executing it. If \`bash\` is not in PATH, use the full path to Git Bash or WSL bash (e.g. \`"C:\\Program Files\\Git\\bin\\bash.exe" scripts/search_vault.sh ...\`).
5. Note discovery does NOT require source notes to have flashcard tags. AI can read any source note as content input. The flashcard tag is only required on the **file the plugin will scan** (see Terminology). For AI-generated cards, AI sets the tag on the ai-cards output file.
6. Only use untagged \`question :: answer\` notes when the user explicitly wants legacy flashcard migration.
7. For each note found: run \`bash scripts/get_mtime.sh "<note-path>"\` to get current mtime. If the note is already in scan-record AND mtime matches, skip it \u2014 it has not changed since last processing. Only read and process notes that are new or have a changed mtime.
8. Read each non-skipped note and identify:
   a. Key concepts/topics covered in the note
   b. Existing user-written \`question :: answer\` cards (read-only \u2014 do NOT modify the source note)
   c. Whether AI needs to generate new cards (note has no cards)
   d. Whether the note contains embedded images (\`![[...]]\` or \`![](...)\` referring to png/jpg/jpeg/gif/bmp/svg/webp/avif). If it does AND the user has not opted out of image testing, follow \`references/image-cards.md\` (mandatory image-reading + \`%%card%%\` format) for those cards. This applies in addition to \u2014 not instead of \u2014 the rest of step 8.

   - If the note already has user-written cards \u2192 only read them, proceed to generate hints
   - If the note has no cards \u2192 AI generates cards, saves to \`engram-review/ai-cards/{note-name}.md\`
     - frontmatter must include a tag matching the prefix from step 1 (e.g. \`flashcards/topic\`)
     - frontmatter must also include \`TARGET DECK: {topic}\` where \`{topic}\` is the sub-path after the prefix (e.g. tag \`flashcards/backend/architecture\` \u2192 \`TARGET DECK: backend/architecture\`). This enables Obsidian_to_Anki compatibility.
     - do NOT insert cards into the source note
     - **Card format rules:**
       - Default format: \`question :: answer\` (single line, plain text).
       - Use normal \`Q:/A:\` for everyday multi-line cards. Existing \`---\` fenced Q/A is supported by the plugin and is not deprecated.
       - Use \`%%card%%\` only for long pasted or AI-generated answers that may contain their own Markdown separators such as \`---\`. In this mode, everything after \`A:\` is answer content until the closing \`%%card%%\`.
       - If the question OR answer contains code snippets, use \`%%card%%\` and wrap code in fenced code blocks with the appropriate language tag. Example:
         \`\`\`
         %%card%%
         Q: Which approach is truly parallel?
         A:
         Approach B is parallel because both tasks start simultaneously:
         \`\`\`csharp
         var sqlTask = RunSql(...);
         var vectorTask = context.CallActivity(...);
         var sqlRows = await sqlTask;
         var vectorRows = await vectorTask;
         \`\`\`
         %%card%%
         \`\`\`
       - Never inline code directly into a \`question :: answer\` single-line card \u2014 it will not render correctly.

9. Before generating cards, collect all L2 search keywords from every card to be generated across all non-skipped notes.
   Combine into a single call:
   \`bash scripts/search_vault.sh "<kw1 kw2 kw3 ...>" 50\`
   One call covers all cards. Split into 2\u20133 batches only if keywords exceed 15.
   Match results back to each card's concept before writing L2.
10. Generate \`engram-review/hints/{note-name}.json\`.
    If the file already exists (re-run), overwrite it entirely with fresh hints based on current cards.
    Do NOT merge with old hints \u2014 the card set may have changed.
    CRITICAL: \`cards\` MUST be an object (dict/map), NOT an array.
    Keys are the exact \`front\` text of each card (must match \`question :: answer\` verbatim).
    Required format:
    \`\`\`json
    {
      "note": "path/to/source.md",
      "generated": "YYYY-MM-DD",
      "cards": {
        "Exact front text of card 1": { "l1": "...", "l2": "...", "l3": "..." },
        "Exact front text of card 2": { "l1": "...", "l2": "", "l3": "..." }
      }
    }
    \`\`\`
    **Card-level \`source\` field (use when a card comes from a different note than the file-level \`note\`):**
    \`\`\`json
    {
      "note": "Study/Azure.md",
      "generated": "YYYY-MM-DD",
      "cards": {
        "What is VNet?":   { "l1": "...", "l2": "...", "l3": "...", "source": "Study/Azure.md" },
        "What is subnet?": { "l1": "...", "l2": "...", "l3": "...", "source": ["Study/Azure.md", "Study/Network.md"] }
      }
    }
    \`\`\`
    - \`source\`: card-level related-note metadata for Review Deck and Memory Map discovery. String or array of vault-relative paths.
    - If card is derived from a note, set \`source\` to that note's path. If card synthesizes multiple notes, prefer an array so the plugin can search the shared topic/folder context.
    - If card is AI-creative (no specific source), omit \`source\` or set \`null\` to keep the Memory Map button disabled.
    - CRITICAL: \`"note"\` (file-level) MUST be the full vault-relative path of the **primary source note** (e.g. \`"Study/Azure Notes.md"\`), NOT the ai-cards file path. It is a fallback relation signal, not the ai-cards path.
    - CRITICAL for aggregated decks: keep the ai-cards filename, flashcard tag, and primary \`note\` semantically tied to the topic. The plugin now tries same-folder first, then topic/deck context, then canvas file-node relations.
    WRONG \u2014 do NOT use array format:
    \`\`\`json
    { "cards": [{ "front": "...", "l1": "..." }] }
    \`\`\`
11. CRITICAL: Before finishing, verify tag coverage based on card flow:
    - **User-written cards** (cards live in the source note): verify the **source note** has at least one tag matching the prefix from step 1. If missing, report to user and ask before adding \u2014 do NOT add silently.
    - **AI-generated cards** (cards live in \`engram-review/ai-cards/\`): verify the **ai-cards file** has the tag in its frontmatter. This should already be set at creation time (step 8). No user confirmation needed. Do NOT check or modify source notes for tags in this flow.
12. Update \`engram-review/scan-record.json\`: set \`lastScan\` to current ISO timestamp; for each note that was read and processed, set \`processedAt\`, \`mtime\`, and \`cards\`; preserve all existing entries for skipped notes; write the file back.
13. Report: how many notes were skipped (already up-to-date), how many were processed, how many cards total, how many L2 hints were left empty, and how many notes had the tag prefix added.

## Update Flow

1. Read \`.obsidian/plugins/engram-quest/data.json\` to get the \`flashcardTags\` prefix (same as Setup Flow step 1).
2. Load scan record: read \`engram-review/scan-record.json\` if it exists.
3. Find relevant notes again.
4. For each note: run \`bash scripts/get_mtime.sh "<note-path>"\` and compare to scan-record. Skip notes whose mtime has not changed since last processing.
5. Read existing hints JSON if present for non-skipped notes.
6. Add hints only for new cards in non-skipped notes.
7. Preserve existing hints when possible.
8. Update \`engram-review/scan-record.json\` with processed notes (same as Setup Flow step 11).

## Edit Flow

Use when the user wants to fix or change an existing card's front text, back text, or hints.

1. Identify the target card. If the user doesn't specify, ask which note/deck the card is in.
2. Determine card type:
   - **User-written card**: the \`question :: answer\` line lives in the source note.
   - **AI-generated card**: the \`question :: answer\` line lives in \`engram-review/ai-cards/{note-name}.md\`.
   Check \`engram-review/ai-cards/\` first; if the card is found there, it is AI-generated.
3. Find and update the \`question :: answer\` line in the correct file (ai-cards file or source note):
   - If editing front text: replace the text before \`::\`.
   - If editing back text: replace the text after \`::\`.
   - SR scheduling data is stored in \`engram-review/sr/\` \u2014 do NOT look for or modify SR comments in the markdown file.
4. Read \`engram-review/hints/{note-name}.json\`.
   - If the front text changed: rename the key in \`cards\` to match the new front text exactly.
   - Update \`l1\`, \`l2\`, \`l3\` values if the user requested hint changes, or if the front change makes existing hints inaccurate.
5. Write both the updated card file and the updated hints JSON.
6. If the front text changed: read \`engram-review/sr/{note-name}.json\`, rename the key matching the old front text to the new front text, and write the file back. Skip if the file does not exist or the key is not present.
   CRITICAL: card front text may contain \`"\`, \`\\\`, newlines, or other special characters. Always use a proper JSON library to parse and serialize \u2014 never construct the JSON string manually. Python: \`json.loads\` / \`json.dumps(data, ensure_ascii=False, indent=2)\`. Node.js: \`JSON.parse\` / \`JSON.stringify(data, null, 2)\`.
7. Update \`engram-review/scan-record.json\`: update \`mtime\` for the affected note.
8. Report: which card was changed, what changed (front / back / hints), and confirm all files were updated.

CRITICAL: The key in \`engram-review/hints/{note-name}.json\` must always exactly match the front text in the card file. If they diverge, the plugin cannot load the hint.

## Delete Flow

Use when the user wants to remove one or more cards entirely, or remove hints for a note.

### Delete a card

1. Identify the target card(s) to delete.
2. Determine card type (same as Edit Flow step 2): check \`engram-review/ai-cards/\` first.
3. Remove the \`question :: answer\` line from the correct file:
   - **AI-generated card**: remove the line from \`engram-review/ai-cards/{note-name}.md\`. If the file becomes empty (no cards left), delete the entire ai-cards file.
   - **User-written card**: remove the line from the source note. There are no SR comments in markdown files \u2014 do NOT look for them.
4. Write the updated file.
5. Read \`engram-review/hints/{note-name}.json\`. Remove the key matching the deleted card's front text. If no keys remain in \`cards\`, delete the entire hint file.
   CRITICAL: Always use a proper JSON library to parse and serialize \u2014 never construct JSON strings manually. Python: \`json.loads\` / \`json.dumps(data, ensure_ascii=False, indent=2)\`. Node.js: \`JSON.parse\` / \`JSON.stringify(data, null, 2)\`.
6. Read \`engram-review/sr/{note-name}.json\`. Remove the key matching the deleted card's front text. If the file exists and the key is present, write it back without that key.
   CRITICAL: Same serialization rule \u2014 use a JSON library, not string manipulation.
7. Update \`engram-review/scan-record.json\`: update \`mtime\` and \`cards\` count for the affected note. If the note now has 0 cards, remove its entry from \`notes\`.
8. Report: which card(s) were deleted, whether the hint file was updated or removed.

### Remove all hints for a note

1. Delete \`engram-review/hints/{note-name}.json\`.
2. Remove the note's entry from \`engram-review/scan-record.json\`.
3. Report: hint file removed, scan-record updated.

CRITICAL: Never delete the source note itself unless the user explicitly asks to delete the note file.

## Single Note Flow

### Mode A: source note
\`\`\`\`markdown
\`\`\`review-deck
source: path/to/note.md
style: ocean
title: Review Deck
\`\`\`
\`\`\`\`

### Mode B: inline cards
\`\`\`\`markdown
\`\`\`review-deck
style: ocean
title: Review Deck
cards:
  - front: Question
    back: Answer
    emoji: brain
    hint_l1: Recall trigger
    hint_l2: Context anchor
    hint_l3: Narrowing hint
\`\`\`
\`\`\`\`

### Mode C: tag-based
\`\`\`\`markdown
\`\`\`review-deck
tag: flashcards/azure
style: ocean
title: Azure Review
\`\`\`
\`\`\`\`

## Image Card Flow

Use when the user explicitly asks for image-based cards from a specific note or set of notes. This is a streamlined flow \u2014 it skips full vault discovery and scan-record gating because the user has already pointed at the source. For full image-card pedagogy (question patterns, hint design, key-matching trap) read \`references/image-cards.md\`.

CRITICAL: Image cards are useless if AI does not actually look at the image. Skipping the image-reading step produces "what is this diagram called?" trivia, not learnable cards.

1. Read \`.obsidian/plugins/engram-quest/data.json\` to get the \`flashcardTags\` prefix (same as Setup Flow step 1).
2. Read the source note(s) the user pointed at.
3. Extract every \`![[...]]\` and \`![](...)\` embed path. Filter to image extensions: png, jpg, jpeg, gif, bmp, svg, webp, avif.
4. For each image: use the Read tool with the image's vault path. This brings the image into context as visual content (vision-capable models only). Note down:
   - Visible labels, axes, headings, legend text
   - Spatial relationships (top/bottom, left/right, arrows, groupings)
   - Distinctive visual tokens that an answer can quote for verification
5. Decide which images become cards (default 1 image = 1 card; skip purely decorative images). Confirm count with the user if it differs from what they asked for.
6. Run a single batched \`bash scripts/search_vault.sh "<keywords>" 50\` covering all cards' L2 anchors (same batching rule as Setup Flow step 9).
7. Generate the ai-cards file at \`engram-review/ai-cards/{note-name}.md\` using \`%%card%%\` format. Frontmatter must include a tag matching the prefix from step 1 and \`TARGET DECK: {topic}\` (same rules as Setup Flow step 8). Follow \`references/image-cards.md\` for question design, answer requirements, and embed placement.
8. Generate the hints JSON at \`engram-review/hints/{note-name}.json\`. CRITICAL: copy the parsed \`front\` text verbatim from the ai-cards file as the JSON key (see hint-key trap in \`references/image-cards.md\`).
9. Update \`engram-review/scan-record.json\` for the affected note.
10. Report: how many images were processed, how many became cards, how many were skipped as decorative.

When the user did NOT explicitly ask for image cards but the source note contains images, follow Setup Flow instead and apply step 8.d's image-handling within it.

## L1 / L2 / L3 Design

### L1
CRITICAL: Force active recall through **strategic inquiry**. Do not write simple "what is" questions.

Preferred patterns:
- Consequences of getting it wrong: "If you use X instead of Y, what breaks?"
- Application scenarios: "When would you reach for this in practice?"
- Trade-off framing: "Why choose this over the alternative?"

PROHIBITED in L1:
- Questions that simply rephrase the answer
- Questions that give away the answer

### L2
CRITICAL: L2 must come from a vault search. Use the batched search from Step 9 \u2014 do NOT run a separate \`search_vault.sh\` call per card. Match each card's keyword against the already-collected batch results before writing L2.

- L2 must come from actual vault results: first-person notes, diary entries, timestamps, expressed confusions, or past learning scenes.
- Use personalized language: "On 2025/3/3 you noted...", "You once struggled with...", "You compared this to..."
- If search returns no personal context, set \`l2\` to empty string.

PROHIBITED in L2:
- Generic textbook descriptions
- Content invented without vault search results
- Inference expansion: if vault uses a vague term (e.g. "a plugin", "a website", "a project"), keep it vague \u2014 do not replace it with a specific name or inferred identity. Only use words that literally appear in the vault search results.

### L3
- Give 1 or 2 structural narrowing keywords that point to the knowledge domain, not the answer.

PROHIBITED in L3:
- The answer itself or its spelling
- Synonyms or direct definitions of the answer (e.g., if answer is "Orange", do not write "Fruit color" or "Red-yellow color")
- Any keyword that makes the answer immediately obvious

## Card Quality Rules

### Card Front Requirements

**Self-contained**: the question must be understandable without reading the source note.
- Must include the framework/concept name and enough context.
- PROHIBITED: questions lacking a clear subject ("What are the four AI workflow stages in order?" \u2192 which workflow?)
- Good example: "In the Skill standardization workflow, how do you decide whether to upgrade to a zero-token script?"

**Test understanding, not trivia**:
- PROHIBITED: asking for a count ("How many?"), list order, or bare names.
- Correct approach: give the known number/structure in the question; test the substance in the answer.
- Bad example: "How many atomic operations does Text-to-MAM consolidate memory operations into?"
- Good example: "Text-to-MAM consolidates memory operations into 12 atomic operations across three phases \u2014 what is the responsibility of each phase?"

### Card Back Requirements

- Must be substantive content, not just a number or a name.
- A correct answer should demonstrate genuine understanding, not just recent exposure.

## Style Selection

- \`ocean\`: tech, engineering, AI
- \`forest\`: strategy, business
- \`galaxy\`: language, creative topics
`,"skillsindex.md":`# EngramQuest Skills Index

Skills are execution instructions for AI agents (Claude Code, Gemini CLI, Cursor, etc.).
Each skill is installed as an independent module with its own \`references/\` and \`scripts/\`.

| Module | Skill file | Purpose |
|---|---|---|
| quest-map | \`quest-map/skills.md\` | Generate \`quest-map\` markdown files |
| review-deck | \`review-deck/skills.md\` | Generate or update review-deck cards and hints |
| memory-map | \`memory-map/skills.md\` | Generate \`.canvas\` memory maps |
| lesson-academy | \`lesson-academy/skills.md\` | Build interactive HTML lesson courses for the Lesson Academy tab |
| quadrant | \`quadrant/skills.md\` | Upgrade a flashcard into an A4 four-quadrant super-memory card (Pro) |

---

## Core Principles

### Skills are agent instructions, not human docs
- Must be written entirely in English \u2014 unambiguous, no mixed language.
- Generated output (cards, hints, quest content, map nodes) must match the user's language.

### Skills describe goals and decision logic, not rigid steps
- Write what information is needed and how to get it.
- The AI decides which steps to run based on the user's actual request.
- A single-note request must not trigger a full topic-wide discovery flow.

### Each skill is installed independently
- \`references/\` and \`scripts/\` inside each skill folder must be self-contained.
- Do not reference files from another skill's folder.
- \`references/obsidian-cli.md\` must exist in every skill that uses vault search.

### Script paths must match the installed location
- Skills are installed to \`.claude/skills/engram-quest-<module>/\` (or equivalent for other tools).
- Scripts live at \`.claude/skills/engram-quest-<module>/scripts/<script>.sh\`.
- VAULT_ROOT from script location = 5 levels up (\`../../../../..\`).

---

## Vault Search Strategy

When a skill needs to search the vault, use this priority order \u2014 stop as soon as you have enough information:

1. **Graph index** (\`graphify-out/GRAPH_REPORT.md\` or \`graph.json\`) \u2014 read directly if it exists. Usually covers key concepts and note paths without any search.
2. **Obsidian CLI** (\`obsidian search query="..." format=json\`) \u2014 uses Obsidian's built-in index. Fast, accurate, supports tag/path/frontmatter syntax. See \`references/obsidian-cli.md\` for full syntax.
3. **grep fallback** (\`bash scripts/search_vault.sh\`) \u2014 only when Obsidian CLI is unavailable, or for L2 hint personal context search.

**\`scripts/search_vault.sh\` permitted uses:**
- L2 hint generation: find personal vault context (diary, learning notes). Batch all keywords into one call.
- Fallback for note discovery when Obsidian CLI is unavailable.

---

## Knowledge Index Awareness

Before reading raw vault files, check if \`graphify-out/GRAPH_REPORT.md\` or \`graph.json\` exists.
If found, use its key concepts, relationships, and community groupings to accelerate content generation.
Only fall back to raw note reading for details the index does not cover.

---

## Portability Rules

- Do not hard-code personal vault paths or note names.
- Do not write instructions that only work on one OS.
- Keep cross-platform helpers in \`scripts/*.sh\`.
`,"synapse/references/obsidian-cli.md":`# Obsidian CLI Reference

## What it is

Obsidian CLI is the **official** Obsidian command line interface (v1.12+).
It uses Obsidian's built-in index \u2014 the same index the GUI uses.
Results are fast, accurate, and support full Obsidian search syntax.

**Requirement**: Obsidian app must be running. Enable via Settings \u2192 General \u2192 Enable Command line interface.

## Search syntax

\`\`\`bash
# Full-text search
obsidian search query="keyword"

# Tag search
obsidian search query="tag:#flashcards/azure" limit=20

# Path filter
obsidian search query="path:Study keyword"

# JSON output (use this for programmatic processing)
obsidian search query="keyword" format=json

# Target a specific vault
obsidian vault="Obsidian_Note" search query="keyword"
\`\`\`

## Query operators

\`\`\`
keyword                  full-text
tag:#flashcards/azure    tag search
path:Study               path filter
file:azure               filename filter
"exact phrase"           exact match
kw1 kw2                  AND (space)
kw1 OR kw2               OR
\`\`\`

## Read / write notes

\`\`\`bash
obsidian read path="folder/note.md"
obsidian read file="Note Name"
obsidian create name="New Note" content="# Hello" silent
obsidian append file="My Note" content="new line"
obsidian property:set name="status" value="done" file="My Note"
\`\`\`

## Known silent failures (v1.12)

| Wrong | Problem | Correct |
|---|---|---|
| \`tasks todo\` | scoped to active file only, returns empty | \`tasks all todo\` |
| \`tags counts\` | returns empty | \`tags all counts\` |
| \`create name="x" content="y"\` | opens GUI | add \`silent\` flag |

## Fallback when CLI is unavailable

If \`obsidian search\` fails (CLI not installed or Obsidian not running), fall back to:

\`\`\`bash
bash scripts/search_vault.sh "<topic>" 30
\`\`\`

\`search_vault.sh\` is grep-based \u2014 slower, no index, no Obsidian syntax. Use only as fallback for note discovery, or for L2 hint personal context search (its only other permitted use).
`,"synapse/references/plugin-architecture.md":'# Synapse Plugin Architecture Reference\n\n## Data Locations\n\n| File | Purpose |\n|---|---|\n| `engram-review/synapse/_status.json` | Global feature flag + last-run metadata |\n| `engram-review/synapse/{srFileName}.json` | Per-note anchor recommendations |\n| `engram-review/sr/{srFileName}.json` | SR schedules (read-only input for this skill) |\n| `engram-review/ai-cards/{name}.md` | AI-generated cards (read-only input) |\n\n`srFileName(notePath)` = replace `/` with `__` and strip `.md`.\n\nExample: `engram-review/ai-cards/Foo.md` \u2192 SR file `engram-review__ai-cards__Foo.json`\n\u2192 Synapse file `engram-review__ai-cards__Foo.json` (parallel naming).\n\n## _status.json Schema\n\n```json\n{\n  "enabled": true,\n  "reason": "ok",\n  "masteredPoolSize": 23,\n  "threshold": 10,\n  "generatedAt": "2026-05-10T10:00:00Z",\n  "skillVersion": "1.0"\n}\n```\n\n| Field | Type | Notes |\n|---|---|---|\n| `enabled` | boolean | The plugin shows \u26A1 buttons only when this is `true` |\n| `reason` | string | `"ok"`, `"pool-too-small"`, `"not-generated"` |\n| `masteredPoolSize` | number | Count of cards with stability \u2265 7 at last run. The plugin compares this against current pool to decide refresh banner. |\n| `threshold` | number | The pool-size gate threshold (currently 10) |\n| `generatedAt` | ISO8601 | Plugin uses this to detect staleness (> 7 days \u2192 banner) |\n| `skillVersion` | string | Reserved for forward compatibility |\n\n## Per-Note JSON Schema\n\n```json\n{\n  "_meta": {\n    "generatedAt": "2026-05-10T10:00:00Z",\n    "skillVersion": "1.0",\n    "masteredPoolSize": 23,\n    "sourceNotePath": "engram-review/ai-cards/Event_Storming_01_Fundamentals.md"\n  },\n  "cards": {\n    "<exact card front text>": {\n      "anchors": [\n        {\n          "front": "<anchor card front text>",\n          "notePath": "engram-review/ai-cards/Event_Storming_02_Strategic_Design.md",\n          "stability": 17.108,\n          "score": 9,\n          "reason": "Both define DDD strategic boundaries"\n        }\n      ],\n      "userRejected": []\n    }\n  }\n}\n```\n\n| Field | Type | Notes |\n|---|---|---|\n| `_meta.sourceNotePath` | string | The note this synapse file is for; helpful for debugging |\n| `cards.{front}` | object | Key is the **exact card front string** (whitespace-trimmed) |\n| `anchors[].front` | string | Anchor\'s front text (used by the plugin for lookup) |\n| `anchors[].notePath` | string | Anchor\'s full vault-relative path |\n| `anchors[].stability` | number | Anchor\'s FSRS stability at the time of generation |\n| `anchors[].score` | number 7-10 | AI\'s self-rating |\n| `anchors[].reason` | string | One short sentence shown in the UI |\n| `userRejected[]` | string[] | Reserved for v2 \u2014 entries are `"<notePath>|<front>"` keys |\n\n## Plugin Runtime Behavior\n\nThe plugin reads this data via `src/review/synapse.js`:\n\n1. **`loadSynapseStatus(adapter)`** \u2014 reads `_status.json` once per session\n2. **`isSynapseEnabled(status)`** \u2014 returns `status.enabled === true`\n3. **`loadSynapseBatch(adapter, notePaths)`** \u2014 batch-reads per-note files for all\n   notes in the current session\n4. **`attachSynapseToCards(cards, batch)`** \u2014 sets `card.synapseAnchors = [...]` on\n   each card by matching `card.notePath + card.front`\n5. **Pro gate**: `plugin.settings.licenseValid` is checked inline in session.js. False \u2192\n   the \u26A1 button never renders, and `_loadSynapseAsync` returns early.\n\n## Reverse-engineering notePath from SR filename\n\n```\nSR file:   engram-review/sr/3.\u5C08\u6848__BanquetAIQA__sql server\u8CC7\u6599\u5EAB.json\nstrip:     3.\u5C08\u6848__BanquetAIQA__sql server\u8CC7\u6599\u5EAB\nreplace:   3.\u5C08\u6848/BanquetAIQA/sql server\u8CC7\u6599\u5EAB\nappend:    3.\u5C08\u6848/BanquetAIQA/sql server\u8CC7\u6599\u5EAB.md\n```\n\nEdge cases:\n- File names that legitimately contain `__` (rare but possible) cannot be perfectly\n  inverted. Synapse must use the SR file \u2194 Synapse file naming as the source of truth,\n  and look up the actual notePath from the file\'s metadata if needed.\n- Use `obsidian search` or `app.vault.getAbstractFileByPath` to verify a note exists\n  before recording its path in anchors.\n\n## Pool-Change Detection (informational)\n\nThe plugin uses `shouldShowRefreshBanner` (in `src/review/synapse.js`) with these defaults:\n\n- `POOL_DELTA_THRESHOLD = 5` \u2014 pool size diff that triggers refresh banner\n- `POOL_AGE_DAYS_THRESHOLD = 7` \u2014 age in days before staleness banner\n\nThese are runtime-only; the skill doesn\'t need them. The skill simply writes accurate\n`masteredPoolSize` and `generatedAt` so the runtime can compute drift.\n\n## Read-Only Invariant\n\nSource notes (`*.md` outside `engram-review/`) are NEVER modified by the plugin or AI.\nThe `migrateSrCommentsToJson` migration (in `src/review/decks.js`) actively moves any\ninline `<!--SR:-->` comments out of source notes into `engram-review/sr/*.json`.\n\nThis means: any data the plugin or AI generates lives exclusively under `engram-review/`.\nSynapse output follows the same rule \u2014 no `<!--SY:-->` comments anywhere.\n',"synapse/references/scoring-guide.md":`# Synapse Scoring Guide

## Why scoring matters

Synapse must work across vault types: domain-rich (well-clustered), domain-sparse (a few cards
per topic), single-domain (one focus area), and beginner (very few mastered cards). The
scoring rubric is the load-bearing rule that prevents AI from inventing weak cross-domain links
when no real anchor exists.

## The 0\u201310 scale

| Score | Meaning | Example |
|---|---|---|
| 10 | Same conceptual system, mutually defining | Aggregate \u2194 Bounded Context (both DDD strategic) |
| 9 | Same conceptual system, one defines the other | Saga Pattern \u2194 Long-running Transaction |
| 8 | Same domain, parallel concepts | Backlink Quality \u2194 Domain Authority (SEO) |
| 7 | Same domain, useful but not essential link | Azure SQL Pricing \u2194 Azure Functions Pricing |
| 4-6 | Overlapping domain, link too weak to help memory | Saga Pattern \u2194 Database Indexing |
| 0-3 | Cross-domain or coincidental similarity | DDD Aggregate \u2194 Marketing Funnel |

## Hard rules for the AI

1. **Return only \u2265 7 scores.** Lower scores are not "weak recommendations" \u2014 they are
   "do not show this".

2. **Empty array is a valid answer.** If the AI cannot find \u2265 7 anchors, return \`[]\`. The
   plugin shows a dimmed \u26A1 button for cards with no anchors. **Padding is forbidden.**

3. **Cross-domain pairs are 0-3.** Even if both cards mention "system" or "design" in their
   front text, that is a coincidence, not a memory anchor.

4. **Self-pairing is invalid.** A card cannot anchor itself. The AI should never return an
   anchor whose front equals the target front.

5. **Three is the max.** Even when 5 anchors all score \u2265 7, return only the top 3 (highest
   score wins; ties broken by AI judgment).

6. **Reason must be specific.** "Both are technical" is not a valid reason. "\u540C\u5C6C DDD
   \u6230\u7565\u8A2D\u8A08" or "Both define cache eviction policies" are valid reasons.

## Why these rules exist (the four defenses)

Synapse's "correct linking" guarantee rests on four mechanisms:

### 1. AI self-rating with cutoff (this rubric)
The 7+ cutoff is the primary filter. It runs inside the AI prompt \u2014 no post-processing
needed.

### 2. Pool size gate (in skills.md, Step 2)
If the user's mastered pool is < 10 cards, the entire feature is disabled. Prevents AI
from straining to find anchors when the pool is too small.

### 3. Reason transparency (rendered by plugin)
Each anchor displays its reason next to the card front in the UI. Users see exactly why
a link was suggested, and can spot AI hallucinations immediately.

### 4. User feedback hook (v2, schema reserved)
The \`userRejected\` array in each \`cards.{front}\` entry is reserved for a future "X this
anchor" UI. Skill should preserve this list across runs.

## Failure modes to avoid

\u274C "The user has 3 mastered cards in DDD and 2 in SEO. The 6 unmasted DDD cards have no
strong DDD anchor." \u2192 Don't fall back to SEO anchors. Return [] for those cards.

\u274C "Two cards both contain the word 'pattern'." \u2192 Coincidence. Score 0-3.

\u274C "The user might find this distantly related." \u2192 If the link is "distant", it doesn't help
memory. Score 0-3.

\u2705 "I cannot find a 7+ anchor for this card. Return []." \u2192 Correct.

## Future: User feedback loop (v2)

When the runtime adds a "\u2715 this anchor" button, rejected (notePath, front) pairs go into
\`userRejected: [...]\`. The skill must:

1. Read the existing per-note synapse file (if any)
2. Preserve \`userRejected\` arrays when overwriting
3. **Exclude rejected anchors from new recommendations** \u2014 even if they would otherwise
   score 9, the user has explicitly said no

This rule is not yet implemented in the runtime, but the schema and AI prompt already
support it. When v2 ships, only the prompt needs updating to add an "EXCLUDED ANCHORS"
section per-target.
`,"synapse/scripts/dump_sr_pool.js":`// Synapse pool + diff dumper.
//
// Reads engram-review/sr/*.json (current SR state) and engram-review/synapse/*.json
// (last run's anchor recommendations) to decide whether the upcoming Synapse run
// should be:
//   "full"        \u2014 first run, or _status.json missing, or --full forced.
//                    LLM must process every target.
//   "incremental" \u2014 _status.json exists; only new targets and targets with stale
//                    anchors need LLM work. Other targets are preserved as-is.
//   "noop"        \u2014 nothing changed since last run. Skill should bump generatedAt
//                    and exit without an LLM call.
//
// Stale-anchor detection: an anchor is stale iff its \`front\` is NOT in the current
// mastered pool (stability \u2265 7). This catches cards demoted from mastered (e.g.
// after Again rating). Anchors that point to a card no longer in sr/ at all are
// also stale.
//
// Each mastered entry now includes a \`back\` field extracted from the source note,
// so the skill can embed back text into anchor records for runtime display without
// relying on the target card being present in the same review session.
//
// Usage:  node scripts/dump_sr_pool.js [--full]
//         (cwd should be the vault root)

"use strict";

const fs = require("fs");
const path = require("path");

const SR_DIR = "engram-review/sr";
const SYNAPSE_DIR = "engram-review/synapse";
const STATUS_FILE = path.join(SYNAPSE_DIR, "_status.json");
const MASTERED_STABILITY_THRESHOLD = 7;

const forceFull = process.argv.includes("--full");

function srFileNameToNotePath(srFileName) {
  return srFileName.replace(/__/g, "/") + ".md";
}

// Parse card formats (::, %%card%%, fenced ---Q:A---, plain Q:/A:) and return
// Map<front, back>. Mirrors the subset of parseFlashcards used by the plugin runtime.
// Self-contained because the script is deployed without access to the repo's src/.
function extractBacksFromMarkdown(text) {
  const backs = new Map();
  const lines = text.replace(/\\r\\n/g, "\\n").split("\\n");
  const CARD_FENCE = /^\\s*%%\\s*card\\s*%%\\s*$/i;
  const STRIP_MD = s => s.replace(/^[*_=]+|[*_=]+$/g, "").trim();

  let i = 0;
  let inCodeFence = false;
  let codeFenceChar = "";
  let codeFenceLen = 0;

  while (i < lines.length) {
    const line = lines[i];

    // Track fenced code blocks (\`\`\` or ~~~) to avoid false :: matches inside them
    const fenceMatch = /^[ \\t]*(\`{3,}|~{3,})/.exec(line);
    if (fenceMatch) {
      const ch = fenceMatch[1][0], len = fenceMatch[1].length;
      if (!inCodeFence) { inCodeFence = true; codeFenceChar = ch; codeFenceLen = len; i++; continue; }
      if (ch === codeFenceChar && len >= codeFenceLen) { inCodeFence = false; }
      i++; continue;
    }
    if (inCodeFence) { i++; continue; }

    // %%card%% block with embedded Q:/A: (single pair, same as plugin runtime)
    if (CARD_FENCE.test(line)) {
      const blockLines = [];
      let j = i + 1;
      while (j < lines.length && !CARD_FENCE.test(lines[j])) blockLines.push(lines[j++]);
      i = j + 1;
      let qi = -1, ai = -1;
      for (let k = 0; k < blockLines.length; k++) {
        if (qi === -1 && /^\\s*Q:\\s*/i.test(blockLines[k])) { qi = k; continue; }
        if (qi !== -1 && /^\\s*A:\\s*/i.test(blockLines[k])) { ai = k; break; }
      }
      if (qi !== -1 && ai !== -1) {
        const qm = blockLines[qi].match(/^\\s*Q:\\s*(.*)/i);
        const am = blockLines[ai].match(/^\\s*A:\\s*(.*)/i);
        const frontLines = [qm ? qm[1] : "", ...blockLines.slice(qi + 1, ai)];
        const backLines  = [am ? am[1] : "", ...blockLines.slice(ai + 1)];
        while (frontLines.length && !frontLines[frontLines.length - 1].trim()) frontLines.pop();
        while (backLines.length  && !backLines[backLines.length  - 1].trim()) backLines.pop();
        const front = frontLines.join("\\n").trim();
        const back  = backLines.join("\\n").trim();
        if (front && back) backs.set(front, back);
      }
      continue;
    }

    // --- fenced Q:/A: block (multiple cards inside)
    if (/^---\\s*$/.test(line)) {
      let peek = i + 1;
      while (peek < lines.length && lines[peek].trim() === "") peek++;
      if (peek < lines.length && /^\\s*Q:\\s*/i.test(lines[peek])) {
        const fencedLines = [];
        let j = i + 1;
        while (j < lines.length && !/^---\\s*$/.test(lines[j])) fencedLines.push(lines[j++]);
        i = j + 1;
        let fi = 0;
        while (fi < fencedLines.length) {
          const qmf = fencedLines[fi].match(/^\\s*Q:\\s*(.+)/i);
          if (!qmf) { fi++; continue; }
          const frontLines = [qmf[1]];
          fi++;
          while (fi < fencedLines.length && !/^\\s*A:\\s*/i.test(fencedLines[fi])) frontLines.push(fencedLines[fi++]);
          while (frontLines.length && !frontLines[frontLines.length - 1].trim()) frontLines.pop();
          const amf = fi < fencedLines.length ? fencedLines[fi].match(/^\\s*A:\\s*(.*)/i) : null;
          if (!amf) continue;
          const backLines = [amf[1]];
          fi++;
          while (fi < fencedLines.length && !/^\\s*Q:\\s*/i.test(fencedLines[fi])) backLines.push(fencedLines[fi++]);
          while (backLines.length && !backLines[backLines.length - 1].trim()) backLines.pop();
          const front = frontLines.join("\\n").trim();
          const back = backLines.join("\\n").trim();
          if (front && back) backs.set(front, back);
        }
        continue;
      }
    }

    // Q:/A: non-fenced (stop at 2 blank lines or next Q: or ---)
    const qaMatch = line.match(/^\\s*Q:\\s*(.+)/i);
    if (qaMatch) {
      const frontLines = [qaMatch[1]];
      let j = i + 1;
      while (j < lines.length && !/^\\s*A:\\s*/i.test(lines[j])) {
        if (lines[j].trim() !== "") frontLines.push(lines[j]);
        j++;
      }
      const am = j < lines.length ? lines[j].match(/^\\s*A:\\s*(.*)/i) : null;
      if (am) {
        const backLines = [am[1]];
        j++;
        let blanks = 0;
        while (j < lines.length && !/^\\s*Q:\\s*/i.test(lines[j]) && !/^---\\s*$/.test(lines[j])) {
          if (lines[j].trim() === "") { if (++blanks >= 2) break; }
          else blanks = 0;
          backLines.push(lines[j]);
          j++;
        }
        while (backLines.length && !backLines[backLines.length - 1].trim()) backLines.pop();
        const front = frontLines.join("\\n").trim();
        const back = backLines.join("\\n").trim();
        if (front && back) backs.set(front, back);
        i = j;
        continue;
      }
    }

    // front :: back (single-line; skip inline-code and cloze lines)
    const sepIdx = line.indexOf("::");
    if (sepIdx >= 1) {
      const before = line.slice(0, sepIdx);
      if ((before.match(/\`/g) || []).length % 2 === 0 && !/\\{\\{c\\d+::/.test(before)) {
        const front = STRIP_MD(before.trim());
        const back = STRIP_MD(line.slice(sepIdx + 2).trim());
        if (front && back) backs.set(front, back);
      }
    }

    i++;
  }
  return backs;
}

function readBacksFromNote(notePath) {
  try {
    return extractBacksFromMarkdown(fs.readFileSync(notePath, "utf8"));
  } catch (e) {
    if (e.code !== "ENOENT") console.warn(\`[synapse] could not read backs from \${notePath}:\`, e.message);
    return new Map();
  }
}

function readJsonOrNull(p) {
  try { return JSON.parse(fs.readFileSync(p, "utf8")); }
  catch { return null; }
}

function listJsonFiles(dir) {
  if (!fs.existsSync(dir)) return [];
  return fs.readdirSync(dir).filter(f => f.endsWith(".json")).sort();
}

function buildPoolsFromSr() {
  if (!fs.existsSync(SR_DIR)) {
    return { error: "sr-dir-missing", path: SR_DIR };
  }
  const masteredByFront = new Map(); // front -> { id, front, notePath, stability }
  const targetsByFront = new Map();  // front -> { id, front, notePath, stability }
  const fileErrors = [];
  let mid = 1, tid = 1;

  for (const filename of listJsonFiles(SR_DIR)) {
    const fullPath = path.join(SR_DIR, filename);
    const stem = filename.replace(/\\.json$/i, "");
    const notePath = srFileNameToNotePath(stem);
    const data = readJsonOrNull(fullPath);
    if (!data) { fileErrors.push(filename); continue; }
    if (typeof data !== "object") continue;

    for (const front of Object.keys(data)) {
      const meta = data[front];
      const stability = (meta && typeof meta.stability === "number") ? meta.stability : 0;
      const entry = { front, notePath, stability };
      if (stability >= MASTERED_STABILITY_THRESHOLD) {
        masteredByFront.set(front, { id: \`m\${mid++}\`, ...entry });
      } else {
        targetsByFront.set(front, { id: \`t\${tid++}\`, ...entry });
      }
    }
  }
  return { masteredByFront, targetsByFront, fileErrors };
}

function loadExistingSynapseFiles() {
  // Map: targetFront -> { notePath, anchorFronts: Set<front> }
  const existingByFront = new Map();
  const skillFiles = [];
  if (!fs.existsSync(SYNAPSE_DIR)) return { existingByFront, skillFiles };

  for (const filename of listJsonFiles(SYNAPSE_DIR)) {
    if (filename === "_status.json") continue;
    const fullPath = path.join(SYNAPSE_DIR, filename);
    const doc = readJsonOrNull(fullPath);
    if (!doc || !doc.cards) continue;
    skillFiles.push(filename);
    const sourceNotePath = doc._meta && doc._meta.sourceNotePath;
    for (const front of Object.keys(doc.cards)) {
      const entry = doc.cards[front];
      const anchorFronts = new Set(
        ((entry && entry.anchors) || []).map(a => a && a.front).filter(Boolean)
      );
      existingByFront.set(front, {
        notePath: sourceNotePath || srFileNameToNotePath(filename.replace(/\\.json$/i, "")),
        synapseFile: filename,
        anchorFronts
      });
    }
  }
  return { existingByFront, skillFiles };
}

function decideMode({ status, masteredByFront, targetsByFront, existingByFront, forceFull }) {
  if (forceFull) return "full";
  if (!status || !status.generatedAt) return "full";
  if (status.enabled === false) return "full"; // probably first successful run after pool grew
  // Threshold: if mastered pool size grew/shrank by > 20% (and at least 10), force full.
  const last = status.masteredPoolSize || 0;
  const now = masteredByFront.size;
  if (last > 0 && Math.abs(now - last) >= Math.max(10, last * 0.2)) return "full";
  // Otherwise incremental (workQueue may still end up empty \u2192 caller emits noop)
  return "incremental";
}

function buildWorkQueue({ masteredByFront, targetsByFront, existingByFront }) {
  const newTargets = [];     // targets with no existing synapse entry
  const staleTargets = [];   // targets whose existing anchors include cards no longer mastered
  const preservedFronts = []; // targets whose anchors are all still valid

  for (const [front, target] of targetsByFront) {
    const prior = existingByFront.get(front);
    if (!prior) {
      newTargets.push({ ...target, reason: "new-target" });
      continue;
    }
    const stale = [];
    for (const anchorFront of prior.anchorFronts) {
      if (!masteredByFront.has(anchorFront)) stale.push(anchorFront);
    }
    if (stale.length > 0) {
      staleTargets.push({ ...target, reason: "stale-anchor", staleAnchors: stale });
    } else if (prior.anchorFronts.size === 0) {
      // Target was processed last time but yielded no anchors. Don't redo unless full.
      preservedFronts.push(front);
    } else {
      preservedFronts.push(front);
    }
  }
  return { newTargets, staleTargets, preservedFronts };
}

function main() {
  const result = buildPoolsFromSr();
  if (result.error) {
    process.stdout.write(JSON.stringify({ mode: "error", error: result.error, path: result.path }, null, 2));
    process.exit(2);
  }
  const { masteredByFront, targetsByFront, fileErrors } = result;

  const status = readJsonOrNull(STATUS_FILE);
  const { existingByFront, skillFiles } = loadExistingSynapseFiles();

  const mode = decideMode({ status, masteredByFront, targetsByFront, existingByFront, forceFull });

  const masteredArr = [...masteredByFront.values()];
  const targetsArr = [...targetsByFront.values()];

  // Pool gate (same threshold as runtime) \u2014 checked before the note-file reads below
  if (masteredArr.length < 10) {
    process.stdout.write(JSON.stringify({
      mode: "pool-too-small",
      stats: {
        masteredCount: masteredArr.length,
        targetCount: targetsArr.length,
        threshold: 10,
        filesScanned: listJsonFiles(SR_DIR).length,
        fileErrors: fileErrors.length
      }
    }, null, 2));
    return;
  }

  // Attach back text to mastered entries so the skill can embed it into anchor records.
  // Read each source note once (cache by notePath) and look up the back by front text.
  const masteredNotePaths = new Set(masteredArr.map(e => e.notePath).filter(Boolean));
  const noteBacksCache = new Map();
  for (const np of masteredNotePaths) noteBacksCache.set(np, readBacksFromNote(np));
  for (const entry of masteredArr) {
    const cache = noteBacksCache.get(entry.notePath);
    entry.back = (cache && cache.get(entry.front)) || "";
  }

  if (mode === "full" || forceFull) {
    process.stdout.write(JSON.stringify({
      mode: "full",
      forced: forceFull,
      previousMasteredPoolSize: status ? (status.masteredPoolSize || 0) : 0,
      stats: {
        masteredCount: masteredArr.length,
        targetCount: targetsArr.length,
        existingSynapseFiles: skillFiles.length,
        threshold: MASTERED_STABILITY_THRESHOLD,
        filesScanned: listJsonFiles(SR_DIR).length,
        fileErrors: fileErrors.length
      },
      mastered: masteredArr,
      targets: targetsArr
    }, null, 2));
    return;
  }

  // Incremental
  const { newTargets, staleTargets, preservedFronts } = buildWorkQueue({
    masteredByFront, targetsByFront, existingByFront
  });
  const workQueue = [...newTargets, ...staleTargets];

  if (workQueue.length === 0) {
    process.stdout.write(JSON.stringify({
      mode: "noop",
      reason: "no-changes-since-last-run",
      stats: {
        masteredCount: masteredArr.length,
        targetCount: targetsArr.length,
        preservedTargets: preservedFronts.length,
        threshold: MASTERED_STABILITY_THRESHOLD,
        filesScanned: listJsonFiles(SR_DIR).length,
        fileErrors: fileErrors.length
      }
    }, null, 2));
    return;
  }

  process.stdout.write(JSON.stringify({
    mode: "incremental",
    stats: {
      masteredCount: masteredArr.length,
      targetCount: targetsArr.length,
      newTargets: newTargets.length,
      staleTargets: staleTargets.length,
      preservedTargets: preservedFronts.length,
      threshold: MASTERED_STABILITY_THRESHOLD,
      filesScanned: listJsonFiles(SR_DIR).length,
      fileErrors: fileErrors.length
    },
    mastered: masteredArr,    // LLM still needs the FULL pool to pick anchors from
    workQueue,                // ONLY these go through the LLM
    preservedFronts           // skill must NOT touch synapse files for these
  }, null, 2));
}

main();
`,"synapse/scripts/dump_sr_pool.sh":`#!/usr/bin/env bash
# Wrapper so the existing installer rewrite (\`bash scripts/...\` \u2192
# \`bash {targetRoot}/scripts/...\`) handles path resolution. Delegates to the
# Node implementation in the same directory.
#
# Pass \`--full\` to force a full rebuild even when _status.json indicates
# incremental would be sufficient.
#
# Exits 3 with a structured JSON error on stderr if \`node\` is not available;
# the SKILL.md "If \`node\` is not available" section explains the fallback path.

if ! command -v node >/dev/null 2>&1; then
  printf '{"mode":"error","error":"node-not-found","message":"Install Node.js >= 18, or use the fallback path described in SKILL.md Step 1."}\\n' >&2
  exit 3
fi

exec node "$(dirname "$0")/dump_sr_pool.js" "$@"
`,"synapse/skills.md":`---
name: engram-synapse
description:
  Build memory anchor recommendations (Synapse) for EngramQuest review cards (Pro feature).
  Trigger when the user invokes /engram-quest-synapse, asks to "build synapse",
  "update synapse anchors", "refresh synapse", or wants AI-suggested cross-card connections
  to help memorize difficult flashcards. Always run after the user has accumulated enough
  mastered cards (FSRS stability \u2265 7) \u2014 the skill will gracefully no-op when the pool is too small.
---

# EngramQuest Synapse Skill (Pro)

## What This Skill Does

Synapse helps users memorize difficult cards by linking them to cards they have **already mastered**.
The plugin then shows these "anchor cards" during review, triggering elaborative encoding,
active recall, and contextual anchoring \u2014 three core memory-science principles.

This skill:
1. Scans every \`engram-review/sr/*.json\` file ONCE to extract both the mastered pool
   (stability \u2265 7) and the target list (stability < 7) from the same data
2. For each target, asks the AI to recommend up to 3 anchors from the mastered pool
3. Writes results to \`engram-review/synapse/{srFileName}.json\` with a \`_status.json\` header

After running, the user opens the EngramQuest Review Deck and sees a \u26A1 button on cards
that have anchors. Clicking it reveals the anchor recommendations before answering.

## Performance Contract \u2014 ONE-SHOT design

This skill MUST run as a single-shot pipeline, not as a per-card loop:

1. **One bash command** dumps every \`sr/*.json\` into a single stdout stream
2. **One mental step** parses the dump into mastered pool + target list (in-memory)
3. **One LLM call** receives the FULL mastered pool + FULL target list and returns
   all pairings in one JSON response. Modern context windows easily fit 500+ cards;
   batching is unnecessary and costs round-trip latency.
4. **Parallel file writes** for \`synapse/{srFileName}.json\` outputs and \`_status.json\`

Expected runtime on a 200-card vault: **~30 seconds**, dominated by the single LLM
inference. If your run takes longer than 2 minutes, you are doing it wrong \u2014 re-read
this section and switch to one-shot.

**Anti-patterns that destroyed the previous design (do not do these)**:
- Reading sr/*.json files one-at-a-time with N separate bash/read tool calls
- Scanning \`engram-review/ai-cards/*.md\` or running \`obsidian search\`
- Splitting targets into batches of 30 and making N sequential LLM calls
- Re-reading files that were already in the dump

**Cards without SR records (never reviewed) are intentionally invisible to this skill.**
They will be picked up automatically the next time Synapse runs after the user reviews
them once. Trade-off: new cards don't get anchors until first review \u2014 acceptable because
new cards already feel interesting and rarely need anchoring.

## Output Language Rule

Reasons (one-line explanation per anchor) must match the language of the source notes.
If the user's vault is in Traditional Chinese, write reasons in Traditional Chinese.
JSON keys and structural fields stay in English.

## CRITICAL Output Scope

- Write ONLY to \`engram-review/synapse/*.json\` and \`engram-review/synapse/_status.json\`
- Do NOT modify source notes, ai-cards, sr/, hints/, or any other vault file
- Do NOT write \`<!--SY:-->\` comments into markdown \u2014 those are not used by the runtime

Source notes are read-only. AI artifacts live exclusively in \`engram-review/\`.

---

## Step 1: ONE script call \u2192 mode + ready-to-feed JSON

Run the bundled dumper. It reads \`engram-review/sr/*.json\` plus any existing
\`engram-review/synapse/*.json\`, decides whether this run should be full / incremental /
no-op, and emits a single JSON document with everything needed for the next steps.

\`\`\`bash
# Default: auto-detect mode based on _status.json + pool drift
bash scripts/dump_sr_pool.sh

# Force a full rebuild (re-pair every target against the current pool)
bash scripts/dump_sr_pool.sh --full
\`\`\`

**Do NOT iterate sr/ or synapse/ with individual Read tool calls \u2014 it will be 10\xD7 slower.**

### If \`node\` is not available

The dumper requires Node.js (\u2265 18). If the script exits with \`node-not-found\` or
the shell reports \`command not found: node\`, you have two options:

1. **Recommended** \u2014 install Node.js and re-run. The script is < 1 second; nothing
   else in this skill substitutes for the diff logic it implements.
2. **Last-resort fallback** \u2014 if node truly cannot be installed in this environment,
   read every \`engram-review/sr/*.json\` with one Read call per file, in-memory filter
   \`stability >= 7\` for the mastered pool and \`< 7\` for targets, and skip incremental
   mode entirely (treat every run as \`full\`, no \`_status.json\` diffing). This is
   intentionally slow \u2014 only acceptable on environments without node.

Do NOT silently fall through. If you choose option 2, tell the user: "node not
available, running in fallback mode (full rebuild every run)."

### Output \`mode\` field \u2014 five possible values

| \`mode\` | When | What the skill should do next |
|---|---|---|
| \`full\` | First run, no \`_status.json\`, or pool drifted > 20% / \u2265 10 cards, or \`--full\` | Run the full LLM prompt over \`targets[]\` (Step 2 onward) |
| \`incremental\` | \`_status.json\` exists, pool drifted < 20%, and \`workQueue[]\` is non-empty | Run the LLM prompt only over \`workQueue[]\`. **Do NOT touch \`synapse/*.json\` files for fronts in \`preservedFronts[]\`.** |
| \`noop\` | \`_status.json\` exists, no new targets, no stale anchors | Skip the LLM. Just bump \`_status.json.generatedAt\` to now and report "nothing to update" |
| \`pool-too-small\` | Mastered pool < 10 cards | Write \`_status.json\` with \`enabled: false, reason: "pool-too-small"\` and tell the user to keep reviewing |
| \`error\` | sr/ folder missing | Tell the user no SR data exists yet |

### Output schema (\`full\` and \`incremental\`)

\`\`\`json
{
  "mode": "full" | "incremental",
  "stats": { "masteredCount": 164, "targetCount": 89, "newTargets": 5,
             "staleTargets": 1, "preservedTargets": 83, "threshold": 7,
             "filesScanned": 96, "fileErrors": 0 },
  "mastered": [
    { "id": "m1", "front": "<front>", "back": "<back>", "notePath": "engram-review/ai-cards/Foo.md", "stability": 17.108 }
  ],
  "targets":   [ /* full mode only \u2014 full target list */ ],
  "workQueue": [ /* incremental mode only \u2014 only these need LLM work */
    { "id": "t1", "front": "<front>", "notePath": "Study/Bar.md", "stability": 2.3,
      "reason": "new-target" },
    { "id": "t7", "front": "<front>", "notePath": "Study/Baz.md", "stability": 4.1,
      "reason": "stale-anchor", "staleAnchors": ["<old anchor front>"] }
  ],
  "preservedFronts": [ "<front>", "<front>", ... ]
}
\`\`\`

### Pool semantics (same in all modes)

| Set | Condition | Role |
|---|---|---|
| **Mastered pool** | \`stability \u2265 7\` | Stable memory hooks \u2014 eligible to be recommended as anchors |
| **Targets** | \`stability < 7\` (or missing) | Cards that need help \u2014 anchors are generated for these |

Mastered cards do NOT become targets. They already remember themselves; recommending
anchors for them adds runtime cost without user value.

**Do NOT scan \`engram-review/ai-cards/*.md\`. Do NOT run \`obsidian search\`.** Cards
without an sr/ entry are intentionally skipped (see "Performance Contract" above).

## Step 2: Pool Size Gate

If \`masteredPool.length < 10\` \u2192 write status as disabled and stop:

\`\`\`json
// engram-review/synapse/_status.json
{
  "enabled": false,
  "reason": "pool-too-small",
  "masteredPoolSize": 4,
  "threshold": 10,
  "generatedAt": "2026-05-10T10:00:00Z"
}
\`\`\`

Tell the user: "Synapse needs at least 10 mastered cards (FSRS stability \u2265 7).
You currently have N. Keep reviewing \u2014 the plugin will prompt you again."

## Step 3: ONE LLM call \u2014 full pool \xD7 the work queue

Send a SINGLE prompt to the AI with the entire mastered pool and **only the cards that
need pairing this run**:

- \`mode === "full"\`: send \`mastered\` + the full \`targets\` list
- \`mode === "incremental"\`: send \`mastered\` + the \`workQueue\` list (subset of targets)
- \`mode === "noop"\`: skip Step 3 entirely; jump to Step 5 to bump \`generatedAt\`

Do NOT split into batches. Modern models (Claude Sonnet 4+, Gemini 2.5+, GPT-5) handle
hundreds of cards in one prompt without issue.

\`\`\`
You are a memory aid system. The user has flashcards under spaced repetition.

MASTERED POOL (FSRS stability \u2265 7, eligible to be anchors):
[id=1] {front of mastered card 1}
[id=2] {front of mastered card 2}
[id=3] {front of mastered card 3}
... (full list \u2014 every mastered card)

TARGETS (FSRS stability < 7 or unseen, need anchors):
[t1] {front of target 1}
[t2] {front of target 2}
[t3] {front of target 3}
... (full list \u2014 every target)

For each target, pick up to 3 cards from the MASTERED POOL whose recall would
help the user remember the target. Score each candidate 0-10:

  10 = same conceptual system, mutually defining (e.g. Aggregate \u2194 Bounded Context)
  7-9 = same domain, related but not interdefined
  4-6 = domains overlap but the link is weak
  0-3 = unrelated; cross-domain noise

RULES:
- Return at most 3 anchors per target, ALL with score \u2265 7
- If no anchor scores \u2265 7 for a target, return [] for it. NEVER pad with weak links.
- Cross-domain pairings (e.g. DDD card \u2194 SEO card) are 0-3 by definition.
- \`reason\` is one short sentence (\u2264 20 words / \u2264 20 \u6F22\u5B57), explaining the link.
- Match reason language to the source notes' language.

OUTPUT \u2014 single JSON array, one entry per target, in target order:
[
  { "target": "<exact target front string>",
    "anchors": [{ "id": 1, "score": 9, "reason": "Both define DDD strategic boundaries" }] },
  { "target": "<another front>", "anchors": [] },
  ...
]

Do not output anything except the JSON array.
\`\`\`

For full prompt rationale and the four "correct linking" defenses (self-rating, pool gate,
reason transparency, future user-feedback hook), see \`references/scoring-guide.md\`.

### Why one shot, not batches

The previous design batched targets into groups of 30 and made N sequential LLM calls.
This was wrong because:
- Each call has 5\u201330s of round-trip + thinking overhead. N calls \xD7 overhead dominates.
- The mastered pool was repeated in every call (wasted tokens).
- The model can cross-analyze ALL targets against ALL anchors more accurately when it
  sees the full picture in one context.

A 200-target / 50-anchor vault produces a prompt around 10k input tokens. Trivial.

### Optional: parallel subagents (Claude Code only \u2014 skip otherwise)

If your IDE has parallel subagent dispatch (Claude Code's Agent tool), and you have
**> 80 targets**, you can convert one long call into N short parallel calls:

1. Split \`targets[]\` evenly into K = 3\u20134 groups (\u2248 25\u201330 per group).
2. Dispatch K subagents **in a single message** so they fire concurrently.
3. Each subagent receives the **full mastered pool** + its slice of targets +
   the same scoring rubric. (Splitting the pool would cause weak-link hallucinations \u2014
   subagents need every anchor candidate to pick from.)
4. Main agent merges the K JSON arrays in target order.

Wall time drops ~K\xD7 because Claude API parallelizes well. Skip this if:
- Target count < 80 (subagent overhead exceeds savings)
- IDE is Gemini CLI / Cursor / anything without first-class parallel agents
- Cost > latency (parallel dispatch = K \xD7 cost of one call)

This is an OPTIONAL optimization \u2014 the single-shot path is the canonical flow.

## Step 4: Write Per-Note Synapse Files

**In \`incremental\` mode**: read the existing \`engram-review/synapse/{srFileName}.json\`
first (if it exists), and **merge** the new \`cards\` entries with old ones \u2014 preserving
all \`cards.{front}\` entries that are NOT in the workQueue (i.e. preserved fronts) and
all \`userRejected\` arrays. Only overwrite the entries you just paired.

**In \`full\` mode**: regenerate each file from scratch, but still preserve \`userRejected\`
arrays from any pre-existing file (the v2 user-feedback hook depends on this).

For each affected notePath, write \`engram-review/synapse/{srFileName(notePath)}.json\`:

\`\`\`json
{
  "_meta": {
    "generatedAt": "2026-05-10T10:00:00Z",
    "skillVersion": "1.0",
    "masteredPoolSize": 23,
    "sourceNotePath": "engram-review/ai-cards/Event_Storming_01_Fundamentals.md"
  },
  "cards": {
    "<target card front>": {
      "anchors": [
        {
          "front": "Aggregate \u5283\u5206\u61C9\u8A72\u57FA\u65BC\u4EC0\u9EBC\u9700\u6C42\uFF1F",
          "back": "<back text copied from mastered pool entry \u2014 do NOT pass to LLM; copy from mastered[].back after LLM returns>",
          "notePath": "engram-review/ai-cards/Event_Storming_01_Fundamentals.md",
          "stability": 17.108,
          "score": 9,
          "reason": "\u540C\u5C6C DDD \u6230\u7565\u8A2D\u8A08"
        }
      ],
      "userRejected": []
    }
  }
}
\`\`\`

For full schema reference, see \`references/plugin-architecture.md\`.

**Skip targets with empty anchors** \u2014 do not write empty \`cards.{front}\` entries unless
preserving previous \`userRejected\` lists. (See incremental update below.)

**Resolve anchor \`notePath\`**: look up the mastered card's notePath from the pool entry.
If a mastered card's source SR file maps to a real file in the vault, use that path.

## Step 5: Write Status File

Always write this, including in \`noop\` mode (just bumps \`generatedAt\`):

\`\`\`json
// engram-review/synapse/_status.json
{
  "enabled": true,
  "masteredPoolSize": 164,
  "generatedAt": "2026-05-10T10:00:00Z",
  "skillVersion": "1.0",
  "lastMode": "incremental"
}
\`\`\`

The plugin reads this on load. \`enabled: true\` activates the \u26A1 feature globally.

## Step 6: Final Report

Report tailored to \`mode\`:

\`\`\`
[full mode]
\u2705 Synapse rebuilt
  Mastered pool: 164 cards (stability \u2265 7)
  Targets paired: 89 (76 found anchors, 13 left empty \u2014 no strong link)
  Files written: 41 in engram-review/synapse/

[incremental mode]
\u2705 Synapse updated (incremental)
  New targets paired: 5 (4 found anchors, 1 empty)
  Stale targets re-paired: 1
  Preserved (no change): 83
  Total runtime: 12s

[noop mode]
\u2139\uFE0F  Synapse already up to date \u2014 nothing to do.
  Mastered pool: 164 (unchanged)
  Last update: 2026-05-09 23:00 (less than a day ago)
\`\`\`

---

## Mode flow summary (the contract this skill implements)

\`\`\`
[Step 1] bash scripts/dump_sr_pool.sh
   \u2514\u2192 mode in JSON output

mode === "pool-too-small"  \u2192 Step 5 only (write disabled status), tell user
mode === "noop"            \u2192 Step 5 only (bump generatedAt), Step 6 \u2139
mode === "incremental"     \u2192 Steps 3-4 over workQueue + preserve preservedFronts \u2192 Step 5, 6
mode === "full"            \u2192 Steps 3-4 over targets \u2192 Step 5, 6
mode === "error"           \u2192 Tell user, exit
\`\`\`

The skill is invoked by:
- User saying \`/engram-quest-synapse\` (auto mode-detect)
- User saying \`/engram-quest-synapse --full\` (force full)
- Another skill chaining into Synapse after bulk SR updates (incremental)

## Stale logic detail

When \`engram-review/synapse/_status.json\` already exists:

1. Compare \`currentMastered\` vs \`_status.json.masteredPoolSize\`
2. If \`abs(diff) \u2265 max(5, last \xD7 0.10)\` \u2192 **full rebuild** (pool structure changed enough that
   anchors based on the old pool may not be valid). Run Steps 1-6 from scratch.
3. Otherwise \u2192 **partial rebuild**:
   - Re-scan only notes whose mtime > \`_status.generatedAt\`
   - Plus any note whose existing synapse file references anchors that are no longer
     in the current mastered pool
   - Preserve \`userRejected\` lists when overwriting per-note files

Pure mtime-only incremental misses "note unchanged but new mastered card should anchor it"
cases, so the pool-change check is the primary trigger.

---

## Important Rules

- **Never pad weak links.** If AI cannot find \u22657-score anchors, return \`[]\`. The plugin's
  \u26A1 button will simply be dimmed for that card. Better dim than misleading.
- **Same-card pairing**: a card cannot anchor itself. Filter out target == anchor.front.
- **Failed file writes**: report the error, continue with the next file. Don't abort the whole run.
- **Gemini / Claude / Cursor parity**: this skill should run identically across all three.
  Avoid IDE-specific syntax. Use plain Obsidian CLI + bash where vault operations are needed.
- **Read-only invariant**: never touch source notes, ai-cards markdown, hints, or sr files.
  Synapse output is exclusively under \`engram-review/synapse/\`.
`};Qi.exports=Dh});var Ha=Y((hg,ji)=>{"use strict";var Ui={};try{Ui=Bi()}catch(t){}function Oh(t){let e=t!==void 0?t:Ui,n="2026-06-10",a=[{id:"quest-map",title:"Quest Map",summary:"Generate plugin-native HTML Quest Map packages for the EngramQuest Hub."},{id:"review-deck",title:"Review Deck",summary:"Generate and update review-deck hints for flashcard notes."},{id:"memory-map",title:"Memory Map",summary:"Generate memory-map canvas files for the EngramQuest plugin."},{id:"lesson-academy",title:"Lesson Academy",summary:"Build interactive HTML lesson courses tailored to what the user wants to learn."},{id:"synapse",title:"Synapse (Pro)",summary:"Pre-compute memory anchors that link difficult cards to mastered ones. Pro feature.",pro:!0},{id:"quadrant",title:"Quadrant Card (Pro)",summary:"Upgrade a flashcard into an A4 four-quadrant super-memory card (question, answer, verbal metaphor, visual image). Pro feature.",pro:!0}],s={claude:{id:"claude",label:"Claude Code",kind:"skills",baseDir:".claude/skills",summary:"Project-local Claude Code skills."},codex:{id:"codex",label:"Codex / Antigravity",kind:"skills",baseDir:".agents/skills",summary:"Project-local skills for Codex and Antigravity (shared .agents/skills)."},gemini:{id:"gemini",label:"Gemini CLI",kind:"skills",baseDir:".gemini/skills",summary:"Project-local Gemini CLI skills."},cursor:{id:"cursor",label:"Cursor",kind:"rules",baseDir:".cursor/skills",summary:"Project-local Cursor skills."}},i={"quest-map":[{source:"quest-map/skills.md",target:"SKILL.md"},{source:"quest-map/references/user-guide.md",target:"references/user-guide.md"},{source:"quest-map/references/obsidian-cli.md",target:"references/obsidian-cli.md"},{source:"quest-map/references/engram-data-layout.md",target:"references/engram-data-layout.md"},{source:"quest-map/references/html-first-contract.md",target:"references/html-first-contract.md"},{source:"quest-map/references/html-quality.md",target:"references/html-quality.md"},{source:"quest-map/references/boss-design.md",target:"references/boss-design.md"},{source:"quest-map/references/domain-patterns.md",target:"references/domain-patterns.md"},{source:"quest-map/references/challenge-formats.md",target:"references/challenge-formats.md"},{source:"quest-map/references/visual-challenges.md",target:"references/visual-challenges.md"},{source:"quest-map/references/parser-constraints.md",target:"references/parser-constraints.md"},{source:"quest-map/references/yaml-template.md",target:"references/yaml-template.md"},{source:"quest-map/scripts/list_quest_icons.sh",target:"scripts/list_quest_icons.sh"},{source:"quest-map/assets/examples/mission-basic.html",target:"assets/examples/mission-basic.html"},{source:"quest-map/assets/examples/boss-cascade.html",target:"assets/examples/boss-cascade.html"}],"review-deck":[{source:"review-deck/skills.md",target:"SKILL.md"},{source:"review-deck/references/user-guide.md",target:"references/user-guide.md"},{source:"review-deck/references/plugin-architecture.md",target:"references/plugin-architecture.md"},{source:"review-deck/references/obsidian-cli.md",target:"references/obsidian-cli.md"},{source:"review-deck/references/engram-data-layout.md",target:"references/engram-data-layout.md"},{source:"review-deck/references/image-cards.md",target:"references/image-cards.md"},{source:"review-deck/scripts/search_vault.sh",target:"scripts/search_vault.sh"},{source:"review-deck/scripts/get_mtime.sh",target:"scripts/get_mtime.sh"}],"memory-map":[{source:"memory-map/skills.md",target:"SKILL.md"},{source:"memory-map/references/create.md",target:"references/create.md"},{source:"memory-map/references/update.md",target:"references/update.md"},{source:"memory-map/references/explain.md",target:"references/explain.md"},{source:"memory-map/references/user-guide.md",target:"references/user-guide.md"},{source:"memory-map/references/obsidian-cli.md",target:"references/obsidian-cli.md"},{source:"memory-map/references/engram-data-layout.md",target:"references/engram-data-layout.md"},{source:"memory-map/scripts/search_vault.sh",target:"scripts/search_vault.sh"}],"lesson-academy":[{source:"lesson-academy/skills.md",target:"SKILL.md"},{source:"lesson-academy/references/html-recipe.md",target:"references/html-recipe.md"},{source:"lesson-academy/references/lesson-format.md",target:"references/lesson-format.md"},{source:"lesson-academy/references/obsidian-cli.md",target:"references/obsidian-cli.md"},{source:"lesson-academy/references/visual-teaching.md",target:"references/visual-teaching.md"}],synapse:[{source:"synapse/skills.md",target:"SKILL.md"},{source:"synapse/references/scoring-guide.md",target:"references/scoring-guide.md"},{source:"synapse/references/plugin-architecture.md",target:"references/plugin-architecture.md"},{source:"synapse/references/obsidian-cli.md",target:"references/obsidian-cli.md"},{source:"synapse/scripts/dump_sr_pool.sh",target:"scripts/dump_sr_pool.sh"},{source:"synapse/scripts/dump_sr_pool.js",target:"scripts/dump_sr_pool.js"}],quadrant:[{source:"quadrant/skills.md",target:"SKILL.md"},{source:"quadrant/references/recipes.md",target:"references/recipes.md"},{source:"quadrant/references/q3-q4-quality.md",target:"references/q3-q4-quality.md"},{source:"quadrant/references/layout-contract.md",target:"references/layout-contract.md"},{source:"quadrant/references/obsidian-cli.md",target:"references/obsidian-cli.md"},{source:"quadrant/assets/recipe-A-flywheel.html",target:"assets/recipe-A-flywheel.html"},{source:"quadrant/assets/recipe-B-comic.html",target:"assets/recipe-B-comic.html"},{source:"quadrant/assets/recipe-C-emoji.html",target:"assets/recipe-C-emoji.html"}]};function r(){return Array.from(arguments).filter(Boolean).join("/").replace(/\/+/g,"/")}function o(d,h,u){return e[u]?Promise.resolve(e[u]):d.read(r(h,u))}async function l(d,h,u,x){let m=[];for(let g of x){let b=r(d.baseDir,`engram-quest-${g.id}`);for(let f of i[g.id]||[]){let v=await o(h,u,f.source);f.target==="SKILL.md"&&(v=v.replace(/\bbash scripts\//g,`bash ${b}/scripts/`)),m.push({path:r(b,f.target),content:v})}}return m}async function c(d,h,u,x){let m=[];for(let g of x){let b=await o(h,u,`${g.id}/skills.md`);m.push({path:r(d.baseDir,`engram-quest-${g.id}.mdc`),content:["---",`description: Use when the user asks to create or update an EngramQuest ${g.id}.`,"globs:","alwaysApply: false","---",`<!-- engram-quest-installer:${n};tool:cursor;module:${g.id};source:${u}/${g.id}/skills.md -->`,"",b.trim(),""].join(`
`)})}return m}async function p(d,h,u,{isPro:x=!1}={}){let m=s[d];if(!m||!h)return[];let g=u+"/plugins/engram-quest/bundled-skills",b=x?a:a.filter(f=>!f.pro);return m.kind==="rules"?c(m,h,g,b):l(m,h,g,b)}return{INSTALLER_VERSION:n,MODULES:a,TOOL_TARGETS:s,getInstallEntries:p}}ji.exports=Oh});var Qa=Y((ug,Ji)=>{"use strict";var et=require("obsidian"),{t:ue}=ge(),Ne=Ha()(),{registerSelectionCopy:Wi}=kt();function $a(t,e){return ue(t,{claude:"SKILLS_TOOL_CLAUDE",codex:"SKILLS_TOOL_CODEX",gemini:"SKILLS_TOOL_GEMINI",cursor:"SKILLS_TOOL_CURSOR"}[e]||"")||e}function Gi(t,e){return String(t||"").replace(/\{(\w+)\}/g,(n,a)=>{var s;return(s=e[a])!=null?s:""})}async function Yi(t,e){return await t.exists(e)?t.read(e):null}async function zh(t,e){let n=et.normalizePath(e||"");if(!n)return;let a=n.split("/").filter(Boolean),s="";for(let i of a)if(s=s?`${s}/${i}`:i,!t.vault.getAbstractFileByPath(s))try{await t.vault.createFolder(s)}catch(r){if(!String(r&&r.message||r).includes("Folder already exists"))throw r}}async function yn(t,e){let n=!!(t.settings&&t.settings.licenseValid),a=t.app.vault.adapter,s=await Ne.getInstallEntries(e,a,t.app.vault.configDir,{isPro:n}),i=[];for(let c of s){let p=await Yi(a,c.path),d="create";p!==null&&(d=p===c.content?"unchanged":"replace"),i.push({path:c.path,content:c.content,action:d})}let r=i.some(c=>c.action==="create"),o=i.some(c=>c.action==="replace"),l="not_installed";return!r&&!o?l="installed":i.some(c=>c.action!=="create")&&(l="update_available"),{toolId:e,tool:Ne.TOOL_TARGETS[e],items:i,status:l}}async function Ph(t,e){let n=t.app.vault.adapter,a=await Ne.getInstallEntries(e,n,t.app.vault.configDir);for(let s of a){let i=await Yi(n,s.path);if(i&&i.includes("engram-quest-installer:"))return!0}return!1}async function Fa(t,e){let n=t.app.vault.adapter;for(let a of e.items){let s=a.path.includes("/")?a.path.slice(0,a.path.lastIndexOf("/")):"";await zh(t.app,s),await n.write(a.path,a.content)}}async function Hh(t){for(let e of Object.values(Ne.TOOL_TARGETS)){if(!await Ph(t,e.id))continue;let n=await yn(t,e.id);n.items.some(a=>a.action==="replace"||a.action==="create")&&await Fa(t,n)}}function Ki(){return!!Ne&&typeof Ne.getInstallEntries=="function"&&Object.keys(Ne.TOOL_TARGETS||{}).length>0}var Vi=class extends et.Modal{constructor(t,e,n,a){super(t),this.plugin=e,this.preview=n,this.onDone=a}onOpen(){let t=this.plugin.settings,{contentEl:e}=this;e.empty(),Wi(this),this.modalEl.style.cssText="width:min(92vw,860px);max-width:none;",e.createEl("h2",{text:ue(t,"SKILLS_PREVIEW_TITLE")}),e.createEl("p",{text:ue(t,"SKILLS_PREVIEW_DESC")});let n=e.createEl("div",{attr:{style:"display:flex;flex-direction:column;gap:8px;max-height:52vh;overflow:auto;margin:16px 0;"}});this.preview.items.forEach(r=>{n.createEl("div",{attr:{style:"border:1px solid var(--background-modifier-border);border-radius:10px;padding:10px 12px;"}}).createEl("div",{text:`${ue(t,r.action==="create"?"SKILLS_ACTION_CREATE":r.action==="replace"?"SKILLS_ACTION_REPLACE":"SKILLS_ACTION_UNCHANGED")}  ${r.path}`,attr:{style:"font-family:monospace;font-size:12px;word-break:break-all;"}})});let a=e.createEl("div",{attr:{style:"display:flex;justify-content:flex-end;gap:10px;margin-top:18px;"}}),s=a.createEl("button",{text:ue(t,"HUB_CLOSE")}),i=a.createEl("button",{text:ue(t,"SKILLS_CONFIRM_INSTALL"),cls:"mod-cta"});s.addEventListener("click",()=>this.close()),i.addEventListener("click",async()=>{i.disabled=!0;try{await Fa(this.plugin,this.preview),new et.Notice(Gi(ue(t,"SKILLS_INSTALL_DONE"),{tool:$a(t,this.preview.toolId)})),new et.Notice(Gi(ue(t,"SKILLS_NEXT_STEP"),{tool:$a(t,this.preview.toolId)})),this.onDone&&await this.onDone(),this.close()}catch(r){console.error("EngramQuest: skill install failed",r),new et.Notice("Install failed: "+String(r)),i.disabled=!1}})}},$h=class extends et.Modal{constructor(t,e){super(t),this.plugin=e}async onOpen(){Wi(this),this.modalEl.style.cssText="width:min(94vw,920px);max-width:none;",await this.render()}async render(){let t=this.plugin.settings,{contentEl:e}=this;if(e.empty(),e.createEl("h2",{text:ue(t,"SKILLS_INSTALL_TITLE")}),e.createEl("p",{text:ue(t,"SKILLS_INSTALL_DESC")}),e.createEl("p",{text:ue(t,"SKILLS_INSTALL_PROJECT_LOCAL"),attr:{style:"color:var(--text-muted);margin-top:-4px;"}}),!Ki()){e.createEl("p",{text:"Installer assets are unavailable in this plugin build.",attr:{style:"color:var(--text-error);"}});return}let n=e.createEl("div",{attr:{style:"display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px;margin-top:18px;"}}),a=e.createEl("p",{text:ue(t,"SKILLS_MODAL_LOADING"),attr:{style:"color:var(--text-muted);margin-top:14px;"}});try{for(let s of Object.values(Ne.TOOL_TARGETS)){let i=await yn(this.plugin,s.id),r=n.createEl("div",{attr:{style:"border:1px solid var(--background-modifier-border);border-radius:14px;padding:14px;display:flex;flex-direction:column;gap:10px;"}});r.createEl("div",{text:$a(t,s.id),attr:{style:"font-size:16px;font-weight:700;"}}),r.createEl("div",{text:s.summary,attr:{style:"color:var(--text-muted);font-size:13px;"}}),r.createEl("div",{text:`${ue(t,"SKILLS_TARGET_PATH")}: ${s.baseDir}`,attr:{style:"font-family:monospace;font-size:12px;word-break:break-all;"}}),r.createEl("div",{text:`${ue(t,"SKILLS_MODULES")}: ${(t.licenseValid?Ne.MODULES:Ne.MODULES.filter(o=>!o.pro)).map(o=>o.id).join(", ")}`,attr:{style:"font-size:12px;color:var(--text-muted);"}}),r.createEl("div",{text:i.status==="installed"?ue(t,"SKILLS_STATUS_INSTALLED"):i.status==="update_available"?ue(t,"SKILLS_STATUS_UPDATE"):ue(t,"SKILLS_STATUS_NOT_INSTALLED"),attr:{style:`display:inline-flex;align-self:flex-start;padding:4px 10px;border-radius:999px;font-size:12px;font-weight:600;${i.status==="installed"?"background:#dcfce7;color:#166534;":i.status==="update_available"?"background:#fef3c7;color:#92400e;":"background:#e5e7eb;color:#374151;"}`}}),r.createEl("button",{text:ue(t,"SKILLS_PREVIEW_BUTTON"),cls:"mod-cta",attr:{style:"margin-top:auto;"}}).addEventListener("click",async()=>{try{let o=await yn(this.plugin,s.id);new Vi(this.app,this.plugin,o,async()=>{try{await this.render()}catch(l){console.error("EngramQuest: skills render failed",l)}}).open()}catch(o){console.error("EngramQuest: skill preview failed",o),new et.Notice("Failed to load skill info: "+String(o))}})}}catch(s){console.error("EngramQuest: skills modal render failed",s),a.textContent="Failed to load skill data: "+String(s),a.style.color="var(--text-error)";return}a.remove()}};Ji.exports={SkillsPreviewModal:Vi,SkillsInstallModal:$h,isInstallerAvailable:Ki,getInstallPreview:yn,doInstall:Fa,autoUpdateSkills:Hh}});var Xi=Y((fg,Zi)=>{"use strict";var pe=require("obsidian"),{t:ae}=ge(),{giHTML:Fh}=be(),{SkillsInstallModal:Qh,isInstallerAvailable:Bh}=Qa(),Uh=class extends pe.PluginSettingTab{constructor(t,e){super(t,e),this.plugin=e}display(){let{containerEl:t}=this,e=this.plugin.settings;t.empty();let n=t.createEl("div",{attr:{style:"display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;margin:0 0 18px;"}}),a=n.createEl("a",{attr:{href:"https://github.com/bahfahh/engram-quest",target:"_blank",rel:"noopener",style:"display:inline-flex;align-items:center;gap:6px;padding:6px 14px;border-radius:8px;background:#ec4899;color:#fff;text-decoration:none;font-size:13px;font-weight:600;border:none;"}});a.createEl("span",{text:"\u2665"}),a.createEl("span",{text:"Sponsor"});let s=n.createEl("a",{attr:{href:"https://ko-fi.com/wen_aidev",target:"_blank",rel:"noopener",style:"display:inline-flex;align-items:center;gap:6px;padding:6px 14px;border-radius:8px;background:#fcd34d;color:#1f2937;text-decoration:none;font-size:13px;font-weight:600;border:none;"}});s.createEl("span").innerHTML=Fh("coffee"),s.createEl("span",{text:"Buy me a coffee"}),new pe.Setting(t).setName(ae(e,"LANGUAGE")).setDesc(ae(e,"LANGUAGE_DESC")).addDropdown(i=>i.addOption("en",ae(e,"LANGUAGE_ENGLISH")).addOption("zh-tw",ae(e,"LANGUAGE_ZH_TW")).addOption("system",ae(e,"LANGUAGE_SYSTEM")).setValue(e.language||"en").onChange(async r=>{this.plugin.settings.language=r,await this.plugin.saveData(this.plugin.settings),this.display()})),new pe.Setting(t).setName(ae(e,"SETTINGS_APPEARANCE")).setHeading(),new pe.Setting(t).setName(ae(e,"SETTINGS_THEME_NAME")).setDesc(ae(e,"SETTINGS_THEME_DESC")).addDropdown(i=>i.addOption("bright",`\u2600\uFE0F ${ae(e,"SETTINGS_THEME_BRIGHT")}`).addOption("dark",`\u{1F319} ${ae(e,"SETTINGS_THEME_DARK")}`).addOption("minimal",`\u2728 ${ae(e,"SETTINGS_THEME_MINIMAL")}`).setValue(e.lhTheme).onChange(async r=>{this.plugin.settings.lhTheme=r,await this.plugin.saveData(this.plugin.settings)})),new pe.Setting(t).setName(ae(e,"SETTINGS_REVIEW_DECK")).setHeading(),new pe.Setting(t).setName(ae(e,"SETTINGS_FLASHCARD_TAGS_NAME")).setDesc(ae(e,"SETTINGS_FLASHCARD_TAGS_DESC")).addTextArea(i=>{i.setPlaceholder("Flashcards").setValue(e.flashcardTags).onChange(async r=>{this.plugin.settings.flashcardTags=r.trim(),await this.plugin.saveData(this.plugin.settings)}),i.inputEl.style.cssText="width:200px;height:72px;font-family:monospace;font-size:13px"}),new pe.Setting(t).setName(ae(e,"SETTINGS_SR_SCAN_NAME")).setDesc(ae(e,"SETTINGS_SR_SCAN_DESC")).addToggle(i=>i.setValue(e.enableSRScan).onChange(async r=>{this.plugin.settings.enableSRScan=r,await this.plugin.saveData(this.plugin.settings)})),new pe.Setting(t).setName(ae(e,"SETTINGS_MAX_INTERVAL_NAME")).setDesc(ae(e,"SETTINGS_MAX_INTERVAL_DESC")).addText(i=>i.setPlaceholder("36525").setValue(String(e.maxInterval)).onChange(async r=>{let o=parseInt(r);!isNaN(o)&&o>=1&&(this.plugin.settings.maxInterval=o,await this.plugin.saveData(this.plugin.settings))})),new pe.Setting(t).setName(ae(e,"SETTINGS_RETENTION_NAME")).setDesc(ae(e,"SETTINGS_RETENTION_DESC")).addSlider(i=>{var r;return i.setLimits(.7,.99,.01).setValue((r=e.requestedRetention)!=null?r:.9).setDynamicTooltip().onChange(async o=>{this.plugin.settings.requestedRetention=o,await this.plugin.saveData(this.plugin.settings)})}),new pe.Setting(t).setName(ae(e,"SETTINGS_MEMORY_MAP")).setHeading(),new pe.Setting(t).setName(ae(e,"SETTINGS_MEMORY_MAP_FOLDER_NAME")).setDesc(ae(e,"SETTINGS_MEMORY_MAP_FOLDER_DESC")).addText(i=>{i.setPlaceholder("Maps/MemoryMaps").setValue(e.memoryMapFolder||"").onChange(async r=>{this.plugin.settings.memoryMapFolder=r.trim(),await this.plugin.saveData(this.plugin.settings);let o=this.app.vault.adapter;try{await o.exists(".memory-map")||await this.app.vault.createFolder(".memory-map"),await o.write(".memory-map/config.json",JSON.stringify({memoryMapFolder:this.plugin.settings.memoryMapFolder},null,2))}catch(l){console.error("EngramQuest: could not write .memory-map/config.json",l)}}),i.inputEl.style.cssText="width:200px;font-family:monospace;font-size:13px"}),new pe.Setting(t).setName("Lesson Academy").setHeading(),new pe.Setting(t).setName("Lesson storage folders").setDesc('One folder path per line. The first line is where new courses are created; every line is scanned for existing courses. A line may contain one "*" segment (e.g. 8.TRACK/Learning/*/lessons) to match every matching subfolder.').addTextArea(i=>{i.setPlaceholder("engram-quest/lessons").setValue((e.lessonRoots||[]).join(`
`)).onChange(async r=>{this.plugin.settings.lessonRoots=r.split(`
`).map(o=>o.trim()).filter(Boolean),await this.plugin.saveData(this.plugin.settings)}),i.inputEl.style.cssText="width:280px;height:72px;font-family:monospace;font-size:12px"}),new pe.Setting(t).setName("Quest Map").setHeading(),new pe.Setting(t).setName("Quest storage folders").setDesc("Same rules as Lesson storage folders, for Quest Map packages.").addTextArea(i=>{i.setPlaceholder("engram-quest/quests").setValue((e.questRoots||[]).join(`
`)).onChange(async r=>{this.plugin.settings.questRoots=r.split(`
`).map(o=>o.trim()).filter(Boolean),await this.plugin.saveData(this.plugin.settings)}),i.inputEl.style.cssText="width:280px;height:72px;font-family:monospace;font-size:12px"}),new pe.Setting(t).setName(ae(e,"SETTINGS_AI_SKILLS")).setHeading(),new pe.Setting(t).setName(ae(e,"SETTINGS_AI_SKILLS")).setDesc(ae(e,"SETTINGS_AI_SKILLS_DESC")).addButton(i=>i.setButtonText(ae(e,"SETTINGS_AI_SKILLS_BUTTON")).setCta().onClick(()=>{if(!Bh()){new pe.Notice("EngramQuest installer assets are unavailable.");return}new Qh(this.app,this.plugin).open()})),new pe.Setting(t).setName(ae(e,"SETTINGS_AUTO_UPDATE_SKILLS_NAME")).setDesc(ae(e,"SETTINGS_AUTO_UPDATE_SKILLS_DESC")).addToggle(i=>i.setValue(!!e.autoUpdateInstalledSkills).onChange(async r=>{this.plugin.settings.autoUpdateInstalledSkills=r,await this.plugin.saveData(this.plugin.settings)}))}};Zi.exports={SettingsTab:Uh}});var to=Y((xg,eo)=>{"use strict";var jh=require("obsidian"),{giHTML:Gh}=be(),Ve=[{id:"ch1-why",titleEn:"Why Harness?",titleZh:"Why Harness?",emoji:"MAP",themeColor:"blue",summary:"A strong model alone is not enough. Reliable agents need structure around the model so work stays consistent, testable, and recoverable.",keyPoints:[{title:"Context drift",why:"Agents fail when the right context is missing or mixed with irrelevant detail.",example:"A coding agent edits the wrong file because the repo constraints were never loaded."},{title:"Weak self-checking",why:"Without checks, an agent often assumes it is correct and moves on too early.",example:"The patch compiles locally but breaks the real workflow because no verification step ran."},{title:"Harness as control layer",why:"A harness adds instructions, guardrails, state, and feedback loops around the model.",example:"AGENTS.md, tests, and progress files keep the agent aligned with the task."}],realWorld:"Teams using structured agent workflows typically get more predictable output than teams relying on prompts alone."},{id:"ch2-three-pillars",titleEn:"Three Pillars",titleZh:"Three Pillars",emoji:"TRI",themeColor:"green",summary:"Harness engineering usually rests on three pillars: context engineering, architectural constraints, and feedback loops.",keyPoints:[{title:"Context engineering",why:"Give the agent the minimum correct context at the correct time.",example:"Load specs, current task state, and file ownership before asking for edits."},{title:"Architectural constraints",why:"Clear boundaries reduce destructive or low-quality actions.",example:"Require non-interactive git usage and forbid unsafe resets."},{title:"Feedback loops",why:"Verification closes the gap between looks done and actually works.",example:"Run syntax checks and targeted tests before declaring success."}],realWorld:"The biggest reliability jump often comes from adding lightweight verification rather than adding more prompt text."},{id:"ch3-principles",titleEn:"Design Principles",titleZh:"Design Principles",emoji:"RULE",themeColor:"amber",summary:"Good harnesses do not try to micromanage every step. They shape decisions so the agent can act quickly without drifting.",keyPoints:[{title:"Specify what and why",why:"Tell the agent the goal and constraints clearly; avoid over-prescribing every keystroke.",example:"Fix the Review Deck scan default and preserve migration behavior."},{title:"Make failure visible",why:"If something breaks, the system should expose it early.",example:"A syntax check catches broken strings before the plugin reaches runtime."},{title:"Prefer observable progress",why:"Visible state helps both humans and agents recover from interruptions.",example:"A now.md note records product decisions and implementation status."}],realWorld:"The best agent systems feel boring in production: fewer surprises, clearer boundaries, faster recovery."},{id:"ch4-scale",titleEn:"Scale & Teams",titleZh:"Scale and Teams",emoji:"TEAM",themeColor:"purple",summary:"As more agents or teammates join, coordination matters more than raw model quality.",keyPoints:[{title:"Shared rules",why:"Teams need one source of truth for conventions and safety rules.",example:"AGENTS.md and PROJECT_TRUTH.md define workflow expectations."},{title:"Scoped ownership",why:"Parallel work is safer when responsibilities are explicit.",example:"One agent owns docs while another owns runtime UI fixes."},{title:"Reviewable outputs",why:"People must be able to inspect and trust what the agent changed.",example:"Small diffs and reproducible checks beat giant opaque rewrites."}],realWorld:"Scaling agents is usually a coordination problem before it becomes a model problem."},{id:"ch5-future",titleEn:"The Future",titleZh:"The Future",emoji:"NEXT",themeColor:"slate",isBoss:!0,summary:"The frontier is moving from prompt better toward design better systems around the model.",keyPoints:[{title:"Model plus system",why:"Capability comes from the combination of model, tools, memory, and verification.",example:"A smaller model with a good harness can outperform a larger model used carelessly."},{title:"Long-running workflows",why:"Persistent state and checkpointing matter for multi-step work.",example:"A project note preserves decisions across debugging sessions."},{title:"Human steering",why:"Humans should define direction and quality bars; agents should execute within them.",example:"The user sets product behavior, the agent implements and verifies it."}],realWorld:"Reliable agent products will be differentiated less by raw model access and more by workflow design."}],Ba={blue:{g1:"#93c5fd",g2:"#3b82f6",depth:"#1e3a8a",light:"#bfdbfe"},green:{g1:"#86efac",g2:"#22c55e",depth:"#14532d",light:"#bbf7d0"},amber:{g1:"#fde047",g2:"#f59e0b",depth:"#78350f",light:"#fef08a"},purple:{g1:"#d8b4fe",g2:"#a855f7",depth:"#581c87",light:"#e9d5ff"},slate:{g1:"#cbd5e1",g2:"#64748b",depth:"#0f172a",light:"#e2e8f0"}},lt=[{cx:100,cy:155},{cx:300,cy:255},{cx:500,cy:95},{cx:700,cy:255},{cx:900,cy:145}];function Wh(){let t=`M ${lt[0].cx} ${lt[0].cy} `;for(let e=0;e<lt.length-1;e++){let n=lt[e],a=lt[e+1],s=a.cx-n.cx;t+=`C ${n.cx+s*.65} ${n.cy}, ${a.cx-s*.65} ${a.cy}, ${a.cx} ${a.cy} `}return t}function Yh(t){let e=Wh(),n="",a="";return Ve.forEach((s,i)=>{let r=Ba[s.themeColor];n+=`
      <radialGradient id="hglow${i}" cx="50%" cy="50%" r="50%">
        <stop offset="0%" stop-color="${r.light}" stop-opacity=".7"/>
        <stop offset="100%" stop-color="${r.light}" stop-opacity="0"/>
      </radialGradient>
      <linearGradient id="hsurf${i}" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0%" stop-color="${r.g1}"/>
        <stop offset="100%" stop-color="${r.g2}"/>
      </linearGradient>`}),Ve.forEach((s,i)=>{let{cx:r,cy:o}=lt[i],l=i===t,c=i<t,p=Ba[s.themeColor];a+=`<g class="hm-island" data-index="${i}" style="cursor:pointer">`,l&&(a+=`<ellipse cx="${r}" cy="${o+5}" rx="70" ry="38" fill="url(#hglow${i})"/>`),a+=`<ellipse cx="${r}" cy="${o+17}" rx="44" ry="9" fill="${p.depth}" opacity=".85"/>`,a+=`<ellipse cx="${r}" cy="${o+8}" rx="44" ry="12" fill="url(#hsurf${i})" stroke="${l?"rgba(255,255,255,0.85)":"rgba(255,255,255,0.2)"}" stroke-width="${l?2:.5}"/>`,a+=`<text x="${r}" y="${l?o-48:o-42}" text-anchor="middle" dominant-baseline="middle" font-size="${l?34:28}" style="user-select:none">${s.emoji}</text>`,c&&(a+=`<circle cx="${r+28}" cy="${o-44}" r="10" fill="#22c55e" stroke="white" stroke-width="1.5"/><text x="${r+28}" y="${o-44}" text-anchor="middle" dominant-baseline="middle" fill="white" font-size="11" font-weight="700">GO</text>`),l&&(a+=`<rect x="${r-28}" y="${o-78}" width="56" height="18" rx="9" fill="#0f172a" stroke="#4ade80" stroke-width="1.5"/><circle cx="${r-16}" cy="${o-69}" r="3" fill="#4ade80"/><text x="${r+2}" y="${o-69}" text-anchor="middle" dominant-baseline="middle" fill="white" font-size="9" font-weight="700">Current</text>`),a+=`<rect x="${r-52}" y="${o+28}" width="104" height="34" rx="17" fill="rgba(255,255,255,0.93)" stroke="${l?p.g2:"rgba(255,255,255,0.6)"}" stroke-width="${l?2:1}"/><text x="${r}" y="${o+40}" text-anchor="middle" dominant-baseline="middle" fill="#1e293b" font-size="11" font-weight="700">${s.titleEn}</text><text x="${r}" y="${o+53}" text-anchor="middle" dominant-baseline="middle" fill="#64748b" font-size="9">${s.titleZh}</text></g>`}),`
    <div style="position:relative;width:100%;border-radius:16px;border:1px solid var(--background-modifier-border);
                box-shadow:0 10px 30px -10px rgba(0,0,0,0.15);overflow:hidden">
      <div style="width:100%;overflow-x:auto;overflow-y:hidden;scrollbar-width:thin">
        <div style="min-width:1100px;background:linear-gradient(180deg,#87ceeb 0%,#b8e0f7 35%,#7ec8e3 65%,#a8d8ea 100%)">
          <svg viewBox="0 0 1100 420" style="display:block;width:100%;height:auto" xmlns="http://www.w3.org/2000/svg">
            <defs>${n}</defs>
            <path d="${e}" fill="none" stroke="white" stroke-width="5" stroke-dasharray="10 15" opacity=".45"/>
            <path d="${e}" fill="none" stroke="#67e8f9" stroke-width="3" stroke-dasharray="10 15" opacity=".6"/>
            ${a}
          </svg>
        </div>
      </div>
    </div>`}function Kh(t,e){let n=new jh.Modal(t),a=e;function s(){let i=Ve[a],r=Ba[i.themeColor];n.contentEl.empty(),n.modalEl.style.cssText="width:min(92vw,820px);max-width:none;max-height:90vh;overflow:hidden;display:flex;flex-direction:column",n.contentEl.style.cssText="padding:0;flex:1;display:flex;flex-direction:column;overflow:hidden;min-height:0",n.contentEl.createEl("div",{attr:{style:"height:6px;background:var(--background-modifier-border);flex-shrink:0"}}).createEl("div",{attr:{style:`height:100%;border-radius:0 4px 4px 0;width:${(a+1)/Ve.length*100}%;background:linear-gradient(90deg,${r.g1},${r.g2});transition:width .5s`}});let o=n.contentEl.createEl("div",{attr:{style:"padding:28px 32px;overflow-y:auto;flex:1;min-height:0"}}),l=o.createEl("div",{attr:{style:"display:flex;align-items:flex-start;gap:16px;margin-bottom:16px"}});l.createEl("span",{text:i.emoji,attr:{style:"font-size:36px;flex-shrink:0"}});let c=l.createEl("div");c.createEl("h2",{text:i.titleEn,attr:{style:"font-size:22px;font-weight:800;margin:0;line-height:1.3;color:var(--text-normal)"}}),c.createEl("p",{text:`Chapter ${a+1} of ${Ve.length} \xB7 ${i.titleZh}`,attr:{style:"margin:4px 0 0;font-size:12px;color:var(--text-muted)"}}),o.createEl("p",{text:i.summary,attr:{style:"font-size:14px;line-height:1.7;color:var(--text-normal);margin-bottom:24px"}}),o.createEl("h3",{text:"Key takeaways",attr:{style:"font-size:11px;font-weight:700;color:var(--text-faint);letter-spacing:.08em;text-transform:uppercase;margin-bottom:16px"}}),i.keyPoints.forEach((g,b)=>{let f=o.createEl("div",{attr:{style:"border-radius:12px;border:1px solid var(--background-modifier-border);background:var(--background-secondary);padding:16px 20px;margin-bottom:12px"}}),v=f.createEl("div",{attr:{style:"display:flex;align-items:flex-start;gap:12px;margin-bottom:8px"}});v.createEl("span",{text:String(b+1),attr:{style:"flex-shrink:0;width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;background:var(--interactive-accent);color:white"}}),v.createEl("p",{text:g.title,attr:{style:"font-size:15px;font-weight:700;margin:0;color:var(--text-normal)"}}),f.createEl("p",{text:g.why,attr:{style:"font-size:14px;line-height:1.65;color:var(--text-normal);margin:0 0 12px 36px"}});let y=f.createEl("div",{attr:{style:"margin-left:36px;border-radius:8px;border:1px solid var(--background-modifier-border);background:var(--background-primary);padding:12px 16px"}});y.createEl("span",{text:"E.g. ",attr:{style:"font-size:10px;font-weight:700;color:var(--text-faint);letter-spacing:.08em;text-transform:uppercase"}}),y.createEl("span",{text:g.example,attr:{style:"font-size:13px;line-height:1.65;color:var(--text-normal);white-space:pre-wrap"}})});let p=o.createEl("div",{attr:{style:"margin-bottom:32px;border-radius:12px;border:1px solid var(--interactive-accent);background:var(--background-secondary);padding:16px 20px"}}),d=p.createEl("div",{attr:{style:"display:flex;align-items:center;gap:8px;margin-bottom:8px"}});d.createEl("span").innerHTML=Gh("bulb"),d.createEl("span",{text:"Real-world insight",attr:{style:"font-size:10px;font-weight:700;color:var(--interactive-accent);letter-spacing:.08em;text-transform:uppercase"}}),p.createEl("p",{text:i.realWorld,attr:{style:"font-size:14px;line-height:1.7;color:var(--text-normal);margin:0"}});let h=o.createEl("div",{attr:{style:"display:flex;align-items:center;justify-content:space-between;border-top:1px solid var(--background-modifier-border);padding-top:20px"}}),u=h.createEl("button",{text:"Prev",attr:{style:`border-radius:9999px;padding:8px 16px;font-size:14px;font-weight:600;color:var(--text-muted);background:transparent;border:none;cursor:pointer;visibility:${a===0?"hidden":"visible"}`}}),x=h.createEl("div",{attr:{style:"display:flex;gap:6px"}});Ve.forEach((g,b)=>x.createEl("button",{attr:{style:`height:8px;border-radius:9999px;border:none;cursor:pointer;transition:all .3s;width:${b===a?"24px":"8px"};background:${b===a?"var(--interactive-accent)":"var(--background-modifier-border)"}`}}).addEventListener("click",()=>{a=b,s()}));let m=h.createEl("button",{text:a===Ve.length-1?"Done":"Next",attr:{style:`border-radius:9999px;padding:8px 16px;font-size:14px;font-weight:700;color:white;border:none;cursor:pointer;background:linear-gradient(135deg,${r.g1},${r.g2})`}});u.addEventListener("click",()=>{a>0&&(a--,s())}),m.addEventListener("click",()=>{a<Ve.length-1?(a++,s()):n.close()})}n.open(),s()}eo.exports={renderHarnessMap:Yh,openHarnessChapter:Kh}});var ao=Y((vg,no)=>{"use strict";var yg=require("obsidian");function Vh(t,e,n,a,s,i,r){let l=s.vault.configDir+"/plugins/engram-quest/assets/quest-map/",c=activeDocument.body.classList.contains("theme-dark"),p=e==="galaxy",d=c?"bg_dark.png":"bg_light.png",h=s.vault.adapter.getResourcePath(l+d),u=l+"generated/",x=s.vault.adapter.getResourcePath(u+"crown-current-node.webp"),m=s.vault.adapter.getResourcePath(u+"node-number-frame.webp"),g=s.vault.adapter.getResourcePath(u+"boss-gate.webp"),b=s.vault.adapter.getResourcePath(u+"boss-lock-badge.webp"),f=i(t.length),v=Math.max(900,t.length>1?f[t.length-1].cx+160:900),y="";if(f.length>0){y=`M ${f[0].cx} ${f[0].cy} `;for(let B=0;B<f.length-1;B++){let P=f[B],K=f[B+1],Z=K.cx-P.cx;y+=`C ${P.cx+Z*.65} ${P.cy}, ${K.cx-Z*.65} ${K.cy}, ${K.cx} ${K.cy} `}}let w=p?"rgba(125,211,252,0.9)":c?"rgba(255,210,80,0.85)":"rgba(80,120,200,0.75)",k=c?"rgba(0,0,0,0.5)":"rgba(0,0,0,0.15)",E=650,S=c?'<defs><filter id="qmGlow"><feGaussianBlur stdDeviation="2.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs>':"",_=c?' filter="url(#qmGlow)"':"",C=`
  <div class="qm-map-outer">
    <div class="qm-scroll-wrapper">
    <div class="qm-hybrid-container${p?" qm-galaxy":""}" style="${p?"":`background-image:url('${h}');`}min-width:${v}px;height:${E}px;">
      <svg class="qm-svg-layer" viewBox="0 0 ${v} ${E}" preserveAspectRatio="xMinYMin slice">
        ${S}
        <path d="${y}" fill="none" stroke="rgba(0,0,0,0.3)" stroke-width="4" stroke-linecap="round" transform="translate(0,5)" />
        <path d="${y}" fill="none" stroke="${w}" stroke-width="3" stroke-linecap="round" stroke-dasharray="6 14" style="animation:dashFlow 1s linear infinite;"${_} />
      </svg>
  `,L=r&&Number.isFinite(r.completedCount)?r.completedCount:0;t.forEach((B,P)=>{let K=f[P].cx,Z=f[P].cy,W=!!(a&&a.has(P)),V=P===n&&!W,X=!!B.boss,oe=P>n&&!W;!r&&W&&L++;let re="";if(W)re=p?'<div class="qm-badge" style="top:50%;left:50%;transform:translate(-50%,-50%);z-index:6;"><svg viewBox="0 0 24 24" width="36" height="36" fill="none" stroke="#4ade80" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" style="filter:drop-shadow(0 0 8px rgba(74,222,128,.6));"><circle cx="12" cy="12" r="10" stroke="rgba(74,222,128,.45)"/><path d="M7.5 12.5 11 16l6-7"/></svg></div>':`<img src="${x}" class="qm-badge qm-badge-crown" alt="" style="width:52px;height:52px;object-fit:contain;filter:drop-shadow(0 4px 8px rgba(255,210,80,.45));top:50%;left:50%;transform:translate(-50%,-50%);z-index:6;" />`;else if(oe&&X&&!p)re=`<img src="${b}" class="qm-badge qm-badge-boss-lock" alt="" style="top:-10px;right:18px;width:58px;height:58px;object-fit:contain;filter:drop-shadow(0 8px 14px rgba(60,0,90,.35));" />`;else if(oe){let U=p?["#38bdf8","#7dd3fc","#0ea5e9","#0c4a6e"]:c?["#b8860b","#d4a017","#a07010","#6b4000"]:["#9ca3af","#d1d5db","#9ca3af","#6b7280"];re=`<div class="qm-badge qm-badge-lock"><svg viewBox="0 0 32 40"><path d="M8 16V12a8 8 0 0116 0v4" fill="none" stroke="${U[0]}" stroke-width="2.5" stroke-linecap="round"/><rect x="4" y="16" width="24" height="18" rx="3" fill="${U[1]}" stroke="${U[2]}" stroke-width="1"/><circle cx="16" cy="26" r="2.5" fill="${U[3]}"/><rect x="15" y="26" width="2" height="4" rx="1" fill="${U[3]}"/></svg></div>`}let D=oe?"grayscale(70%) brightness(.68) saturate(.55)":W?"brightness(1.08) saturate(1.08)":"none",N;if(p){let U=D==="none"?"":`filter:${D};`;X?N=`<div class="qm-gx qm-gx-boss" style="${U}"><svg viewBox="0 0 150 150"><defs><radialGradient id="bc${P}" cx="42%" cy="36%"><stop offset="0%" stop-color="#fff8e6"/><stop offset="34%" stop-color="#ffcf5c"/><stop offset="70%" stop-color="#f97316"/><stop offset="100%" stop-color="#7c2d12"/></radialGradient><radialGradient id="bh${P}"><stop offset="0%" stop-color="rgba(255,190,80,.55)"/><stop offset="65%" stop-color="rgba(255,150,40,.14)"/><stop offset="100%" stop-color="rgba(255,150,40,0)"/></radialGradient></defs><circle cx="75" cy="75" r="73" fill="url(#bh${P})"/><g stroke="#ffd27a" stroke-width="3.5" stroke-linecap="round" opacity=".92"><line x1="75" y1="5" x2="75" y2="27"/><line x1="75" y1="123" x2="75" y2="145"/><line x1="5" y1="75" x2="27" y2="75"/><line x1="123" y1="75" x2="145" y2="75"/><line x1="26" y1="26" x2="42" y2="42"/><line x1="108" y1="108" x2="124" y2="124"/><line x1="124" y1="26" x2="108" y2="42"/><line x1="42" y1="108" x2="26" y2="124"/></g><circle cx="75" cy="75" r="37" fill="url(#bc${P})" stroke="#fff1c9" stroke-width="2"/><text x="75" y="76" text-anchor="middle" dominant-baseline="central" font-size="26" font-weight="900" fill="#5b2a00" opacity=".9" style="font-family:sans-serif">\u2605</text></svg></div>`:N=`<div class="qm-gx" style="${U}"><svg viewBox="0 0 120 104"><defs><radialGradient id="pg${P}" cx="38%" cy="30%" r="80%"><stop offset="0%" stop-color="#dff4ff"/><stop offset="42%" stop-color="#38bdf8"/><stop offset="100%" stop-color="#0a3350"/></radialGradient></defs><ellipse cx="60" cy="54" rx="55" ry="16" fill="none" stroke="rgba(125,211,252,.7)" stroke-width="4" transform="rotate(-16 60 54)"/><circle cx="60" cy="50" r="32" fill="url(#pg${P})" stroke="rgba(205,238,255,.55)" stroke-width="1.5"/><text x="60" y="51" text-anchor="middle" dominant-baseline="central" font-size="30" font-weight="900" fill="#eaf6ff" paint-order="stroke" stroke="rgba(4,18,31,.55)" stroke-width="3" style="font-family:sans-serif">${P+1}</text></svg></div>`}else N=X?`<img src="${g}" class="qm-platform qm-boss-gate-img" style="width:150px;filter:drop-shadow(0 18px 24px rgba(75,0,130,.38));" />`:`<div class="qm-node-number-wrap" style="position:relative;width:94px;height:94px;z-index:4;display:flex;align-items:center;justify-content:center;pointer-events:none;filter:drop-shadow(0 10px 16px rgba(0,0,0,.28)) ${D};">
          <img src="${m}" class="qm-node-number-frame" alt="" style="position:absolute;inset:0;width:100%;height:100%;object-fit:contain;" />
          <span class="qm-node-number" style="position:relative;z-index:1;color:#fff;font-size:34px;font-weight:900;line-height:1;text-shadow:0 2px 5px rgba(0,0,0,.65),0 0 8px rgba(99,102,241,.45);font-variant-numeric:tabular-nums;">${P+1}</span>
        </div>`;C+=`
      <div class="qm-island-group ${V?"qm-active":""} ${X?"qm-diff-boss":""} ${oe?"qm-island-locked":W?"qm-visited qm-completed":""}" data-index="${P}" style="left:${K}px;top:${Z}px;animation-delay:${-(P*.7)}s;">
        ${V&&!p?`<img src="${x}" class="qm-crown-img" alt="" style="position:absolute;top:-72px;left:50%;transform:translateX(-50%);width:76px;height:76px;object-fit:contain;z-index:7;filter:drop-shadow(0 6px 10px rgba(0,0,0,.35));animation:crownFloat 3s ease-in-out infinite;" />`:""}
        ${V?'<div class="qm-current-badge">\u25B6 Current</div>':""}
        ${re}
        ${N}
        <div class="qm-label-wrap">${B.title||""}</div>
      </div>
    `}),C+="</div></div>";let A=t[n]||{},T=r&&Number.isFinite(r.progressPct)?r.progressPct:t.length>0?Math.round(L/t.length*100):0,q=r&&Number.isFinite(r.bestScorePct)?r.bestScorePct:null,I=q!=null?q:T,R=I>=90?3:I>=60?2:I>0?1:0,O="";for(let B=0;B<3;B++)B<R?O+=`<svg viewBox="0 0 24 24" width="20" height="20"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26" fill="${c?"#f5c842":"#f59e0b"}" stroke="${c?"#c8930e":"#d97706"}" stroke-width="0.8"/></svg>`:O+=`<svg viewBox="0 0 24 24" width="20" height="20"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26" fill="none" stroke="${c?"rgba(255,255,255,0.15)":"#d1d5db"}" stroke-width="1.2"/></svg>`;let $=c?"qm-panel-dark":"qm-panel-light",z=c?"qm-pfill-dark":"qm-pfill-light";return C+=`
    <div class="qm-bottom-panel ${$}">
      <div class="qm-bp-header">
        <div class="qm-bp-title">\u{1F4CD} ${A.title||""}</div>
        <div class="qm-bp-stars">${O}</div>
      </div>
      ${A.summary?`<div class="qm-bp-desc">${A.summary}</div>`:""}
      <div class="qm-bp-prog-wrap">
        <div class="qm-bp-prog-bar"><div class="qm-bp-prog-fill ${z}" style="width:${T}%"></div></div>
        <span class="qm-bp-prog-label">${L}/${t.length}${q!==null?` \xB7 ${q}%`:""}</span>
      </div>
    </div>
  `,C+="</div>",C}no.exports={renderQuestMap:Vh}});var io=Y((wg,so)=>{"use strict";var{giHTML:ro}=be();function Jh(t=new Date){return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`}function vn(t){return t>=95?{icon:"trophy",key:"DECK_TIER_LEGEND",color:"#fbbf24"}:t>=80?{icon:"crown",key:"DECK_TIER_MASTER",color:"#7dd3fc"}:t>=50?{icon:"star",key:"DECK_TIER_EXPERT",color:"#38bdf8"}:t>=20?{icon:"flame",key:"DECK_TIER_RISING",color:"#f97316"}:{icon:"sprout",key:"DECK_TIER_NOVICE",color:"#10b981"}}function Zh(t,e,n){return t==="due"?e(n,"DUE"):t==="mastered"?e(n,"MASTERED"):t==="learning"?"LEARNING":"NEW"}function Xh(t,e,n){let a={g1:"#dbeafe",g2:"#93c5fd"},s=t[e]||t.ocean||{};return Array.isArray(s.colors)&&s.colors[n%s.colors.length]?s.colors[n%s.colors.length]:a}function eu(t,e,n,a,s){let i=Jh(),r=t.length,o=t.filter(h=>h.srMeta&&h.srMeta.due<=i).length,l=t.filter(h=>{var x,m,g;let u=(g=(x=h.srMeta)==null?void 0:x.stability)!=null?g:(m=h.srMeta)==null?void 0:m.interval;return h.srMeta&&h.srMeta.state===2&&u>=21}).length,c=r>0?Math.round(l/r*100):0,p='<div class="rd-wrap">';p+=`<div class="rd-stats">
    <span>${a(e.settings,"TOTAL")}: ${r}</span>
    <span>${a(e.settings,"DUE")}: ${o}</span>
    <span>${a(e.settings,"MASTERED")}: ${l}</span>
    <span>${a(e.settings,"RATE")}: ${c}%</span>
    <span class="rd-achievement" style="margin-left:auto;color:${vn(c).color}">${ro(vn(c).icon)} ${a(e.settings,vn(c).key)}</span>
  </div>`,e.title&&(p+=`<div class="rd-title">${e.title}</div>`);let d=Math.min(e.columns||4,6);return p+=`<div class="rd-grid" style="grid-template-columns:repeat(${d},1fr)">`,t.forEach((h,u)=>{let x=s(h.srMeta),m=Xh(n,e.style,u),g="border:2px solid var(--background-modifier-border)",b="rd-card";x==="due"?(g="border:2px solid #f59e0b",b+=" rd-due-glow"):x==="learning"?g="border:2px solid #3b82f6":x==="mastered"&&(g="border:2px solid #22c55e"),p+=`<div class="${b}" data-index="${u}" style="${g};border-radius:12px;cursor:pointer;position:relative;overflow:hidden;aspect-ratio:2/3;transition:transform .18s,box-shadow .18s">
      <div class="rd-card-face" style="position:absolute;inset:0;background:linear-gradient(135deg,${m.g1},${m.g2});display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px">
        <span style="font-size:28px">${h.emoji||ro("card")}</span>
      </div>
      <span style="position:absolute;top:6px;right:8px;font-size:12px;font-weight:700">${Zh(x,a,e.settings)}</span>
    </div>`}),p+="</div></div>",p}so.exports={getDeckAchievement:vn,renderReviewDeck:eu}});var lo=Y((Eg,oo)=>{"use strict";var wn=require("obsidian"),{loadSrData:tu,saveSrData:nu}=Se();function au(t,e,n,a,s){let i=new wn.Modal(t);i.modalEl.style.cssText="width:min(92vw,600px);max-width:none;max-height:90vh;overflow:hidden;display:flex;flex-direction:column",i.contentEl.style.cssText="padding:0;flex:1;display:flex;flex-direction:column;overflow:hidden;min-height:0";let r=0,o=!1,l=e.srMeta||null;function c(){let p=s.computeFsrs(1,l,a),d=s.computeFsrs(2,l,a),h=s.computeFsrs(3,l,a),u=s.computeFsrs(4,l,a);i.contentEl.empty();let x=i.contentEl.createEl("div",{attr:{style:"padding:28px 28px 24px;overflow-y:auto;flex:1;display:flex;flex-direction:column;gap:14px"}}),m=x.createEl("div",{attr:{style:"font-size:17px;font-weight:700;color:var(--text-normal);line-height:1.55"}});wn.MarkdownRenderer.renderMarkdown(e.front||"",m,e.notePath||"",null),x.createEl("div",{attr:{style:"border-top:1px dashed var(--background-modifier-border)"}});let g=["","#dbeafe","#fef9c3","#dcfce7"],b=["","L1 Active Recall","L2 Contextual Anchor","L3 Narrowing Hint"],f=[null,e.hint_l1,e.hint_l2,e.hint_l3];for(let w=1;w<=r;w++){let k=x.createEl("div",{attr:{style:`border-radius:10px;padding:13px 16px;background:${g[w]};border:1px solid rgba(0,0,0,0.07)`}});k.createEl("div",{attr:{style:"font-size:10px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:.08em;margin-bottom:6px"},text:b[w]});let E=k.createEl("div",{attr:{style:"font-size:14px;color:#1f2937;line-height:1.65"}});wn.MarkdownRenderer.renderMarkdown(f[w]||s.translate(a,"NO_HINT"),E,e.notePath||"",null)}if(o){let w=x.createEl("div",{attr:{style:"font-size:15px;line-height:1.7;color:var(--text-normal);background:var(--background-secondary);border-radius:10px;padding:15px 17px;border:1px solid var(--background-modifier-border)"}});wn.MarkdownRenderer.renderMarkdown(e.back||"",w,e.notePath||"",null)}x.createEl("div",{attr:{style:"flex:1"}});let v=x.createEl("div",{attr:{style:"display:flex;flex-direction:column;gap:8px"}});if(o){let w=v.createEl("div",{attr:{class:"rd-rating-row"}});[{q:1,label:s.translate(a,"AGAIN"),calc:p,color:"#ef4444"},{q:2,label:s.translate(a,"HARD"),calc:d,color:"#f59e0b"},{q:3,label:s.translate(a,"GOOD"),calc:h,color:"#3b82f6"},{q:4,label:s.translate(a,"EASY"),calc:u,color:"#22c55e"}].forEach(k=>{let E=w.createEl("button",{attr:{class:"rd-rating-btn",style:`background:${k.color}`}});E.createEl("span",{text:k.label}),E.createEl("small",{text:`${k.calc.interval}${s.translate(a,"DAYS")}`}),E.addEventListener("click",async()=>{let S=s.computeFsrs(k.q,l,a);if(e.notePath)try{let _=await tu(t.vault.adapter,e.notePath);_[e.front]={due:S.due,interval:S.interval,stability:S.stability,difficulty:S.difficulty,state:S.state,repetitions:S.repetitions},await nu(t.vault.adapter,e.notePath,_)}catch(_){console.error("review-deck: write FSRS meta failed",_)}e.srMeta={due:S.due,interval:S.interval,stability:S.stability,difficulty:S.difficulty,state:S.state,repetitions:S.repetitions},e.srComment="",l=e.srMeta,i.close(),n&&n()})});return}(e.hint_l1||e.hint_l2||e.hint_l3)&&r<3&&v.createEl("button",{attr:{class:"rd-action-btn",style:"border:1.5px solid var(--interactive-accent);background:transparent;color:var(--interactive-accent)"},text:s.translate(a,r===0?"RECALL":"HINT_NEXT")}).addEventListener("click",()=>{r+=1,c()}),v.createEl("button",{attr:{class:"rd-action-btn",style:"border:none;background:var(--interactive-accent);color:white"},text:s.translate(a,"SHOW_ANSWER")}).addEventListener("click",()=>{o=!0,c()})}i.open(),c()}oo.exports={openReviewCardModal:au}});var _e=require("obsidian"),Be=_e.moment;function ru(t){try{let e=t.split(`
`),n={theme:"sky-island",layout:"linear",nodes:[]},a=null,s=!1;for(let i of e){let r=i.trim();if(!(!r||r.startsWith("#"))){if(r.startsWith("theme:"))n.theme=r.split(":")[1].trim();else if(r.startsWith("layout:"))n.layout=r.split(":")[1].trim();else if(r==="nodes:")s=!0;else if(s&&r.startsWith("- id:"))a={id:r.split(":")[1].trim(),requires:[]},n.nodes.push(a);else if(s&&a){if(r.startsWith("label:")&&(a.label=r.slice(6).trim()),r.startsWith("emoji:")&&(a.emoji=r.slice(6).trim()),r.startsWith("note:")&&(a.note=r.slice(5).trim()),r.startsWith("desc:")&&(a.desc=r.slice(5).trim()),r.startsWith("completed:")&&(a.completed=r.includes("true")),r.startsWith("boss:")&&(a.boss=r.includes("true")),r.startsWith("progress:")&&(a.progress=parseInt(r.split(":")[1])),r.startsWith("requires:")){let o=r.match(/\[([^\]]+)\]/);o&&(a.requires=o[1].split(",").map(l=>l.trim()))}r.startsWith("x:")&&(a.x=parseInt(r.split(":")[1])),r.startsWith("y:")&&(a.y=parseInt(r.split(":")[1]))}}}return n}catch(e){return null}}var co={"sky-island":{bg:"linear-gradient(180deg, #87CEEB 0%, #b8e4f7 60%, #d4f0ff 100%)",completed:"radial-gradient(circle at 35% 35%, #6ee96e, #2d8a2d)",active:"radial-gradient(circle at 35% 35%, #ffe066, #c8960a)",locked:"radial-gradient(circle at 35% 35%, #b0c4de, #607080)",boss:"radial-gradient(circle at 35% 35%, #da8fff, #8a2be2)",pathColor:"#5b9bd5",textColor:"#1a3a5c"},minimal:{bg:"#ffffff",completed:"#4caf50",active:"#ff9800",locked:"#9e9e9e",boss:"#9c27b0",pathColor:"#bdbdbd",textColor:"#333333"}};function Wa(t){return co[t]||co["sky-island"]}function su(t,e){if(e!=="world"){if(e==="linear")t.forEach((n,a)=>{let s=Math.floor(a/4),i=s%2===0?a%4:3-a%4;n._x=60+i*200,n._y=60+s*180});else if(e==="tree"){let n={};t.forEach(a=>{let s=a.requires.length===0?0:Math.max(...a.requires.map(i=>{let r=t.find(o=>o.id===i);return r?(r._level||0)+1:0}));a._level=s,n[s]||(n[s]=[]),n[s].push(a)}),Object.entries(n).forEach(([a,s])=>{s.forEach((i,r)=>{i._x=60+parseInt(a)*220,i._y=60+r*180-(s.length-1)*90})})}}}function Nt(t,e){if(t.completed)return"completed";if(t.progress>0)return"active";let n=t.requires.every(a=>{var s;return(s=e.find(i=>i.id===a))==null?void 0:s.completed});return t.requires.length>0&&!n?"locked":"available"}function iu(t,e){let n=Wa(e),a="";return t.forEach(s=>{s.requires.forEach(i=>{let r=t.find(h=>h.id===i);if(!r)return;let o=(r._x||r.x||0)+55,l=(r._y||r.y||0)+55,c=(s._x||s.x||0)+55,p=(s._y||s.y||0)+55,d=(o+c)/2;a+=`<path d="M${o},${l} Q${d},${l} ${d},${(l+p)/2} Q${d},${p} ${c},${p}"
        stroke="${n.pathColor}" stroke-width="3" stroke-dasharray="8 8"
        fill="none" stroke-linecap="round" opacity="0.75"/>`})}),a}function ou(t){let e=Wa(t.theme);su(t.nodes,t.layout);let n=300,a=300;t.nodes.forEach(l=>{n=Math.max(n,(l._x||l.x||0)+160),a=Math.max(a,(l._y||l.y||0)+160)});let s=t.nodes.map((l,c)=>{var p,d,h,u;let x=Nt(l,t.nodes),m=(d=(p=l._x)!=null?p:l.x)!=null?d:0,g=(u=(h=l._y)!=null?h:l.y)!=null?u:0,b=l.emoji||"?\u599D",f=x==="completed"?"OK":x==="active"?"GO":x==="locked"?"LOCK":"",v=x==="active"?"lp-pulse":"",y=l.boss?e.boss:x==="completed"?e.completed:x==="active"?e.active:e.locked;return`
      <div class="lp-node ${v}" style="left:${m}px;top:${g}px" data-idx="${c}">
        <div class="lp-island" style="background:${y}">
          <span class="lp-emoji">${b}</span>
          ${f?`<span class="lp-badge">${f}</span>`:""}
        </div>
        <div class="lp-label" style="color:${e.textColor}">${l.label}</div>
        ${x==="active"&&l.progress?`<div class="lp-mini-bar"><div style="width:${l.progress}%;background:${e.pathColor}"></div></div>`:""}
      </div>`}).join(""),i=iu(t.nodes,t.theme),r=t.nodes.find(l=>Nt(l,t.nodes)==="active")||t.nodes[0],o=Nt(r,t.nodes);return`
    <div class="lp-wrap" style="--lp-bg:${e.bg}">
      <div class="lp-clouds">
        <div class="lp-cloud" style="top:30px;left:5%"></div>
        <div class="lp-cloud" style="top:50px;right:8%;transform:scale(0.8)"></div>
        <div class="lp-cloud" style="top:15px;left:38%;transform:scale(0.6)"></div>
      </div>
      <div class="lp-map" style="height:${a+40}px">
        <svg style="position:absolute;inset:0;width:100%;height:100%;pointer-events:none"
          viewBox="0 0 ${n} ${a}" preserveAspectRatio="xMidYMid meet">
          ${i}
        </svg>
        ${s}
      </div>
      <div class="lp-panel">
        <h3 class="lp-panel-title">${(r==null?void 0:r.label)||""}</h3>
        <p class="lp-panel-desc">${(r==null?void 0:r.desc)||"Select a node to see details."}</p>
        <button class="lp-btn" ${o==="locked"?"disabled":""}>
          ${o==="locked"?"Locked":"Open"}
        </button>
        <div class="lp-progress-row">
          <div class="lp-bar-bg">
            <div class="lp-bar-fill" style="width:${r!=null&&r.completed?100:(r==null?void 0:r.progress)||0}%;background:${e.pathColor}"></div>
          </div>
          <span class="lp-progress-text">${r!=null&&r.completed?"Completed":r!=null&&r.progress?r.progress+"% done":"Not started"}</span>
        </div>
      </div>
    </div>`}var lu=nr(),Je=[{id:"ch1-why",titleEn:"Why Harness?",titleZh:"Why Harness?",emoji:"MAP",themeColor:"blue",summary:"A strong model alone is not enough. Reliable agents need structure around the model so work stays consistent, testable, and recoverable.",keyPoints:[{title:"Context drift",why:"Agents fail when the right context is missing or mixed with irrelevant detail.",example:"A coding agent edits the wrong file because the repo constraints were never loaded."},{title:"Weak self-checking",why:"Without checks, an agent often assumes it is correct and moves on too early.",example:"The patch compiles locally but breaks the real workflow because no verification step ran."},{title:"Harness as control layer",why:"A harness adds instructions, guardrails, state, and feedback loops around the model.",example:"AGENTS.md, tests, and progress files keep the agent aligned with the task."}],realWorld:"Teams using structured agent workflows typically get more predictable output than teams relying on prompts alone."},{id:"ch2-three-pillars",titleEn:"Three Pillars",titleZh:"Three Pillars",emoji:"TRI",themeColor:"green",summary:"Harness engineering usually rests on three pillars: context engineering, architectural constraints, and feedback loops.",keyPoints:[{title:"Context engineering",why:"Give the agent the minimum correct context at the correct time.",example:"Load specs, current task state, and file ownership before asking for edits."},{title:"Architectural constraints",why:"Clear boundaries reduce destructive or low-quality actions.",example:"Require non-interactive git usage and forbid unsafe resets."},{title:"Feedback loops",why:"Verification closes the gap between looks done and actually works.",example:"Run syntax checks and targeted tests before declaring success."}],realWorld:"The biggest reliability jump often comes from adding lightweight verification rather than adding more prompt text."},{id:"ch3-principles",titleEn:"Design Principles",titleZh:"Design Principles",emoji:"RULE",themeColor:"amber",summary:"Good harnesses do not try to micromanage every step. They shape decisions so the agent can act quickly without drifting.",keyPoints:[{title:"Specify what and why",why:"Tell the agent the goal and constraints clearly; avoid over-prescribing every keystroke.",example:"Fix the Review Deck scan default and preserve migration behavior."},{title:"Make failure visible",why:"If something breaks, the system should expose it early.",example:"A syntax check catches broken strings before the plugin reaches runtime."},{title:"Prefer observable progress",why:"Visible state helps both humans and agents recover from interruptions.",example:"A now.md note records product decisions and implementation status."}],realWorld:"The best agent systems feel boring in production: fewer surprises, clearer boundaries, faster recovery."},{id:"ch4-scale",titleEn:"Scale & Teams",titleZh:"Scale and Teams",emoji:"TEAM",themeColor:"purple",summary:"As more agents or teammates join, coordination matters more than raw model quality.",keyPoints:[{title:"Shared rules",why:"Teams need one source of truth for conventions and safety rules.",example:"AGENTS.md and PROJECT_TRUTH.md define workflow expectations."},{title:"Scoped ownership",why:"Parallel work is safer when responsibilities are explicit.",example:"One agent owns docs while another owns runtime UI fixes."},{title:"Reviewable outputs",why:"People must be able to inspect and trust what the agent changed.",example:"Small diffs and reproducible checks beat giant opaque rewrites."}],realWorld:"Scaling agents is usually a coordination problem before it becomes a model problem."},{id:"ch5-future",titleEn:"The Future",titleZh:"The Future",emoji:"NEXT",themeColor:"slate",isBoss:!0,summary:"The frontier is moving from prompt better toward design better systems around the model.",keyPoints:[{title:"Model plus system",why:"Capability comes from the combination of model, tools, memory, and verification.",example:"A smaller model with a good harness can outperform a larger model used carelessly."},{title:"Long-running workflows",why:"Persistent state and checkpointing matter for multi-step work.",example:"A project note preserves decisions across debugging sessions."},{title:"Human steering",why:"Humans should define direction and quality bars; agents should execute within them.",example:"The user sets product behavior, the agent implements and verifies it."}],realWorld:"Reliable agent products will be differentiated less by raw model access and more by workflow design."}],Ua={blue:{g1:"#93c5fd",g2:"#3b82f6",depth:"#1e3a8a",light:"#bfdbfe"},green:{g1:"#86efac",g2:"#22c55e",depth:"#14532d",light:"#bbf7d0"},amber:{g1:"#fde047",g2:"#f59e0b",depth:"#78350f",light:"#fef08a"},purple:{g1:"#d8b4fe",g2:"#a855f7",depth:"#581c87",light:"#e9d5ff"},slate:{g1:"#cbd5e1",g2:"#64748b",depth:"#0f172a",light:"#e2e8f0"}},ct=[{cx:100,cy:155},{cx:300,cy:255},{cx:500,cy:95},{cx:700,cy:255},{cx:900,cy:145}];function cu(){let t=`M ${ct[0].cx} ${ct[0].cy} `;for(let e=0;e<ct.length-1;e++){let n=ct[e],a=ct[e+1],s=a.cx-n.cx;t+=`C ${n.cx+s*.65} ${n.cy}, ${a.cx-s*.65} ${a.cy}, ${a.cx} ${a.cy} `}return t}function po(t){let e=cu(),n="",a="";return Je.forEach((s,i)=>{let r=Ua[s.themeColor];n+=`
      <radialGradient id="hglow${i}" cx="50%" cy="50%" r="50%">
        <stop offset="0%" stop-color="${r.light}" stop-opacity=".7"/>
        <stop offset="100%" stop-color="${r.light}" stop-opacity="0"/>
      </radialGradient>
      <linearGradient id="hsurf${i}" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0%" stop-color="${r.g1}"/>
        <stop offset="100%" stop-color="${r.g2}"/>
      </linearGradient>`}),Je.forEach((s,i)=>{let{cx:r,cy:o}=ct[i],l=i===t,c=i<t,p=Ua[s.themeColor];a+=`<g class="hm-island" data-index="${i}" style="cursor:pointer">`,l&&(a+=`<ellipse cx="${r}" cy="${o+5}" rx="70" ry="38" fill="url(#hglow${i})"/>`),a+=`<ellipse cx="${r}" cy="${o+17}" rx="44" ry="9" fill="${p.depth}" opacity=".85"/>`,a+=`<ellipse cx="${r}" cy="${o+8}" rx="44" ry="12" fill="url(#hsurf${i})"
      stroke="${l?"rgba(255,255,255,0.85)":"rgba(255,255,255,0.2)"}"
      stroke-width="${l?2:.5}"/>`,a+=`<text x="${r}" y="${l?o-48:o-42}" text-anchor="middle" dominant-baseline="middle"
      font-size="${l?34:28}" style="user-select:none">${s.emoji}</text>`,c&&(a+=`
      <circle cx="${r+28}" cy="${o-44}" r="10" fill="#22c55e" stroke="white" stroke-width="1.5"/>
      <text x="${r+28}" y="${o-44}" text-anchor="middle" dominant-baseline="middle"
            fill="white" font-size="11" font-weight="700">GO</text>`),l&&(a+=`
      <rect x="${r-28}" y="${o-78}" width="56" height="18" rx="9" fill="#0f172a" stroke="#4ade80" stroke-width="1.5"/>
      <circle cx="${r-16}" cy="${o-69}" r="3" fill="#4ade80"/>
      <text x="${r+2}" y="${o-69}" text-anchor="middle" dominant-baseline="middle"
            fill="white" font-size="9" font-weight="700">Current</text>`),a+=`
      <rect x="${r-52}" y="${o+28}" width="104" height="34" rx="17"
            fill="rgba(255,255,255,0.93)" stroke="${l?p.g2:"rgba(255,255,255,0.6)"}"
            stroke-width="${l?2:1}"/>
      <text x="${r}" y="${o+40}" text-anchor="middle" dominant-baseline="middle"
        fill="#1e293b" font-size="11" font-weight="700">${s.titleEn}</text>
      <text x="${r}" y="${o+53}" text-anchor="middle" dominant-baseline="middle"
        fill="#64748b" font-size="9">${s.titleZh}</text>`,a+="</g>"}),`
    <div style="position:relative;width:100%;border-radius:16px;border:1px solid var(--background-modifier-border);
                box-shadow:0 10px 30px -10px rgba(0,0,0,0.15);overflow:hidden">
      <div style="width:100%;overflow-x:auto;overflow-y:hidden;scrollbar-width:thin">
        <div style="min-width:1100px;background:linear-gradient(180deg,#87ceeb 0%,#b8e0f7 35%,#7ec8e3 65%,#a8d8ea 100%)">
          <svg viewBox="0 0 1100 420" style="display:block;width:100%;height:auto" xmlns="http://www.w3.org/2000/svg">
            <defs>${n}</defs>
            <path d="${e}" fill="none" stroke="white"   stroke-width="5" stroke-dasharray="10 15" opacity=".45"/>
            <path d="${e}" fill="none" stroke="#67e8f9" stroke-width="3" stroke-dasharray="10 15" opacity=".6"/>
            ${a}
          </svg>
        </div>
      </div>
    </div>`}function ho(t,e){let n=new _e.Modal(t),a=e;function s(){let i=Je[a],r=Ua[i.themeColor];n.contentEl.empty(),n.modalEl.style.cssText="width:min(92vw,820px);max-width:none;max-height:90vh;overflow:hidden;display:flex;flex-direction:column",n.contentEl.style.cssText="padding:0;flex:1;display:flex;flex-direction:column;overflow:hidden;min-height:0",n.contentEl.createEl("div",{attr:{style:"height:6px;background:var(--background-modifier-border);flex-shrink:0"}}).createEl("div",{attr:{style:`height:100%;border-radius:0 4px 4px 0;width:${(a+1)/Je.length*100}%;background:linear-gradient(90deg,${r.g1},${r.g2});transition:width .5s`}});let o=n.contentEl.createEl("div",{attr:{style:"padding:28px 32px;overflow-y:auto;flex:1;min-height:0"}}),l=o.createEl("div",{attr:{style:"display:flex;align-items:flex-start;gap:16px;margin-bottom:16px"}});l.createEl("span",{text:i.emoji,attr:{style:"font-size:36px;flex-shrink:0"}});let c=l.createEl("div");c.createEl("h2",{text:i.titleEn,attr:{style:"font-size:22px;font-weight:800;margin:0;line-height:1.3;color:var(--text-normal)"}}),c.createEl("p",{text:`Chapter ${a+1} of ${Je.length} \xB7 ${i.titleZh}`,attr:{style:"margin:4px 0 0;font-size:12px;color:var(--text-muted)"}}),o.createEl("p",{text:i.summary,attr:{style:"font-size:14px;line-height:1.7;color:var(--text-normal);margin-bottom:24px"}}),o.createEl("h3",{text:"Key takeaways",attr:{style:"font-size:11px;font-weight:700;color:var(--text-faint);letter-spacing:.08em;text-transform:uppercase;margin-bottom:16px"}}),i.keyPoints.forEach((g,b)=>{let f=o.createEl("div",{attr:{style:"border-radius:12px;border:1px solid var(--background-modifier-border);background:var(--background-secondary);padding:16px 20px;margin-bottom:12px"}}),v=f.createEl("div",{attr:{style:"display:flex;align-items:flex-start;gap:12px;margin-bottom:8px"}});v.createEl("span",{text:String(b+1),attr:{style:"flex-shrink:0;width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;background:var(--interactive-accent);color:white"}}),v.createEl("p",{text:g.title,attr:{style:"font-size:15px;font-weight:700;margin:0;color:var(--text-normal)"}}),f.createEl("p",{text:g.why,attr:{style:"font-size:14px;line-height:1.65;color:var(--text-normal);margin:0 0 12px 36px"}});let y=f.createEl("div",{attr:{style:"margin-left:36px;border-radius:8px;border:1px solid var(--background-modifier-border);background:var(--background-primary);padding:12px 16px"}});y.createEl("span",{text:"E.g. ",attr:{style:"font-size:10px;font-weight:700;color:var(--text-faint);letter-spacing:.08em;text-transform:uppercase"}}),y.createEl("span",{text:g.example,attr:{style:"font-size:13px;line-height:1.65;color:var(--text-normal);white-space:pre-wrap"}})});let p=o.createEl("div",{attr:{style:"margin-bottom:32px;border-radius:12px;border:1px solid var(--interactive-accent);background:var(--background-secondary);padding:16px 20px"}}),d=p.createEl("div",{attr:{style:"display:flex;align-items:center;gap:8px;margin-bottom:8px"}});d.createEl("span",{text:"?\uE5C4"}),d.createEl("span",{text:"Real-world insight",attr:{style:"font-size:10px;font-weight:700;color:var(--interactive-accent);letter-spacing:.08em;text-transform:uppercase"}}),p.createEl("p",{text:i.realWorld,attr:{style:"font-size:14px;line-height:1.7;color:var(--text-normal);margin:0"}});let h=o.createEl("div",{attr:{style:"display:flex;align-items:center;justify-content:space-between;border-top:1px solid var(--background-modifier-border);padding-top:20px"}}),u=h.createEl("button",{text:"Prev",attr:{style:"border-radius:9999px;padding:8px 16px;font-size:14px;font-weight:600;color:var(--text-muted);background:transparent;border:none;cursor:pointer;visibility:"+(a===0?"hidden":"visible")}}),x=h.createEl("div",{attr:{style:"display:flex;gap:6px"}});Je.forEach((g,b)=>{x.createEl("button",{attr:{style:`height:8px;border-radius:9999px;border:none;cursor:pointer;transition:all .3s;width:${b===a?"24px":"8px"};background:${b===a?"var(--interactive-accent)":"var(--background-modifier-border)"}`}}).addEventListener("click",()=>{a=b,s()})});let m=h.createEl("button",{text:a===Je.length-1?"Done":"Next",attr:{style:"border-radius:9999px;padding:8px 16px;font-size:14px;font-weight:700;color:white;border:none;cursor:pointer;background:linear-gradient(135deg,"+r.g1+","+r.g2+")"}});u.addEventListener("click",()=>{a>0&&(a--,s())}),m.addEventListener("click",()=>{a<Je.length-1?(a++,s()):n.close()})}n.open(),s()}var tt={ocean:{colors:[{g1:"#93c5fd",g2:"#3b82f6",depth:"#1e3a8a",light:"#bfdbfe"},{g1:"#6ee7b7",g2:"#10b981",depth:"#064e3b",light:"#d1fae5"},{g1:"#a5b4fc",g2:"#6366f1",depth:"#312e81",light:"#e0e7ff"},{g1:"#60a5fa",g2:"#1d4ed8",depth:"#1e3a8a",light:"#dbeafe"},{g1:"#34d399",g2:"#059669",depth:"#064e3b",light:"#d1fae5"}]},forest:{colors:[{g1:"#86efac",g2:"#22c55e",depth:"#14532d",light:"#dcfce7"},{g1:"#fde047",g2:"#f59e0b",depth:"#78350f",light:"#fef9c3"},{g1:"#a3e635",g2:"#65a30d",depth:"#365314",light:"#ecfccb"},{g1:"#fdba74",g2:"#ea580c",depth:"#7c2d12",light:"#ffedd5"},{g1:"#4ade80",g2:"#15803d",depth:"#14532d",light:"#dcfce7"}]},galaxy:{colors:[{g1:"#d8b4fe",g2:"#a855f7",depth:"#581c87",light:"#f3e8ff"},{g1:"#818cf8",g2:"#4338ca",depth:"#1e1b4b",light:"#e0e7ff"},{g1:"#f9a8d4",g2:"#ec4899",depth:"#831843",light:"#fce7f3"},{g1:"#c084fc",g2:"#7c3aed",depth:"#4c1d95",light:"#ede9fe"},{g1:"#94a3b8",g2:"#475569",depth:"#0f172a",light:"#f1f5f9"}]}};function dt(t){let e=t.match(/\[([^\]]*)\]/);return e?e[1].split(",").map(n=>n.trim()).filter(Boolean):[]}function uo(t){let e={version:1,style:"ocean",difficulty:"medium",nodes:[]},n=null,a=null,s=!1,i=!1,r=!1;for(let o of t.split(`
`)){let l=o.trim();if(!l)continue;let c=o.length-o.trimStart().length;if(!s){l.startsWith("version:")?e.version=parseInt(l.split(":")[1])||1:l.startsWith("style:")?e.style=l.slice(6).trim():l.startsWith("difficulty:")?e.difficulty=l.slice(11).trim():l==="nodes:"&&(s=!0);continue}if(l.startsWith("- id:")&&c<=2){a&&(n.points.push(a),a=null),n&&e.nodes.push(n),i=!1,r=!1,n={id:l.slice(5).trim(),emoji:"\u{1F4DD}",points:[],boss:!1};continue}if(n){if(l==="points:"){i=!0,r=!1;continue}if(l==="challenge:"){r=!0,i=!1,n.challenge={};continue}if(i){if(l.startsWith("- title:")&&c>=4){a&&n.points.push(a),a={title:l.slice(8).trim(),body:""};continue}if(a&&l.startsWith("body:")){a.body=l.slice(5).trim();continue}c<4&&!l.startsWith("-")&&!l.startsWith("body:")&&(i=!1)}if(r&&n.challenge){let p=n.challenge;if(l.startsWith("type:"))p.type=l.slice(5).trim();else if(l.startsWith("question:"))p.question=l.slice(9).trim();else if(l.startsWith("statement:"))p.statement=l.slice(10).trim();else if(l.startsWith("sentence:"))p.sentence=l.slice(9).trim();else if(l.startsWith("prompt:"))p.prompt=l.slice(7).trim();else if(l.startsWith("hint:"))p.hint=l.slice(5).trim();else if(l.startsWith("link:"))p.link=l.slice(5).trim();else if(l.startsWith("image:"))p.image=l.slice(6).trim();else if(l.startsWith("mode:"))p.mode=l.slice(5).trim();else if(l.startsWith("options:"))p.options=dt(l);else if(l.startsWith("items:"))p.items=dt(l);else if(l.startsWith("keywords:"))p.keywords=dt(l);else if(l.startsWith("answers:"))p.answers=dt(l);else if(l.startsWith("reveal_answer:"))p.reveal_answer=l.slice(14).trim()==="true";else if(l.startsWith("region_x:"))p.region_x=parseFloat(l.slice(9).trim());else if(l.startsWith("region_y:"))p.region_y=parseFloat(l.slice(9).trim());else if(l.startsWith("region_width:"))p.region_width=parseFloat(l.slice(13).trim());else if(l.startsWith("region_height:"))p.region_height=parseFloat(l.slice(14).trim());else if(l.startsWith("answer:")){let h=l.slice(7).trim();h==="true"?p.answer=!0:h==="false"?p.answer=!1:h.startsWith("[")?p.answer=dt(h).map(Number):/^-?\d+$/.test(h)?p.answer=parseInt(h,10):p.answer=h}else if(l==="pairs:")p.pairs=[];else if(l.startsWith("- [")&&Array.isArray(p.pairs)){let h=l.match(/\[([^\]]+)\]/);h&&p.pairs.push(h[1].split(",").map(u=>u.trim()))}let d=["type:","question:","statement:","sentence:","prompt:","hint:","link:","image:","mode:","options:","items:","keywords:","answers:","answer:","reveal_answer:","region_x:","region_y:","region_width:","region_height:","pairs:","- ["];c<=4&&!d.some(h=>l.startsWith(h))&&(r=!1)}!i&&!r&&(l.startsWith("title:")?n.title=l.slice(6).trim():l.startsWith("emoji:")?n.emoji=l.slice(6).trim():l.startsWith("icon:")?n.icon=l.slice(5).trim():l.startsWith("summary:")?n.summary=l.slice(8).trim():l.startsWith("insight:")?n.insight=l.slice(8).trim():l.startsWith("boss:")&&(n.boss=l.includes("true")))}}return a&&n&&n.points.push(a),n&&e.nodes.push(n),e}var En={easy:{showHint:!0,maxRetries:99,labelKey:"DIFF_EASY",color:"#22c55e"},medium:{showHint:!1,maxRetries:99,labelKey:"DIFF_MEDIUM",color:"#3b82f6"},hard:{showHint:!1,maxRetries:3,labelKey:"DIFF_HARD",color:"#ef4444"}};function ht(t){t.classList.remove("qm-shake"),t.offsetWidth,t.classList.add("qm-shake"),t.addEventListener("animationend",()=>t.classList.remove("qm-shake"),{once:!0})}function ja(t){return String(t!=null?t:"").trim().toLowerCase().replace(/\s+/g," ")}function kn(t){let e=[];return Array.isArray(t.answers)&&e.push(...t.answers),typeof t.answer=="string"&&t.answer.trim()&&e.push(t.answer.trim()),typeof t.sentence=="string"&&(t.sentence.match(/\{\{c\d+::(.*?)\}\}/g)||[]).forEach(n=>{let a=n.replace(/^\{\{c\d+::/,"").replace(/\}\}$/,"").trim();a&&e.push(a)}),Array.from(new Set(e.map(n=>String(n).trim()).filter(Boolean)))}function _n(t,e){let n=ja(t);return n?e.some(a=>{let s=ja(a);return n===s||n.includes(s)||s.includes(n)}):!1}function Dt(t,e){if(e){if(t.vault.getAbstractFileByPath(e)){t.workspace.openLinkText(e,"",!1);return}window.open("obsidian://open?file="+encodeURIComponent(e))}}function Ot(t,e){return t?t.replace(/\{\{c\d+::(.*?)\}\}/g,(n,a)=>e?'<span style="display:inline-block;padding:0 8px;border-radius:8px;background:#dcfce7;color:#166534;font-weight:700">'+a+"</span>":'<span style="display:inline-block;min-width:72px;padding:0 8px;border-bottom:2px solid var(--interactive-accent);color:var(--text-faint);font-weight:700;text-align:center">____</span>'):""}function Ya(t,e){return!e||!t.vault.getAbstractFileByPath(e)?null:t.vault.adapter.getResourcePath(e)}function Ka(t,e,n,a,s,i){let r=En[n]||En.medium,o=0,l=t.createEl("div",{attr:{style:"margin-top:20px;border-top:1px solid var(--background-modifier-border);padding-top:18px"}}),c=l.createEl("div",{attr:{style:"display:flex;align-items:center;gap:8px;margin-bottom:14px"}});c.createEl("span",{text:"\u{1F5FA}\uFE0F"}),c.createEl("span",{text:Oe(s,"TAB_QUEST"),attr:{style:"font-size:12px;font-weight:700;color:var(--text-faint);letter-spacing:.08em;text-transform:uppercase"}});let p=c.createEl("span",{text:Oe(s,r.labelKey),attr:{style:`font-size:11px;font-weight:700;padding:2px 8px;border-radius:20px;color:white;background:${r.color}`}}),d=null,h=null;function u(x){o++,ht(x),r.showHint&&e.hint&&!d&&(d=l.createEl("div",{attr:{style:"margin-top:10px;padding:10px 14px;border-radius:8px;background:var(--background-secondary);border:1px solid var(--background-modifier-border);font-size:13px;color:var(--text-muted)"}}),d.createEl("span",{text:"\u{1F4A1} "}),d.createEl("span",{text:e.hint})),r.maxRetries!==99&&o>=r.maxRetries&&(h||(h=l.createEl("div",{attr:{style:"margin-top:12px;padding:12px 16px;border-radius:10px;background:#fef2f2;border:1px solid #fca5a5;font-size:13px;color:#dc2626;display:flex;align-items:center;gap:10px"}}),h.createEl("span",{text:te(s)==="zh-tw"?"\u5DF2\u9054\u91CD\u8A66\u4E0A\u9650\uFF0C\u8ACB\u5148\u56DE\u53BB\u8907\u7FD2\u9019\u500B\u7AE0\u7BC0\u3002":"Retry limit reached. Review this chapter first."}),e.link&&h.createEl("button",{text:te(s)==="zh-tw"?"\u958B\u555F\u7B46\u8A18":"Open Note",attr:{style:"margin-left:auto;padding:6px 12px;border-radius:6px;background:#dc2626;color:white;border:none;cursor:pointer;font-size:12px;font-weight:600;white-space:nowrap"}}).addEventListener("click",()=>Dt(i,e.link))))}if(e.type==="quiz"&&e.options){l.createEl("p",{text:e.question||"",attr:{style:"font-size:14px;font-weight:600;color:var(--text-normal);margin-bottom:12px;line-height:1.5"}});let x=l.createEl("div",{attr:{style:"display:grid;grid-template-columns:1fr 1fr;gap:8px"}});e.options.forEach((m,g)=>{let b=x.createEl("button",{text:m,attr:{class:"qm-ch-btn"}});b.addEventListener("click",()=>{b.disabled||(g===e.answer?(b.style.background="#22c55e",b.style.color="white",b.style.borderColor="#22c55e",x.querySelectorAll("button").forEach(f=>f.disabled=!0),window.setTimeout(()=>a(),500)):u(b))})})}else if(e.type==="truefalse"){l.createEl("p",{text:e.statement||"",attr:{style:"font-size:14px;font-weight:600;color:var(--text-normal);margin-bottom:12px;line-height:1.5;padding:12px 16px;border-radius:8px;background:var(--background-secondary)"}});let x=l.createEl("div",{attr:{style:"display:flex;gap:12px"}});[{label:te(s)==="zh-tw"?"\u6B63\u78BA":"True",val:!0},{label:te(s)==="zh-tw"?"\u932F\u8AA4":"False",val:!1}].forEach(({label:m,val:g})=>{let b=x.createEl("button",{text:m,attr:{class:"qm-ch-btn",style:"width:auto;padding:10px 28px;font-size:14px"}});b.addEventListener("click",()=>{b.disabled||(g===e.answer?(b.style.background="#22c55e",b.style.color="white",b.style.borderColor="#22c55e",x.querySelectorAll("button").forEach(f=>f.disabled=!0),window.setTimeout(()=>a(),500)):u(b))})})}else if(e.type==="order"&&e.items){l.createEl("p",{text:e.question||(te(s)==="zh-tw"?"\u8ACB\u4F9D\u6B63\u78BA\u9806\u5E8F\u9EDE\u64CA\uFF1A":"Click in the correct order:"),attr:{style:"font-size:14px;font-weight:600;color:var(--text-normal);margin-bottom:10px"}});let x=l.createEl("div",{attr:{style:"display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px"}}),m=l.createEl("div",{attr:{style:"min-height:36px;padding:8px 12px;border-radius:8px;background:var(--background-secondary);font-size:13px;color:var(--text-muted);margin-bottom:8px;display:flex;gap:6px;flex-wrap:wrap;align-items:center"}});m.createEl("span",{text:te(s)==="zh-tw"?"\u9806\u5E8F\uFF1A":"Sequence:",attr:{style:"color:var(--text-faint);font-size:11px"}});let g=[],b=[];e.items.forEach((f,v)=>{let y=x.createEl("button",{text:f,attr:{class:"qm-ch-chip"}});b.push(y),y.addEventListener("click",()=>{y.disabled||(g.push(v),y.disabled=!0,m.createEl("span",{text:f,attr:{style:"padding:2px 10px;border-radius:12px;background:var(--interactive-accent);color:white;font-size:12px;font-weight:600"}}),g.length===e.items.length&&(Array.isArray(e.answer)&&g.every((w,k)=>w===e.answer[k])?(b.forEach(w=>{w.style.borderColor="#22c55e"}),window.setTimeout(()=>a(),500)):(b.forEach(w=>ht(w)),window.setTimeout(()=>{g.length=0,b.forEach(w=>{w.disabled=!1,w.style.borderColor=""}),m.querySelectorAll("span:not(:first-child)").forEach(w=>w.remove()),u(x)},700))))})})}else if(e.type==="match"&&e.pairs){l.createEl("p",{text:te(s)==="zh-tw"?"\u914D\u5C0D\u984C\uFF1A\u5148\u9EDE\u5DE6\u908A\uFF0C\u518D\u9EDE\u53F3\u908A\u3002":"Match the pairs: click left, then right.",attr:{style:"font-size:13px;color:var(--text-muted);margin-bottom:12px"}});let x=l.createEl("div",{attr:{style:"display:grid;grid-template-columns:1fr 1fr;gap:8px"}}),m=x.createEl("div",{attr:{style:"display:flex;flex-direction:column;gap:6px"}}),g=x.createEl("div",{attr:{style:"display:flex;flex-direction:column;gap:6px"}}),b=e.pairs.map((k,E)=>E);for(let k=b.length-1;k>0;k--){let E=Math.floor(Math.random()*(k+1));[b[k],b[E]]=[b[E],b[k]]}let f=null,v=0,y=[],w=[];e.pairs.forEach((k,E)=>{let S=m.createEl("button",{text:k[0],attr:{class:"qm-ch-btn","data-idx":E}});y.push(S),S.addEventListener("click",()=>{S.disabled||(y.forEach(_=>_.classList.remove("qm-selected")),f=E,S.classList.add("qm-selected"))})}),b.forEach(k=>{let E=g.createEl("button",{text:e.pairs[k][1],attr:{class:"qm-ch-btn","data-orig":k}});w.push(E),E.addEventListener("click",()=>{E.disabled||f===null||(f===k?([y[f],E].forEach(S=>{S.style.background="#22c55e",S.style.color="white",S.style.borderColor="#22c55e",S.disabled=!0,S.classList.remove("qm-selected")}),f=null,v++,v===e.pairs.length&&window.setTimeout(()=>a(),500)):(u(E),ht(y[f]),y[f].classList.remove("qm-selected"),f=null))})})}else if(e.type==="cloze"&&e.sentence){let x=function(){return b||(b=l.createEl("div",{attr:{style:"margin-top:10px;padding:10px 14px;border-radius:8px;background:var(--background-secondary);border:1px solid var(--background-modifier-border);font-size:13px;color:var(--text-muted)"}})),b},m=function(){v.empty(),v.appendChild(_e.sanitizeHTMLToDom(Ot(e.sentence,!0))),x().textContent=(te(s)==="zh-tw"?"\u6B63\u78BA\u7B54\u6848\uFF1A":"Correct answer: ")+g.join(" / "),f&&(f.disabled=!0)},g=kn(e),b=null,f=null,v=l.createEl("div",{attr:{style:"font-size:15px;line-height:1.8;color:var(--text-normal);margin-bottom:12px;padding:14px 16px;border-radius:10px;background:var(--background-secondary);border:1px solid var(--background-modifier-border)"}});v.appendChild(_e.sanitizeHTMLToDom(Ot(e.sentence,!1)));let y=l.createEl("div",{attr:{style:"display:flex;gap:8px;align-items:center;flex-wrap:wrap"}}),w=y.createEl("input",{attr:{type:"text",placeholder:te(s)==="zh-tw"?"\u8F38\u5165\u586B\u7A7A\u7B54\u6848":"Type the cloze answer",class:"qm-ch-input"}}),k=y.createEl("button",{text:te(s)==="zh-tw"?"\u9001\u51FA":"Submit",attr:{style:"padding:10px 20px;border-radius:8px;background:var(--interactive-accent);color:white;border:none;cursor:pointer;font-size:13px;font-weight:700;white-space:nowrap"}}),E=()=>{let S=w.value.trim();S&&(_n(S,g)?(w.style.borderColor="#22c55e",k.style.background="#22c55e",v.empty(),v.appendChild(_e.sanitizeHTMLToDom(Ot(e.sentence,!0))),x().textContent=te(s)==="zh-tw"?"\u7B54\u5C0D\u4E86":"Correct",w.disabled=!0,k.disabled=!0,f&&(f.disabled=!0),window.setTimeout(()=>a(),500)):(u(w),x().textContent=te(s)==="zh-tw"?"\u7B54\u6848\u4E0D\u6B63\u78BA\u3002\u4F60\u53EF\u4EE5\u518D\u8A66\u4E00\u6B21\uFF0C\u6216\u76F4\u63A5\u986F\u793A\u7B54\u6848\u3002":"That is not correct. Try once more or reveal the answer.",e.reveal_answer!==!1&&!f&&(f=y.createEl("button",{text:Oe(s,"SHOW_ANSWER"),attr:{style:"padding:10px 16px;border-radius:8px;background:#475569;color:white;border:none;cursor:pointer;font-size:13px;font-weight:700;white-space:nowrap"}}),f.addEventListener("click",m))))};k.addEventListener("click",E),w.addEventListener("keydown",S=>{S.key==="Enter"&&E()})}else if(e.type==="image-occlusion"&&e.image){let x=kn(e),m=Ya(i,e.image),g=null,b=null;if(!m)l.createEl("div",{text:te(s)==="zh-tw"?"\u627E\u4E0D\u5230\u5716\u7247\u6A94\u6848\uFF0C\u8ACB\u6AA2\u67E5\u5716\u7247\u8DEF\u5F91\u3002":"Image file not found. Check the image path.",attr:{style:"padding:12px 14px;border-radius:10px;background:#fef2f2;border:1px solid #fca5a5;color:#dc2626;font-size:13px"}});else{let f=function(){return b||(b=l.createEl("div",{attr:{style:"margin-top:10px;padding:10px 14px;border-radius:8px;background:var(--background-secondary);border:1px solid var(--background-modifier-border);font-size:13px;color:var(--text-muted)"}})),b},v=function(){k.style.background="rgba(34,197,94,0.82)",k.style.borderColor="#22c55e",k.style.color="#052e16",k.textContent=x.join(" / "),f().textContent=(te(s)==="zh-tw"?"\u6B63\u78BA\u7B54\u6848\uFF1A":"Correct answer: ")+x.join(" / "),g&&(g.disabled=!0)};e.prompt&&l.createEl("p",{text:e.prompt,attr:{style:"font-size:14px;font-weight:600;color:var(--text-normal);margin-bottom:12px;line-height:1.5"}});let y=l.createEl("div",{attr:{style:"position:relative;width:min(100%,760px);margin-bottom:12px;border-radius:14px;overflow:hidden;border:1px solid var(--background-modifier-border);background:var(--background-secondary)"}}),w=y.createEl("img",{attr:{src:m,alt:e.prompt||e.answer||"image occlusion",style:"display:block;width:100%;height:auto"}}),k=y.createEl("div",{attr:{style:"position:absolute;background:rgba(15,23,42,0.82);border:2px solid rgba(255,255,255,0.85);border-radius:10px;display:flex;align-items:center;justify-content:center;color:#f8fafc;font-weight:700;font-size:13px;text-align:center;padding:6px;box-sizing:border-box"}});k.textContent="\u5DF2\u906E\u7F69";let E=()=>{let T=w.naturalWidth||w.width||1,q=w.naturalHeight||w.height||1;k.style.left=(e.region_x||0)/T*100+"%",k.style.top=(e.region_y||0)/q*100+"%",k.style.width=(e.region_width||120)/T*100+"%",k.style.height=(e.region_height||60)/q*100+"%"};w.complete?E():w.addEventListener("load",E,{once:!0});let S=l.createEl("div",{attr:{style:"display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:12px"}});S.createEl("button",{text:Oe(s,"VIEW_SOURCE_IMAGE"),attr:{style:"padding:8px 14px;border-radius:8px;background:#334155;color:white;border:none;cursor:pointer;font-size:12px;font-weight:700"}}).addEventListener("click",()=>Dt(i,e.image)),e.link&&S.createEl("button",{text:Oe(s,"OPEN_SOURCE_NOTE"),attr:{style:"padding:8px 14px;border-radius:8px;background:#475569;color:white;border:none;cursor:pointer;font-size:12px;font-weight:700"}}).addEventListener("click",()=>Dt(i,e.link));let _=l.createEl("div",{attr:{style:"display:flex;gap:8px;align-items:center;flex-wrap:wrap"}}),C=_.createEl("input",{attr:{type:"text",placeholder:te(s)==="zh-tw"?"\u8F38\u5165\u88AB\u906E\u4F4F\u7684\u7B54\u6848":"Type the occluded answer",class:"qm-ch-input"}}),L=_.createEl("button",{text:te(s)==="zh-tw"?"\u9001\u51FA":"Submit",attr:{style:"padding:10px 20px;border-radius:8px;background:var(--interactive-accent);color:white;border:none;cursor:pointer;font-size:13px;font-weight:700;white-space:nowrap"}}),A=()=>{let T=C.value.trim();T&&(_n(T,x)?(C.style.borderColor="#22c55e",L.style.background="#22c55e",v(),f().textContent=te(s)==="zh-tw"?"\u7B54\u5C0D\u4E86":"Correct",C.disabled=!0,L.disabled=!0,window.setTimeout(()=>a(),500)):(u(C),f().textContent=te(s)==="zh-tw"?"\u7B54\u6848\u4E0D\u6B63\u78BA\u3002\u4F60\u53EF\u4EE5\u518D\u8A66\u4E00\u6B21\uFF0C\u6216\u76F4\u63A5\u986F\u793A\u7B54\u6848\u3002":"That is not correct. Try once more or reveal the answer.",e.reveal_answer!==!1&&!g&&(g=_.createEl("button",{text:Oe(s,"SHOW_ANSWER"),attr:{style:"padding:10px 16px;border-radius:8px;background:#475569;color:white;border:none;cursor:pointer;font-size:13px;font-weight:700;white-space:nowrap"}}),g.addEventListener("click",v))))};L.addEventListener("click",A),C.addEventListener("keydown",T=>{T.key==="Enter"&&A()})}}else if(e.type==="input"&&e.keywords){l.createEl("p",{text:e.question||"",attr:{style:"font-size:14px;font-weight:600;color:var(--text-normal);margin-bottom:12px;line-height:1.5"}});let x=l.createEl("div",{attr:{style:"display:flex;gap:8px"}}),m=x.createEl("input",{attr:{type:"text",placeholder:te(s)==="zh-tw"?"\u8F38\u5165\u95DC\u9375\u5B57\u7B54\u6848":"Type the keyword answer",class:"qm-ch-input"}}),g=x.createEl("button",{text:te(s)==="zh-tw"?"\u9001\u51FA":"Submit",attr:{style:"padding:10px 20px;border-radius:8px;background:var(--interactive-accent);color:white;border:none;cursor:pointer;font-size:13px;font-weight:700;white-space:nowrap"}}),b=()=>{let f=m.value.trim().toLowerCase();f&&(e.keywords.some(v=>f.includes(v.toLowerCase()))?(m.style.borderColor="#22c55e",g.style.background="#22c55e",m.disabled=!0,g.disabled=!0,window.setTimeout(()=>a(),500)):(ht(m),u(m)))};g.addEventListener("click",b),m.addEventListener("keydown",f=>{f.key==="Enter"&&b()})}}function Va(t,e){let n=tt[t]||tt.ocean;return n.colors[e%n.colors.length]}function Ja(t){if(t===0)return[];if(t===1)return[{cx:550,cy:260}];let e=[225,325,165,315,215,335,175,305],n=Math.min(220,Math.floor(950/(t-1)));return Array.from({length:t},(a,s)=>({cx:120+s*n,cy:e[s%e.length]}))}function go(t,e,n,a,s){let i=s.vault.configDir,r=s.vault.adapter.getResourcePath(i+"/plugins/engram-quest/assets/quest-map/bg.png"),o=s.vault.adapter.getResourcePath(i+"/plugins/engram-quest/assets/quest-map/platform.png"),l=i+"/plugins/engram-quest/assets/quest-map/icons/",c=Ja(t.length),p=Math.max(1100,t.length>1?c[t.length-1].cx+200:1100),d="";if(c.length>0){d=`M ${c[0].cx} ${c[0].cy} `;for(let u=0;u<c.length-1;u++){let x=c[u],m=c[u+1],g=m.cx-x.cx;d+=`C ${x.cx+g*.65} ${x.cy}, ${m.cx-g*.65} ${m.cy}, ${m.cx} ${m.cy} `}}let h=`
  <div class="qm-scroll-wrapper">
    <div class="qm-hybrid-container" style="background-image: url('${r}'); min-width: ${p}px;">
      <svg class="qm-svg-layer" viewBox="0 0 ${p} 520" preserveAspectRatio="xMinYMin slice">
        <filter id="pathGlow${e}" x="-20%" y="-100%" width="140%" height="300%">
          <feGaussianBlur stdDeviation="4" result="blur"/>
          <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
        </filter>
      <path d="${d}" fill="none" stroke="rgba(0,0,0,0.2)" stroke-width="8" stroke-linecap="round" transform="translate(0, 10)" />
      <path d="${d}" fill="none" stroke="rgba(255,255,255,0.4)" stroke-width="6" stroke-linecap="round" filter="url(#pathGlow${e})" />
      <path d="${d}" fill="none" stroke="#ffffff" stroke-width="3" stroke-dasharray="10 16" stroke-linecap="round" style="animation: dashFlow 1s linear infinite;" />
    </svg>
  `;return t.forEach((u,x)=>{let m=c[x].cx,g=c[x].cy,b=x===n,f=u.boss,v=a&&a.has(x)&&!b,y="";e==="forest"?y="qm-style-forest":e==="galaxy"&&(y="qm-style-galaxy");let w=`qm-island-group ${b?"qm-active":""} ${f?"qm-diff-boss":""} ${v?"qm-visited":""} ${y}`,k=20,E=-(x*.7)+"s",S=u.icon?s.vault.adapter.getResourcePath(l+u.icon):"",_=u.emoji||"?\u599D",C="";S?C=`<img src="${S}" class="qm-icon-prop ${f?"qm-icon-boss":""}" />`:C=`<div class="qm-emoji">${_}</div>`,h+=`
      <div class="${w}" data-index="${x}" style="left: ${m}px; top: ${g+k}px; animation-delay: ${E};">
        ${C}
        ${b?'<div class="qm-current-badge">Current</div>':""}
        ${v?'<div class="qm-visited-badge">\u2713</div>':""}
        <img src="${o}" class="qm-platform" />
        <div class="qm-label-wrap">${u.title||""}</div>
      </div>
    `}),h+="</div></div>",h}function mo(t,e,n,a,s,i){let r=new _e.Modal(t),o=n,l=s||"medium";function c(d){r.contentEl.style.opacity="0",r.contentEl.style.transition="opacity 0.12s",window.setTimeout(()=>{d(),r.contentEl.style.opacity="1"},120)}function p(){let d=e[o],h=Va(a,o);r.contentEl.empty(),r.modalEl.style.cssText="width:min(92vw,820px);max-width:none;max-height:90vh;overflow:hidden;display:flex;flex-direction:column",r.contentEl.style.cssText="padding:0;flex:1;display:flex;flex-direction:column;overflow:hidden;min-height:0",r.contentEl.createEl("div",{attr:{style:"height:6px;background:var(--background-modifier-border);flex-shrink:0"}}).createEl("div",{attr:{style:`height:100%;border-radius:0 4px 4px 0;width:${(o+1)/e.length*100}%;background:linear-gradient(90deg,${h.g1},${h.g2});transition:width .5s`}});let u=r.contentEl.createEl("div",{attr:{style:"padding:28px 32px;overflow-y:auto;flex:1;min-height:0"}}),x=u.createEl("div",{attr:{style:"display:flex;align-items:flex-start;gap:16px;margin-bottom:16px"}});x.createEl("span",{text:d.emoji,attr:{style:"font-size:36px;flex-shrink:0"}});let m=x.createEl("div");if(m.createEl("h2",{text:d.title||"",attr:{style:"font-size:22px;font-weight:800;margin:0;line-height:1.3;color:var(--text-normal)"}}),m.createEl("p",{text:te(i)==="zh-tw"?`\u7B2C ${o+1} / ${e.length} \u95DC`:`Chapter ${o+1} of ${e.length}`,attr:{style:"margin:4px 0 0;font-size:12px;color:var(--text-muted)"}}),d.summary&&u.createEl("p",{text:d.summary,attr:{style:"font-size:14px;line-height:1.7;color:var(--text-normal);margin-bottom:24px"}}),d.points&&d.points.length>0&&(u.createEl("h3",{text:te(i)==="zh-tw"?"\u91CD\u9EDE\u6574\u7406":"Key takeaways",attr:{style:"font-size:11px;font-weight:700;color:var(--text-faint);letter-spacing:.08em;text-transform:uppercase;margin-bottom:16px"}}),d.points.forEach((k,E)=>{let S=u.createEl("div",{attr:{style:"border-radius:12px;border:1px solid var(--background-modifier-border);background:var(--background-secondary);padding:16px 20px;margin-bottom:12px"}}),_=S.createEl("div",{attr:{style:"display:flex;align-items:flex-start;gap:12px;margin-bottom:8px"}});_.createEl("span",{text:String(E+1),attr:{style:`flex-shrink:0;width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;background:${h.g2};color:white`}}),_.createEl("p",{text:k.title,attr:{style:"font-size:15px;font-weight:700;margin:0;color:var(--text-normal)"}}),k.body&&S.createEl("p",{text:k.body,attr:{style:"font-size:14px;line-height:1.65;color:var(--text-normal);margin:8px 0 0 36px"}})})),d.insight){let k=u.createEl("div",{attr:{style:"margin-top:8px;margin-bottom:16px;border-radius:12px;border:1px solid var(--interactive-accent);background:var(--background-secondary);padding:16px 20px"}}),E=k.createEl("div",{attr:{style:"display:flex;align-items:center;gap:8px;margin-bottom:8px"}});E.createEl("span",{text:"TIP"}),E.createEl("span",{text:te(i)==="zh-tw"?"\u63D0\u793A":"Insight",attr:{style:"font-size:10px;font-weight:700;color:var(--interactive-accent);letter-spacing:.08em;text-transform:uppercase"}}),k.createEl("p",{text:d.insight,attr:{style:"font-size:14px;line-height:1.7;color:var(--text-normal);margin:0"}})}let g=!d.challenge,b=u.createEl("div",{attr:{style:"display:flex;align-items:center;justify-content:space-between;border-top:1px solid var(--background-modifier-border);padding-top:20px;margin-top:16px"}}),f=b.createEl("button",{text:te(i)==="zh-tw"?"\u4E0A\u4E00\u95DC":"Prev",attr:{style:"border-radius:9999px;padding:8px 16px;font-size:14px;font-weight:600;color:var(--text-muted);background:transparent;border:none;cursor:pointer;visibility:"+(o===0?"hidden":"visible")}}),v=b.createEl("div",{attr:{style:"display:flex;gap:6px;align-items:center"}});e.forEach((k,E)=>{v.createEl("button",{attr:{style:`height:8px;border-radius:9999px;border:none;cursor:pointer;transition:all .3s;width:${E===o?"24px":"8px"};background:${E===o?"var(--interactive-accent)":"var(--background-modifier-border)"}`}}).addEventListener("click",()=>{c(()=>{o=E,p()})})});let y=o===e.length-1?te(i)==="zh-tw"?"\u5B8C\u6210":"Done":te(i)==="zh-tw"?"\u4E0B\u4E00\u95DC":"Next",w=b.createEl("button",{text:y,attr:{style:"border-radius:9999px;padding:8px 16px;font-size:14px;font-weight:700;color:white;border:none;cursor:pointer;background:linear-gradient(135deg,"+h.g1+","+h.g2+");transition:opacity 0.2s;opacity:"+(g?"1":"0.35")}});w.disabled=!g,d.challenge&&Ka(u,d.challenge,l,()=>{g=!0,w.disabled=!1,w.style.opacity="1",w.style.transform="scale(1.05)",window.setTimeout(()=>w.style.transform="",300)},i,t),f.addEventListener("click",()=>{o>0&&c(()=>{o--,p()})}),w.addEventListener("click",()=>{if(o<e.length-1)c(()=>{o++,p()});else{let k=r.modalEl.createEl("div",{attr:{style:"position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;background:rgba(0,0,0,0.75);border-radius:inherit;z-index:99;animation:qm-celebrate-in 0.4s ease-out forwards"}});k.createEl("div",{text:"\u{1F389}",attr:{style:"font-size:64px;line-height:1;margin-bottom:16px"}}),k.createEl("div",{text:te(i)==="zh-tw"?"\u5168\u90E8\u5B8C\u6210\uFF01":"All complete!",attr:{style:"font-size:28px;font-weight:800;color:white;margin-bottom:8px"}}),k.createEl("div",{text:te(i)==="zh-tw"?`\u5DF2\u5B8C\u6210 ${e.length} \u500B\u95DC\u5361`:`Completed ${e.length} stages`,attr:{style:"font-size:14px;color:rgba(255,255,255,0.7)"}}),window.setTimeout(()=>{k.style.animation="qm-celebrate-out 0.4s ease-in forwards",window.setTimeout(()=>r.close(),400)},1e3)}})}r.open(),r.modalEl.style.animation="qm-modal-in 0.22s ease-out",p()}var{getLocale:du,t:pu,tAlt:hu,interpolate:kg,normalizeLocale:_g,DICT:Sg}=ge();function te(t){return du(t,Be&&Be.locale&&Be.locale())}function Oe(t,e,n){return pu(t,e,n,Be&&Be.locale&&Be.locale())}function Le(t,e,n){return hu(t,e,n,Be&&Be.locale&&Be.locale())}var{computeFsrs:pt,normalizeFsrsCard:Ag,retrievability:Tg,intervalFromRetention:Cg,initStability:Lg,initDifficulty:Rg,clampDifficulty:Ig,nextDifficulty:Mg,nextStabilityReview:qg,nextStabilityForgot:Ng,W:Dg,DECAY:Og,FACTOR:zg}=$t(),fo=/<!--SR:!(\d{4}-\d{2}-\d{2}),(\d+),([\d.]+),([\d.]+),(\d)-->/,bo=/<!--SR:!(\d{4}-\d{2}-\d{2}),(\d+),(\d+)-->/,Za=/<!--SR:![\d\-.,]+-->/;function xo(t){let e=t.match(fo);if(e)return{due:e[1],interval:parseInt(e[2]),stability:parseFloat(e[3]),difficulty:parseFloat(e[4]),state:parseInt(e[5]),repetitions:parseInt(e[2])<=1?1:2};let n=t.match(bo);if(!n)return null;let a=parseInt(n[2]),s=parseInt(n[3]),i=Math.max(.5,Math.round(a*.9*1e3)/1e3),r=Math.round(Math.min(10,Math.max(1,13-s/30))*1e3)/1e3,o=a>=6?2:1;return{due:n[1],interval:a,stability:i,difficulty:r,state:o,repetitions:a<=1?1:a<=6?2:3,_migratedFromSM2:!0}}function Qe(t){var e;if(!t||t.state===0)return"unseen";let n=new Date().toISOString().split("T")[0];return t.due<=n?"due":t.state===1||t.state===3?"learning":((e=t.stability)!=null?e:t.interval)>=21?"mastered":"learning"}function zt(t){let e=t.split(`
`),n=[];for(let a=0;a<e.length;a++){let s=e[a],i=s.indexOf("::");if(i<1)continue;let r=s.slice(0,i).trim(),o=s.slice(i+2).trim();if(!r||!o)continue;let l=null,c="";a+1<e.length&&Za.test(e[a+1])&&(c=e[a+1].trim(),l=xo(c)),n.push({front:r,back:o,emoji:"",hint_l1:"",hint_l2:"",hint_l3:"",srMeta:l,srComment:c,notePath:null})}return n}function yo(t){let e={tag:null,source:null,cards:null,style:"ocean",title:"",columns:4},n=t.split(`
`),a=!1,s=null;for(let i of n){let r=i.trim();if(!(!r||r.startsWith("#"))){if(r.startsWith("tag:")){e.tag=r.slice(4).trim(),a=!1;continue}if(r.startsWith("source:")){e.source=r.slice(7).trim(),a=!1;continue}if(r.startsWith("style:")){e.style=r.slice(6).trim(),a=!1;continue}if(r.startsWith("title:")){e.title=r.slice(6).trim(),a=!1;continue}if(r.startsWith("columns:")){e.columns=parseInt(r.slice(8))||4,a=!1;continue}if(r==="cards:"){e.cards=[],a=!0,s=null;continue}a&&(r.startsWith("- front:")?(s={front:r.slice(8).trim(),back:"",emoji:"",hint_l1:"",hint_l2:"",hint_l3:"",srMeta:null,srComment:"",notePath:null},e.cards.push(s)):s&&(r.startsWith("back:")&&(s.back=r.slice(5).trim()),r.startsWith("emoji:")&&(s.emoji=r.slice(6).trim()),r.startsWith("hint_l1:")&&(s.hint_l1=r.slice(8).trim()),r.startsWith("hint_l2:")&&(s.hint_l2=r.slice(8).trim()),r.startsWith("hint_l3:")&&(s.hint_l3=r.slice(8).trim())))}}return e}function Pt(t,e){e!=null&&e.cards&&t.forEach(n=>{let a=e.cards[n.front];a&&(n.hint_l1=a.l1||"",n.hint_l2=a.l2||"",n.hint_l3=a.l3||"")})}function vo(t,e){let n=tt[e.style]||tt.ocean,a=new Date().toISOString().split("T")[0],s=t.length,i=t.filter(p=>p.srMeta&&p.srMeta.due<=a).length,r=t.filter(p=>{var d;return p.srMeta&&p.srMeta.state===2&&((d=p.srMeta.stability)!=null?d:p.srMeta.interval)>=21}).length,o=s>0?Math.round(r/s*100):0,l='<div class="rd-wrap">';l+=`<div class="rd-stats">
    <span>\u{1F4DA} ${Le("TOTAL",e.settings)}: ${s}</span>
    <span>?\uE702 ${Le("DUE",e.settings)}: ${i}</span>
    <span>\u2705 ${Le("MASTERED",e.settings)}: ${r}</span>
    <span>\u{1F4C8} ${Le("RATE",e.settings)}: ${o}%</span>
  </div>`,e.title&&(l+=`<div class="rd-title">${e.title}</div>`);let c=Math.min(e.columns||4,6);return l+=`<div class="rd-grid" style="grid-template-columns:repeat(${c},1fr)">`,t.forEach((p,d)=>{let h=Qe(p.srMeta),u=n.colors[d%n.colors.length],x="border:2px solid var(--background-modifier-border)",m="";h==="due"?(x="border:2px solid #f59e0b",m='class="rd-card rd-due-glow"'):h==="learning"?x="border:2px solid #3b82f6":h==="mastered"&&(x="border:2px solid #22c55e"),m||(m='class="rd-card"');let g=h==="due"?"DUE":h==="mastered"?"MASTERED":h==="learning"?"LEARNING":"NEW";l+=`<div ${m} data-index="${d}" style="${x};border-radius:12px;cursor:pointer;position:relative;overflow:hidden;aspect-ratio:2/3;transition:transform .18s,box-shadow .18s">
      <div class="rd-card-face" style="position:absolute;inset:0;background:linear-gradient(135deg,${u.g1},${u.g2});display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px">
        <span style="font-size:28px">${p.emoji||"\u{1F4DD}"}</span>
      </div>
      <span style="position:absolute;top:6px;right:8px;font-size:14px">${g}</span>
    </div>`}),l+="</div></div>",l}function wo(t,e,n,a={}){let s=new _e.Modal(t);s.modalEl.style.cssText="width:min(92vw,600px);max-width:none;max-height:90vh;overflow:hidden;display:flex;flex-direction:column",s.contentEl.style.cssText="padding:0;flex:1;display:flex;flex-direction:column;overflow:hidden;min-height:0";let i=0,r=!1,o=e.srMeta||null,l=pt(1,o,a),c=pt(2,o,a),p=pt(3,o,a),d=pt(4,o,a);function h(){s.contentEl.empty();let u=s.contentEl.createEl("div",{attr:{style:"padding:28px 28px 24px;overflow-y:auto;flex:1;display:flex;flex-direction:column;gap:14px"}});u.createEl("div",{attr:{style:"font-size:17px;font-weight:700;color:var(--text-normal);line-height:1.55"}}).textContent=e.front,u.createEl("div",{attr:{style:"border-top:1px dashed var(--background-modifier-border)"}});let x=["","#dbeafe","#fef9c3","#dcfce7"],m=["","L1 \xB7 Active Recall","L2 \xB7 Contextual Anchor","L3 \xB7 Narrowing Hint"],g=[null,e.hint_l1,e.hint_l2,e.hint_l3];for(let f=1;f<=i;f++){let v=g[f],y=u.createEl("div",{attr:{style:`border-radius:10px;padding:13px 16px;background:${x[f]};border:1px solid rgba(0,0,0,0.07)`}});y.createEl("div",{attr:{style:"font-size:10px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:.08em;margin-bottom:6px"}}).textContent=m[f],y.createEl("div",{attr:{style:"font-size:14px;color:#1f2937;line-height:1.65"}}).textContent=v||Le("NO_HINT",a)}r&&(u.createEl("div",{attr:{style:"font-size:15px;line-height:1.7;color:var(--text-normal);background:var(--background-secondary);border-radius:10px;padding:15px 17px;border:1px solid var(--background-modifier-border)"}}).textContent=e.back),u.createEl("div",{attr:{style:"flex:1"}});let b=u.createEl("div",{attr:{style:"display:flex;flex-direction:column;gap:8px"}});if(r){let f=b.createEl("div",{attr:{class:"rd-rating-row"}});[{q:1,label:Le("AGAIN",a),calc:l,color:"#ef4444"},{q:2,label:Le("HARD",a),calc:c,color:"#f59e0b"},{q:3,label:Le("GOOD",a),calc:p,color:"#3b82f6"},{q:4,label:Le("EASY",a),calc:d,color:"#22c55e"}].forEach(v=>{let y=f.createEl("button",{attr:{class:"rd-rating-btn",style:`background:${v.color}`}});y.createEl("span").textContent=v.label,y.createEl("small").textContent=`${v.calc.interval}${Le("DAYS",a)}`,y.addEventListener("click",async()=>{let w=pt(v.q,o,a);if(e.notePath)try{let k=e.notePath.split("/").pop().replace(/\.md$/i,""),E=await ve.loadSrData(t.vault.adapter,k);E[e.front]={due:w.due,interval:w.interval,stability:w.stability,difficulty:w.difficulty,state:w.state,repetitions:w.repetitions},await ve.saveSrData(t.vault.adapter,k,E)}catch(k){console.error("review-deck: write FSRS meta failed",k)}e.srMeta={due:w.due,interval:w.interval,stability:w.stability,difficulty:w.difficulty,state:w.state,repetitions:w.repetitions},e.srComment="",s.close(),n&&n()})})}else{if((e.hint_l1||e.hint_l2||e.hint_l3)&&i<3){let v=b.createEl("button",{attr:{class:"rd-action-btn",style:"border:1.5px solid var(--interactive-accent);background:transparent;color:var(--interactive-accent)"}});v.textContent=Le(i===0?"RECALL":"HINT_NEXT",a),v.addEventListener("click",()=>{i++,h()})}let f=b.createEl("button",{attr:{class:"rd-action-btn",style:"border:none;background:var(--interactive-accent);color:white"}});f.textContent=Le("SHOW_ANSWER",a),f.addEventListener("click",()=>{r=!0,h()})}}s.open(),h()}function Eo(t,e){let n=(e||"").split(/[\s\n]+/).map(a=>a.replace(/^#/,"").trim().toLowerCase()).filter(Boolean);if(n.length===0)return null;for(let a of t){let s=a.replace(/^#/,""),i=s.toLowerCase();for(let r of n)if(i===r||i.startsWith(r+"/")){let o=s.slice(r.length).replace(/^\//,""),l=o?o.split("/"):[];return l.length>0?l[0]:r.split("/").pop()}}return null}async function uu(t,e={}){var n,a,s;let i=t.vault.getMarkdownFiles(),r={},o=new Date().toISOString().split("T")[0];for(let l of i){let c=await t.vault.read(l),p=zt(c);if(p.length===0)continue;let d=t.metadataCache.getFileCache(l),h=[];if(d!=null&&d.tags&&h.push(...d.tags.map(g=>String(g.tag).replace(/^#/,""))),(n=d==null?void 0:d.frontmatter)!=null&&n.tags){let g=d.frontmatter.tags;typeof g=="string"?h.push(...g.split(",").map(b=>b.trim())):Array.isArray(g)?h.push(...g.map(String)):h.push(String(g))}h=[...new Set(h.filter(Boolean))];let u=Eo(h,e.flashcardTags);if(!((a=e.enableSRScan)!=null&&a)&&!u)continue;let x=u||((s=l.parent)==null?void 0:s.name)||"flashcards";r[x]||(r[x]={name:x,cards:[]}),p.forEach(g=>{g.notePath=l.path});let m=l.name.replace(/\.md$/i,"");try{let g=await ve.loadSrData(t.vault.adapter,m);ve.mergeSrIntoCards(p,g)}catch(g){}try{let g=`engram-review/hints/${m}.json`;await t.vault.adapter.exists(g)&&Pt(p,JSON.parse(await t.vault.adapter.read(g)))}catch(g){}r[x].cards.push(...p)}return Object.values(r).map(l=>{let c=l.cards.filter(h=>Qe(h.srMeta)==="due").length,p=l.cards.filter(h=>Qe(h.srMeta)==="unseen").length,d=l.cards.length;return{...l,due:c,unseen:p,total:d}}).sort((l,c)=>c.due-l.due||c.total-l.total)}var gu=Pa().HubModal,mu=ia().ReviewSessionModal,{SkillsPreviewModal:Pg,SkillsInstallModal:Hg,isInstallerAvailable:fu,getInstallPreview:$g,doInstall:Fg,autoUpdateSkills:bu}=Qa(),xu={lhTheme:"bright",language:"en",flashcardTags:"flashcards",enableSRScan:!1,maxInterval:36525,requestedRetention:.9,autoUpdateInstalledSkills:!0,memoryMapFolder:"",lessonRoots:["engram-quest/lessons"],questRoots:["engram-quest/quests"]},yu=Xi().SettingsTab,vu=class extends _e.Plugin{async onload(){console.debug("EngramQuest plugin loaded");let t=await this.loadData();if(this.settings=Object.assign({},xu,t),t&&t.flashcardTags===""&&t.enableSRScan===!0&&(this.settings.flashcardTags="flashcards",this.settings.enableSRScan=!1,await this.saveData(this.settings)),this.settings.autoUpdateInstalledSkills&&fu())try{await bu(this)}catch(e){console.error("EngramQuest auto update for installed skills failed",e)}try{let e=this.app.vault.adapter;await e.exists(".memory-map")||await this.app.vault.createFolder(".memory-map"),await e.write(".memory-map/config.json",JSON.stringify({memoryMapFolder:this.settings.memoryMapFolder},null,2))}catch(e){console.error("EngramQuest: could not init .memory-map/config.json",e)}Ga.migrateReviewDeckFolder(this.app.vault.adapter).catch(e=>console.warn("engram-review: onload migration failed",e)),Ga.migrateSrCommentsToJson(this.app).catch(e=>console.warn("engram-review: SR migration failed",e)),this.addStyle(lu+`
      .premium-card {
        background: #FFFFFF; border: 1px solid rgba(0, 0, 0, 0.06);
        box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.02), 0 2px 4px -2px rgba(0, 0, 0, 0.02);
        transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
        position: relative; overflow: hidden;
      }
      .premium-card::before {
        content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0;
        border: 1px solid rgba(255, 255, 255, 0.6); border-radius: inherit; pointer-events: none;
      }
      .premium-card:hover {
        transform: translateY(-4px);
        box-shadow: 0 12px 24px -8px rgba(99, 102, 241, 0.12), 0 4px 8px -3px rgba(0, 0, 0, 0.04);
        border-color: rgba(99, 102, 241, 0.2);
      }
      .icon-container {
        display: inline-flex; align-items: center; justify-content: center;
        width: 44px; height: 44px; border-radius: 12px; margin-bottom: 12px;
        background: linear-gradient(135deg, rgba(255,255,255,1), rgba(245,245,245,0.6));
        border: 1px solid rgba(0,0,0,0.04);
        box-shadow: inset 0 2px 4px rgba(255,255,255,0.8), 0 2px 4px rgba(0,0,0,0.02);
      }
      .icon-container svg { width:24px; height:24px; }
      .lh-mm-path.truncate {
        white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
      }
    `),this.addSettingTab(new yu(this.app,this)),this.registerEvent(this.app.vault.on("rename",async(e,n)=>{if(!(e instanceof _e.TFile)||!e.path.endsWith(".md"))return;let a=this.app.vault.adapter,s=b=>b.replace(/\//g,"__").replace(/\.md$/i,""),i=b=>Array.isArray(b)?b.map(f=>f===n?e.path:f):b===n?e.path:b,r=`engram-review/sr/${s(n)}.json`,o=`engram-review/sr/${s(e.path)}.json`;if(await a.exists(r)){let b=await a.read(r);await a.write(o,b),await a.remove(r)}let l=`engram-quest/state/${s(n)}.json`,c=`engram-quest/state/${s(e.path)}.json`;if(l!==c&&await a.exists(l))try{await a.mkdir("engram-quest/state").catch(()=>{});let b=await a.read(l);await a.write(c,b),await a.remove(l)}catch(b){console.warn("EngramQuest: failed to migrate quest state on rename",b)}let p=n.split("/").pop().replace(/\.md$/i,""),d=e.basename,h=`engram-review/hints/${p}.json`,u=`engram-review/hints/${d}.json`;if(await a.exists(h)){let b=JSON.parse(await a.read(h));if(b.note=i(b.note),b.cards&&typeof b.cards=="object")for(let f of Object.keys(b.cards)){let v=b.cards[f];v&&Object.prototype.hasOwnProperty.call(v,"source")&&(v.source=i(v.source))}await a.write(u,JSON.stringify(b,null,2)),h!==u&&await a.remove(h)}let x=`engram-review/ai-cards/${p}.md`,m=`engram-review/ai-cards/${d}.md`;if(await a.exists(x)&&x!==m){let b=await a.read(x);await a.write(m,b),await a.remove(x)}let g="engram-review/scan-record.json";if(await a.exists(g))try{let b=JSON.parse(await a.read(g));b.notes&&b.notes[n]&&(b.notes[e.path]=b.notes[n],delete b.notes[n],await a.write(g,JSON.stringify(b,null,2)))}catch(b){console.warn("EngramQuest: failed to update scan-record on rename",b)}})),this.addRibbonIcon("book-open",Oe(this.settings,"RIBBON_TOOLTIP"),async()=>{let e=new gu(this.app,this);await e.loadData(),e.open()}),this.registerMarkdownCodeBlockProcessor("harness-map",(e,n,a)=>{let s=0,i=()=>{n.empty(),n.appendChild(_e.sanitizeHTMLToDom(po(s))),r()},r=()=>{n.querySelectorAll(".hm-island").forEach(o=>{o.addEventListener("click",()=>{s=parseInt(o.dataset.index),i(),ho(this.app,s)})})};i()}),this.registerMarkdownCodeBlockProcessor("quest-map",async(e,n,a)=>{let s=uo(e);if(!s||s.nodes.length===0){n.createEl("div",{attr:{style:"padding:12px;color:#e44;font-family:monospace"},text:"Invalid quest-map: no valid YAML nodes found."});return}let i=tt[s.style]?s.style:"ocean",r=a&&typeof a.sourcePath=="string"?a.sourcePath:"inline-quest.md",o=await De.loadQuestState(this.app.vault.adapter,r),l=JSON.stringify(o);o=De.migrateQuestCompletion(o,s.nodes),JSON.stringify(o)!==l&&De.saveQuestState(this.app.vault.adapter,r,o).catch(x=>console.warn("EngramQuest: failed to migrate quest state",x));let c=De.buildQuestRenderState(s.nodes,o),p=c.activeIndex,d=c.visitedSet,h=()=>{c=De.buildQuestRenderState(s.nodes,o),p=c.activeIndex,d=c.visitedSet,n.empty(),n.appendChild(_e.sanitizeHTMLToDom(go(s.nodes,i,p,d,this.app,c))),u()},u=()=>{n.querySelectorAll(".qm-island-group").forEach(x=>{x.addEventListener("click",()=>{let m=parseInt(x.dataset.index);m>c.activeIndex&&!d.has(m)||(x.classList.add("qm-bounce"),x.addEventListener("animationend",()=>x.classList.remove("qm-bounce"),{once:!0}),window.setTimeout(()=>{let g=m;p=m,h(),mo(this.app,s.nodes,g,i,s.difficulty,this.settings,r,async(b,f,v={})=>{o=De.markQuestNodeComplete(o,b,v),c=De.buildQuestRenderState(s.nodes,o),c.completedCount===s.nodes.length&&(o=De.updateQuestBestRun(o,s.nodes),c=De.buildQuestRenderState(s.nodes,o));try{await De.saveQuestState(this.app.vault.adapter,r,o)}catch(w){console.warn("EngramQuest: failed to save quest state",w)}let y=c.activeIndex;p=y,d=c.visitedSet,h(),window.setTimeout(()=>{let w=n.querySelector(`.qm-island-group[data-index="${y}"]`);w&&(w.classList.add("qm-bounce"),w.addEventListener("animationend",()=>w.classList.remove("qm-bounce"),{once:!0}))},400)})},200))})})};h()}),this.registerMarkdownCodeBlockProcessor("review-deck",async(e,n,a)=>{var s;let i=yo(e);n.createEl("div",{attr:{style:"padding:10px;color:var(--text-muted);font-size:13px"},text:"Loading review deck..."});let r=[];if(i.cards&&i.cards.length>0)r=i.cards;else if(i.source){let c=this.app.vault.getAbstractFileByPath(i.source);if(c){let p=await this.app.vault.read(c);r=zt(p),r.forEach(h=>{h.notePath=i.source});let d=i.source.split("/").pop().replace(/\.md$/i,"");try{let h=`engram-review/hints/${d}.json`;await this.app.vault.adapter.exists(h)&&Pt(r,JSON.parse(await this.app.vault.adapter.read(h)))}catch(h){}}}else if(i.tag){let c=i.tag.replace(/^#/,""),p=this.app.vault.getMarkdownFiles();for(let d of p){let h=this.app.metadataCache.getFileCache(d);if(!((s=h==null?void 0:h.frontmatter)!=null&&s.tags)||!(Array.isArray(h.frontmatter.tags)?h.frontmatter.tags:[h.frontmatter.tags]).some(g=>String(g).replace(/^#/,"")===c))continue;let u=await this.app.vault.read(d),x=zt(u);if(x.length===0)continue;x.forEach(g=>{g.notePath=d.path});let m=d.name.replace(/\.md$/i,"");try{let g=`engram-review/hints/${m}.json`;await this.app.vault.adapter.exists(g)&&Pt(x,JSON.parse(await this.app.vault.adapter.read(g)))}catch(g){}r.push(...x)}}if(r.length===0){n.createEl("div",{attr:{style:"padding:12px;color:#e44;font-family:monospace"},text:"Invalid review-deck: provide valid tag, source, or cards."});return}let o={due:0,learning:1,unseen:2,mastered:3};r.sort((c,p)=>{var d,h;return((d=o[Qe(c.srMeta)])!=null?d:2)-((h=o[Qe(p.srMeta)])!=null?h:2)});let l=()=>{n.empty(),n.appendChild(_e.sanitizeHTMLToDom(vo(r,{...i,settings:this.settings}))),n.querySelectorAll(".rd-card").forEach(c=>{c.addEventListener("click",()=>{let p=parseInt(c.dataset.index);wo(this.app,r[p],l,this.settings)})})};l()}),this.registerMarkdownCodeBlockProcessor("engram-quest",(e,n,a)=>{let s=ru(e);if(!s||s.nodes.length===0){n.createEl("div",{attr:{style:"padding:12px;color:#e44;font-family:monospace"},text:"Invalid engram-quest block: no valid YAML nodes found."});return}n.empty(),n.appendChild(_e.sanitizeHTMLToDom(ou(s)));let i=Wa(s.theme),r=n.querySelector(".lp-wrap"),o=r.querySelector(".lp-panel-title"),l=r.querySelector(".lp-panel-desc"),c=r.querySelector(".lp-btn"),p=r.querySelector(".lp-bar-fill"),d=r.querySelector(".lp-progress-text");r.querySelectorAll(".lp-node").forEach(u=>{u.addEventListener("click",()=>{let x=parseInt(u.dataset.idx),m=s.nodes[x],g=Nt(m,s.nodes);o.textContent=m.label,l.textContent=m.desc||"Select a node to see details.",c.textContent=g==="locked"?"Locked":"Open",c.disabled=g==="locked";let b=m.completed?100:m.progress||0;p.style.width=b+"%",p.style.background=i.pathColor,d.textContent=m.completed?"Completed":m.progress?m.progress+"% done":"Not started";let f=c.cloneNode(!0);f.addEventListener("click",()=>{m.note&&window.open("obsidian://open?file="+encodeURIComponent(m.note))}),c.replaceWith(f)})});let h=s.nodes.find(u=>Nt(u,s.nodes)==="active")||s.nodes[0];c.addEventListener("click",()=>{h!=null&&h.note&&window.open("obsidian://open?file="+encodeURIComponent(h.note))})})}addStyle(t){let e=document.createElement("style");e.id="engram-quest-styles",e.textContent=t,document.head.appendChild(e),this.register(()=>e.remove())}async openNoteReview(t){let e=this.app.vault.getAbstractFileByPath(t),n=[];if(e){let i=await this.app.vault.read(e);n=zt(i),n.forEach(o=>{o.notePath=t});let r=t.split("/").pop().replace(/\.md$/i,"");try{let o=await ve.loadSrData(this.app.vault.adapter,r);ve.mergeSrIntoCards(n,o)}catch(o){}try{let o=`engram-review/hints/${r}.json`;await this.app.vault.adapter.exists(o)&&Pt(n,JSON.parse(await this.app.vault.adapter.read(o)))}catch(o){}}if(!n.length)return new _e.Notice("Note n\xE0y ch\u01B0a c\xF3 th\u1EBB #flashcards"),!1;let a={due:0,learning:1,unseen:2,mastered:3};n.sort((i,r)=>{var o,l;return((o=a[Qe(i.srMeta)])!=null?o:2)-((l=a[Qe(r.srMeta)])!=null?l:2)});let s=e&&e.basename||"Review";return new mu(this.app,n,s,this,null,{}).open(),!0}onunload(){console.debug("EngramQuest plugin unloaded")}},Qg=en().HelpModal,wu=Ha(),ko=to(),Re=Lt(),Eu=ao(),_o=va(),De=Rt(),ve=Se(),Ga=pa(),ku=io(),_u=lo(),Bg=Pa(),Ug=en(),jg=wu();po=ko.renderHarnessMap;ho=ko.openHarnessChapter;En=Re.questDifficultyPresets;dt=Re.splitInlineList;uo=Re.parseQuestMap;ht=Re.retriggerShake;ja=Re.normalizeAnswer;kn=Re.collectExpectedAnswers;_n=Re.matchesExpectedAnswer;Dt=Re.openQuestLink;Ot=Re.renderClozeSentence;Ya=Re.getQuestImageResource;Va=function(t,e){return Re.getQuestTheme(t,e,tt)};Ja=Re.getQuestNodePositions;go=function(t,e,n,a,s,i){return Eu.renderQuestMap(t,e,n,a,s,Ja,i)};Ka=function(t,e,n,a,s,i,r){return _o.renderQuestChallenge(t,e,n,a,s,i,r,{questDifficultyPresets:En,translateKey:Oe,getLanguage:te,retriggerShake:ht,openQuestLink:Dt,collectExpectedAnswers:kn,matchesExpectedAnswer:_n,renderClozeSentence:Ot,getQuestImageResource:Ya})};mo=function(t,e,n,a,s,i,r,o){return _o.openQuestChapterModal(t,e,n,a,s,i,r,{getQuestTheme:Va,getLanguage:te,renderQuestChallenge:Ka},o)};fo=ve.advancedSrPattern;bo=ve.legacySrPattern;Za=ve.anySrPattern;xo=ve.parseSrComment;Qe=ve.getReviewStatus;zt=ve.parseFlashcards;yo=ve.parseReviewDeckBlock;Pt=ve.mergeReviewHints;Eo=ve.matchFlashcardTagPrefix;uu=function(t,e){return Ga.scanReviewDecks(t,e,ve)};vo=function(t,e){return ku.renderReviewDeck(t,e,tt,Oe,Qe)};wo=function(t,e,n,a){return _u.openReviewCardModal(t,e,n,a,{computeFsrs:pt,translate:Oe,anySrPattern:Za})};module.exports=vu;
