← Notes results

Notes

k3 · full

Initial view of Notes

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 98,548 bytes · SHA-256 f64f3a9f7340
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Inkwell — Notes</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&family=Source+Serif+4:opsz,wght@8..60,400;8..60,600;8..60,700&display=swap" rel="stylesheet">
<style>
/* ============ TOKENS ============ */
:root{
  --font-ui:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;
  --font-mono:'JetBrains Mono',ui-monospace,'SF Mono',Menlo,Consolas,monospace;
  --font-serif:'Source Serif 4',Georgia,'Times New Roman',serif;
  --radius:10px;
  --radius-sm:7px;
}
[data-theme="light"]{
  --bg:#efede9;
  --sidebar:#e9e6e1;
  --sidebar-border:rgba(30,28,25,.08);
  --list:#f6f5f2;
  --editor:#fbfbf9;
  --card:#ffffff;
  --hover:rgba(30,28,25,.055);
  --active:rgba(30,28,25,.09);
  --border:rgba(30,28,25,.10);
  --border-strong:rgba(30,28,25,.18);
  --text:#232220;
  --text-2:#6e6b64;
  --text-3:#a5a29a;
  --accent:#5b54e8;
  --accent-hover:#4a43d6;
  --accent-weak:rgba(91,84,232,.11);
  --accent-text:#ffffff;
  --danger:#dc3b3b;
  --danger-weak:rgba(220,59,59,.10);
  --code-bg:#f0efeb;
  --pre-bg:#f3f2ee;
  --shadow-sm:0 1px 2px rgba(25,23,20,.05),0 3px 10px -4px rgba(25,23,20,.08);
  --shadow-lg:0 4px 12px rgba(25,23,20,.06),0 16px 48px -8px rgba(25,23,20,.18);
  --overlay:rgba(35,32,28,.35);
  --tooltip:#232220;
  --selection:rgba(91,84,232,.18);
}
[data-theme="dark"]{
  --bg:#101114;
  --sidebar:#16171c;
  --sidebar-border:rgba(255,255,255,.06);
  --list:#1a1b21;
  --editor:#131418;
  --card:#20222a;
  --hover:rgba(255,255,255,.055);
  --active:rgba(255,255,255,.09);
  --border:rgba(255,255,255,.075);
  --border-strong:rgba(255,255,255,.16);
  --text:#e8e9ee;
  --text-2:#9a9da8;
  --text-3:#5f626d;
  --accent:#8f89ff;
  --accent-hover:#a09aff;
  --accent-weak:rgba(143,137,255,.15);
  --accent-text:#12131a;
  --danger:#f0616b;
  --danger-weak:rgba(240,97,107,.13);
  --code-bg:#22242c;
  --pre-bg:#0d0e12;
  --shadow-sm:0 1px 2px rgba(0,0,0,.3),0 3px 10px -4px rgba(0,0,0,.4);
  --shadow-lg:0 4px 12px rgba(0,0,0,.35),0 20px 56px -8px rgba(0,0,0,.6);
  --overlay:rgba(0,0,0,.55);
  --tooltip:#2b2d36;
  --selection:rgba(143,137,255,.28);
}
/* ============ BASE ============ */
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{
  font-family:var(--font-ui);
  font-size:14px;line-height:1.5;
  color:var(--text);
  background:var(--bg);
  overflow:hidden;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}
::selection{background:var(--selection)}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:var(--border-strong);border-radius:6px;border:3px solid transparent;background-clip:content-box}
::-webkit-scrollbar-thumb:hover{background:var(--text-3);border:3px solid transparent;background-clip:content-box}
button{font-family:inherit;font-size:inherit;color:inherit;background:none;border:none;cursor:pointer}
input,textarea,select{font-family:inherit;font-size:inherit;color:inherit}
svg{display:block;flex-shrink:0}
.mono{font-family:var(--font-mono)}

#app{display:flex;height:100vh;width:100vw;overflow:hidden}

/* ============ ICON BUTTONS ============ */
.icon-btn{
  display:inline-flex;align-items:center;justify-content:center;
  width:30px;height:30px;border-radius:8px;color:var(--text-2);
  transition:background .12s,color .12s;position:relative;flex-shrink:0;
}
.icon-btn:hover{background:var(--hover);color:var(--text)}
.icon-btn.on{color:var(--accent);background:var(--accent-weak)}
.icon-btn:disabled{opacity:.35;pointer-events:none}

/* ============ SIDEBAR ============ */
#sidebar{
  width:248px;min-width:180px;max-width:420px;
  background:var(--sidebar);
  border-right:1px solid var(--sidebar-border);
  display:flex;flex-direction:column;flex-shrink:0;
  user-select:none;
}
.sb-head{display:flex;align-items:center;gap:10px;padding:16px 16px 12px}
.logo{
  width:32px;height:32px;border-radius:9px;background:var(--accent);
  display:flex;align-items:center;justify-content:center;color:var(--accent-text);
  box-shadow:var(--shadow-sm);flex-shrink:0;
}
.logo svg{width:17px;height:17px}
.sb-title{font-size:15px;font-weight:700;letter-spacing:-.01em;flex:1;min-width:0}
.sb-title small{display:block;font-size:10.5px;font-weight:500;color:var(--text-3);letter-spacing:.02em}
.sb-actions{padding:2px 14px 10px}
.new-note-btn{
  width:100%;display:flex;align-items:center;gap:9px;
  padding:8px 11px;border-radius:9px;
  background:var(--card);border:1px solid var(--border);
  font-weight:600;font-size:13px;color:var(--text);
  box-shadow:var(--shadow-sm);transition:border-color .12s,transform .08s,box-shadow .12s;
}
.new-note-btn:hover{border-color:var(--accent);box-shadow:0 1px 2px rgba(25,23,20,.05),0 4px 14px -4px var(--accent-weak)}
.new-note-btn:active{transform:scale(.985)}
.new-note-btn svg{color:var(--accent)}
.new-note-btn kbd{
  margin-left:auto;font-family:var(--font-mono);font-size:10px;font-weight:500;
  color:var(--text-3);background:var(--hover);padding:2px 6px;border-radius:5px;
}
.sb-scroll{flex:1;overflow-y:auto;overflow-x:hidden;padding:2px 8px 12px}
.sb-nav{display:flex;flex-direction:column;gap:1px;margin-bottom:6px}
.sb-item{
  display:flex;align-items:center;gap:10px;
  padding:6.5px 9px;border-radius:8px;cursor:pointer;
  font-size:13px;font-weight:500;color:var(--text-2);
  transition:background .1s,color .1s;position:relative;width:100%;text-align:left;
}
.sb-item svg{width:16px;height:16px;opacity:.85}
.sb-item:hover{background:var(--hover);color:var(--text)}
.sb-item.active{background:var(--accent-weak);color:var(--accent);font-weight:600}
.sb-item .count{
  margin-left:auto;font-size:11px;font-weight:600;color:var(--text-3);
  font-variant-numeric:tabular-nums;
}
.sb-item.active .count{color:var(--accent);opacity:.8}
.sb-item .dot{width:9px;height:9px;border-radius:50%;flex-shrink:0;margin:0 3.5px}
.sb-item.drag-over{background:var(--accent-weak);outline:1.5px dashed var(--accent);outline-offset:-1.5px}
.sb-section{margin-top:14px}
.sb-section-head{
  display:flex;align-items:center;gap:4px;
  padding:4px 9px 5px;font-size:10.5px;font-weight:700;
  letter-spacing:.09em;color:var(--text-3);text-transform:uppercase;
  cursor:pointer;border-radius:6px;width:100%;text-align:left;
}
.sb-section-head:hover{color:var(--text-2)}
.sb-section-head .chev{transition:transform .15s;opacity:.7}
.sb-section.collapsed .chev{transform:rotate(-90deg)}
.sb-section-head .add-btn{
  margin-left:auto;width:20px;height:20px;border-radius:6px;
  display:flex;align-items:center;justify-content:center;
  color:var(--text-3);opacity:0;transition:opacity .12s,background .12s;
}
.sb-section-head:hover .add-btn{opacity:1}
.sb-section-head .add-btn:hover{background:var(--hover);color:var(--text)}
.sb-section.collapsed .sb-section-body{display:none}
.sb-inline-input{
  width:100%;padding:6px 9px;margin:2px 0;border-radius:8px;
  border:1.5px solid var(--accent);background:var(--card);
  font-size:13px;outline:none;box-shadow:0 0 0 3px var(--accent-weak);
}
.sb-foot{
  padding:10px 12px;border-top:1px solid var(--sidebar-border);
  display:flex;align-items:center;gap:6px;font-size:11.5px;color:var(--text-3);
}
.sb-foot .grow{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tag-row{display:flex;flex-wrap:wrap;gap:5px;padding:3px 9px}
.tag-chip{
  display:inline-flex;align-items:center;gap:4px;
  padding:3px 9px;border-radius:999px;font-size:12px;font-weight:500;
  background:var(--hover);color:var(--text-2);cursor:pointer;border:1px solid transparent;
  transition:all .12s;
}
.tag-chip:hover{background:var(--active);color:var(--text)}
.tag-chip.active{background:var(--accent-weak);color:var(--accent);border-color:var(--accent)}
.tag-chip .n{opacity:.55;font-size:10.5px;font-weight:600}

/* ============ RESIZER ============ */
.resizer{width:5px;cursor:col-resize;flex-shrink:0;margin:0 -2px;z-index:20;position:relative}
.resizer:hover::after,.resizer.dragging::after{
  content:'';position:absolute;inset:0 2px;background:var(--accent);opacity:.55;border-radius:2px;
}

/* ============ NOTE LIST ============ */
#listPane{
  width:320px;min-width:220px;max-width:520px;
  background:var(--list);border-right:1px solid var(--border);
  display:flex;flex-direction:column;flex-shrink:0;
}
.list-head{padding:14px 16px 10px;border-bottom:1px solid var(--border)}
.list-title-row{display:flex;align-items:center;gap:8px;margin-bottom:10px}
.list-title{font-size:17px;font-weight:700;letter-spacing:-.015em;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.list-count{font-size:11.5px;font-weight:600;color:var(--text-3);background:var(--hover);padding:2px 8px;border-radius:999px;flex-shrink:0}
.search-wrap{position:relative}
.search-wrap svg{position:absolute;left:10px;top:50%;transform:translateY(-50%);color:var(--text-3);width:15px;height:15px;pointer-events:none}
#searchInput{
  width:100%;padding:7px 30px 7px 32px;border-radius:9px;
  border:1px solid var(--border);background:var(--card);
  font-size:13px;outline:none;transition:border-color .12s,box-shadow .12s;
}
#searchInput:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
#searchInput::placeholder{color:var(--text-3)}
.search-clear{
  position:absolute;right:6px;top:50%;transform:translateY(-50%);
  width:20px;height:20px;border-radius:6px;display:none;
  align-items:center;justify-content:center;color:var(--text-3);
}
.search-clear:hover{background:var(--hover);color:var(--text)}
.search-clear.show{display:flex}
#notesScroll{flex:1;overflow-y:auto;padding:6px 0 20px}
.list-label{
  padding:12px 18px 4px;font-size:10.5px;font-weight:700;
  letter-spacing:.09em;text-transform:uppercase;color:var(--text-3);
}
.note-card{
  margin:4px 10px;padding:12px 13px 11px;border-radius:12px;
  border:1px solid transparent;cursor:pointer;position:relative;
  transition:background .12s,border-color .12s,box-shadow .12s;
}
.note-card:hover{background:var(--card);box-shadow:var(--shadow-sm)}
.note-card.active{
  background:var(--card);border-color:var(--accent);
  box-shadow:var(--shadow-sm),0 0 0 1px var(--accent);
}
.note-card.dragging{opacity:.4}
.nc-top{display:flex;align-items:flex-start;gap:6px}
.nc-title{
  font-size:13.5px;font-weight:600;letter-spacing:-.005em;line-height:1.35;
  flex:1;min-width:0;overflow:hidden;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;
}
.nc-title.empty{color:var(--text-3);font-style:italic;font-weight:500}
.nc-pin{color:var(--accent);width:13px;height:13px;margin-top:2px}
.nc-fav{color:#e8a23d;width:13px;height:13px;margin-top:2px}
[data-theme="dark"] .nc-fav{color:#f0b45a}
.nc-excerpt{
  font-size:12px;color:var(--text-2);line-height:1.45;margin-top:3px;
  overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  word-break:break-word;
}
.nc-meta{display:flex;align-items:center;gap:6px;margin-top:8px;font-size:11px;color:var(--text-3)}
.nc-nb{display:inline-flex;align-items:center;gap:5px;font-weight:500}
.nc-nb .dot{width:7px;height:7px;border-radius:50%}
.nc-tags{display:flex;gap:4px;overflow:hidden}
.nc-tag{background:var(--hover);padding:1px 7px;border-radius:999px;font-size:10.5px;font-weight:500;white-space:nowrap}
.nc-date{margin-left:auto;white-space:nowrap;font-variant-numeric:tabular-nums}
.list-empty{
  padding:48px 28px;text-align:center;color:var(--text-3);
}
.list-empty svg{width:34px;height:34px;margin:0 auto 12px;opacity:.5}
.list-empty p{font-size:13px;font-weight:500}
.list-empty span{font-size:12px;display:block;margin-top:4px}
</style>
<style>
/* ============ EDITOR PANE ============ */
#editorPane{flex:1;display:flex;flex-direction:column;background:var(--editor);min-width:0;position:relative}
.editor-topbar{
  display:flex;align-items:center;gap:6px;
  padding:10px 18px;border-bottom:1px solid var(--border);
  flex-shrink:0;
}
.crumb{display:flex;align-items:center;gap:7px;font-size:12.5px;color:var(--text-2);flex:1;min-width:0}
.crumb .dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.crumb b{font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.crumb .sep{color:var(--text-3)}
.top-actions{display:flex;align-items:center;gap:2px}
.editor-toolbar{
  display:flex;align-items:center;gap:2px;flex-wrap:wrap;
  padding:7px 18px;border-bottom:1px solid var(--border);
  flex-shrink:0;
}
.tb-sep{width:1px;height:18px;background:var(--border-strong);margin:0 6px;flex-shrink:0}
.tb-btn{
  display:inline-flex;align-items:center;justify-content:center;
  min-width:30px;height:30px;padding:0 6px;border-radius:8px;
  color:var(--text-2);font-size:12.5px;font-weight:600;
  transition:background .1s,color .1s;
}
.tb-btn:hover{background:var(--hover);color:var(--text)}
.tb-btn.on{background:var(--accent-weak);color:var(--accent)}
.tb-btn svg{width:15.5px;height:15.5px}
.editor-scroll{flex:1;overflow-y:auto;overflow-x:hidden}
.editor-inner{max-width:780px;margin:0 auto;padding:44px 56px 120px}
#titleInput{
  width:100%;border:none;outline:none;background:transparent;
  font-size:34px;font-weight:800;letter-spacing:-.02em;line-height:1.2;
  color:var(--text);padding:0;margin-bottom:6px;font-family:var(--font-ui);
}
#titleInput::placeholder{color:var(--text-3)}
.meta-line{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--text-3);margin-bottom:26px;flex-wrap:wrap}
.meta-line .dot-sep{opacity:.6}
#nbSelect{
  border:1px solid var(--border);background:transparent;border-radius:7px;
  font-size:12px;color:var(--text-2);padding:2.5px 6px;outline:none;cursor:pointer;
  font-weight:500;
}
#nbSelect:hover{border-color:var(--border-strong);color:var(--text)}
#editor{outline:none;font-size:15.5px;line-height:1.72;letter-spacing:-.003em;min-height:300px;word-wrap:break-word}
body[data-efont="serif"] #editor{font-family:var(--font-serif);font-size:17px}
body[data-efont="mono"] #editor{font-family:var(--font-mono);font-size:14px;line-height:1.7}
#editor.placeholder::before{
  content:attr(data-ph);color:var(--text-3);position:absolute;pointer-events:none;
}
#editor p{margin:0 0 10px}
#editor h1{font-size:27px;font-weight:800;letter-spacing:-.02em;margin:26px 0 10px;line-height:1.3}
#editor h2{font-size:21px;font-weight:700;letter-spacing:-.015em;margin:22px 0 8px;line-height:1.35}
#editor h3{font-size:17px;font-weight:700;margin:18px 0 6px;line-height:1.4}
#editor h1:first-child,#editor h2:first-child,#editor h3:first-child,#editor p:first-child{margin-top:0}
#editor ul,#editor ol{margin:0 0 10px;padding-left:24px}
#editor li{margin:3px 0}
#editor li::marker{color:var(--text-3)}
#editor blockquote{
  margin:0 0 12px;padding:6px 16px;border-left:3px solid var(--accent);
  color:var(--text-2);background:var(--accent-weak);border-radius:0 8px 8px 0;
}
#editor pre{
  margin:0 0 12px;padding:14px 16px;background:var(--pre-bg);
  border:1px solid var(--border);border-radius:10px;
  font-family:var(--font-mono);font-size:13px;line-height:1.6;
  overflow-x:auto;white-space:pre-wrap;word-break:break-word;
}
#editor code{
  font-family:var(--font-mono);font-size:.88em;
  background:var(--code-bg);padding:2px 6px;border-radius:6px;
  border:1px solid var(--border);
}
#editor pre code{background:none;border:none;padding:0;font-size:inherit}
#editor mark{
  background:color-mix(in srgb,var(--accent) 26%,transparent);
  color:inherit;padding:1px 3px;border-radius:4px;
}
#editor a{color:var(--accent);text-decoration:underline;text-underline-offset:2px;text-decoration-thickness:1px;cursor:pointer}
#editor hr{border:none;border-top:1.5px solid var(--border-strong);margin:22px 0}
#editor ul.todo{list-style:none;padding-left:2px}
#editor ul.todo li{display:flex;align-items:flex-start;gap:9px;margin:5px 0}
#editor ul.todo input[type=checkbox]{
  appearance:none;-webkit-appearance:none;flex-shrink:0;
  width:17px;height:17px;margin-top:4px;border-radius:5.5px;
  border:1.5px solid var(--border-strong);background:var(--card);
  cursor:pointer;position:relative;transition:all .12s;
}
#editor ul.todo input[type=checkbox]:hover{border-color:var(--accent)}
#editor ul.todo input[type=checkbox]:checked{background:var(--accent);border-color:var(--accent)}
#editor ul.todo input[type=checkbox]:checked::after{
  content:'';position:absolute;left:5px;top:2px;width:4px;height:8px;
  border:solid var(--accent-text);border-width:0 2px 2px 0;transform:rotate(42deg);
}
#editor ul.todo li:has(input:checked){color:var(--text-3)}
#editor ul.todo li:has(input:checked) .td-text,#editor ul.todo li.checked{text-decoration:line-through}
.editor-footer{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding:9px 20px;border-top:1px solid var(--border);
  font-size:11.5px;color:var(--text-3);flex-shrink:0;background:var(--editor);
}
.ed-tags{display:flex;align-items:center;gap:6px;flex-wrap:wrap;flex:1;min-width:0}
.ed-tag{
  display:inline-flex;align-items:center;gap:5px;
  background:var(--hover);padding:2.5px 5px 2.5px 9px;border-radius:999px;
  font-size:11.5px;font-weight:500;color:var(--text-2);
}
.ed-tag button{
  width:15px;height:15px;border-radius:50%;display:flex;align-items:center;justify-content:center;
  color:var(--text-3);font-size:12px;line-height:1;
}
.ed-tag button:hover{background:var(--danger-weak);color:var(--danger)}
#tagInput{
  border:none;outline:none;background:transparent;font-size:12px;
  color:var(--text);width:90px;padding:3px 0;
}
#tagInput::placeholder{color:var(--text-3)}
.word-stats{white-space:nowrap;font-variant-numeric:tabular-nums}
.editor-empty{
  flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;
  color:var(--text-3);text-align:center;padding:40px;gap:6px;
}
.editor-empty .big-icon{
  width:64px;height:64px;border-radius:18px;background:var(--hover);
  display:flex;align-items:center;justify-content:center;margin-bottom:10px;color:var(--text-3);
}
.editor-empty h3{font-size:16px;font-weight:600;color:var(--text-2)}
.editor-empty p{font-size:13px;max-width:340px}
.editor-empty kbd{
  font-family:var(--font-mono);font-size:10.5px;background:var(--hover);
  padding:2px 6px;border-radius:5px;border:1px solid var(--border);
}
/* focus mode */
body.focus #sidebar,body.focus #listPane,body.focus .resizer{display:none}

/* ============ COMMAND PALETTE ============ */
.overlay{
  position:fixed;inset:0;background:var(--overlay);z-index:100;
  display:none;align-items:flex-start;justify-content:center;
  backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);
  animation:fadeIn .12s ease;
}
.overlay.show{display:flex}
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
#palettePanel{
  width:min(580px,calc(100vw - 40px));margin-top:14vh;
  background:var(--card);border:1px solid var(--border);
  border-radius:16px;box-shadow:var(--shadow-lg);overflow:hidden;
  animation:popIn .16s cubic-bezier(.2,.9,.3,1.2);
}
@keyframes popIn{from{opacity:0;transform:scale(.96) translateY(8px)}to{opacity:1;transform:none}}
.palette-input-wrap{display:flex;align-items:center;gap:11px;padding:15px 18px;border-bottom:1px solid var(--border)}
.palette-input-wrap svg{color:var(--text-3);width:17px;height:17px}
#paletteInput{flex:1;border:none;outline:none;background:transparent;font-size:15px;color:var(--text)}
#paletteInput::placeholder{color:var(--text-3)}
.palette-input-wrap kbd{
  font-family:var(--font-mono);font-size:10px;color:var(--text-3);
  border:1px solid var(--border);border-radius:5px;padding:2.5px 6px;
}
#paletteResults{max-height:380px;overflow-y:auto;padding:7px}
.pal-group{font-size:10.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--text-3);padding:9px 11px 4px}
.pal-item{
  display:flex;align-items:center;gap:11px;padding:8.5px 11px;border-radius:9px;
  cursor:pointer;font-size:13.5px;font-weight:500;
}
.pal-item svg{width:16px;height:16px;color:var(--text-3)}
.pal-item .sub{margin-left:auto;font-size:11.5px;color:var(--text-3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:180px}
.pal-item.sel{background:var(--accent-weak)}
.pal-item.sel svg{color:var(--accent)}
.pal-empty{padding:28px;text-align:center;color:var(--text-3);font-size:13px}

/* ============ CONTEXT MENU / DROPDOWN ============ */
.ctx-menu{
  position:fixed;z-index:200;min-width:210px;
  background:var(--card);border:1px solid var(--border);
  border-radius:12px;box-shadow:var(--shadow-lg);padding:5px;
  animation:popIn .12s cubic-bezier(.2,.9,.3,1.15);
}
.ctx-item{
  display:flex;align-items:center;gap:10px;width:100%;
  padding:7px 10px;border-radius:8px;font-size:13px;font-weight:500;
  color:var(--text);cursor:pointer;text-align:left;position:relative;
}
.ctx-item:hover{background:var(--hover)}
.ctx-item svg{width:15px;height:15px;color:var(--text-2)}
.ctx-item.danger{color:var(--danger)}
.ctx-item.danger svg{color:var(--danger)}
.ctx-item.danger:hover{background:var(--danger-weak)}
.ctx-item .arrow{margin-left:auto;color:var(--text-3);width:13px;height:13px}
.ctx-item .check{margin-left:auto;color:var(--accent);width:15px;height:15px}
.ctx-divider{height:1px;background:var(--border);margin:5px 8px}
.ctx-label{font-size:10.5px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text-3);padding:7px 10px 3px}
.ctx-item .dot{width:9px;height:9px;border-radius:50%;margin:0 3px}
.ctx-submenu{
  position:absolute;left:calc(100% - 4px);top:-5px;min-width:180px;
  background:var(--card);border:1px solid var(--border);
  border-radius:12px;box-shadow:var(--shadow-lg);padding:5px;display:none;
  max-height:280px;overflow-y:auto;
}
.ctx-item.has-sub:hover>.ctx-submenu{display:block}

/* ============ MODAL DIALOG ============ */
#dialogOverlay{align-items:center}
#dialogPanel{
  width:min(400px,calc(100vw - 40px));
  background:var(--card);border:1px solid var(--border);
  border-radius:16px;box-shadow:var(--shadow-lg);padding:22px;
  animation:popIn .16s cubic-bezier(.2,.9,.3,1.2);
}
#dialogPanel h3{font-size:15.5px;font-weight:700;margin-bottom:6px}
#dialogPanel .dlg-msg{font-size:13px;color:var(--text-2);margin-bottom:16px;line-height:1.5}
#dialogInput{
  width:100%;padding:9px 12px;border-radius:9px;margin-bottom:16px;
  border:1.5px solid var(--border);background:var(--editor);
  font-size:14px;outline:none;transition:border-color .12s,box-shadow .12s;
}
#dialogInput:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}
.dlg-actions{display:flex;justify-content:flex-end;gap:8px}
.btn{
  padding:8px 15px;border-radius:9px;font-size:13px;font-weight:600;
  transition:all .12s;border:1px solid var(--border);
}
.btn:hover{background:var(--hover)}
.btn.primary{background:var(--accent);color:var(--accent-text);border-color:transparent}
.btn.primary:hover{background:var(--accent-hover)}
.btn.danger{background:var(--danger);color:#fff;border-color:transparent}
.btn.danger:hover{filter:brightness(1.08)}

/* ============ TOAST ============ */
#toast{
  position:fixed;bottom:26px;left:50%;transform:translateX(-50%) translateY(16px);
  background:var(--tooltip);color:var(--editor);
  padding:9px 17px;border-radius:999px;font-size:13px;font-weight:500;
  box-shadow:var(--shadow-lg);z-index:300;opacity:0;pointer-events:none;
  transition:opacity .2s,transform .2s;display:flex;align-items:center;gap:8px;
}
[data-theme="dark"] #toast{color:var(--text)}
#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
#toast svg{width:15px;height:15px;color:var(--accent)}

/* ============ RESPONSIVE ============ */
.mobile-only{display:none!important}
@media (max-width:960px){
  .mobile-only{display:inline-flex!important}
  #sidebar{
    position:fixed;left:0;top:0;bottom:0;z-index:90;width:270px;
    transform:translateX(-105%);transition:transform .22s ease;
    box-shadow:var(--shadow-lg);
  }
  body.sb-open #sidebar{transform:none}
  #sidebarScrim{
    position:fixed;inset:0;background:var(--overlay);z-index:85;
    display:none;backdrop-filter:blur(2px);
  }
  body.sb-open #sidebarScrim{display:block}
  .resizer{display:none}
  #listPane{flex:1;width:auto;max-width:none;border-right:none}
  body.pane-editor #listPane{display:none}
  body.pane-editor #editorPane{display:flex}
  #editorPane{display:none;position:fixed;inset:0;z-index:60}
  .editor-inner{padding:32px 22px 100px}
  #titleInput{font-size:27px}
}
</style>
</head>
<body data-efont="sans">
<div id="app">
  <!-- ============ SIDEBAR ============ -->
  <aside id="sidebar">
    <div class="sb-head">
      <div class="logo" id="logoMark"></div>
      <div class="sb-title">Inkwell<small>Personal Knowledge</small></div>
      <button class="icon-btn" id="themeBtn" title="Toggle theme (⌘⇧L)"></button>
    </div>
    <div class="sb-actions">
      <button class="new-note-btn" id="newNoteBtn"><span id="newNoteIcon"></span>New note<kbd>⌘N</kbd></button>
    </div>
    <div class="sb-scroll" id="sbScroll"></div>
    <div class="sb-foot">
      <span class="grow" id="storageInfo"></span>
      <button class="icon-btn" id="settingsBtn" title="Settings"></button>
    </div>
  </aside>
  <div class="resizer" id="resizer1"></div>

  <!-- ============ NOTE LIST ============ -->
  <section id="listPane">
    <div class="list-head">
      <div class="list-title-row">
        <button class="icon-btn mobile-only" id="hamburgerBtn" title="Menu"></button>
        <div class="list-title" id="listTitle">All Notes</div>
        <span class="list-count" id="listCount">0</span>
        <button class="icon-btn" id="sortBtn" title="Sort notes"></button>
      </div>
      <div class="search-wrap">
        <span id="searchIcon"></span>
        <input id="searchInput" type="text" placeholder="Search notes…" autocomplete="off" spellcheck="false">
        <button class="search-clear" id="searchClear" title="Clear search"></button>
      </div>
    </div>
    <div id="notesScroll"></div>
  </section>
  <div class="resizer" id="resizer2"></div>

  <!-- ============ EDITOR ============ -->
  <main id="editorPane">
    <div class="editor-topbar">
      <button class="icon-btn mobile-only" id="backBtn" title="Back to list"></button>
      <div class="crumb" id="crumb"></div>
      <div class="top-actions">
        <button class="icon-btn" id="pinBtn" title="Pin note"></button>
        <button class="icon-btn" id="favBtn" title="Favorite"></button>
        <button class="icon-btn" id="focusBtn" title="Focus mode"></button>
        <button class="icon-btn" id="moreBtn" title="More actions"></button>
      </div>
    </div>
    <div class="editor-toolbar" id="toolbar"></div>
    <div class="editor-scroll" id="editorScroll">
      <div class="editor-inner">
        <input id="titleInput" type="text" placeholder="Untitled" autocomplete="off" spellcheck="false">
        <div class="meta-line" id="metaLine"></div>
        <div id="editor" contenteditable="true" spellcheck="true" data-ph="Start writing, or type '/' for ideas — # heading, - list, [] to-do, > quote, ``` code…"></div>
      </div>
    </div>
    <div class="editor-footer">
      <div class="ed-tags" id="edTags"></div>
      <span class="word-stats" id="wordStats"></span>
    </div>
  </main>
</div>

<div id="sidebarScrim"></div>

<!-- Command palette -->
<div class="overlay" id="paletteOverlay">
  <div id="palettePanel">
    <div class="palette-input-wrap">
      <span id="palIcon"></span>
      <input id="paletteInput" type="text" placeholder="Search notes or run a command…" autocomplete="off" spellcheck="false">
      <kbd>esc</kbd>
    </div>
    <div id="paletteResults"></div>
  </div>
</div>

<!-- Dialog -->
<div class="overlay" id="dialogOverlay">
  <div id="dialogPanel">
    <h3 id="dlgTitle"></h3>
    <div class="dlg-msg" id="dlgMsg"></div>
    <input id="dialogInput" type="text" autocomplete="off" spellcheck="false" style="display:none">
    <div class="dlg-actions">
      <button class="btn" id="dlgCancel">Cancel</button>
      <button class="btn primary" id="dlgOk">OK</button>
    </div>
  </div>
</div>

<div id="toast"></div>
<input type="file" id="importFile" accept=".json,application/json" style="display:none">

<script>
'use strict';
/* ================================================================
   INKWELL — a single-file notes app
   ================================================================ */

/* ---------- utils ---------- */
const $  = (s, r=document) => r.querySelector(s);
const $$ = (s, r=document) => [...r.querySelectorAll(s)];
const uid = () => Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
const esc = s => String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const debounce = (fn, ms) => { let t; return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), ms); }; };
const clamp = (v, a, b) => Math.min(b, Math.max(a, v));

function plainText(html){
  const d = document.createElement('div');
  d.innerHTML = html || '';
  return (d.textContent || '').replace(/\s+/g, ' ').trim();
}
function wordCount(text){
  const w = text.trim().split(/\s+/).filter(Boolean);
  return w.length;
}
function relDate(ts){
  const d = new Date(ts), now = new Date();
  const sameDay = d.toDateString() === now.toDateString();
  const yest = new Date(now); yest.setDate(now.getDate() - 1);
  if (sameDay) return d.toLocaleTimeString([], {hour:'numeric', minute:'2-digit'});
  if (d.toDateString() === yest.toDateString()) return 'Yesterday';
  if (now - d < 6 * 864e5) return d.toLocaleDateString([], {weekday:'short'});
  if (d.getFullYear() === now.getFullYear()) return d.toLocaleDateString([], {month:'short', day:'numeric'});
  return d.toLocaleDateString([], {month:'short', day:'numeric', year:'numeric'});
}
function fullDate(ts){
  return new Date(ts).toLocaleDateString([], {month:'long', day:'numeric', year:'numeric'});
}
function fuzzyScore(q, str){
  q = q.toLowerCase(); str = str.toLowerCase();
  if (!q) return 1;
  if (str.startsWith(q)) return 100 - (str.length - q.length) * .01;
  if (str.includes(q)) return 60 - str.indexOf(q) * .1;
  let qi = 0, score = 0;
  for (let i = 0; i < str.length && qi < q.length; i++){
    if (str[i] === q[qi]){ score += 10; qi++; }
  }
  return qi === q.length ? score * .3 : 0;
}

/* ---------- icons (feather-style) ---------- */
const P = (d, extra='') => `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ${extra}>${d}</svg>`;
const ICONS = {
  logo: P('<path d="M12 19l7-7 3 3-7 7-3-3z"/><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/><path d="M2 2l7.586 7.586"/><circle cx="11" cy="11" r="2"/>'),
  plus: P('<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>'),
  search: P('<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>'),
  note: P('<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/>'),
  book: P('<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/>'),
  pin: P('<path d="M9 4h6l1 7 3 3v1H5v-1l3-3 1-7z"/><line x1="12" y1="15" x2="12" y2="21"/>'),
  star: P('<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 12 2"/>'),
  trash: P('<polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/>'),
  tag: P('<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/>'),
  sun: P('<circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>'),
  moon: P('<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>'),
  chevD: P('<polyline points="6 9 12 15 18 9"/>'),
  chevR: P('<polyline points="9 18 15 12 9 6"/>'),
  bold: P('<path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/><path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/>'),
  italic: P('<line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/>'),
  under: P('<path d="M6 3v7a6 6 0 0 0 6 6 6 6 0 0 0 6-6V3"/><line x1="4" y1="21" x2="20" y2="21"/>'),
  strike: P('<line x1="5" y1="12" x2="19" y2="12"/><path d="M16 6c-.9-1.6-2.6-2.5-4.5-2.5C8.4 3.5 6 5.2 6 8c0 1.2.4 2 1.2 2.7"/><path d="M8 18c.9 1.6 2.6 2.5 4.5 2.5 3.1 0 5.5-1.7 5.5-4.5 0-.7-.1-1.2-.4-1.7"/>'),
  h1: P('<path d="M4 12h8"/><path d="M4 18V6"/><path d="M12 18V6"/><path d="M17 12l3-2v8"/>'),
  h2: P('<path d="M4 12h8"/><path d="M4 18V6"/><path d="M12 18V6"/><path d="M21 18h-4c0-4 4-3 4-6 0-1.5-2-2.5-4-1"/>'),
  h3: P('<path d="M4 12h8"/><path d="M4 18V6"/><path d="M12 18V6"/><path d="M17.5 10.5c1.7-1 3.5 0 3.5 1.5a2 2 0 1 1-4 0"/><path d="M21 17.5a2 2 0 1 1-4 0c0-1.5 1.8-2.5 3.5-1.5"/>'),
  ul: P('<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>'),
  ol: P('<line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/>'),
  quote: P('<path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z"/><path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z"/>'),
  code: P('<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>'),
  link: P('<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>'),
  todo: P('<polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>'),
  mark: P('<path d="M9 11l-6 6v3h9l3-3"/><path d="M22 12l-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4l8 8z"/>'),
  hr: P('<line x1="3" y1="12" x2="21" y2="12"/><polyline points="8 8 12 4 16 8"/><polyline points="16 16 12 20 8 16"/>'),
  more: P('<circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/>'),
  x: P('<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>'),
  check: P('<polyline points="20 6 9 17 4 12"/>'),
  sort: P('<line x1="4" y1="6" x2="16" y2="6"/><line x1="4" y1="12" x2="12" y2="12"/><line x1="4" y1="18" x2="8" y2="18"/><polyline points="15 15 18 18 21 15"/><line x1="18" y1="9" x2="18" y2="18"/>'),
  settings: P('<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>'),
  download: P('<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>'),
  upload: P('<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/>'),
  focus: P('<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>'),
  back: P('<line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/>'),
  menu: P('<line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="18" x2="21" y2="18"/>'),
  copy: P('<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>'),
  edit: P('<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>'),
  restore: P('<polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>'),
  hash: P('<line x1="4" y1="9" x2="20" y2="9"/><line x1="4" y1="15" x2="20" y2="15"/><line x1="10" y1="3" x2="8" y2="21"/><line x1="16" y1="3" x2="14" y2="21"/>'),
  clock: P('<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>'),
  cmd: P('<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"/>'),
};
const icon = name => ICONS[name] || ICONS.note;

const NB_COLORS = ['#e0604f','#e8963d','#e3c04a','#6fbf5a','#3fb8a6','#4f9de8','#7b6fe8','#b06fd8','#e05f9a','#8a919e'];
</script>
<script>
/* ---------- state & storage ---------- */
const STORE_KEY = 'inkwell.v1';
let state = null;

function defaultState(){
  return {
    notes: [], notebooks: [],
    view: { type: 'all', id: null },
    selectedId: null,
    search: '',
    sort: 'updated',
    theme: matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light',
    efont: 'sans',
    focus: false,
    collapsed: {},
    widths: { sidebar: 248, list: 320 },
    nbColorIdx: 0,
  };
}
function save(){
  try { localStorage.setItem(STORE_KEY, JSON.stringify(state)); } catch(e){}
}
const saveSoon = debounce(save, 250);
function load(){
  try {
    const raw = localStorage.getItem(STORE_KEY);
    if (raw){ state = Object.assign(defaultState(), JSON.parse(raw)); return; }
  } catch(e){}
  state = defaultState();
  seed();
  save();
}

/* ---------- seed content ---------- */
function seed(){
  const now = Date.now(), H = 36e5, D = 864e5;
  const nb1 = { id: uid(), name: 'Personal', color: NB_COLORS[6] };
  const nb2 = { id: uid(), name: 'Work', color: NB_COLORS[5] };
  const nb3 = { id: uid(), name: 'Reading', color: NB_COLORS[4] };
  state.notebooks = [nb1, nb2, nb3];
  state.nbColorIdx = 3;

  const welcome = {
    id: uid(), title: 'Welcome to Inkwell ✦', notebook: nb1.id, tags: ['guide'],
    pinned: true, fav: true, trashed: false,
    created: now - 2 * D, updated: now - 2 * H,
    content:
`<h1>Welcome to Inkwell</h1>
<p>A calm place for your thoughts. Everything lives in this one file and syncs to your browser's local storage automatically — no account, no cloud, no fuss.</p>
<h2>Writing</h2>
<p>The editor supports rich text with Markdown-style shortcuts. Try typing these at the start of a line:</p>
<ul><li><code>#</code> then space — big heading (also <code>##</code> and <code>###</code>)</li><li><code>-</code> or <code>*</code> then space — bullet list</li><li><code>1.</code> then space — numbered list</li><li><code>[]</code> then space — a to-do checkbox</li><li><code>&gt;</code> then space — a quote block</li><li><code>&#96;&#96;&#96;</code> then enter — a code block</li><li><code>---</code> then enter — a divider</li></ul>
<h2>Your to-do list works too</h2>
<ul class="todo"><li><input type="checkbox" checked contenteditable="false"><span class="td-text">Click this checkbox — it persists</span></li><li><input type="checkbox" contenteditable="false"><span class="td-text">Press <kbd>⌘K</kbd> to open the command palette</span></li><li><input type="checkbox" contenteditable="false"><span class="td-text">Drag a note onto a notebook in the sidebar</span></li></ul>
<h2>Stay organized</h2>
<p>Create <mark>notebooks</mark> and <mark>tags</mark>, pin what matters, and star your favorites. Right-click any note for more options, or hit the <strong>⋯</strong> menu up top.</p>
<blockquote>Simplicity is the soul of efficiency. — Austin Freeman</blockquote>
<p>Happy writing. — The Inkwell Team</p>`
  };
  const meeting = {
    id: uid(), title: 'Q3 Planning — Meeting Notes', notebook: nb2.id, tags: ['meetings'],
    pinned: false, fav: false, trashed: false,
    created: now - D, updated: now - 5 * H,
    content:
`<h2>Q3 Planning</h2>
<p><strong>Attendees:</strong> Maya, Jon, Priya, Sam</p>
<h3>Decisions</h3>
<ol><li>Ship the onboarding revamp by <mark>Aug 15</mark></li><li>Freeze new features during the migration window</li><li>Weekly sync moves to Tuesdays</li></ol>
<h3>Action items</h3>
<ul class="todo"><li><input type="checkbox" checked contenteditable="false"><span class="td-text">Maya — draft roadmap doc</span></li><li><input type="checkbox" contenteditable="false"><span class="td-text">Jon — audit analytics events</span></li><li><input type="checkbox" contenteditable="false"><span class="td-text">Priya — schedule user interviews</span></li></ul>
<pre><code>// velocity target
const goal = lastQuarter.points * 1.1;</code></pre>
<p>Next check-in: <a href="#">roadmap review</a> on Friday.</p>`
  };
  const reading = {
    id: uid(), title: 'Reading List', notebook: nb3.id, tags: ['books', 'queue'],
    pinned: true, fav: false, trashed: false,
    created: now - 6 * D, updated: now - D,
    content:
`<h2>To read</h2>
<ul class="todo"><li><input type="checkbox" checked contenteditable="false"><span class="td-text">The Design of Everyday Things — Don Norman</span></li><li><input type="checkbox" contenteditable="false"><span class="td-text">A Pattern Language — Christopher Alexander</span></li><li><input type="checkbox" contenteditable="false"><span class="td-text">The Creative Act — Rick Rubin</span></li><li><input type="checkbox" contenteditable="false"><span class="td-text">Several Short Sentences About Writing — Klinkenborg</span></li></ul>
<hr>
<h3>Notes on <em>Design of Everyday Things</em></h3>
<p>Affordances vs. signifiers — a door that begs to be pulled but must be pushed is a design failure, not a user failure. Good design <mark>makes the right action obvious</mark>.</p>
<blockquote>When you have trouble with things, it's not your fault — it's bad design.</blockquote>`
  };
  const ideas = {
    id: uid(), title: 'Ideas parking lot', notebook: nb1.id, tags: ['ideas'],
    pinned: false, fav: false, trashed: false,
    created: now - 3 * D, updated: now - 26 * H,
    content:
`<p>A scratch pad for half-formed thoughts:</p>
<ul><li>A tiny CLI that turns meeting audio into action items</li><li>Weekend project: mechanical-keyboard soundboard</li><li>Essay — <em>software as a garden, not a building</em></li><li>Gift idea for Ana: letterpress workshop</li></ul>
<p><code>idea ≠ plan</code> — park it here, promote it later.</p>`
  };
  const trashed = {
    id: uid(), title: 'Old grocery list', notebook: null, tags: [],
    pinned: false, fav: false, trashed: true,
    created: now - 12 * D, updated: now - 10 * D,
    content: `<p>Eggs, spinach, sourdough, olive oil, coffee beans, parmesan.</p>`
  };
  state.notes = [welcome, meeting, reading, ideas, trashed];
  state.selectedId = welcome.id;
}

/* ---------- queries ---------- */
const getNote = id => state.notes.find(n => n.id === id);
const getNb = id => state.notebooks.find(b => b.id === id);
const activeNote = () => getNote(state.selectedId);

function visibleNotes(){
  const v = state.view, q = state.search.trim().toLowerCase();
  let list = state.notes.filter(n => {
    if (v.type === 'trash') return n.trashed;
    if (n.trashed) return false;
    if (v.type === 'notebook') return n.notebook === v.id;
    if (v.type === 'tag') return (n.tags || []).includes(v.id);
    if (v.type === 'fav') return n.fav;
    if (v.type === 'pinned') return n.pinned;
    return true;
  });
  if (q){
    list = list.filter(n =>
      (n.title || '').toLowerCase().includes(q) ||
      plainText(n.content).toLowerCase().includes(q) ||
      (n.tags || []).some(t => t.toLowerCase().includes(q))
    );
  }
  const by = {
    updated: (a,b) => b.updated - a.updated,
    created: (a,b) => b.created - a.created,
    title:   (a,b) => (a.title || 'Untitled').localeCompare(b.title || 'Untitled'),
  }[state.sort] || ((a,b) => b.updated - a.updated);
  list.sort(by);
  if (state.view.type !== 'trash' && !q && state.sort !== 'title'){
    list.sort((a,b) => (b.pinned - a.pinned));
  }
  return list;
}
function allTags(){
  const m = new Map();
  state.notes.forEach(n => { if (!n.trashed) (n.tags || []).forEach(t => m.set(t, (m.get(t) || 0) + 1)); });
  return [...m.entries()].sort((a,b) => a[0].localeCompare(b[0]));
}
const nbCount = id => state.notes.filter(n => !n.trashed && n.notebook === id).length;

/* ---------- render: sidebar ---------- */
function renderSidebar(){
  const sb = $('#sbScroll');
  const v = state.view;
  const act = (type, id=null) => (v.type === type && v.id === id) ? ' active' : '';
  const live = state.notes.filter(n => !n.trashed);

  let html = `<nav class="sb-nav">
    <button class="sb-item${act('all')}" data-view="all">${icon('note')}<span>All Notes</span><span class="count">${live.length}</span></button>
    <button class="sb-item${act('pinned')}" data-view="pinned">${icon('pin')}<span>Pinned</span><span class="count">${live.filter(n=>n.pinned).length}</span></button>
    <button class="sb-item${act('fav')}" data-view="fav">${icon('star')}<span>Favorites</span><span class="count">${live.filter(n=>n.fav).length}</span></button>
    <button class="sb-item${act('trash')}" data-view="trash">${icon('trash')}<span>Trash</span><span class="count">${state.notes.filter(n=>n.trashed).length}</span></button>
  </nav>`;

  const nbCollapsed = state.collapsed.notebooks ? ' collapsed' : '';
  html += `<div class="sb-section${nbCollapsed}">
    <button class="sb-section-head" data-section="notebooks">
      <span class="chev">${icon('chevD')}</span>Notebooks
      <span class="add-btn" data-addnb title="New notebook">${icon('plus')}</span>
    </button>
    <div class="sb-section-body">`;
  state.notebooks.forEach(nb => {
    html += `<button class="sb-item${act('notebook', nb.id)}" data-view="notebook" data-id="${nb.id}" data-nb="${nb.id}">
      <span class="dot" style="background:${nb.color}"></span>
      <span style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(nb.name)}</span>
      <span class="count">${nbCount(nb.id)}</span></button>`;
  });
  html += `</div></div>`;

  const tags = allTags();
  const tgCollapsed = state.collapsed.tags ? ' collapsed' : '';
  html += `<div class="sb-section${tgCollapsed}">
    <button class="sb-section-head" data-section="tags"><span class="chev">${icon('chevD')}</span>Tags</button>
    <div class="sb-section-body"><div class="tag-row">`;
  if (!tags.length) html += `<span style="font-size:12px;color:var(--text-3);padding:2px 0">No tags yet — add one in the editor footer.</span>`;
  tags.forEach(([t, c]) => {
    html += `<span class="tag-chip${act('tag', t)}" data-view="tag" data-id="${esc(t)}">${esc(t)}<span class="n">${c}</span></span>`;
  });
  html += `</div></div></div>`;

  sb.innerHTML = html;
  $('#storageInfo').textContent = `${state.notes.length} notes · ${state.notebooks.length} notebooks`;
}

/* ---------- render: list ---------- */
const SORT_LABELS = { updated:'Last updated', created:'Date created', title:'Title A–Z' };
function viewTitle(){
  const v = state.view;
  if (v.type === 'notebook') return getNb(v.id)?.name || 'Notebook';
  if (v.type === 'tag') return '#' + v.id;
  return { all:'All Notes', pinned:'Pinned', fav:'Favorites', trash:'Trash' }[v.type] || 'Notes';
}
function renderList(){
  const notes = visibleNotes();
  $('#listTitle').textContent = viewTitle();
  $('#listCount').textContent = notes.length;
  const scroll = $('#notesScroll');
  const keepScroll = scroll.scrollTop;

  if (!notes.length){
    const msgs = {
      trash: ['Trash is empty', 'Deleted notes will rest here.'],
      tag: ['No notes with this tag', 'Add tags in the editor footer.'],
    };
    const [t, s] = msgs[state.view.type] || (state.search
      ? ['No results', `Nothing matches “${state.search}”.`]
      : ['Nothing here yet', 'Press ⌘N to write your first note.']);
    scroll.innerHTML = `<div class="list-empty">${icon('note')}<p>${esc(t)}</p><span>${esc(s)}</span></div>`;
    return;
  }

  let html = '';
  const pinned = notes.filter(n => n.pinned), rest = notes.filter(n => !n.pinned);
  const grouped = state.view.type !== 'trash' && !state.search && state.sort !== 'title' && pinned.length && rest.length;

  const card = n => {
    const nb = getNb(n.notebook);
    const text = plainText(n.content);
    return `<div class="note-card${n.id === state.selectedId ? ' active' : ''}" data-id="${n.id}" draggable="true">
      <div class="nc-top">
        ${n.pinned ? `<span class="nc-pin">${icon('pin')}</span>` : ''}
        ${n.fav ? `<span class="nc-fav">${icon('star')}</span>` : ''}
        <div class="nc-title${n.title ? '' : ' empty'}">${esc(n.title || 'Untitled')}</div>
      </div>
      ${text ? `<div class="nc-excerpt">${esc(text.slice(0, 140))}</div>` : ''}
      <div class="nc-meta">
        ${nb ? `<span class="nc-nb"><span class="dot" style="background:${nb.color}"></span>${esc(nb.name)}</span>` : ''}
        <span class="nc-tags">${(n.tags||[]).slice(0,2).map(t => `<span class="nc-tag">#${esc(t)}</span>`).join('')}</span>
        <span class="nc-date">${relDate(n.updated)}</span>
      </div>
    </div>`;
  };

  if (grouped){
    html += `<div class="list-label">Pinned</div>` + pinned.map(card).join('');
    html += `<div class="list-label">Notes</div>` + rest.map(card).join('');
  } else {
    html += notes.map(card).join('');
  }
  scroll.innerHTML = html;
  scroll.scrollTop = keepScroll;
}

function renderAll(){ renderSidebar(); renderList(); }
</script>
<script>
/* ---------- render: editor ---------- */
const editorEl = () => $('#editor');
let savedRange = null;

function renderEditor(){
  const n = activeNote();
  const pane = $('#editorPane');
  const hasEditor = !!n && !n.trashed;
  ['#toolbar', '#editorScroll', '.editor-footer'].forEach(s => $(s).style.display = hasEditor ? '' : 'none');
  $('#pinBtn').style.display = $('#favBtn').style.display = $('#moreBtn').style.display = hasEditor ? '' : 'none';

  const crumb = $('#crumb');
  if (!n){
    crumb.innerHTML = '';
    pane.querySelector('.editor-scroll').style.display = 'none';
    if (!$('.editor-empty', pane)){
      const d = document.createElement('div');
      d.className = 'editor-empty';
      d.innerHTML = `<div class="big-icon">${icon('logo')}</div>
        <h3>No note selected</h3>
        <p>Pick a note from the list, or press <kbd>⌘N</kbd> to start a new one. Press <kbd>⌘K</kbd> for the command palette.</p>`;
      pane.appendChild(d);
    }
    $('.editor-empty', pane).style.display = 'flex';
    return;
  }
  const ee = $('.editor-empty', pane); if (ee) ee.style.display = 'none';

  const nb = getNb(n.notebook);
  crumb.innerHTML = nb
    ? `<span class="dot" style="background:${nb.color}"></span><span>${esc(nb.name)}</span><span class="sep">/</span><b>${esc(n.title || 'Untitled')}</b>`
    : `<span>${n.trashed ? 'Trash' : 'Notes'}</span><span class="sep">/</span><b>${esc(n.title || 'Untitled')}</b>`;

  $('#pinBtn').classList.toggle('on', !!n.pinned);
  $('#favBtn').classList.toggle('on', !!n.fav);

  if (hasEditor){
    const ti = $('#titleInput'), ed = editorEl();
    if (ti.value !== (n.title || '')) ti.value = n.title || '';
    if (ed.dataset.noteId !== n.id){
      ed.innerHTML = n.content || '<p><br></p>';
      ed.dataset.noteId = n.id;
    }
    ed.contentEditable = 'true';
    updateMetaLine(n);
    renderTags(n);
    updateStats(n);
    updatePlaceholder();
  }
}
function updateMetaLine(n){
  $('#metaLine').innerHTML =
    `<span style="display:inline-flex;align-items:center;gap:5px" title="Created ${fullDate(n.created)}">${icon('clock')}${fullDate(n.created)}</span>
     <span class="dot-sep">·</span>
     <span>edited ${relDate(n.updated)}</span>
     <span class="dot-sep">·</span>
     <select id="nbSelect" title="Notebook">
       <option value="">No notebook</option>
       ${state.notebooks.map(b => `<option value="${b.id}" ${b.id === n.notebook ? 'selected' : ''}>${esc(b.name)}</option>`).join('')}
     </select>`;
}
function renderTags(n){
  $('#edTags').innerHTML =
    (n.tags || []).map(t => `<span class="ed-tag">#${esc(t)}<button data-rmtag="${esc(t)}" title="Remove tag">×</button></span>`).join('') +
    `<input id="tagInput" type="text" placeholder="+ add tag" autocomplete="off" spellcheck="false">`;
}
function updateStats(n){
  const text = plainText(n ? n.content : '');
  const words = wordCount(text);
  $('#wordStats').textContent = `${words} word${words === 1 ? '' : 's'} · ${text.length} chars`;
}
function updatePlaceholder(){
  const ed = editorEl();
  const empty = !plainText(ed.innerHTML) && !ed.querySelector('img,hr,ul,ol,pre,blockquote,h1,h2,h3');
  ed.classList.toggle('placeholder', empty);
}

/* ---------- toolbar ---------- */
const TB = [
  { c:'bold', i:'bold', t:'Bold (⌘B)' }, { c:'italic', i:'italic', t:'Italic (⌘I)' },
  { c:'underline', i:'under', t:'Underline (⌘U)' }, { c:'strikeThrough', i:'strike', t:'Strikethrough (⌘⇧X)' },
  'sep',
  { c:'h1', i:'h1', t:'Heading 1' }, { c:'h2', i:'h2', t:'Heading 2' }, { c:'h3', i:'h3', t:'Heading 3' },
  'sep',
  { c:'insertUnorderedList', i:'ul', t:'Bullet list' }, { c:'insertOrderedList', i:'ol', t:'Numbered list' },
  { c:'todo', i:'todo', t:'To-do list' },
  'sep',
  { c:'blockquote', i:'quote', t:'Quote' }, { c:'codeblock', i:'code', t:'Code block' },
  { c:'inlinecode', i:'cmd', t:'Inline code (⌘E)' }, { c:'mark', i:'mark', t:'Highlight' },
  'sep',
  { c:'link', i:'link', t:'Insert link' }, { c:'hr', i:'hr', t:'Divider' }, { c:'clear', i:'x', t:'Clear formatting' },
];
function buildToolbar(){
  $('#toolbar').innerHTML = TB.map(b => b === 'sep'
    ? '<span class="tb-sep"></span>'
    : `<button class="tb-btn" data-cmd="${b.c}" title="${b.t}">${icon(b.i)}</button>`).join('');
}
function updateToolbarState(){
  const n = activeNote(); if (!n) return;
  $$('.tb-btn').forEach(btn => {
    const c = btn.dataset.cmd;
    let on = false;
    try {
      if (['bold','italic','underline','strikeThrough','insertUnorderedList','insertOrderedList'].includes(c))
        on = document.queryCommandState(c);
      else if (['h1','h2','h3','blockquote'].includes(c)){
        const b = currentBlock(); on = b && b.tagName && b.tagName.toLowerCase() === c;
      } else if (c === 'codeblock'){ on = !!inPre(); }
    } catch(e){}
    btn.classList.toggle('on', !!on);
  });
}

/* ---------- selection helpers ---------- */
function currentBlock(){
  const sel = getSelection(); if (!sel.rangeCount) return null;
  let node = sel.anchorNode, ed = editorEl();
  if (!node || !ed.contains(node) || node === ed) return null;
  if (node.nodeType === 3) node = node.parentNode;
  while (node && node.parentNode && node.parentNode !== ed) node = node.parentNode;
  return (node && node.parentNode === ed) ? node : null;
}
function inPre(){
  const sel = getSelection(); if (!sel.rangeCount) return null;
  let node = sel.anchorNode;
  if (node && node.nodeType === 3) node = node.parentNode;
  return node ? node.closest && node.closest('#editor pre') : null;
}
function saveSelection(){
  const sel = getSelection();
  if (sel.rangeCount && editorEl().contains(sel.anchorNode)) savedRange = sel.getRangeAt(0).cloneRange();
}
function restoreSelection(){
  if (!savedRange) return;
  const sel = getSelection(); sel.removeAllRanges();
  try { sel.addRange(savedRange); } catch(e){}
}
function placeCaret(el, atEnd = true){
  const r = document.createRange(); r.selectNodeContents(el); r.collapse(!atEnd);
  const s = getSelection(); s.removeAllRanges(); s.addRange(r);
}

/* ---------- editor commands ---------- */
function exec(cmd){
  const ed = editorEl(); ed.focus(); restoreSelection();
  switch(cmd){
    case 'h1': case 'h2': case 'h3': {
      const b = currentBlock();
      const tag = b && b.tagName && b.tagName.toLowerCase() === cmd ? 'p' : cmd;
      document.execCommand('formatBlock', false, tag); break;
    }
    case 'blockquote': {
      const b = currentBlock();
      document.execCommand('formatBlock', false, b && b.tagName === 'BLOCKQUOTE' ? 'p' : 'blockquote'); break;
    }
    case 'codeblock': insertCodeBlock(); break;
    case 'inlinecode': wrapSelection('code'); break;
    case 'mark': wrapSelection('mark'); break;
    case 'todo': insertTodo(); break;
    case 'hr': insertHR(); break;
    case 'link': insertLink(); break;
    case 'clear':
      document.execCommand('removeFormat'); document.execCommand('formatBlock', false, 'p'); break;
    default: document.execCommand(cmd, false, null);
  }
  onEditorInput();
  updateToolbarState();
}
function wrapSelection(tag){
  const sel = getSelection();
  if (!sel.rangeCount) return;
  const r = sel.getRangeAt(0);
  const container = r.commonAncestorContainer;
  const existing = (container.nodeType === 3 ? container.parentNode : container).closest
    ? (container.nodeType === 3 ? container.parentNode : container).closest(tag) : null;
  if (existing && editorEl().contains(existing)){ // unwrap
    const parent = existing.parentNode;
    while (existing.firstChild) parent.insertBefore(existing.firstChild, existing);
    parent.removeChild(existing);
    return;
  }
  if (r.collapsed) return;
  const el = document.createElement(tag);
  try { r.surroundContents(el); }
  catch(e){ el.appendChild(r.extractContents()); r.insertNode(el); }
  sel.removeAllRanges();
  const nr = document.createRange(); nr.selectNodeContents(el); nr.collapse(false);
  sel.addRange(nr);
}
function insertCodeBlock(){
  const pre = document.createElement('pre');
  pre.innerHTML = '<code><br></code>';
  const b = currentBlock();
  if (b && !plainText(b.outerHTML) && b.tagName !== 'PRE'){ b.replaceWith(pre); }
  else {
    const sel = getSelection();
    if (sel.rangeCount){ const r = sel.getRangeAt(0); r.deleteContents(); r.insertNode(pre); }
    else editorEl().appendChild(pre);
  }
  placeCaret(pre.querySelector('code') || pre);
}
function insertHR(){
  const hr = document.createElement('hr');
  const p = document.createElement('p'); p.innerHTML = '<br>';
  const b = currentBlock();
  if (b){ b.after(hr); hr.after(p); } else { editorEl().append(hr, p); }
  placeCaret(p, false);
}
function insertLink(){
  saveSelection();
  uiPrompt('Insert link', 'Paste a URL to link the selected text.', 'https://').then(url => {
    if (!url) return;
    restoreSelection();
    document.execCommand('createLink', false, url);
    $$('#editor a').forEach(a => { a.target = '_blank'; a.rel = 'noopener'; });
    onEditorInput();
  });
}
function makeTodoLI(checked = false){
  const li = document.createElement('li');
  const cb = document.createElement('input');
  cb.type = 'checkbox'; cb.setAttribute('contenteditable', 'false');
  if (checked) cb.setAttribute('checked', '');
  const span = document.createElement('span'); span.className = 'td-text'; span.innerHTML = '<br>';
  li.append(cb, span);
  return li;
}
function insertTodo(){
  const ul = document.createElement('ul'); ul.className = 'todo';
  const li = makeTodoLI();
  ul.appendChild(li);
  const b = currentBlock();
  if (b && !plainText(b.outerHTML) && !['UL','OL','PRE'].includes(b.tagName)) b.replaceWith(ul);
  else {
    const sel = getSelection();
    if (sel.rangeCount){ const r = sel.getRangeAt(0); r.deleteContents(); r.insertNode(ul); }
    else editorEl().appendChild(ul);
  }
  placeCaret(li.querySelector('.td-text'), false);
}
</script>
<script>
/* ---------- editor input & markdown shortcuts ---------- */
const persistEditor = debounce(() => {
  const n = activeNote(); if (!n) return;
  n.content = editorEl().innerHTML;
  n.updated = Date.now();
  saveSoon();
  updateStats(n);
  renderTagsIfIdle(n);
}, 350);
const rerenderListSoon = debounce(() => { renderSidebar(); renderList(); }, 900);

function renderTagsIfIdle(n){
  if (document.activeElement && document.activeElement.id === 'tagInput') return;
  renderTags(n);
}
function onEditorInput(){
  const n = activeNote(); if (!n) return;
  updatePlaceholder();
  persistEditor();
  rerenderListSoon();
}

function editorKeydown(e){
  const mod = e.metaKey || e.ctrlKey;
  const block = currentBlock();
  const pre = inPre();
  const txt = block ? block.textContent.replace(/\u00a0/g, ' ') : '';

  // mod shortcuts
  if (mod && !e.shiftKey && e.key.toLowerCase() === 'e'){ e.preventDefault(); exec('inlinecode'); return; }
  if (mod && e.shiftKey && e.key.toLowerCase() === 'x'){ e.preventDefault(); exec('strikeThrough'); return; }
  if (mod && ['1','2','3'].includes(e.key)){ e.preventDefault(); exec('h' + e.key); return; }
  if (mod && e.shiftKey && e.key.toLowerCase() === 't'){ e.preventDefault(); exec('todo'); return; }

  // --- inside code block ---
  if (pre){
    if (e.key === 'Enter' && !e.shiftKey){
      e.preventDefault();
      const sel = getSelection();
      // caret offset within the pre's text
      let offset = sel.anchorOffset;
      let node = sel.anchorNode;
      if (node !== pre.firstChild && pre.contains(node)){
        // compute absolute offset across text nodes
        offset = 0;
        const walker = document.createTreeWalker(pre, NodeFilter.SHOW_TEXT);
        let tn;
        while ((tn = walker.nextNode())){
          if (tn === node){ offset += sel.anchorOffset; break; }
          offset += tn.textContent.length;
        }
      }
      const text = pre.textContent;
      const before = text.slice(0, offset);
      // exit: caret at end and current line empty
      if (offset >= text.length && /\n\n?$/.test(before)){
        pre.textContent = text.replace(/\n+$/, '') || '\n';
        const p = document.createElement('p'); p.innerHTML = '<br>';
        pre.after(p);
        placeCaret(p, false);
      } else {
        document.execCommand('insertText', false, '\n');
      }
      onEditorInput(); return;
    }
    if (e.key === 'Tab'){ e.preventDefault(); document.execCommand('insertText', false, '  '); return; }
    if (e.key === 'Backspace' && !pre.textContent.trim()){
      e.preventDefault();
      const p = document.createElement('p'); p.innerHTML = '<br>';
      pre.replaceWith(p); placeCaret(p, false); onEditorInput(); return;
    }
    return;
  }

  // --- space-triggered markdown ---
  if (e.key === ' ' && block && !e.shiftKey){
    const t = txt.trim();
    let handled = true;
    if (/^#{1,3}$/.test(t)){ replaceBlockWith(block, 'h' + t.length); }
    else if (t === '-' || t === '*'){ clearBlock(block); document.execCommand('insertUnorderedList'); }
    else if (t === '1.'){ clearBlock(block); document.execCommand('insertOrderedList'); }
    else if (t === '>'){ clearBlock(block); document.execCommand('formatBlock', false, 'blockquote'); }
    else if (t === '[]' || t === '[ ]'){ block.remove(); insertTodo(); }
    else handled = false;
    if (handled){ e.preventDefault(); onEditorInput(); return; }
  }

  // --- enter-triggered markdown ---
  if (e.key === 'Enter' && !e.shiftKey && block){
    const t = txt.trim();
    if (t === '```'){ e.preventDefault(); block.remove(); insertCodeBlock(); onEditorInput(); return; }
    if (/^-{3,}$/.test(t)){ e.preventDefault(); block.remove(); insertHR(); onEditorInput(); return; }
    // exit heading on enter
    if (/^H[1-3]$/.test(block.tagName)){
      const sel = getSelection();
      const r = sel.getRangeAt(0);
      const atEnd = r.endOffset === (r.endContainer.textContent || '').length;
      if (atEnd && sel.isCollapsed){
        e.preventDefault();
        const p = document.createElement('p'); p.innerHTML = '<br>';
        block.after(p); placeCaret(p, false);
        onEditorInput(); return;
      }
    }
    // todo list: enter creates next todo, empty todo exits
    const selNode = getSelection().anchorNode;
    const selEl = selNode ? (selNode.nodeType === 3 ? selNode.parentNode : selNode) : null;
    const liEl = selEl && selEl.closest ? selEl.closest('li') : null;
    if (liEl && liEl.parentElement && liEl.parentElement.classList.contains('todo')){
      e.preventDefault();
      const td = liEl.querySelector('.td-text');
      const ulEl = liEl.parentElement;
      if (!td || !plainText(td.innerHTML)){
        const p = document.createElement('p'); p.innerHTML = '<br>';
        ulEl.after(p);
        liEl.remove();
        if (!ulEl.children.length) ulEl.remove();
        placeCaret(p, false);
      } else {
        const nl = makeTodoLI();
        liEl.after(nl);
        placeCaret(nl.querySelector('.td-text'), false);
      }
      onEditorInput(); return;
    }
  }

  // --- backspace in empty special blocks -> paragraph ---
  if (e.key === 'Backspace' && block && !txt && ['BLOCKQUOTE'].includes(block.tagName)){
    e.preventDefault();
    const p = document.createElement('p'); p.innerHTML = '<br>';
    block.replaceWith(p); placeCaret(p, false); onEditorInput(); return;
  }
}
function replaceBlockWith(block, tag){
  const el = document.createElement(tag); el.innerHTML = '<br>';
  block.replaceWith(el); placeCaret(el, false);
}
function clearBlock(block){ block.innerHTML = ''; }

/* ---------- note actions ---------- */
function createNote(opts = {}){
  const v = state.view;
  const n = {
    id: uid(), title: '', content: '',
    notebook: v.type === 'notebook' ? v.id : (opts.notebook ?? null),
    tags: v.type === 'tag' ? [v.id] : [],
    pinned: false, fav: false, trashed: false,
    created: Date.now(), updated: Date.now(),
  };
  state.notes.unshift(n);
  state.selectedId = n.id;
  save(); renderAll(); renderEditor();
  $('#titleInput').focus();
  if (matchMedia('(max-width: 960px)').matches) document.body.classList.add('pane-editor');
  return n;
}
function selectNote(id, { focusEditor = false } = {}){
  state.selectedId = id;
  saveSoon(); renderList(); renderEditor();
  if (focusEditor) editorEl().focus();
  if (matchMedia('(max-width: 960px)').matches) document.body.classList.add('pane-editor');
}
function setView(type, id = null){
  state.view = { type, id };
  state.search = ''; $('#searchInput').value = ''; $('#searchClear').classList.remove('show');
  const vis = visibleNotes();
  if (!vis.find(n => n.id === state.selectedId)) state.selectedId = vis[0]?.id || null;
  saveSoon(); renderAll(); renderEditor();
  document.body.classList.remove('sb-open');
}
function trashNote(id){
  const n = getNote(id); if (!n) return;
  n.trashed = true; n.pinned = false;
  if (state.selectedId === id){
    const vis = visibleNotes();
    state.selectedId = vis.find(x => x.id !== id)?.id || null;
  }
  save(); renderAll(); renderEditor();
  toast('Moved to trash', 'trash');
}
function deleteForever(id){
  state.notes = state.notes.filter(n => n.id !== id);
  if (state.selectedId === id) state.selectedId = visibleNotes()[0]?.id || null;
  save(); renderAll(); renderEditor();
  toast('Deleted permanently', 'trash');
}
function restoreNote(id){
  const n = getNote(id); if (!n) return;
  n.trashed = false; save(); renderAll(); renderEditor();
  toast('Note restored', 'restore');
}
function duplicateNote(id){
  const n = getNote(id); if (!n) return;
  const c = { ...n, id: uid(), title: (n.title || 'Untitled') + ' copy', pinned: false,
    created: Date.now(), updated: Date.now(), tags: [...(n.tags || [])] };
  state.notes.unshift(c); state.selectedId = c.id;
  save(); renderAll(); renderEditor(); toast('Note duplicated', 'copy');
}
function togglePin(id){ const n = getNote(id); if (!n) return; n.pinned = !n.pinned; save(); renderAll(); renderEditor(); }
function toggleFav(id){ const n = getNote(id); if (!n) return; n.fav = !n.fav; save(); renderAll(); renderEditor(); }
function moveNote(id, nbId){
  const n = getNote(id); if (!n) return;
  n.notebook = nbId || null; n.updated = Date.now();
  save(); renderAll(); renderEditor();
  toast(nbId ? `Moved to ${getNb(nbId)?.name}` : 'Removed from notebook', 'book');
}

/* ---------- notebooks ---------- */
let nbInputOpen = false;
function openNotebookInput(){
  if (nbInputOpen) return; nbInputOpen = true;
  const body = $('.sb-section:not(.collapsed) [data-section="notebooks"]')?.closest('.sb-section')?.querySelector('.sb-section-body')
    || $$('.sb-section')[0].querySelector('.sb-section-body');
  const input = document.createElement('input');
  input.className = 'sb-inline-input'; input.placeholder = 'Notebook name…';
  body.prepend(input); input.focus();
  const done = commit => {
    nbInputOpen = false;
    const name = input.value.trim();
    input.remove();
    if (commit && name) addNotebook(name);
    else renderSidebar();
  };
  input.addEventListener('keydown', e => {
    if (e.key === 'Enter') done(true);
    if (e.key === 'Escape') done(false);
    e.stopPropagation();
  });
  input.addEventListener('blur', () => done(true));
}
function addNotebook(name){
  const nb = { id: uid(), name, color: NB_COLORS[state.nbColorIdx++ % NB_COLORS.length] };
  state.notebooks.push(nb);
  save(); renderSidebar();
  setView('notebook', nb.id);
  toast(`Notebook “${name}” created`, 'book');
}
function renameNotebook(id){
  const nb = getNb(id); if (!nb) return;
  uiPrompt('Rename notebook', '', nb.name).then(v => {
    if (v && v.trim()){ nb.name = v.trim(); save(); renderAll(); renderEditor(); }
  });
}
function deleteNotebook(id){
  const nb = getNb(id); if (!nb) return;
  const count = nbCount(id);
  uiConfirm('Delete notebook', `“${nb.name}” will be removed. ${count} note${count === 1 ? '' : 's'} inside will be kept and moved to “No notebook”.`, 'Delete').then(ok => {
    if (!ok) return;
    state.notes.forEach(n => { if (n.notebook === id) n.notebook = null; });
    state.notebooks = state.notebooks.filter(b => b.id !== id);
    if (state.view.type === 'notebook' && state.view.id === id) state.view = { type: 'all', id: null };
    save(); renderAll(); renderEditor();
    toast('Notebook deleted', 'trash');
  });
}

/* ---------- markdown export ---------- */
function htmlToMd(html){
  const d = document.createElement('div'); d.innerHTML = html || '';
  const inline = node => {
    let out = '';
    node.childNodes.forEach(ch => {
      if (ch.nodeType === 3){ out += ch.textContent; return; }
      const t = ch.tagName?.toLowerCase();
      const inner = inline(ch);
      if (t === 'strong' || t === 'b') out += `**${inner}**`;
      else if (t === 'em' || t === 'i') out += `*${inner}*`;
      else if (t === 's' || t === 'strike' || t === 'del') out += `~~${inner}~~`;
      else if (t === 'code') out += '`' + ch.textContent + '`';
      else if (t === 'mark') out += `==${inner}==`;
      else if (t === 'a') out += `[${inner}](${ch.getAttribute('href') || ''})`;
      else if (t === 'br') out += '  \n';
      else out += inner;
    });
    return out;
  };
  const blocks = (node, depth = 0) => {
    let out = '';
    node.childNodes.forEach(ch => {
      if (ch.nodeType === 3){ const t = ch.textContent.trim(); if (t) out += t + '\n\n'; return; }
      const t = ch.tagName?.toLowerCase();
      if (t === 'h1') out += `# ${inline(ch).trim()}\n\n`;
      else if (t === 'h2') out += `## ${inline(ch).trim()}\n\n`;
      else if (t === 'h3') out += `### ${inline(ch).trim()}\n\n`;
      else if (t === 'p' || t === 'div'){ const s = inline(ch).trim(); out += (s || '') + '\n\n'; }
      else if (t === 'blockquote') out += inline(ch).trim().split('\n').map(l => '> ' + l).join('\n') + '\n\n';
      else if (t === 'pre') out += '```\n' + ch.textContent.replace(/\n$/, '') + '\n```\n\n';
      else if (t === 'hr') out += '---\n\n';
      else if (t === 'ul' || t === 'ol'){
        const todo = ch.classList.contains('todo');
        [...ch.children].forEach((li, i) => {
          const pad = '  '.repeat(depth);
          if (todo){
            const cb = li.querySelector('input');
            const txt = inline(li).trim();
            out += `${pad}- [${cb && cb.hasAttribute('checked') ? 'x' : ' '}] ${txt}\n`;
          } else {
            const bullet = t === 'ul' ? '-' : (i + 1) + '.';
            out += `${pad}${bullet} ${inline(li).trim()}\n`;
          }
        });
        out += '\n';
      }
      else out += blocks(ch, depth);
    });
    return out;
  };
  return blocks(d).replace(/\n{3,}/g, '\n\n').trim() + '\n';
}
function noteToMd(n){
  const nb = getNb(n.notebook);
  let fm = `---\ntitle: ${n.title || 'Untitled'}\ncreated: ${new Date(n.created).toISOString()}\n`;
  if (nb) fm += `notebook: ${nb.name}\n`;
  if (n.tags?.length) fm += `tags: ${n.tags.join(', ')}\n`;
  fm += `---\n\n`;
  return fm + (n.title ? `# ${n.title}\n\n` : '') + htmlToMd(n.content);
}
function downloadFile(name, content, type = 'text/plain'){
  const a = document.createElement('a');
  a.href = URL.createObjectURL(new Blob([content], { type }));
  a.download = name; a.click();
  setTimeout(() => URL.revokeObjectURL(a.href), 2000);
}
function exportNote(id){
  const n = getNote(id); if (!n) return;
  downloadFile((n.title || 'untitled').replace(/[^\w\- ]+/g, '').trim().replace(/\s+/g, '-').toLowerCase() + '.md', noteToMd(n), 'text/markdown');
  toast('Exported as Markdown', 'download');
}
</script>
<script>
/* ---------- toast ---------- */
let toastTimer;
function toast(msg, ic = 'check'){
  const t = $('#toast');
  t.innerHTML = icon(ic) + esc(msg);
  t.classList.add('show');
  clearTimeout(toastTimer);
  toastTimer = setTimeout(() => t.classList.remove('show'), 2200);
}

/* ---------- dialog (prompt / confirm) ---------- */
function uiDialog({ title, msg = '', value = null, placeholder = '', okText = 'OK', danger = false }){
  return new Promise(resolve => {
    const ov = $('#dialogOverlay'), input = $('#dialogInput');
    $('#dlgTitle').textContent = title;
    $('#dlgMsg').textContent = msg;
    $('#dlgMsg').style.display = msg ? '' : 'none';
    const ok = $('#dlgOk');
    ok.textContent = okText;
    ok.className = 'btn ' + (danger ? 'danger' : 'primary');
    input.style.display = value !== null ? '' : 'none';
    input.value = value ?? '';
    input.placeholder = placeholder;
    ov.classList.add('show');
    if (value !== null){ input.focus(); input.select(); }
    else ok.focus();
    const done = val => {
      ov.classList.remove('show');
      ok.onclick = $('#dlgCancel').onclick = input.onkeydown = ov.onclick = null;
      resolve(val);
    };
    ok.onclick = () => done(value !== null ? input.value : true);
    $('#dlgCancel').onclick = () => done(null);
    ov.onclick = e => { if (e.target === ov) done(null); };
    input.onkeydown = e => {
      if (e.key === 'Enter') done(input.value);
      if (e.key === 'Escape') done(null);
      e.stopPropagation();
    };
    ov.onkeydown = e => { if (e.key === 'Escape') done(null); };
  });
}
const uiPrompt = (title, msg, value = '', placeholder = '') => uiDialog({ title, msg, value, placeholder });
const uiConfirm = (title, msg, okText = 'Delete') => uiDialog({ title, msg, okText, danger: true });

/* ---------- context menu ---------- */
let ctxEl = null;
function closeMenu(){ if (ctxEl){ ctxEl.remove(); ctxEl = null; } }
function openMenu(items, x, y){
  closeMenu();
  const m = document.createElement('div');
  m.className = 'ctx-menu';
  items.forEach(it => {
    if (it === '-'){ m.insertAdjacentHTML('beforeend', '<div class="ctx-divider"></div>'); return; }
    if (it.label2){ m.insertAdjacentHTML('beforeend', `<div class="ctx-label">${esc(it.label2)}</div>`); return; }
    const b = document.createElement('button');
    b.className = 'ctx-item' + (it.danger ? ' danger' : '') + (it.sub ? ' has-sub' : '');
    b.innerHTML = `${it.icon ? icon(it.icon) : ''}${it.dot ? `<span class="dot" style="background:${it.dot}"></span>` : ''}
      <span>${esc(it.label)}</span>
      ${it.checked ? `<span class="check">${icon('check')}</span>` : ''}
      ${it.sub ? `<span class="arrow">${icon('chevR')}</span>` : ''}`;
    if (it.sub){
      const sub = document.createElement('div');
      sub.className = 'ctx-submenu';
      it.sub.forEach(s => {
        const sb = document.createElement('button');
        sb.className = 'ctx-item';
        sb.innerHTML = `${s.dot ? `<span class="dot" style="background:${s.dot}"></span>` : ''}<span>${esc(s.label)}</span>${s.checked ? `<span class="check">${icon('check')}</span>` : ''}`;
        sb.onclick = e => { e.stopPropagation(); closeMenu(); s.action && s.action(); };
        sub.appendChild(sb);
      });
      b.appendChild(sub);
    }
    if (it.action) b.onclick = e => { e.stopPropagation(); closeMenu(); it.action(); };
    m.appendChild(b);
  });
  document.body.appendChild(m);
  ctxEl = m;
  const r = m.getBoundingClientRect();
  m.style.left = clamp(x, 8, innerWidth - r.width - 8) + 'px';
  m.style.top = clamp(y, 8, innerHeight - r.height - 8) + 'px';
}
document.addEventListener('pointerdown', e => { if (ctxEl && !ctxEl.contains(e.target)) closeMenu(); }, true);
addEventListener('blur', closeMenu);

function noteMenu(id, x, y){
  const n = getNote(id); if (!n) return;
  let items;
  if (n.trashed){
    items = [
      { label: 'Restore', icon: 'restore', action: () => restoreNote(id) },
      '-',
      { label: 'Delete forever', icon: 'trash', danger: true,
        action: () => uiConfirm('Delete forever', 'This note will be permanently erased. This cannot be undone.').then(ok => ok && deleteForever(id)) },
    ];
  } else {
    items = [
      { label: n.pinned ? 'Unpin' : 'Pin to top', icon: 'pin', action: () => togglePin(id) },
      { label: n.fav ? 'Remove favorite' : 'Add to favorites', icon: 'star', action: () => toggleFav(id) },
      { label: 'Move to', icon: 'book', sub: [
          { label: 'No notebook', dot: 'var(--text-3)', checked: !n.notebook, action: () => moveNote(id, null) },
          ...state.notebooks.map(b => ({ label: b.name, dot: b.color, checked: n.notebook === b.id, action: () => moveNote(id, b.id) })),
        ] },
      { label: 'Duplicate', icon: 'copy', action: () => duplicateNote(id) },
      '-',
      { label: 'Copy as Markdown', icon: 'cmd', action: () => { navigator.clipboard?.writeText(noteToMd(n)); toast('Markdown copied', 'copy'); } },
      { label: 'Export .md', icon: 'download', action: () => exportNote(id) },
      '-',
      { label: 'Move to trash', icon: 'trash', danger: true, action: () => trashNote(id) },
    ];
  }
  openMenu(items, x, y);
}
function notebookMenu(id, x, y){
  const nb = getNb(id); if (!nb) return;
  openMenu([
    { label: 'Rename', icon: 'edit', action: () => renameNotebook(id) },
    { label: 'Change color', icon: 'tag', sub: NB_COLORS.map(c => ({
        label: '', dot: c, checked: nb.color === c,
        action: () => { nb.color = c; save(); renderAll(); renderEditor(); } })) },
    '-',
    { label: 'Delete notebook', icon: 'trash', danger: true, action: () => deleteNotebook(id) },
  ], x, y);
}

/* ---------- command palette ---------- */
let palIdx = 0, palItems = [];
function openPalette(){
  $('#paletteOverlay').classList.add('show');
  const inp = $('#paletteInput');
  inp.value = ''; palIdx = 0;
  renderPalette('');
  setTimeout(() => inp.focus(), 30);
}
function closePalette(){ $('#paletteOverlay').classList.remove('show'); }
function paletteCommands(){
  return [
    { group: 'Actions', label: 'New note', icon: 'plus', hint: '⌘N', run: () => createNote() },
    { group: 'Actions', label: 'New notebook', icon: 'book', run: () => { closePalette(); openNotebookInput(); } },
    { group: 'Actions', label: 'Toggle theme', icon: 'moon', hint: '⌘⇧L', run: toggleTheme },
    { group: 'Actions', label: 'Toggle focus mode', icon: 'focus', run: toggleFocus },
    { group: 'Actions', label: 'Export current note (.md)', icon: 'download', run: () => activeNote() && exportNote(activeNote().id) },
    { group: 'Actions', label: 'Export all data (JSON)', icon: 'download', run: exportAll },
    { group: 'View', label: 'Go to All Notes', icon: 'note', run: () => setView('all') },
    { group: 'View', label: 'Go to Pinned', icon: 'pin', run: () => setView('pinned') },
    { group: 'View', label: 'Go to Favorites', icon: 'star', run: () => setView('fav') },
    { group: 'View', label: 'Go to Trash', icon: 'trash', run: () => setView('trash') },
    ...state.notebooks.map(b => ({ group: 'Notebooks', label: 'Go to ' + b.name, icon: 'book', dot: b.color, run: () => setView('notebook', b.id) })),
  ];
}
function renderPalette(q){
  const res = $('#paletteResults');
  palItems = [];
  let html = '', lastGroup = null;
  const addGroup = g => { if (g !== lastGroup){ html += `<div class="pal-group">${g}</div>`; lastGroup = g; } };

  paletteCommands().forEach(c => {
    const s = fuzzyScore(q, c.label);
    if (q && s <= 0) return;
    palItems.push({ ...c, score: s });
  });
  palItems.sort((a, b) => b.score - a.score);
  const cmds = palItems.splice(0, palItems.length);

  const notes = state.notes
    .map(n => ({ n, score: Math.max(fuzzyScore(q, n.title || 'Untitled'), fuzzyScore(q, plainText(n.content)) * .6) }))
    .filter(x => !q || x.score > 0)
    .sort((a, b) => b.score - a.score || b.n.updated - a.n.updated)
    .slice(0, q ? 7 : 5);

  const items = [...cmds.slice(0, q ? 8 : 6), ...notes.map(x => ({
    group: x.n.trashed ? 'Trash' : 'Notes',
    label: x.n.title || 'Untitled',
    icon: x.n.trashed ? 'trash' : 'note',
    sub: plainText(x.n.content).slice(0, 60),
    run: () => { setView(x.n.trashed ? 'trash' : 'all'); selectNote(x.n.id); },
  }))];
  palItems = items;

  items.forEach((it, i) => {
    addGroup(it.group);
    html += `<div class="pal-item${i === palIdx ? ' sel' : ''}" data-i="${i}">
      ${it.dot ? `<span class="dot" style="width:9px;height:9px;border-radius:50%;background:${it.dot}"></span>` : icon(it.icon)}
      <span style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(it.label)}</span>
      ${it.hint ? `<span class="sub mono">${it.hint}</span>` : it.sub ? `<span class="sub">${esc(it.sub)}</span>` : ''}
    </div>`;
  });
  res.innerHTML = html || `<div class="pal-empty">No matches. Try a different search.</div>`;
}
function paletteKey(e){
  if (e.key === 'Escape'){ closePalette(); return; }
  if (e.key === 'ArrowDown' || e.key === 'ArrowUp'){
    e.preventDefault();
    palIdx = (palIdx + (e.key === 'ArrowDown' ? 1 : -1) + palItems.length) % Math.max(palItems.length, 1);
    $$('.pal-item').forEach((el, i) => el.classList.toggle('sel', i === palIdx));
    $('.pal-item.sel')?.scrollIntoView({ block: 'nearest' });
    return;
  }
  if (e.key === 'Enter'){
    const it = palItems[palIdx];
    if (it){ closePalette(); it.run(); }
  }
}

/* ---------- theme / focus / fonts ---------- */
function applyTheme(){
  document.documentElement.dataset.theme = state.theme;
  $('#themeBtn').innerHTML = icon(state.theme === 'dark' ? 'sun' : 'moon');
}
function toggleTheme(){
  state.theme = state.theme === 'dark' ? 'light' : 'dark';
  applyTheme(); saveSoon();
}
function toggleFocus(){
  state.focus = !state.focus;
  document.body.classList.toggle('focus', state.focus);
  $('#focusBtn').classList.toggle('on', state.focus);
  saveSoon();
  if (state.focus) editorEl().focus();
}
function applyPrefs(){
  applyTheme();
  document.body.dataset.efont = state.efont;
  document.body.classList.toggle('focus', state.focus);
  $('#focusBtn').classList.toggle('on', state.focus);
  $('#sidebar').style.width = state.widths.sidebar + 'px';
  $('#listPane').style.width = state.widths.list + 'px';
}

/* ---------- import / export ---------- */
function exportAll(){
  downloadFile('inkwell-backup.json', JSON.stringify({ notes: state.notes, notebooks: state.notebooks }, null, 2), 'application/json');
  toast('Backup exported', 'download');
}
function importAll(file){
  const r = new FileReader();
  r.onload = () => {
    try {
      const data = JSON.parse(r.result);
      if (!Array.isArray(data.notes) || !Array.isArray(data.notebooks)) throw 0;
      state.notes = data.notes; state.notebooks = data.notebooks;
      state.selectedId = data.notes.find(n => !n.trashed)?.id || null;
      save(); renderAll(); renderEditor();
      toast(`Imported ${data.notes.length} notes`, 'upload');
    } catch(e){ toast('Import failed — invalid file', 'x'); }
  };
  r.readAsText(file);
}

/* ---------- settings menu ---------- */
function settingsMenu(x, y){
  openMenu([
    { label2: 'Appearance' },
    { label: state.theme === 'dark' ? 'Switch to light' : 'Switch to dark', icon: state.theme === 'dark' ? 'sun' : 'moon', action: toggleTheme },
    { label: 'Editor font', icon: 'edit', sub: [
      { label: 'Sans', checked: state.efont === 'sans', action: () => setFont('sans') },
      { label: 'Serif', checked: state.efont === 'serif', action: () => setFont('serif') },
      { label: 'Mono', checked: state.efont === 'mono', action: () => setFont('mono') },
    ] },
    { label2: 'Sort notes by' },
    { label: 'Last updated', icon: 'sort', checked: state.sort === 'updated', action: () => setSort('updated') },
    { label: 'Date created', icon: 'sort', checked: state.sort === 'created', action: () => setSort('created') },
    { label: 'Title A–Z', icon: 'sort', checked: state.sort === 'title', action: () => setSort('title') },
    { label2: 'Data' },
    { label: 'Export backup (JSON)', icon: 'download', action: exportAll },
    { label: 'Import backup…', icon: 'upload', action: () => $('#importFile').click() },
    { label: 'Restore sample data', icon: 'restore', action: () =>
      uiConfirm('Restore sample data', 'Your current notes will be replaced with the original sample content.', 'Restore').then(ok => {
        if (!ok) return; seed(); save(); applyPrefs(); renderAll(); renderEditor(); toast('Sample data restored', 'restore');
      }) },
    '-',
    { label: 'Erase everything', icon: 'trash', danger: true, action: () =>
      uiConfirm('Erase everything', 'All notes and notebooks will be permanently deleted from this browser.', 'Erase').then(ok => {
        if (!ok) return; state.notes = []; state.notebooks = []; state.selectedId = null;
        save(); renderAll(); renderEditor(); toast('All data erased', 'trash');
      }) },
  ], x, y);
}
function setFont(f){ state.efont = f; document.body.dataset.efont = f; saveSoon(); }
function setSort(s){ state.sort = s; saveSoon(); renderList(); }
</script>
<script>
/* ---------- drag & drop notes -> notebooks ---------- */
function initDnD(){
  const scroll = $('#notesScroll');
  scroll.addEventListener('dragstart', e => {
    const card = e.target.closest('.note-card'); if (!card) return;
    e.dataTransfer.setData('text/note-id', card.dataset.id);
    e.dataTransfer.effectAllowed = 'move';
    card.classList.add('dragging');
  });
  scroll.addEventListener('dragend', () => $$('.note-card.dragging').forEach(c => c.classList.remove('dragging')));
  $('#sbScroll').addEventListener('dragover', e => {
    const item = e.target.closest('[data-nb]');
    if (item && e.dataTransfer.types.includes('text/note-id')){
      e.preventDefault(); e.dataTransfer.dropEffect = 'move';
      $$('.sb-item.drag-over').forEach(el => el.classList.remove('drag-over'));
      item.classList.add('drag-over');
    }
  });
  $('#sbScroll').addEventListener('dragleave', e => {
    if (!e.relatedTarget || !$('#sbScroll').contains(e.relatedTarget))
      $$('.sb-item.drag-over').forEach(el => el.classList.remove('drag-over'));
  });
  $('#sbScroll').addEventListener('drop', e => {
    const item = e.target.closest('[data-nb]');
    $$('.sb-item.drag-over').forEach(el => el.classList.remove('drag-over'));
    if (!item) return;
    e.preventDefault();
    const id = e.dataTransfer.getData('text/note-id');
    if (id) moveNote(id, item.dataset.nb);
  });
}

/* ---------- resizers ---------- */
function initResizers(){
  const bind = (el, get, set, min, max) => {
    el.addEventListener('pointerdown', e => {
      e.preventDefault();
      el.classList.add('dragging');
      const startX = e.clientX, startW = get();
      const move = ev => set(clamp(startW + ev.clientX - startX, min, max));
      const up = () => {
        el.classList.remove('dragging');
        removeEventListener('pointermove', move); removeEventListener('pointerup', up);
        saveSoon();
      };
      addEventListener('pointermove', move); addEventListener('pointerup', up);
    });
  };
  bind($('#resizer1'),
    () => $('#sidebar').offsetWidth,
    w => { $('#sidebar').style.width = w + 'px'; state.widths.sidebar = w; }, 180, 400);
  bind($('#resizer2'),
    () => $('#listPane').offsetWidth,
    w => { $('#listPane').style.width = w + 'px'; state.widths.list = w; }, 220, 520);
}

/* ---------- events ---------- */
function initEvents(){
  /* static buttons */
  $('#logoMark').innerHTML = icon('logo');
  $('#newNoteIcon').innerHTML = icon('plus');
  $('#searchIcon').innerHTML = icon('search');
  $('#searchClear').innerHTML = icon('x');
  $('#sortBtn').innerHTML = icon('sort');
  $('#settingsBtn').innerHTML = icon('settings');
  $('#hamburgerBtn').innerHTML = icon('menu');
  $('#backBtn').innerHTML = icon('back');
  $('#pinBtn').innerHTML = icon('pin');
  $('#favBtn').innerHTML = icon('star');
  $('#focusBtn').innerHTML = icon('focus');
  $('#moreBtn').innerHTML = icon('more');
  $('#palIcon').innerHTML = icon('cmd');

  $('#newNoteBtn').onclick = () => createNote();
  $('#themeBtn').onclick = toggleTheme;
  $('#settingsBtn').onclick = e => { const r = e.currentTarget.getBoundingClientRect(); settingsMenu(r.left, r.top - 8); };
  $('#hamburgerBtn').onclick = () => document.body.classList.add('sb-open');
  $('#sidebarScrim').onclick = () => document.body.classList.remove('sb-open');
  $('#backBtn').onclick = () => document.body.classList.remove('pane-editor');
  $('#pinBtn').onclick = () => togglePin(state.selectedId);
  $('#favBtn').onclick = () => toggleFav(state.selectedId);
  $('#focusBtn').onclick = toggleFocus;
  $('#moreBtn').onclick = e => {
    const r = e.currentTarget.getBoundingClientRect();
    noteMenu(state.selectedId, r.right - 210, r.bottom + 6);
  };
  $('#sortBtn').onclick = e => {
    const r = e.currentTarget.getBoundingClientRect();
    openMenu([
      { label2: 'Sort by' },
      { label: 'Last updated', icon: 'clock', checked: state.sort === 'updated', action: () => setSort('updated') },
      { label: 'Date created', icon: 'plus', checked: state.sort === 'created', action: () => setSort('created') },
      { label: 'Title A–Z', icon: 'sort', checked: state.sort === 'title', action: () => setSort('title') },
    ], r.right - 200, r.bottom + 6);
  };
  $('#importFile').addEventListener('change', e => { if (e.target.files[0]) importAll(e.target.files[0]); e.target.value = ''; });

  /* sidebar */
  const sb = $('#sbScroll');
  sb.addEventListener('click', e => {
    const sec = e.target.closest('.sb-section-head');
    const addNb = e.target.closest('[data-addnb]');
    if (addNb){ e.stopPropagation(); state.collapsed.notebooks = false; renderSidebar(); openNotebookInput(); return; }
    if (sec){
      const key = sec.dataset.section;
      state.collapsed[key] = !state.collapsed[key];
      saveSoon(); renderSidebar(); return;
    }
    const item = e.target.closest('[data-view]');
    if (item) setView(item.dataset.view, item.dataset.id || null);
  });
  sb.addEventListener('contextmenu', e => {
    const item = e.target.closest('[data-nb]');
    if (item){ e.preventDefault(); notebookMenu(item.dataset.nb, e.clientX, e.clientY); }
  });

  /* note list */
  const scroll = $('#notesScroll');
  scroll.addEventListener('click', e => {
    const card = e.target.closest('.note-card');
    if (card) selectNote(card.dataset.id);
  });
  scroll.addEventListener('contextmenu', e => {
    const card = e.target.closest('.note-card');
    if (card){ e.preventDefault(); if (state.selectedId !== card.dataset.id) selectNote(card.dataset.id); noteMenu(card.dataset.id, e.clientX, e.clientY); }
  });
  scroll.addEventListener('dblclick', e => {
    const card = e.target.closest('.note-card');
    if (card && matchMedia('(min-width: 961px)').matches){ selectNote(card.dataset.id); $('#titleInput').focus(); }
  });

  /* search */
  const si = $('#searchInput');
  si.addEventListener('input', () => {
    state.search = si.value;
    $('#searchClear').classList.toggle('show', !!si.value);
    renderList();
  });
  $('#searchClear').onclick = () => { si.value = ''; state.search = ''; $('#searchClear').classList.remove('show'); renderList(); si.focus(); };

  /* toolbar */
  const tb = $('#toolbar');
  tb.addEventListener('pointerdown', e => { if (e.target.closest('.tb-btn')) e.preventDefault(); });
  tb.addEventListener('click', e => {
    const b = e.target.closest('.tb-btn'); if (!b) return;
    exec(b.dataset.cmd);
  });

  /* title */
  const ti = $('#titleInput');
  ti.addEventListener('input', () => {
    const n = activeNote(); if (!n) return;
    n.title = ti.value; n.updated = Date.now();
    saveSoon(); rerenderListSoon();
    $('#crumb b') && ($('#crumb b').textContent = n.title || 'Untitled');
  });
  ti.addEventListener('keydown', e => {
    if (e.key === 'Enter' || e.key === 'ArrowDown'){ e.preventDefault(); editorEl().focus(); placeCaretAtStart(); }
  });
  function placeCaretAtStart(){
    const ed = editorEl();
    const r = document.createRange(); r.selectNodeContents(ed); r.collapse(true);
    const s = getSelection(); s.removeAllRanges(); s.addRange(r);
  }

  /* editor */
  const ed = editorEl();
  ed.addEventListener('input', onEditorInput);
  ed.addEventListener('keydown', editorKeydown);
  ed.addEventListener('click', e => {
    if (e.target.matches('ul.todo input[type=checkbox]')){
      if (e.target.checked) e.target.setAttribute('checked', ''); else e.target.removeAttribute('checked');
      onEditorInput();
    }
    const a = e.target.closest('a');
    if (a && (e.metaKey || e.ctrlKey)){ window.open(a.href, '_blank'); }
  });
  ed.addEventListener('keyup', updateToolbarState);
  ed.addEventListener('mouseup', () => { updateToolbarState(); saveSelection(); });
  ed.addEventListener('paste', e => {
    e.preventDefault();
    const text = e.clipboardData.getData('text/plain');
    document.execCommand('insertText', false, text);
  });
  document.addEventListener('selectionchange', () => {
    if (document.activeElement === ed) updateToolbarState();
  });

  /* meta line: notebook select (delegated, element re-created) */
  $('#metaLine').addEventListener('change', e => {
    if (e.target.id !== 'nbSelect') return;
    const n = activeNote(); if (!n) return;
    n.notebook = e.target.value || null;
    save(); renderAll(); renderEditor();
  });

  /* tags */
  const ft = $('.editor-footer');
  ft.addEventListener('click', e => {
    const rm = e.target.closest('[data-rmtag]');
    if (rm){
      const n = activeNote(); if (!n) return;
      n.tags = (n.tags || []).filter(t => t !== rm.dataset.rmtag);
      save(); renderTags(n); renderAll(); renderTags(n);
    }
  });
  ft.addEventListener('keydown', e => {
    if (e.target.id !== 'tagInput') return;
    e.stopPropagation();
    const n = activeNote(); if (!n) return;
    if ((e.key === 'Enter' || e.key === ',') && e.target.value.trim()){
      e.preventDefault();
      const t = e.target.value.trim().replace(/^#/, '').toLowerCase();
      n.tags = n.tags || [];
      if (!n.tags.includes(t)) n.tags.push(t);
      save(); renderTags(n); renderAll();
      $('#tagInput')?.focus();
    }
    if (e.key === 'Backspace' && !e.target.value && n.tags?.length){
      n.tags.pop(); save(); renderTags(n); renderAll(); $('#tagInput')?.focus();
    }
  });

  /* palette */
  $('#paletteInput').addEventListener('input', e => { palIdx = 0; renderPalette(e.target.value); });
  $('#paletteInput').addEventListener('keydown', paletteKey);
  $('#paletteOverlay').addEventListener('pointerdown', e => { if (e.target.id === 'paletteOverlay') closePalette(); });
  $('#paletteResults').addEventListener('click', e => {
    const it = e.target.closest('.pal-item');
    if (it){ const item = palItems[+it.dataset.i]; closePalette(); item && item.run(); }
  });

  /* global keys */
  document.addEventListener('keydown', e => {
    const mod = e.metaKey || e.ctrlKey;
    if (mod && e.key.toLowerCase() === 'k'){ e.preventDefault(); $('#paletteOverlay').classList.contains('show') ? closePalette() : openPalette(); return; }
    if (mod && e.key.toLowerCase() === 'n'){ e.preventDefault(); createNote(); return; }
    if (mod && e.shiftKey && e.key.toLowerCase() === 'l'){ e.preventDefault(); toggleTheme(); return; }
    if (mod && e.shiftKey && e.key.toLowerCase() === 'f'){ e.preventDefault(); $('#searchInput').focus(); return; }
    if (mod && e.key.toLowerCase() === 's'){ e.preventDefault(); save(); toast('All changes saved', 'check'); return; }
    if (e.key === 'Escape'){
      if (ctxEl) closeMenu();
      else if (document.body.classList.contains('sb-open')) document.body.classList.remove('sb-open');
      else if (state.focus) toggleFocus();
    }
  });
}

/* ---------- init ---------- */
function init(){
  load();
  buildToolbar();
  applyPrefs();
  renderAll();
  renderEditor();
  initEvents();
  initDnD();
  initResizers();
  try { document.execCommand('defaultParagraphSeparator', false, 'p'); } catch(e){}
}
init();
</script>
</body>
</html>
<!-- agent-meta {"model":"k3","provider":"kimi","persona":"full","sessionId":"8a295e3f-51c8-4345-ac4c-9fd0b9e8ea61","tokensIn":1874411,"tokensOut":48020,"tokensTotal":1922431,"turns":32,"toolCalls":31,"failedToolCalls":0,"timestamp":"2026-07-20T20:39:34.980Z"} -->