← Notes results

Notes

Deepseek v4 Flash 0731 · typescript

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 108,532 bytes · SHA-256 4f9f603b086e
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Notes</title>
<style>
/* ── css: base ── */
:root{
  --font-ui: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --font-editor: 'Source Serif 4', 'Iowan Old Style', 'Palatino Linotype', 'Book Antiqua', Georgia, serif;

  --bg-app:#f4f2ee;
  --bg-sidebar:#edeae4;
  --bg-notes:#f6f4f1;
  --bg-editor:#ffffff;
  --bg-elev:#ffffff;
  --bg-hover:rgba(0,0,0,0.05);
  --bg-active:rgba(0,0,0,0.075);
  --bg-selected:#e6ded2;

  --text:#2a2420;
  --text-soft:#6b6258;
  --text-faint:#9a9083;
  --border:#e2ddd4;
  --border-strong:#d4cdc1;

  --accent:#a65a33;
  --accent-strong:#8f4b27;
  --accent-soft:#f1e3d6;
  --accent-text:#7c4a28;

  --chip:#ece7de;
  --mark:#ffe08a;
  --danger:#c0392b;
  --danger-soft:#fbeae7;

  --shadow-1:0 1px 2px rgba(40,30,20,.06);
  --shadow-2:0 6px 24px rgba(40,30,20,.12);
  --shadow-3:0 12px 44px rgba(40,30,20,.18);

  --radius:10px;
  --radius-s:7px;
  --trans:150ms cubic-bezier(.4,0,.2,1);
}
html.dark{
  --bg-app:#1a1816;
  --bg-sidebar:#211e1b;
  --bg-notes:#1d1a18;
  --bg-editor:#23201d;
  --bg-elev:#2a2622;
  --bg-hover:rgba(255,255,255,0.06);
  --bg-active:rgba(255,255,255,0.09);
  --bg-selected:#3a3128;

  --text:#ece6dd;
  --text-soft:#a89e90;
  --text-faint:#6f6659;
  --border:#36302a;
  --border-strong:#453d34;

  --accent:#d5895c;
  --accent-strong:#e09b70;
  --accent-soft:#3a2c22;
  --accent-text:#e2ab85;

  --chip:#322c25;
  --mark:#6b5420;
  --danger:#e06c5c;
  --danger-soft:#3a2622;

  --shadow-1:0 1px 2px rgba(0,0,0,.3);
  --shadow-2:0 6px 24px rgba(0,0,0,.4);
  --shadow-3:0 12px 44px rgba(0,0,0,.5);
}
*{box-sizing:border-box;}
html,body{height:100%;margin:0;}
body{
  font-family:var(--font-ui);
  background:var(--bg-app);
  color:var(--text);
  font-size:14px;
  -webkit-font-smoothing:antialiased;
  overflow:hidden;
}
#app{height:100vh;display:flex;position:relative;}
button{font-family:inherit;color:inherit;background:none;border:none;cursor:pointer;padding:0;}
input,textarea{font-family:inherit;color:inherit;}
::selection{background:var(--mark);color:inherit;}

/* scrollbars */
::-webkit-scrollbar{width:11px;height:11px;}
::-webkit-scrollbar-thumb{background:transparent;border-radius:6px;border:3px solid transparent;background-clip:content-box;}
::-webkit-scrollbar-thumb:hover{background:var(--border-strong);background-clip:content-box;border:3px solid transparent;}
::-webkit-scrollbar-corner{background:transparent;}
.pane{overflow:visible;position:relative;}
/* ── css: layout ── */
#sidebar{
  width:260px;flex:0 0 auto;background:var(--bg-sidebar);
  display:flex;flex-direction:column;overflow:hidden;min-width:0;
  padding:0 10px;
  border-right:1px solid var(--border);
  transition:width var(--trans), flex-basis var(--trans), padding var(--trans);
}
#app.sidebar-collapsed #sidebar{width:0;flex-basis:0;border-right:none;min-width:0;padding:0;overflow:hidden;opacity:0;pointer-events:none;}
#sidebar.collapsing{overflow:hidden;}
/* ── css: sidebar ── */
/* sidebar */
#sidebar{padding:0 10px;gap:2px;}
.side-head{display:flex;align-items:center;justify-content:space-between;padding:14px 6px 12px;}
.app-brand{display:flex;align-items:center;gap:8px;font-weight:700;font-size:15px;letter-spacing:-.2px;color:var(--text);}
.brand-mark{color:var(--accent);font-size:18px;}
.icon-btn{
  width:30px;height:30px;border-radius:var(--radius-s);display:grid;place-items:center;
  color:var(--text-soft);transition:background var(--trans),color var(--trans);
}
.icon-btn:hover{background:var(--bg-hover);color:var(--text);}
.icon-btn.active{background:var(--accent-soft);color:var(--accent-text);}
.icon-btn:active{transform:scale(.94);}

.search-wrap{position:relative;margin:0 2px 12px;}
.search-ico{position:absolute;left:11px;top:50%;transform:translateY(-50%);color:var(--text-faint);pointer-events:none;}
.search-input{
  width:100%;height:36px;border-radius:9px;border:1px solid transparent;
  background:var(--bg-elev);padding:0 30px 0 34px;font-size:13px;outline:none;
  box-shadow:var(--shadow-1);transition:border-color var(--trans),box-shadow var(--trans);
}
.search-input:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft);}
.search-input::placeholder{color:var(--text-faint);}
.search-clear{
  position:absolute;right:8px;top:50%;transform:translateY(-50%);
  width:20px;height:20px;border-radius:50%;display:grid;place-items:center;
  color:var(--text-soft);background:var(--bg-hover);font-size:14px;line-height:1;
}
.search-clear:hover{background:var(--bg-active);color:var(--text);}

.side-nav{display:flex;flex-direction:column;gap:2px;margin-bottom:6px;}
.nav-row{
  display:flex;align-items:center;gap:10px;padding:7px 9px;border-radius:var(--radius-s);
  color:var(--text-soft);font-size:13.5px;font-weight:500;text-align:left;
  transition:background var(--trans),color var(--trans);
}
.nav-row svg{flex:0 0 auto;opacity:.85;}
.nav-row:hover{background:var(--bg-hover);color:var(--text);}
.nav-row.active{background:var(--accent-soft);color:var(--accent-text);font-weight:600;}
.nav-count{margin-left:auto;font-size:11px;color:var(--text-faint);background:var(--chip);padding:1px 7px;border-radius:20px;}
.nav-row.active .nav-count{background:var(--accent);color:#fff;}

.side-section-head{
  display:flex;align-items:center;justify-content:space-between;
  padding:12px 9px 5px;font-size:10.5px;font-weight:700;letter-spacing:.12em;color:var(--text-faint);
}
.mini-btn{
  width:20px;height:20px;border-radius:5px;display:grid;place-items:center;
  color:var(--text-faint);font-size:15px;line-height:1;transition:background var(--trans),color var(--trans);
}
.mini-btn:hover{background:var(--bg-hover);color:var(--accent);}

.notebook-list{display:flex;flex-direction:column;gap:1px;overflow-y:auto;min-height:0;}
.tag-list{display:flex;flex-direction:column;gap:1px;margin-bottom:4px;max-height:40vh;overflow-y:auto;}

.nb-row{
  display:flex;align-items:center;gap:6px;border-radius:var(--radius-s);
  padding:3px 6px 3px 4px;color:var(--text-soft);cursor:pointer;
  transition:background var(--trans),color var(--trans);
}
.nb-row:hover{background:var(--bg-hover);}
.nb-row.active{background:var(--accent-soft);color:var(--accent-text);}
.nb-caret{
  width:18px;height:18px;flex:0 0 auto;display:grid;place-items:center;
  color:var(--text-faint);border-radius:4px;transition:transform var(--trans);
}
.nb-caret svg{transition:transform var(--trans);}
.nb-row.collapsed .nb-caret svg{transform:rotate(-90deg);}
.nb-main{
  display:flex;align-items:center;gap:8px;flex:1;padding:4px 4px;border-radius:var(--radius-s);
  min-width:0;transition:background var(--trans);
}
.nb-row:hover .nb-main{background:var(--bg-hover);}
.nb-ico{flex:0 0 auto;color:var(--accent);display:grid;place-items:center;}
.nb-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;}
.nb-count{font-size:11px;color:var(--text-faint);padding-right:4px;}
.nb-actions{display:flex;gap:0;opacity:0;transition:opacity var(--trans);}
.nb-row:hover .nb-actions{opacity:1;}
.nb-act{
  width:22px;height:22px;border-radius:5px;display:grid;place-items:center;
  color:var(--text-soft);font-size:13px;
}
.nb-act:hover{background:var(--bg-active);color:var(--text);}
.nb-notes{padding:1px 0 2px 28px;display:flex;flex-direction:column;gap:1px;max-height:50vh;overflow-y:auto;}
.nb-note-item{display:flex;gap:7px;align-items:baseline;}
/* nudge indent so caret sits above nested notes */
.nb-note-item .nb-caret-indent{width:18px;flex:0 0 auto;}
.nb-notes .nb-note-link{
  flex:1;text-align:left;color:var(--text-soft);font-size:12.5px;padding:2px 4px;border-radius:5px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.nb-notes .nb-note-link:hover{background:var(--bg-hover);color:var(--text);}
.nb-notes .nb-note-link.active{background:var(--accent-soft);color:var(--accent-text);font-weight:600;}
.nb-note-meta{font-size:10.5px;color:var(--text-faint);}

.tag-item{
  display:flex;align-items:center;gap:8px;padding:5px 9px;border-radius:var(--radius-s);
  color:var(--text-soft);font-size:13px;cursor:pointer;transition:background var(--trans),color var(--trans);
}
.tag-item:hover{background:var(--bg-hover);color:var(--text);}
.tag-item.active{background:var(--accent-soft);color:var(--accent-text);font-weight:600;}
.tag-hash{color:var(--text-faint);font-weight:600;}
.tag-item.active .tag-hash,.tag-item.active .tag-count{color:var(--accent-text);}
.tag-count{margin-left:auto;font-size:11px;color:var(--text-faint);}

/* notebook context menu */
.menu{
  position:fixed;z-index:80;min-width:170px;background:var(--bg-elev);border:1px solid var(--border);
  border-radius:var(--radius);box-shadow:var(--shadow-3);padding:5px;animation:pop .12s ease;
}
@keyframes pop{from{opacity:0;transform:scale(.96) translateY(-3px);}to{opacity:1;transform:none;}}
.menu-item{
  display:flex;align-items:center;gap:10px;width:100%;padding:7px 10px;border-radius:6px;
  font-size:13px;text-align:left;color:var(--text);transition:background var(--trans);
}
.menu-item:hover{background:var(--bg-hover);}
.menu-item.danger{color:var(--danger);}
.menu-item.danger:hover{background:var(--danger-soft);}
.menu-sep{height:1px;background:var(--border);margin:5px 6px;}
.menu-sublabel{font-size:10px;color:var(--text-faint);padding:6px 10px 3px;font-weight:700;letter-spacing:.08em;}
.menu-item .mi-label{flex:1;}
.menu-item .mi-kbd{font-size:11px;color:var(--text-faint);}
/* ── css: notes-pane ── */
/* notes list pane */
.notes-head{
  display:flex;align-items:center;gap:8px;padding:12px 12px;border-bottom:1px solid var(--border);
}
.notes-head-title{flex:1;font-weight:700;font-size:15px;letter-spacing:-.2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.notes-head-actions{display:flex;align-items:center;gap:4px;}
.new-note-btn{
  height:30px;padding:0 12px;border-radius:var(--radius-s);background:var(--accent);color:#fff;
  font-size:14px;font-weight:600;display:flex;align-items:center;gap:6px;box-shadow:var(--shadow-1);
  transition:background var(--trans),transform var(--trans);
}
.new-note-btn:hover{background:var(--accent-strong);}
.new-note-btn:active{transform:scale(.96);}

.notes-toolbar{
  display:flex;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--border);
  background:var(--accent-soft);font-size:12px;color:var(--accent-text);animation:fadeIn .2s ease;
}
@keyframes fadeIn{from{opacity:0;}to{opacity:1;}}
.notes-tb-label{flex:1;font-weight:600;}
.notes-tb-clear{width:18px;height:18px;border-radius:50%;display:grid;place-items:center;font-size:13px;line-height:1;color:var(--accent-text);}
.notes-tb-clear:hover{background:var(--accent);color:#fff;}

.notes-list{flex:1;overflow-y:auto;padding:6px 8px;}
.note-item{
  position:relative;padding:10px 12px 9px;border-radius:var(--radius);cursor:pointer;margin-bottom:2px;
  border:1px solid transparent;transition:background var(--trans),border-color var(--trans),box-shadow var(--trans);
  animation:itemIn .2s ease;
}
@keyframes itemIn{from{opacity:0;transform:translateY(2px);}to{opacity:1;transform:none;}}
.note-item:hover{background:var(--bg-hover);}
.note-item.selected{background:var(--bg-selected);border-color:var(--border-strong);box-shadow:var(--shadow-1);}
.note-item.dragging{opacity:.4;}
.note-item.drop-target{outline:2px dashed var(--accent);outline-offset:-2px;}
.note-item-top{display:flex;align-items:center;gap:6px;}
.ni-pin{flex:0 0 auto;color:var(--accent);display:grid;place-items:center;opacity:0;transition:opacity var(--trans);}
.note-item:hover .ni-pin,.note-item.pinned .ni-pin{opacity:1;}
.note-item.pinned .ni-pin{opacity:1;}
.ni-title{
  flex:1;font-weight:600;font-size:13.5px;color:var(--text);line-height:1.35;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.ni-pin-btn{flex:0 0 auto;width:22px;height:22px;border-radius:5px;display:grid;place-items:center;color:var(--text-faint);opacity:0;transition:opacity var(--trans),background var(--trans),color var(--trans);}
.note-item:hover .ni-pin-btn{opacity:1;}
.ni-pin-btn:hover{background:var(--bg-active);}
.ni-pin-btn.pinned{opacity:1;color:var(--accent);}
.ni-snippet{
  font-size:12px;color:var(--text-soft);margin-top:2px;line-height:1.4;display:-webkit-box;
  -webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word;
}
.ni-snippet em.match{font-style:normal;background:var(--mark);color:inherit;border-radius:2px;padding:0 1px;}
.ni-meta{display:flex;align-items:center;gap:6px;margin-top:6px;font-size:10.5px;color:var(--text-faint);}
.ni-meta .dot{width:3px;height:3px;border-radius:50%;background:var(--text-faint);}
.ni-tags{display:flex;gap:4px;margin-left:auto;overflow:hidden;}
.ni-tag{font-size:10px;color:var(--accent-text);background:var(--chip);padding:1px 6px;border-radius:20px;white-space:nowrap;}
.trash-badge{font-size:10px;color:var(--danger);border:1px solid var(--danger);padding:0 5px;border-radius:20px;}

.notes-empty{
  flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:8px;padding:24px;text-align:center;color:var(--text-soft);animation:fadeIn .3s ease;
}
.notes-empty .ne-ico{color:var(--text-faint);opacity:.7;}
.notes-empty h3{margin:0;font-size:14px;font-weight:600;color:var(--text);}
.notes-empty p{margin:0;font-size:12.5px;max-width:220px;line-height:1.4;}
/* ── css: toolbar ── */
/* editor toolbar */
.toolbar{
  display:flex;align-items:center;gap:4px;padding:8px 14px;border-bottom:1px solid var(--border);
  background:var(--bg-editor);flex:0 0 auto;position:relative;z-index:5;
}
.tb-group{display:flex;align-items:center;gap:2px;}
.tb-sep{width:1px;height:22px;background:var(--border);margin:0 6px;}
.tb-btn{
  min-width:30px;height:30px;padding:0 7px;border-radius:7px;display:inline-flex;align-items:center;
  justify-content:center;color:var(--text-soft);font-size:13px;gap:2px;position:relative;
  transition:background var(--trans),color var(--trans);
}
.tb-btn:hover{background:var(--bg-hover);color:var(--text);}
.tb-btn.active{background:var(--accent-soft);color:var(--accent-text);}
.tb-btn:active{transform:scale(.94);}
.tb-btn b{font-weight:800;}
.tb-btn i{font-style:italic;}
.tb-btn u{text-decoration:underline;}
.tb-btn s{text-decoration:line-through;}
.tb-btn .code-glyph{font-family:'SFMono-Regular',Consolas,'Liberation Mono',monospace;font-size:12px;background:var(--chip);border-radius:3px;padding:1px 3px;}
.tb-btn.head{font-size:11px;font-weight:700;letter-spacing:.02em;color:var(--text);}
.tb-btn .sep-label{font-size:9px;color:var(--text-faint);font-weight:700;}
.tb-btn.quote-glyph{font-size:16px;font-weight:800;color:var(--text-soft);}
.tb-spacer{flex:1;}
.saved{display:flex;align-items:center;gap:6px;font-size:11.5px;color:var(--text-faint);transition:opacity var(--trans);}
.saved .dot{width:7px;height:7px;border-radius:50%;background:var(--text-faint);transition:background var(--trans);}
.saved.saving{color:var(--text-soft);}
.saved.saving .dot{background:var(--accent);animation:pulse 1s infinite;}
@keyframes pulse{0%,100%{opacity:1;}50%{opacity:.4;}}
.saved.saved-ok{color:#4a7c3a;}
html.dark .saved.saved-ok{color:#8fc47a;}
.saved.saved-ok .dot{background:#4a7c3a;}
html.dark .saved.saved-ok .dot{background:#8fc47a;}
/* ── css: editor ── */
/* editor content */
.editor-scroll{flex:1;overflow-y:auto;}
.ed-inner{max-width:760px;margin:0 auto;padding:44px 52px 120px;}
.note-title{
  width:100%;border:none;outline:none;background:transparent;font-family:var(--font-editor);
  font-size:34px;font-weight:700;letter-spacing:-.6px;color:var(--text);line-height:1.2;
  padding:0 2px 6px;margin-bottom:4px;resize:none;
}
.note-title::placeholder{color:var(--text-faint);opacity:.6;}
.editor{
  min-height:60vh;outline:none;caret-color:var(--accent);
  font-family:var(--font-editor);font-size:17px;line-height:1.65;color:var(--text);
  word-wrap:break-word;overflow-wrap:break-word;
}
.editor:empty::before,.editor[data-empty="true"]::before{
  content:attr(data-placeholder);color:var(--text-faint);opacity:.55;pointer-events:none;
}
.editor p{margin:0 0 .55em;}
.editor p:empty{height:.55em;margin-bottom:0;}
.editor h1{font-size:1.75em;font-weight:800;margin:.8em 0 .4em;line-height:1.25;letter-spacing:-.3px;}
.editor h2{font-size:1.4em;font-weight:700;margin:.7em 0 .35em;line-height:1.3;letter-spacing:-.2px;}
.editor h3{font-size:1.15em;font-weight:700;margin:.6em 0 .3em;line-height:1.35;}
.editor h1:first-child,.editor h2:first-child,.editor h3:first-child,.editor p:first-child{margin-top:0;}
.editor ul,.editor ol{margin:.2em 0 .6em;padding-left:1.5em;}
.editor li{margin:.12em 0;}
.editor code{
  font-family:'SFMono-Regular',Consolas,'Liberation Mono',ui-monospace,monospace;font-size:.86em;
  background:var(--chip);border:1px solid var(--border);border-radius:4px;padding:.12em .35em;color:var(--text);
}
.editor blockquote{
  margin:.4em 0;padding:.15em 0 .15em 1em;border-left:3px solid var(--accent);
  color:var(--text-soft);font-style:italic;
}
.editor hr{border:none;border-top:2px solid var(--border-strong);margin:1.2em 0;}
.editor a{color:var(--accent);text-decoration:underline;}

/* task lists */
.editor ul.task-list{list-style:none;padding-left:.3em;}
.editor ul.task-list>li{display:flex;align-items:flex-start;gap:9px;margin:.1em 0;}
.editor ul.task-list>li>input[type=checkbox]{
  appearance:none;-webkit-appearance:none;flex:0 0 auto;width:16px;height:16px;margin:4px 0 0 2px;
  border:1.5px solid var(--border-strong);border-radius:4px;cursor:pointer;position:relative;
  background:var(--bg-elev);transition:background var(--trans),border-color var(--trans);
}
.editor ul.task-list>li>input[type=checkbox]:hover{border-color:var(--accent);}
.editor ul.task-list>li>input[type=checkbox]:checked{background:var(--accent);border-color:var(--accent);}
.editor ul.task-list>li>input[type=checkbox]:checked::after{
  content:'';position:absolute;left:4.5px;top:1.5px;width:4px;height:8px;
  border:solid #fff;border-width:0 2px 2px 0;transform:rotate(45deg);
}
.editor ul.task-list>li.checked{color:var(--text-faint);}
.editor ul.task-list>li.checked .task-text{text-decoration:line-through;opacity:.75;}

/* tags footer */
.ed-footer{border-top:1px solid var(--border);margin-top:36px;padding-top:14px;}
.ed-footer-inner{display:flex;flex-wrap:wrap;gap:8px;align-items:center;}
.note-tags{display:flex;flex-wrap:wrap;gap:6px;}
.tag-chip{
  display:inline-flex;align-items:center;gap:5px;background:var(--chip);color:var(--accent-text);
  font-size:12px;padding:3px 6px 3px 10px;border-radius:20px;font-weight:500;animation:chipIn .15s ease;
}
@keyframes chipIn{from{opacity:0;transform:scale(.9);}to{opacity:1;transform:none;}}
.tag-chip .hash{color:var(--text-faint);font-weight:600;}
.tag-chip .x{width:15px;height:15px;border-radius:50%;display:grid;place-items:center;font-size:12px;line-height:1;color:var(--text-soft);}
.tag-chip .x:hover{background:var(--bg-active);color:var(--text);}
.tag-chip.clickable{cursor:pointer;transition:background var(--trans);}
.tag-chip.clickable:hover{background:var(--accent-soft);}
.tag-input{background:transparent;border:none;outline:none;font-size:12.5px;color:var(--text);width:90px;}
.tag-input::placeholder{color:var(--text-faint);}

/* empty state */
.editor-empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:var(--bg-editor);z-index:4;}
.empty-card{display:flex;flex-direction:column;align-items:center;gap:8px;text-align:center;max-width:320px;padding:40px;animation:itemIn .25s ease;}
.empty-ico{
  width:64px;height:64px;border-radius:20px;display:grid;place-items:center;margin-bottom:8px;
  background:var(--accent-soft);color:var(--accent);box-shadow:var(--shadow-1);
}
.empty-card h2{margin:0;font-size:19px;font-weight:700;color:var(--text);}
.empty-card p{margin:0 0 10px;font-size:13.5px;color:var(--text-soft);line-height:1.5;}
.btn-primary{
  height:36px;padding:0 18px;border-radius:9px;background:var(--accent);color:#fff;
  font-size:13.5px;font-weight:600;box-shadow:var(--shadow-1);transition:background var(--trans),transform var(--trans);
}
.btn-primary:hover{background:var(--accent-strong);}
.btn-primary:active{transform:scale(.97);}
/* ── css: misc ── */
/* inline rename input + drag-drop highlight + small helpers */
.rename-input{
  width:100%;border:1px solid var(--accent);border-radius:5px;background:var(--bg-elev);
  color:var(--text);font-size:13px;padding:3px 6px;outline:none;box-shadow:0 0 0 3px var(--accent-soft);
}
.nb-row.drop-ontop{outline:2px dashed var(--accent);outline-offset:-2px;border-radius:10px;}
.nav-row.drop-ontop{outline:2px dashed var(--accent);outline-offset:-2px;border-radius:10px;}
.drag-ghost{position:fixed;pointer-events:none;z-index:90;opacity:.9;animation:pop .12s ease;}
.dragging-source{opacity:.35;}
.sort-menu .menu-item .check{width:16px;color:var(--accent);}
.kbd{border:1px solid var(--border);border-bottom-width:2px;border-radius:4px;padding:0 4px;font-size:10.5px;color:var(--text-soft);background:var(--bg-hover);}
.toast{
  position:fixed;bottom:22px;left:50%;transform:translateX(-50%);z-index:120;
  background:var(--bg-elev);border:1px solid var(--border);box-shadow:var(--shadow-3);
  padding:10px 18px;border-radius:10px;font-size:13px;font-weight:500;color:var(--text);
  animation:toastIn .25s ease;
}
@keyframes toastIn{from{opacity:0;transform:translate(-50%,10px);}to{opacity:1;transform:translate(-50%,0);}}
</style>
</head>
<body>
<div id="app">
  <div id="scrim" class="scrim"></div>

  <aside id="sidebar" class="pane">
    <div class="side-head">
      <div class="app-brand"><span class="brand-mark">❖</span><span>Notes</span></div>
      <button id="theme-toggle" class="icon-btn" title="Toggle theme" aria-label="Toggle theme">
        <svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21 12.8A9 9 0 1 1 11.2 3 7 7 0 0 0 21 12.8z"></path></svg>
      </button>
    </div>

    <div class="search-wrap">
      <svg class="search-ico" viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"></circle><path d="M21 21l-4.3-4.3"></path></svg>
      <input id="global-search" class="search-input" placeholder="Search notes" spellcheck="false">
      <span id="search-clear" class="search-clear" hidden="">×</span>
    </div>

    <nav class="side-nav">
      <button class="nav-row" id="nav-all">
        <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16v16H4z"></path><path d="M4 9h16"></path><path d="M9 4v5"></path></svg>
        <span class="nav-label">All Notes</span><span class="nav-count" id="count-all"></span>
      </button>
      <button class="nav-row" id="nav-trash">
        <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"></path><path d="M8 6V4h8v2"></path><path d="M19 6l-1 14H6L5 6"></path><path d="M10 11v6M14 11v6"></path></svg>
        <span class="nav-label">Trash</span><span class="nav-count" id="count-trash"></span>
      </button>
    </nav>

    <div class="side-section-head"><span>TAGS</span></div>
    <div class="tag-list" id="tag-list"></div>

    <div class="side-section-head">
      <span>NOTEBOOKS</span>
      <button id="new-notebook" class="mini-btn" title="New notebook">+</button>
    </div>
    <div class="notebook-list" id="notebook-list"></div>
  </aside>

  <div id="resizer-a" class="resizer"></div>

  <section id="notes-pane" class="pane">
    <div class="notes-head">
      <button id="sidebar-toggle" class="icon-btn" title="Toggle sidebar" aria-label="Toggle sidebar">
        <svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 6h16M4 12h10M4 18h16"></path></svg>
      </button>
      <div class="notes-head-title" id="notes-head-title"></div>
      <div class="notes-head-actions">
        <button id="sort-toggle" class="icon-btn" title="Sort notes" aria-label="Sort notes">
          <svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 4v13M8 17l-3-3M8 17l3-3"></path><path d="M16 20V7M16 7l-3 3M16 7l3 3"></path></svg>
        </button>
        <button id="new-note" class="new-note-btn" title="New note">+</button>
      </div>
    </div>
    <div class="notes-toolbar" id="notes-toolbar" hidden="">
      <span class="notes-tb-label" id="notes-tb-label"></span>
      <button id="notes-tb-clear" class="notes-tb-clear">×</button>
    </div>
    <div id="notes-list" class="notes-list"></div>
    <div id="notes-empty" class="notes-empty" hidden=""></div>
  </section>

  <div id="resizer-b" class="resizer"></div>

  <main id="editor-pane" class="pane">
    <div id="toolbar" class="toolbar"><div class="saved" id="saved-indicator"><span class="dot"></span><span id="saved-text">Saved</span></div></div>
    <div id="editor-scroll" class="editor-scroll">
      <div class="ed-inner">
        <input id="note-title" class="note-title" placeholder="Untitled note" spellcheck="false">
        <div id="editor" contenteditable="true" class="editor" data-placeholder="Start writing…"></div>
        <div class="ed-footer">
          <div class="ed-footer-inner">
            <div id="note-tags" class="note-tags"></div>
            <div id="tag-input-wrap" class="tag-input-wrap">
              <input id="tag-input" class="tag-input" placeholder="+ add tag" spellcheck="false">
            </div>
          </div>
        </div>
      </div>
    </div>
    <div id="editor-empty" class="editor-empty" hidden="">
      <div class="empty-card">
        <div class="empty-ico">
          <svg viewBox="0 0 24 24" width="30" height="30" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M16 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><path d="M16 3v5h5"></path></svg>
        </div>
        <h2>No note selected</h2>
        <p>Select a note from the list, or start something new.</p>
        <button id="empty-new" class="btn-primary">+ New note</button>
      </div>
    </div>
  </main>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./store":"src/store.ts","./types":"src/types.ts","./editor":"src/editor.ts","./ui":"src/ui.ts"},"src/store.ts":{"./types":"src/types.ts"},"src/editor.ts":{"./types":"src/types.ts","./store":"src/store.ts"},"src/ui.ts":{"./store":"src/store.ts","./types":"src/types.ts","./editor":"src/editor.ts"}};
function __require(id) {
  if (__cache[id]) return __cache[id].exports;
  var module = __cache[id] = { exports: {} };
  var factory = __mods[id];
  if (!factory) throw new Error("Module not found: " + id);
  factory(module.exports, function (spec) {
    var target = (__map[id] && __map[id][spec]) || spec;
    return __require(target);
  }, module);
  return module.exports;
}

// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
// ─────────────────────────────────────────────────────────────
// main.ts — entry: wires the editor, binds UI, manages layout,
// theme, sidebar collapse, autosave, and global shortcuts.
// ─────────────────────────────────────────────────────────────
const store_1 = require("./store");
const types_1 = require("./types");
const editor_1 = require("./editor");
const ui_1 = require("./ui");
const $ = (sel) => document.querySelector(sel);
// ── autosave (debounced persistence) ─────────────────────────
let saveTimer = 0;
function scheduleSave() {
    window.clearTimeout(saveTimer);
    saveTimer = window.setTimeout(() => (0, store_1.save)(), 120);
}
function onNoteChanged(_noteId) {
    scheduleSave();
    (0, ui_1.renderAll)();
}
// ── layout ───────────────────────────────────────────────────
function applyLayout() {
    const s = (0, store_1.getState)().layout;
    const app = $('#app');
    app.classList.toggle('sidebar-collapsed', s.sidebarCollapsed);
    const sidebar = $('#sidebar');
    if (s.sidebarCollapsed) {
        sidebar.style.width = '0px';
        sidebar.style.flexBasis = '0px';
        sidebar.style.borderRight = 'none';
        sidebar.style.padding = '0';
        sidebar.style.opacity = '0';
    }
    else {
        sidebar.style.width = s.sidebar + 'px';
        sidebar.style.flexBasis = s.sidebar + 'px';
        sidebar.style.borderRight = '';
        sidebar.style.padding = '';
        sidebar.style.opacity = '';
    }
    $('#notes-pane').style.width = s.notes + 'px';
    $('#notes-pane').style.flexBasis = s.notes + 'px';
    $('#resizer-a').style.display = s.sidebarCollapsed ? 'none' : '';
}
function startResize(handle, which) {
    const startX = handle === document.activeElement ? 0 : 0;
    const move = (ev) => {
        const s = (0, store_1.getState)().layout;
        if (which === 'sidebar') {
            const w = Math.round(ev.clientX);
            const clamped = Math.max(types_1.CONST.MIN_SIDEBAR, Math.min(types_1.CONST.MAX_SIDEBAR, w));
            (0, store_1.setLayout)({ sidebar: clamped });
            const sidebar = $('#sidebar');
            sidebar.style.width = clamped + 'px';
            sidebar.style.flexBasis = clamped + 'px';
        }
        else {
            const rect = $('#editor-pane').getBoundingClientRect();
            const w = Math.round(ev.clientX - $('#notes-pane').getBoundingClientRect().left);
            const maxNotes = window.innerWidth - 360 - ((0, store_1.getState)().layout.sidebarCollapsed ? 0 : (0, store_1.getState)().layout.sidebar);
            const clamped = Math.max(types_1.CONST.MIN_NOTES, Math.min(types_1.CONST.MAX_NOTES, maxNotes, w));
            (0, store_1.setLayout)({ notes: clamped });
            const np = $('#notes-pane');
            np.style.width = clamped + 'px';
            np.style.flexBasis = clamped + 'px';
            void rect;
        }
        scheduleSave();
    };
    const up = () => {
        document.body.classList.remove('resizing');
        handle.classList.remove('dragging');
        window.removeEventListener('pointermove', move);
        window.removeEventListener('pointerup', up);
    };
    handle.addEventListener('pointerdown', (e) => {
        e.preventDefault();
        document.body.classList.add('resizing');
        handle.classList.add('dragging');
        window.addEventListener('pointermove', move);
        window.addEventListener('pointerup', up);
    });
    void startX;
}
// ── theme ────────────────────────────────────────────────────
function applyTheme() {
    document.documentElement.classList.toggle('dark', (0, store_1.getState)().layout.theme === 'dark');
}
// ── new note action ──────────────────────────────────────────
function onNewNote(notebookId) {
    const note = (0, store_1.createNote)(notebookId);
    (0, store_1.setSelectedNote)(note.id);
    (0, store_1.save)();
    (0, ui_1.renderAll)();
    (0, editor_1.loadNote)(note);
    (0, ui_1.showEditorEmpty)(false);
    (0, ui_1.focusTitle)();
}
// ── keyboard shortcuts ───────────────────────────────────────
function onGlobalKey(e) {
    const tag = e.target.tagName;
    const inField = tag === 'INPUT' || tag === 'TEXTAREA' || e.target.isContentEditable;
    const mod = e.metaKey || e.ctrlKey;
    // New note
    if (mod && e.key.toLowerCase() === 'n' && !e.shiftKey) {
        if (!inField || tag === 'INPUT') {
            e.preventDefault();
            onNewNote(notebookForNew());
            return;
        }
    }
    // Toggle sidebar
    if (mod && e.key.toLowerCase() === '\\') {
        e.preventDefault();
        toggleSidebar();
        return;
    }
    // Delete selected note (Del on note list)
    if ((e.key === 'Delete' || e.key === 'Backspace') && !inField && (0, editor_1.getCurrentNoteId)()) {
        const view = (0, store_1.getState)().view.kind;
        if (view !== 'trash') {
            e.preventDefault();
            (0, ui_1.deleteSelectedNote)();
        }
    }
}
function notebookForNew() {
    const s = (0, store_1.getState)();
    if (s.view.kind === 'notebook')
        return s.view.notebookId || null;
    const cur = (0, store_1.findNote)(s.selectedNoteId);
    if (cur && cur.notebookId)
        return cur.notebookId;
    return s.notebooks[0]?.id || null;
}
// ── sidebar collapse ─────────────────────────────────────────
function toggleSidebar() {
    const s = (0, store_1.getState)();
    (0, store_1.setSidebarCollapsed)(!s.layout.sidebarCollapsed);
    applyLayout();
    (0, store_1.save)();
}
// ── startup ──────────────────────────────────────────────────
function start() {
    applyTheme();
    const s = (0, store_1.getState)();
    (0, editor_1.initEditor)({
        editor: $('#editor'),
        title: $('#note-title'),
        toolbar: $('#toolbar'),
        savedEl: $('.saved'),
        savedText: $('#saved-text'),
        tagWrap: $('#note-tags'),
        tagInput: $('#tag-input'),
        onTagClick: (tag) => (0, ui_1.setQtagFilter)(tag === (0, ui_1.getQtagFilter)() ? null : tag),
        onNoteChanged,
    });
    (0, ui_1.bindUI)({ onNewNote, focusTitle: ui_1.focusTitle });
    // layout + resize
    applyLayout();
    startResize($('#resizer-a'), 'sidebar');
    startResize($('#resizer-b'), 'notes');
    // theme toggle
    $('#theme-toggle').addEventListener('click', () => {
        (0, store_1.setTheme)((0, store_1.getState)().layout.theme === 'dark' ? 'light' : 'dark');
        applyTheme();
        (0, store_1.save)();
    });
    $('#sidebar-toggle').addEventListener('click', toggleSidebar);
    // initial render + select
    (0, ui_1.renderAll)();
    const selected = (0, store_1.findNote)(s.selectedNoteId);
    if (selected && selected.deletedAt === null) {
        (0, editor_1.loadNote)(selected);
        (0, ui_1.showEditorEmpty)(false);
    }
    else {
        (0, editor_1.clearNote)();
        (0, ui_1.showEditorEmpty)(true);
    }
    window.addEventListener('keydown', onGlobalKey);
    window.addEventListener('beforeunload', () => (0, store_1.save)());
}
start();
};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
// ─────────────────────────────────────────────────────────────
// types.ts — shared types & the tunable constants block
// ─────────────────────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.CONST = void 0;
exports.uid = uid;
// ── Tunable constants ────────────────────────────────────────
exports.CONST = {
    // storage
    STORAGE_KEY: 'notes-app-state-v1',
    AUTOSAVE_DEBOUNCE_MS: 400,
    SAVED_HINT_MS: 1400,
    // layout defaults (px)
    SIDEBAR_WIDTH: 250,
    NOTES_WIDTH: 300,
    MIN_SIDEBAR: 180,
    MAX_SIDEBAR: 460,
    MIN_NOTES: 200,
    MAX_NOTES: 520,
    // content
    SNIPPET_LENGTH: 110,
    UNDO_LIMIT: 120,
    // typography
    EDITOR_FONT: "'Source Serif 4','Iowan Old Style','Palatino Linotype',Georgia,serif",
    UI_FONT: "'Inter','-apple-system','Segoe UI',Roboto,sans-serif",
};
// ── id + time helpers ────────────────────────────────────────
function uid() {
    return (Date.now().toString(36) + Math.random().toString(36).slice(2, 8)).toLowerCase();
}
};

// ── module: src/store.ts ──
__mods["src/store.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getState = getState;
exports.save = save;
exports.createNotebook = createNotebook;
exports.renameNotebook = renameNotebook;
exports.deleteNotebook = deleteNotebook;
exports.toggleNotebookExpanded = toggleNotebookExpanded;
exports.createNote = createNote;
exports.updateNoteTitle = updateNoteTitle;
exports.updateNoteHtml = updateNoteHtml;
exports.setPinned = setPinned;
exports.togglePinned = togglePinned;
exports.moveNoteTo = moveNoteTo;
exports.addTag = addTag;
exports.removeTag = removeTag;
exports.duplicateNote = duplicateNote;
exports.trashNote = trashNote;
exports.restoreNote = restoreNote;
exports.permanentDelete = permanentDelete;
exports.findNote = findNote;
exports.findNotebook = findNotebook;
exports.setSelectedNote = setSelectedNote;
exports.setView = setView;
exports.setSort = setSort;
exports.setTheme = setTheme;
exports.setSidebarCollapsed = setSidebarCollapsed;
exports.setLayout = setLayout;
exports.visibleNotes = visibleNotes;
exports.countInTrash = countInTrash;
exports.htmlToNodes = htmlToNodes;
exports.plainText = plainText;
exports.snippetFor = snippetFor;
// ─────────────────────────────────────────────────────────────
// store.ts — the single source of truth: model + persistence +
// pure helpers. The DOM is always a projection of this state.
// ─────────────────────────────────────────────────────────────
const types_1 = require("./types");
// seed content helper
function seed() {
    const now = Date.now();
    const nbWelcome = { id: (0, types_1.uid)(), name: 'Welcome', expanded: true };
    const nbIdeas = { id: (0, types_1.uid)(), name: 'Ideas', expanded: true };
    const welcome = {
        id: (0, types_1.uid)(),
        notebookId: nbWelcome.id,
        title: 'Welcome to Notes',
        html: '<h1>Welcome to Notes</h1><p>This is a note. Everything you type is saved automatically.</p>' +
            '<h2>Try these</h2><ul><li>Type <code># </code> at the start of a line for a heading</li>' +
            '<li>Type <code>- </code> for a bullet, <code>1. </code> for a numbered list</li>' +
            '<li>Type <code>[] </code> for a checklist, <code>&gt; </code> for a quote</li></ul>' +
            '<p>Bold, <em>italic</em>, <u>underline</u> and more are in the toolbar above.</p>',
        tags: ['getting-started'],
        pinned: true,
        createdAt: now - 120000,
        updatedAt: now - 4000,
        deletedAt: null,
    };
    const ideas = {
        id: (0, types_1.uid)(),
        notebookId: nbIdeas.id,
        title: 'Snippet ideas',
        html: '<p>Collect snippets worth keeping. Search is live from the sidebar — try searching for a word <strong>deep inside a bold span</strong>.</p>' +
            '<blockquote>“Simplicity is the ultimate sophistication.”</blockquote>',
        tags: ['ideas'],
        pinned: false,
        createdAt: now - 60000,
        updatedAt: now - 20000,
        deletedAt: null,
    };
    const tasks = {
        id: (0, types_1.uid)(),
        notebookId: nbIdeas.id,
        title: 'Checklists',
        html: '<p>Checklist items are clickable and persist across reloads:</p>' +
            '<ul class="task-list"><li><input type="checkbox"> Ship the notes app</li>' +
            '<li><input type="checkbox" checked=""> E-mail the team</li></ul>',
        tags: [],
        pinned: false,
        createdAt: now - 30000,
        updatedAt: now - 1000,
        deletedAt: null,
    };
    return {
        version: 3,
        notebooks: [nbWelcome, nbIdeas],
        notes: [welcome, ideas, tasks],
        selectedNoteId: welcome.id,
        view: { kind: 'notebook', notebookId: nbWelcome.id },
        layout: {
            sidebar: types_1.CONST.SIDEBAR_WIDTH,
            notes: types_1.CONST.NOTES_WIDTH,
            sidebarCollapsed: false,
            theme: 'light',
            sort: 'modified',
        },
    };
}
let state = load();
function getState() {
    return state;
}
function load() {
    try {
        const raw = localStorage.getItem(types_1.CONST.STORAGE_KEY);
        if (!raw)
            return seed();
        const parsed = JSON.parse(raw);
        if (!parsed || parsed.version !== 3 || !Array.isArray(parsed.notebooks))
            return seed();
        // ensure required fields (defensive against old/corrupt data)
        parsed.notebooks = parsed.notebooks.map((n) => ({
            id: typeof n.id === 'string' ? n.id : (0, types_1.uid)(),
            name: typeof n.name === 'string' ? n.name : 'Untitled',
            expanded: n.expanded !== false,
        }));
        parsed.notes = parsed.notes.map((n) => ({
            id: typeof n.id === 'string' ? n.id : (0, types_1.uid)(),
            notebookId: n.notebookId ?? null,
            title: n.title ?? '',
            html: n.html ?? '',
            tags: Array.isArray(n.tags) ? n.tags : [],
            pinned: !!n.pinned,
            createdAt: n.createdAt ?? 0,
            updatedAt: n.updatedAt ?? 0,
            deletedAt: n.deletedAt ?? null,
        }));
        return parsed;
    }
    catch {
        return seed();
    }
}
/** Persist state (debounced upstream). Always serializes current model. */
function save() {
    try {
        localStorage.setItem(types_1.CONST.STORAGE_KEY, JSON.stringify(state));
    }
    catch {
        /* storage full / private mode — ignore */
    }
}
// ── Mutations (all go through these so any caller can persist) ──
function createNotebook(name) {
    const nb = { id: (0, types_1.uid)(), name: name || 'Untitled', expanded: true };
    state.notebooks.push(nb);
    return nb;
}
function renameNotebook(id, name) {
    const nb = state.notebooks.find((n) => n.id === id);
    if (nb)
        nb.name = name;
}
function deleteNotebook(id) {
    const notes = state.notes.filter((n) => n.notebookId === id && n.deletedAt === null);
    const trash = state.notes.filter((n) => n.notebookId === id && n.deletedAt !== null);
    // notes inside a deleted notebook move to Trash; trashed ones are purged
    for (const n of notes)
        n.deletedAt = Date.now();
    for (const n of trash)
        n.deletedAt = -1; // mark for purge below
    state.notes = state.notes.filter((n) => n.deletedAt !== -1);
    state.notebooks = state.notebooks.filter((n) => n.id !== id);
    if (state.view.notebookId === id)
        state.view = { kind: 'all' };
}
function toggleNotebookExpanded(id) {
    const nb = state.notebooks.find((n) => n.id === id);
    if (nb)
        nb.expanded = !nb.expanded;
}
function createNote(notebookId, bodyHtml = '') {
    const note = {
        id: (0, types_1.uid)(),
        notebookId,
        title: '',
        html: bodyHtml,
        tags: [],
        pinned: false,
        createdAt: Date.now(),
        updatedAt: Date.now(),
        deletedAt: null,
    };
    state.notes.push(note);
    return note;
}
function updateNoteTitle(id, title) {
    const n = findNote(id);
    if (n) {
        n.title = title;
        n.updatedAt = Date.now();
    }
}
function updateNoteHtml(id, html) {
    const n = findNote(id);
    if (n) {
        n.html = html;
        n.updatedAt = Date.now();
    }
}
function setPinned(id, pinned) {
    const n = findNote(id);
    if (n)
        n.pinned = pinned;
}
function togglePinned(id) {
    const n = findNote(id);
    if (n) {
        n.pinned = !n.pinned;
        n.updatedAt = Date.now();
    }
}
function moveNoteTo(id, notebookId) {
    const n = findNote(id);
    if (n) {
        n.notebookId = notebookId;
        n.updatedAt = Date.now();
    }
}
function addTag(id, tag) {
    const n = findNote(id);
    if (!n)
        return;
    const t = tag.trim().toLowerCase();
    if (t && !n.tags.includes(t))
        n.tags.push(t);
}
function removeTag(id, tag) {
    const n = findNote(id);
    if (n)
        n.tags = n.tags.filter((t) => t !== tag);
}
function duplicateNote(id) {
    const src = findNote(id);
    if (!src)
        throw new Error('no note');
    const copy = {
        ...src,
        id: (0, types_1.uid)(),
        title: src.title ? src.title + ' (copy)' : '',
        pinned: false,
        createdAt: Date.now(),
        updatedAt: Date.now(),
        deletedAt: null,
    };
    state.notes.push(copy);
    return copy;
}
function trashNote(id) {
    const n = findNote(id);
    if (n) {
        n.deletedAt = Date.now();
        n.updatedAt = Date.now();
    }
}
function restoreNote(id) {
    const n = findNote(id);
    if (n)
        n.deletedAt = null;
}
function permanentDelete(id) {
    state.notes = state.notes.filter((n) => n.id !== id);
    if (state.selectedNoteId === id)
        state.selectedNoteId = null;
}
function findNote(id) {
    return id ? state.notes.find((n) => n.id === id) : undefined;
}
function findNotebook(id) {
    return id ? state.notebooks.find((n) => n.id === id) : undefined;
}
function setSelectedNote(id) {
    state.selectedNoteId = id;
}
function setView(view) {
    state.view = view;
}
function setSort(sort) {
    state.layout.sort = sort;
}
function setTheme(theme) {
    state.layout.theme = theme;
}
function setSidebarCollapsed(c) {
    state.layout.sidebarCollapsed = c;
}
function setLayout(layout) {
    Object.assign(state.layout, layout);
}
// ── Projection helpers (pure) ────────────────────────────────
function visibleNotes(view, sort, query = '', queryTags = []) {
    let notes = state.notes.filter((n) => {
        // trash view shows deleted
        if (view.kind === 'trash')
            return n.deletedAt !== null;
        // every other view hides deleted
        if (n.deletedAt !== null)
            return false;
        if (view.kind === 'notebook')
            return n.notebookId === view.notebookId;
        if (view.kind === 'tag')
            return n.tags.includes(view.tag || '');
        return true; // all / search
    });
    // secondary tag filter (clicked tag chips in the results pane)
    for (const t of queryTags) {
        if (t)
            notes = notes.filter((n) => n.tags.includes(t));
    }
    // global search live filter on text content
    if (query.trim()) {
        const q = query.trim().toLowerCase();
        notes = notes.filter((n) => {
            const hay = (plainText(n.html) + ' ' + n.title).toLowerCase();
            return hay.includes(q);
        });
    }
    // sort
    notes = [...notes].sort((a, b) => {
        if (a.pinned !== b.pinned)
            return a.pinned ? -1 : 1;
        if (sort === 'created')
            return b.createdAt - a.createdAt;
        if (sort === 'alpha')
            return a.title.localeCompare(b.title);
        return b.updatedAt - a.updatedAt;
    });
    return notes;
}
function countInTrash() {
    return state.notes.filter((n) => n.deletedAt !== null).length;
}
// ── Text extraction (for snippets + search; covers content, never tag text) ──
function htmlToNodes(html) {
    const tpl = document.createElement('template');
    tpl.innerHTML = html;
    return Array.from(tpl.content.childNodes);
}
function plainText(html) {
    return plainTextFromNodes(htmlToNodes(html));
}
function plainTextFromNodes(nodes) {
    let out = '';
    for (const node of nodes) {
        if (node.nodeType === Node.TEXT_NODE) {
            out += node.textContent || '';
        }
        else if (node.nodeType === Node.ELEMENT_NODE) {
            const el = node;
            if (el.tagName === 'BR') {
                out += '\n';
                continue;
            }
            const block = ['P', 'DIV', 'H1', 'H2', 'H3', 'LI', 'BLOCKQUOTE', 'TR'].includes(el.tagName);
            if (block && out && !out.endsWith('\n'))
                out += '\n';
            out += plainTextFromNodes(Array.from(el.childNodes));
            if (block && !out.endsWith('\n'))
                out += '\n';
        }
    }
    return out;
}
/** Build a snippet with {before, match, after} so search can highlight. */
function snippetFor(note, query) {
    const body = plainText(note.html).replace(/\s+/g, ' ').trim();
    const full = (note.title ? note.title + ' — ' : '') + body;
    const q = (query || '').trim().toLowerCase();
    const ranges = [];
    const text = full.slice(0, types_1.CONST.SNIPPET_LENGTH);
    if (q) {
        const lower = text.toLowerCase();
        let idx = lower.indexOf(q);
        while (idx !== -1) {
            ranges.push([idx, idx + q.length]);
            idx = lower.indexOf(q, idx + q.length);
        }
    }
    return { text, ranges };
}
};

// ── module: src/editor.ts ──
__mods["src/editor.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadNote = loadNote;
exports.clearNote = clearNote;
exports.getCurrentNoteId = getCurrentNoteId;
exports.initEditor = initEditor;
// ─────────────────────────────────────────────────────────────
// editor.ts — contenteditable editor: formatting toolbar, markdown
// shortcuts, checklist handling, list continuation, sanitized paste,
// reliable custom undo/redo. The DOM is a projection of store state.
// ─────────────────────────────────────────────────────────────
const types_1 = require("./types");
const store_1 = require("./store");
let refs;
let noteId = null;
let applying = false;
let undoStack = [];
let redoStack = [];
function pathOf(root, node) {
    const path = [];
    let n = node;
    while (n && n !== root) {
        const parent = n.parentNode;
        if (!parent)
            break;
        path.unshift(Array.prototype.indexOf.call(parent.childNodes, n));
        n = parent;
    }
    return path;
}
function nodeAt(root, path) {
    let n = root;
    for (let i = 0; i < path.length; i++) {
        const next = n.childNodes[path[i]];
        if (!next)
            return null;
        n = next;
    }
    return n;
}
function saveSel() {
    const sel = window.getSelection();
    if (!sel || sel.rangeCount === 0)
        return null;
    const anchorNode = sel.anchorNode;
    const focusNode = sel.focusNode;
    if (!anchorNode || !focusNode)
        return null;
    const root = refs.editor;
    if (!root.contains(anchorNode))
        return null;
    const clamp = (node, off) => node.nodeType === Node.TEXT_NODE ? Math.max(0, Math.min(off, node.nodeValue ? node.nodeValue.length : 0)) : off;
    return {
        anchor: { path: pathOf(root, anchorNode), offset: clamp(anchorNode, sel.anchorOffset) },
        focus: { path: pathOf(root, focusNode), offset: clamp(focusNode, sel.focusOffset) },
    };
}
function restoreSel(s) {
    if (!s)
        return;
    try {
        const sel = window.getSelection();
        if (!sel)
            return;
        const range = document.createRange();
        const a = nodeAt(refs.editor, s.anchor.path);
        const f = nodeAt(refs.editor, s.focus.path);
        if (!a || !f)
            return;
        range.setStart(a, s.anchor.offset);
        range.setEnd(f, s.focus.offset);
        sel.removeAllRanges();
        sel.addRange(range);
    }
    catch { /* ignore */ }
}
function pushHistory() {
    undoStack.push({ html: refs.editor.innerHTML, sel: saveSel() });
    if (undoStack.length > types_1.CONST.UNDO_LIMIT)
        undoStack.shift();
    redoStack.length = 0;
}
function setContent(html, sel) {
    applying = true;
    refs.editor.innerHTML = html;
    normalizeTree(refs.editor);
    restoreSel(sel);
    applying = false;
    updatePlaceholder();
}
function undo() {
    if (!undoStack.length)
        return;
    const prev = undoStack.pop();
    redoStack.push({ html: refs.editor.innerHTML, sel: saveSel() });
    setContent(prev.html, prev.sel);
    commit(false);
}
function redo() {
    if (!redoStack.length)
        return;
    const next = redoStack.pop();
    undoStack.push({ html: refs.editor.innerHTML, sel: saveSel() });
    setContent(next.html, next.sel);
    commit(false);
}
// ── public load ──────────────────────────────────────────────
function loadNote(note) {
    noteId = note.id;
    undoStack = [];
    redoStack = [];
    applying = true;
    refs.title.value = note.title;
    refs.editor.innerHTML = note.html;
    normalizeTree(refs.editor);
    applying = false;
    updatePlaceholder();
    renderTags();
    setSaved('idle');
}
function clearNote() {
    noteId = null;
    applying = true;
    refs.title.value = '';
    refs.editor.innerHTML = '';
    applying = false;
    undoStack = [];
    redoStack = [];
    renderTags();
    updatePlaceholder();
}
function getCurrentNoteId() { return noteId; }
function commit(schedule = true) {
    if (!noteId)
        return;
    (0, store_1.updateNoteHtml)(noteId, refs.editor.innerHTML);
    (0, store_1.updateNoteTitle)(noteId, refs.title.value.trim());
    if (schedule)
        setSaved('saving');
    refs.onNoteChanged(noteId);
}
// Mark browser-visible selection-caret update + template fixes inside editor
function updatePlaceholder() {
    const empty = !refs.editor.childElementCount && !(refs.editor.textContent || '').trim();
    if (empty)
        refs.editor.setAttribute('data-empty', 'true');
    else
        refs.editor.removeAttribute('data-empty');
}
// deletes empty <p><br></p> noise, wraps bare text
function normalizeTree(root) {
    // wrap stray text nodes in <p>
    for (const node of Array.from(root.childNodes)) {
        if (node.nodeType === Node.TEXT_NODE && (node.textContent || '').trim().length) {
            const p = document.createElement('p');
            root.replaceChild(p, node);
            p.appendChild(node);
        }
    }
}
// ── saved indicator ──────────────────────────────────────────
let savedTimer = 0;
let savedTimeout = 0;
function setSaved(mode) {
    clearTimeout(savedTimeout);
    refs.savedEl.className = 'saved' + (mode === 'saving' ? ' saving' : mode === 'ok' ? ' saved-ok' : '');
    refs.savedText.textContent = mode === 'saving' ? 'Saving…' : 'Saved';
    if (mode === 'saving') {
        savedTimer = window.setTimeout(() => setSaved('ok'), types_1.CONST.AUTOSAVE_DEBOUNCE_MS + 80);
    }
}
// ── caret / block helpers ────────────────────────────────────
function getBlockAtCaret(root) {
    const sel = window.getSelection();
    if (!sel || !sel.anchorNode)
        return null;
    let n = sel.anchorNode;
    if (n.nodeType === Node.TEXT_NODE && n.parentElement)
        n = n.parentElement;
    while (n && n !== root) {
        if (n instanceof HTMLElement && /^(P|DIV|LI|H1|H2|H3|BLOCKQUOTE|UL|OL)$/.test(n.tagName))
            return n;
        n = n.parentElement;
    }
    return root;
}
function textOf(li) {
    // exclude checkbox inputs from text
    let s = '';
    for (const node of Array.from(li.childNodes)) {
        if (node instanceof HTMLInputElement)
            continue;
        s += node.textContent || '';
    }
    return s;
}
function placeCaretAtEnd(el) {
    el.normalize();
    const sel = window.getSelection();
    if (!sel)
        return;
    const range = document.createRange();
    const node = el.lastChild || el;
    const off = node.nodeType === Node.TEXT_NODE ? node.nodeValue ? node.nodeValue.length : 0 : el.childNodes.length - (el.lastChild ? 1 : 0) + 1;
    range.setStart(node, node.nodeType === Node.TEXT_NODE ? off : 0);
    range.collapse(true);
    sel.removeAllRanges();
    sel.addRange(range);
}
function placeCaretInText(textEl, offset) {
    const sel = window.getSelection();
    if (!sel)
        return;
    const range = document.createRange();
    range.setStart(textEl, Math.max(0, Math.min(offset, (textEl.textContent || '').length)));
    range.collapse(true);
    sel.removeAllRanges();
    sel.addRange(range);
}
// ── markdown shortcuts ───────────────────────────────────────
const MD_TOKENS = [
    { re: /^(### )(.*)$/, tag: 'h3' },
    { re: /^(## )(.*)$/, tag: 'h2' },
    { re: /^(# )(.*)$/, tag: 'h1' },
    { re: /^(\[\] )(.*)$/, tag: 'task' },
    { re: /^(\d+\. )(.*)$/, tag: 'ol' },
    { re: /^(- )(.*)$/, tag: 'ul' },
    { re: /^(> )(.*)$/, tag: 'quote' },
];
function maybeMarkdownAtCaret() {
    const sel = window.getSelection();
    if (!sel || sel.rangeCount === 0 || !sel.anchorNode)
        return false;
    if (sel.anchorNode !== sel.focusNode || sel.anchorOffset !== sel.focusOffset)
        return false; // only collapsed
    // caret must be in a text node
    const textNode = sel.anchorNode;
    if (textNode.nodeType !== Node.TEXT_NODE)
        return false;
    const block = getBlockAtCaret(refs.editor);
    if (!block)
        return false;
    const lineStart = block.textContent || '';
    const prefix = lineStart.slice(0, sel.anchorOffset);
    for (const tok of MD_TOKENS) {
        // prefix must be exactly the token + a trailing space
        const m = tok.re.exec(prefix + ' '); // add sentinel space
        if (m && (prefix + ' ') === m[0] && prefix.endsWith(' ')) {
            const rest = lineStart.slice(prefix.length);
            convertBlock(block, tok.tag, rest);
            return true;
        }
    }
    return false;
}
function convertBlock(block, tag, rest) {
    let newEl;
    if (tag === 'task') {
        const ul = document.createElement('ul');
        ul.className = 'task-list';
        const li = document.createElement('li');
        const cb = document.createElement('input');
        cb.type = 'checkbox';
        const span = document.createElement('span');
        span.className = 'task-text';
        span.textContent = rest;
        li.appendChild(cb);
        li.appendChild(span);
        ul.appendChild(li);
        newEl = ul;
    }
    else if (tag === 'ul') {
        const ul = document.createElement('ul');
        const li = document.createElement('li');
        li.textContent = rest;
        ul.appendChild(li);
        newEl = ul;
    }
    else if (tag === 'ol') {
        const ol = document.createElement('ol');
        const li = document.createElement('li');
        li.textContent = rest;
        ol.appendChild(li);
        newEl = ol;
    }
    else if (tag === 'quote') {
        newEl = document.createElement('blockquote');
        newEl.textContent = rest;
    }
    else {
        newEl = document.createElement(tag);
        newEl.textContent = rest;
    }
    block.replaceWith(newEl);
    // place caret at end of new content
    const lastText = newEl.lastChild;
    if (lastText && lastText.nodeType === Node.TEXT_NODE)
        placeCaretInText(lastText, (lastText.textContent || '').length);
    else if (newEl.childElementCount)
        placeCaretAtEnd(newEl);
    else
        placeCaretAtEnd(newEl);
}
// ── toolbar commands ─────────────────────────────────────────
function toggleInlineCode() {
    pushHistory();
    const sel = window.getSelection();
    if (!sel || sel.rangeCount === 0 || sel.isCollapsed)
        return;
    const node = sel.anchorNode;
    const inCode = node && node.parentElement && node.parentElement.closest('code');
    if (inCode) { // unwrap
        const codeEl = inCode;
        const range = sel.getRangeAt(0);
        const content = codeEl.innerHTML;
        range.deleteContents();
        const t = document.createElement('span');
        t.innerHTML = content;
        range.insertNode(t);
        codeEl.replaceWith(t);
        document.execCommand('selectAll'); // nudge
    }
    else {
        document.execCommand('insertHTML', false, '<code>' + sel.toString() + '</code>');
    }
}
function applyCommand(cmd, value) {
    pushHistory();
    document.execCommand(cmd, false, value);
    commit(true);
}
function initEditor(opts) {
    refs = opts;
    buildToolbar();
    // typing + markdown
    refs.editor.addEventListener('beforeinput', (e) => {
        if (applying)
            return;
        if (e.inputType && e.inputType.startsWith('history'))
            return;
        try {
            pushHistory();
        }
        catch { /* */ }
    });
    refs.editor.addEventListener('input', () => {
        if (applying)
            return;
        applying = true;
        try {
            maybeMarkdownAtCaret();
            refs.editor.normalize();
        }
        finally {
            applying = false;
        }
        updatePlaceholder();
        commit(true);
        updateToolbar();
    });
    // checkbox toggling
    refs.editor.addEventListener('input', (e) => {
        const t = e.target;
        if (t && t.type === 'checkbox') {
            t.checked ? t.setAttribute('checked', '') : t.removeAttribute('checked');
            const li = t.closest('li');
            if (li)
                li.classList.toggle('checked', t.checked);
            commit(true);
        }
    });
    // keyboard: list continuation + empty exit + undo/redo
    refs.editor.addEventListener('keydown', (e) => {
        if ((e.metaKey || e.ctrlKey) && !e.shiftKey && /^[z]$/i.test(e.key)) {
            e.preventDefault();
            undo();
            return;
        }
        if ((e.metaKey || e.ctrlKey) && e.shiftKey && /^[z]$/i.test(e.key)) {
            e.preventDefault();
            redo();
            return;
        }
        if (e.key === 'Enter')
            onEnter(e);
    });
    // title input
    refs.title.addEventListener('input', () => {
        if (noteId) {
            (0, store_1.updateNoteTitle)(noteId, refs.title.value.trim());
            refs.onNoteChanged(noteId);
            setSaved('saving');
        }
    });
    // tag input
    refs.tagInput.addEventListener('keydown', (e) => {
        if (e.key === 'Enter' || e.key === ',') {
            e.preventDefault();
            const v = refs.tagInput.value.trim();
            if (v && noteId) {
                (0, store_1.addTag)(noteId, v);
                renderTags();
                commit(false);
            }
            refs.tagInput.value = '';
        }
        else if (e.key === 'Backspace' && !refs.tagInput.value && noteId) {
            const n = (0, store_1.getState)().notes.find((x) => x.id === noteId);
            if (n && n.tags.length) {
                (0, store_1.removeTag)(noteId, n.tags[n.tags.length - 1]);
                renderTags();
                commit(false);
            }
        }
    });
    // paste sanitization
    refs.editor.addEventListener('paste', onPaste);
    // toolbar state reflection
    document.addEventListener('selectionchange', () => {
        if (refs.editor.contains(document.activeElement) || (window.getSelection() && refs.editor.contains(window.getSelection().anchorNode))) {
            updateToolbar();
        }
    });
}
function onEnter(e) {
    const block = getBlockAtCaret(refs.editor);
    if (!block || block.tagName !== 'LI')
        return;
    const list = block.parentElement;
    if (!list)
        return;
    if (list.classList.contains('task-list')) {
        e.preventDefault();
        pushHistory();
        const isEmpty = textOf(block).trim() === '';
        if (isEmpty) {
            // remove this li; if it's the only one, replace list with empty p after
            list.removeChild(block);
            const p = document.createElement('p');
            p.innerHTML = '<br>';
            if (list.childElementCount === 0) {
                list.replaceWith(p);
            }
            else {
                list.parentElement.insertBefore(p, list.nextSibling);
            }
            const sel = window.getSelection();
            const r = document.createRange();
            r.setStart(p, 0);
            r.collapse(true);
            sel.removeAllRanges();
            sel.addRange(r);
        }
        else {
            const li = document.createElement('li');
            const cb = document.createElement('input');
            cb.type = 'checkbox';
            const span = document.createElement('span');
            span.className = 'task-text';
            li.appendChild(cb);
            li.appendChild(span);
            list.insertBefore(li, block.nextSibling);
            const sel = window.getSelection();
            const r = document.createRange();
            r.setStart(span, 0);
            r.collapse(true);
            sel.removeAllRanges();
            sel.addRange(r);
        }
        commit(true);
    }
}
// ── paste sanitization ───────────────────────────────────────
const ALLOWED = new Set(['P', 'DIV', 'H1', 'H2', 'H3', 'UL', 'OL', 'LI', 'BLOCKQUOTE', 'BR', 'HR', 'STRONG', 'B', 'EM', 'I', 'U', 'S', 'STRIKE', 'DEL', 'CODE', 'PRE', 'SPAN', 'A', 'INPUT']);
function sanitizeNode(node, outParent) {
    if (node.nodeType === Node.TEXT_NODE) {
        const t = document.createTextNode(node.textContent || '');
        if (outParent)
            outParent.appendChild(t);
        return t;
    }
    if (node.nodeType !== Node.ELEMENT_NODE)
        return null;
    const el = node;
    const tag = el.tagName;
    if (!ALLOWED.has(tag)) {
        // unwrap: copy children into parent
        for (const child of Array.from(el.childNodes))
            sanitizeNode(child, outParent);
        return null;
    }
    let targetTag = tag;
    if (tag === 'B')
        targetTag = 'STRONG';
    if (tag === 'I')
        targetTag = 'EM';
    if (tag === 'STRIKE' || tag === 'DEL')
        targetTag = 'S';
    if (tag === 'DIV')
        targetTag = 'P';
    if (tag === 'PRE')
        targetTag = 'CODE';
    const n = document.createElement(targetTag);
    if (tag === 'A' && el.getAttribute && el.getAttribute('href'))
        n.setAttribute('href', el.getAttribute('href'));
    if (tag === 'INPUT') {
        const inp = document.createElement('input');
        inp.type = 'checkbox';
        if (el.checked)
            inp.setAttribute('checked', '');
        if (outParent)
            outParent.appendChild(inp);
        return inp;
    }
    if (targetTag === 'UL' && el.classList.contains('task-list'))
        n.classList.add('task-list');
    for (const child of Array.from(el.childNodes)) {
        if (child instanceof HTMLInputElement && child.type === 'checkbox') {
            const inp = document.createElement('input');
            inp.type = 'checkbox';
            if (child.checked)
                inp.setAttribute('checked', '');
            n.appendChild(inp);
        }
        else {
            const c = sanitizeNode(child, null);
            if (c)
                n.appendChild(c);
        }
    }
    if (outParent)
        outParent.appendChild(n);
    return n;
}
function sanitizeHtml(htmlText) {
    const doc = new DOMParser().parseFromString(htmlText, 'text/html');
    const build = document.createElement('div');
    for (const child of Array.from(doc.body.childNodes)) {
        const out = sanitizeNode(child, build);
        if (out && !out.parentNode)
            build.appendChild(out);
    }
    return build.innerHTML || '';
}
function onPaste(e) {
    e.preventDefault();
    pushHistory();
    const cd = e.clipboardData;
    if (!cd)
        return;
    const html = cd.getData('text/html');
    const text = cd.getData('text/plain');
    if (html && html.trim()) {
        const clean = sanitizeHtml(html);
        if (clean)
            document.execCommand('insertHTML', false, clean);
        else if (text)
            insertPlainText(text);
    }
    else if (text) {
        insertPlainText(text);
    }
    commit(true);
}
function insertPlainText(text) {
    const escaped = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br>');
    document.execCommand('insertHTML', false, escaped);
}
// ── toolbar UI ───────────────────────────────────────────────
function tbBtn(cmd, inner, title, cls = '') {
    const b = document.createElement('button');
    b.className = 'tb-btn' + (cls ? ' ' + cls : '');
    b.dataset.cmd = cmd;
    b.title = title;
    b.innerHTML = inner;
    return b;
}
function sep() { const d = document.createElement('div'); d.className = 'tb-sep'; return d; }
function buildToolbar() {
    const t = refs.toolbar;
    t.innerHTML = '';
    const g1 = document.createElement('div');
    g1.className = 'tb-group';
    g1.append(tbBtn('bold', '<b>B</b>', 'Bold (Ctrl+B)'), tbBtn('italic', '<i>I</i>', 'Italic (Ctrl+I)'), tbBtn('underline', '<u>U</u>', 'Underline (Ctrl+U)'), tbBtn('strikeThrough', '<s>S</s>', 'Strikethrough'), tbBtn('code', '<span class="code-glyph">{ }</span>', 'Inline code'));
    const g2 = document.createElement('div');
    g2.className = 'tb-group';
    g2.append(tbBtn('h1', 'H<span class="sep-label">1</span>', 'Heading 1', 'head'), tbBtn('h2', 'H<span class="sep-label">2</span>', 'Heading 2', 'head'), tbBtn('h3', 'H<span class="sep-label">3</span>', 'Heading 3', 'head'));
    const g3 = document.createElement('div');
    g3.className = 'tb-group';
    g3.append(tbBtn('ul', '<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M8 6h12M8 12h12M8 18h12"/><circle cx="4" cy="6" r="1.2" fill="currentColor"/><circle cx="4" cy="12" r="1.2" fill="currentColor"/><circle cx="4" cy="18" r="1.2" fill="currentColor"/></svg>', 'Bulleted list'), tbBtn('ol', '<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M10 6h10M10 12h10M10 18h10"/><path d="M4 5h2v3M4 12h2v3c0 .5-.3.8-.8.8H4"/></svg>', 'Numbered list'), tbBtn('task', '<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="3"/><path d="M9 12l2 2 4-4"/></svg>', 'Checklist'), tbBtn('quote', '<span class="quote-glyph">“</span>', 'Blockquote', 'quote-glyph'), tbBtn('hr', '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 12h16"/><path d="M4 5h6M14 5h6M4 19h6M14 19h6"/></svg>', 'Horizontal rule'));
    t.append(g1, sep(), g2, sep(), g3);
    const spacer = document.createElement('div');
    spacer.className = 'tb-spacer';
    t.append(spacer, refs.savedEl);
    t.addEventListener('click', (e) => {
        const btn = e.target.closest('.tb-btn');
        if (!btn)
            return;
        const cmd = btn.dataset.cmd;
        if (cmd === 'bold' || cmd === 'italic' || cmd === 'underline' || cmd === 'strikeThrough')
            applyCommand(cmd);
        else if (cmd === 'code') {
            toggleInlineCode();
            commit(true);
            updateToolbar();
        }
        else if (cmd === 'h1' || cmd === 'h2' || cmd === 'h3') {
            const block = getBlockAtCaret(refs.editor);
            const isActive = block && block.tagName === cmd.toUpperCase();
            applyCommand('formatBlock', isActive ? 'p' : cmd);
            updateToolbar();
        }
        else if (cmd === 'ul') {
            applyCommand('insertUnorderedList');
            commit(true);
            updateToolbar();
        }
        else if (cmd === 'ol') {
            applyCommand('insertOrderedList');
            commit(true);
            updateToolbar();
        }
        else if (cmd === 'quote') {
            const block = getBlockAtCaret(refs.editor);
            const isActive = block && block.tagName === 'BLOCKQUOTE';
            applyCommand('formatBlock', isActive ? 'p' : 'blockquote');
            updateToolbar();
        }
        else if (cmd === 'task') {
            toggleChecklistToolbar();
        }
        else if (cmd === 'hr') {
            applyCommand('insertHorizontalRule');
            commit(true);
            updateToolbar();
        }
        refs.editor.focus();
    });
}
function toggleChecklistToolbar() {
    pushHistory();
    const blocks = selectedBlocks();
    const paragraphs = blocks.filter((b) => b.tagName === 'P');
    const allTask = blocks.length && blocks.every((b) => b.closest('ul.task-list'));
    if (allTask) {
        // convert task list back to bullets (unwrap class + remove checkboxes)
        for (const b of blocks) {
            const li = b.closest('li');
            if (!li)
                continue;
            for (const cb of Array.from(li.querySelectorAll('input[type=checkbox]')))
                cb.remove();
            const ul = li.parentElement;
            if (ul && ul.classList.contains('task-list'))
                ul.classList.remove('task-list');
        }
    }
    else if (paragraphs.length) {
        const ul = document.createElement('ul');
        ul.className = 'task-list';
        for (const p of paragraphs) {
            const li = document.createElement('li');
            const cb = document.createElement('input');
            cb.type = 'checkbox';
            const span = document.createElement('span');
            span.className = 'task-text';
            span.innerHTML = p.innerHTML;
            li.appendChild(cb);
            li.appendChild(span);
            ul.appendChild(li);
            p.replaceWith(li);
        }
        const first = paragraphs[0];
        first.parentElement.insertBefore(ul, first);
    }
    else {
        // make current single block a task item
        const block = getBlockAtCaret(refs.editor);
        if (block) {
            const ul = document.createElement('ul');
            ul.className = 'task-list';
            const li = document.createElement('li');
            const cb = document.createElement('input');
            cb.type = 'checkbox';
            const span = document.createElement('span');
            span.className = 'task-text';
            span.innerHTML = block.innerHTML;
            li.appendChild(cb);
            li.appendChild(span);
            ul.appendChild(li);
            block.replaceWith(ul);
        }
    }
    commit(true);
    updateToolbar();
}
function selectedBlocks() {
    const sel = window.getSelection();
    const blocks = [];
    if (!sel || sel.rangeCount === 0)
        return blocks;
    const range = sel.getRangeAt(0);
    let node = range.startContainer;
    const seen = new Set();
    const walk = (n) => {
        if (n.nodeType === Node.ELEMENT_NODE) {
            const el = n;
            if (/^(P|LI|H1|H2|H3|BLOCKQUOTE)$/.test(el.tagName) && !seen.has(el)) {
                seen.add(el);
                blocks.push(el);
            }
        }
        for (const c of Array.from(n.childNodes))
            walk(c);
    };
    if (range.startContainer instanceof HTMLElement)
        walk(range.startContainer);
    // simple: walk the nearest common ancestor
    const common = range.commonAncestorContainer instanceof HTMLElement ? range.commonAncestorContainer : range.commonAncestorContainer.parentElement;
    if (common) {
        seen.clear();
        blocks.length = 0;
        walk(common);
    }
    return blocks;
}
// ── toolbar state reflection ─────────────────────────────────
function updateToolbar() {
    const btns = Array.from(refs.toolbar.querySelectorAll('.tb-btn'));
    const q = (cmd) => { try {
        return document.queryCommandState(cmd);
    }
    catch {
        return false;
    } };
    const bolds = { bold: q('bold'), italic: q('italic'), underline: q('underline'), strikeThrough: q('strikeThrough') };
    for (const b of btns) {
        const cmd = b.dataset.cmd;
        let on = false;
        if (cmd in bolds)
            on = bolds[cmd];
        else if (cmd === 'code')
            on = !!inCode();
        else if (cmd === 'h1' || cmd === 'h2' || cmd === 'h3') {
            const block = getBlockAtCaret(refs.editor);
            on = !!block && block.tagName === cmd.toUpperCase();
        }
        else if (cmd === 'quote') {
            const block = getBlockAtCaret(refs.editor);
            on = !!block && block.tagName === 'BLOCKQUOTE';
        }
        else if (cmd === 'ul' || cmd === 'ol' || cmd === 'task') {
            const li = closestLi(getBlockAtCaret(refs.editor));
            if (!li)
                on = false;
            else if (cmd === 'ul')
                on = li.parentElement?.tagName === 'UL' && !li.parentElement.classList.contains('task-list');
            else if (cmd === 'ol')
                on = li.parentElement?.tagName === 'OL';
            else
                on = !!li.closest('ul.task-list');
        }
        b.classList.toggle('active', !!on);
    }
}
function closestLi(b) {
    let n = b;
    while (n && !/^(LI|P|H1|H2|H3|BLOCKQUOTE|UL|OL)$/.test(n.tagName))
        n = n.parentElement;
    return n;
}
function inCode() {
    const sel = window.getSelection();
    if (!sel || !sel.anchorNode || !sel.anchorNode.parentElement)
        return false;
    return !!sel.anchorNode.parentElement.closest('code');
}
// ── tags rendering ───────────────────────────────────────────
function renderTags() {
    refs.tagWrap.innerHTML = '';
    if (!noteId)
        return;
    const note = (0, store_1.getState)().notes.find((n) => n.id === noteId);
    if (!note)
        return;
    for (const tag of note.tags) {
        const chip = document.createElement('span');
        chip.className = 'tag-chip clickable';
        chip.innerHTML = '<span class="hash">#</span><span>' + escapeHtml(tag) + '</span><span class="x" data-tag="' + escapeHtml(tag) + '">&times;</span>';
        chip.title = 'Filter by #' + tag;
        chip.addEventListener('click', (e) => {
            if (e.target.classList.contains('x')) {
                if (noteId) {
                    (0, store_1.removeTag)(noteId, tag);
                    renderTags();
                    commit(false);
                }
                e.stopPropagation();
            }
            else {
                refs.onTagClick(tag);
            }
        });
        refs.tagWrap.appendChild(chip);
    }
}
function escapeHtml(s) {
    return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
};

// ── module: src/ui.ts ──
__mods["src/ui.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports._setView = void 0;
exports.showToast = showToast;
exports.setQueryInput = setQueryInput;
exports.renderAll = renderAll;
exports.bindUI = bindUI;
exports.focusTitle = focusTitle;
exports.trash = trash;
exports.deleteSelectedNote = deleteSelectedNote;
exports.getQtagFilter = getQtagFilter;
exports.setQtagFilter = setQtagFilter;
exports.showEditorEmpty = showEditorEmpty;
exports.activeViewKind = activeViewKind;
// ─────────────────────────────────────────────────────────────
// ui.ts — sidebar, notes list, search, tags, context menus,
// sort menu, drag-and-drop. Rendering is driven entirely by
// store state (DOM is a projection). Editor loading is delegated.
// ─────────────────────────────────────────────────────────────
const store_1 = require("./store");
Object.defineProperty(exports, "_setView", { enumerable: true, get: function () { return store_1.setView; } });
const editor_1 = require("./editor");
// ── transient UI state ───────────────────────────────────────
let query = '';
let qtagFilter = null;
const $ = (sel) => document.querySelector(sel);
function titleOf(view) {
    if (view.kind === 'notebook') {
        const n = (0, store_1.findNotebook)(view.notebookId ?? null);
        return n ? n.name : 'Notes';
    }
    if (view.kind === 'tag')
        return '#' + (view.tag || '');
    if (view.kind === 'trash')
        return 'Trash';
    return 'All Notes';
}
// ── selection + navigation ───────────────────────────────────
function selectNote(id) {
    (0, store_1.setSelectedNote)(id);
    const note = (0, store_1.findNote)(id);
    renderAll();
    if (note && note.deletedAt === null)
        (0, editor_1.loadNote)(note);
    else if (note) {
        (0, editor_1.clearNote)();
        showEditorEmpty(true);
    }
    (0, store_1.save)();
}
function goTo(view) {
    (0, store_1.setView)(view);
    qtagFilter = null;
    renderAll();
    (0, store_1.save)();
}
function clearQuery() { query = ''; setQueryInput(); renderAll(); }
function activeViewKind() {
    return (0, store_1.getState)().view.kind;
}
function isTrashView() { return activeViewKind() === 'trash'; }
// ── sidebar render ───────────────────────────────────────────
function renderSidebar() {
    const s = (0, store_1.getState)();
    const counts = { all: 0, trash: (0, store_1.countInTrash)() };
    s.notes.forEach((n) => { if (n.deletedAt === null)
        counts.all++; });
    const allBtn = $('#nav-all');
    const trashBtn = $('#nav-trash');
    allBtn.classList.toggle('active', !query && s.view.kind === 'all');
    trashBtn.classList.toggle('active', !query && s.view.kind === 'trash');
    $('#count-all').textContent = counts.all <= 0 ? '' : String(counts.all);
    $('#count-trash').textContent = counts.trash <= 0 ? '' : String(counts.trash);
    // tags
    const tagCounts = new Map();
    s.notes.forEach((n) => { if (n.deletedAt === null)
        n.tags.forEach((t) => tagCounts.set(t, (tagCounts.get(t) || 0) + 1)); });
    const tags = Array.from(tagCounts.entries()).sort((a, b) => a[0].localeCompare(b[0]));
    const tagList = $('#tag-list');
    tagList.innerHTML = '';
    if (!tags.length) {
        const empty = document.createElement('div');
        empty.className = 'tag-item';
        empty.style.color = 'var(--text-faint)';
        empty.textContent = 'No tags yet';
        tagList.appendChild(empty);
    }
    for (const [tag, n] of tags) {
        const row = document.createElement('div');
        row.className = 'tag-item' + (s.view.kind === 'tag' && s.view.tag === tag && !query ? ' active' : '');
        row.innerHTML = '<span class="tag-hash">#</span><span>' + escapeHtml(tag) + '</span><span class="tag-count">' + n + '</span>';
        row.addEventListener('click', () => goTo({ kind: 'tag', tag }));
        tagList.appendChild(row);
    }
    // notebooks
    const list = $('#notebook-list');
    list.innerHTML = '';
    if (!s.notebooks.length) {
        const empty = document.createElement('div');
        empty.className = 'tag-item';
        empty.style.color = 'var(--text-faint)';
        empty.textContent = 'No notebooks';
        list.appendChild(empty);
    }
    for (const nb of s.notebooks)
        list.appendChild(buildNotebookRow(nb));
}
function buildNotebookRow(nb) {
    const row = document.createElement('div');
    row.className = 'nb-row' + ((0, store_1.getState)().view.kind === 'notebook' && (0, store_1.getState)().view.notebookId === nb.id ? ' active' : '') + (nb.expanded ? '' : ' collapsed');
    row.dataset.drop = 'notebook';
    row.dataset.id = nb.id;
    const caret = document.createElement('button');
    caret.className = 'nb-caret';
    caret.innerHTML = '<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M9 6l6 6-6 6"/></svg>';
    caret.addEventListener('click', (e) => { e.stopPropagation(); (0, store_1.toggleNotebookExpanded)(nb.id); renderAll(); (0, store_1.save)(); });
    const main = document.createElement('div');
    main.className = 'nb-main';
    main.innerHTML = '<span class="nb-ico"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg></span><span class="nb-name">' + escapeHtml(nb.name) + '</span>';
    const ncount = (0, store_1.getState)().notes.filter((n) => n.notebookId === nb.id && n.deletedAt === null).length;
    main.insertAdjacentHTML('beforeend', '<span class="nb-count">' + ncount + '</span>');
    const actions = document.createElement('div');
    actions.className = 'nb-actions';
    actions.innerHTML = '<button class="nb-act" data-act="rename" title="Rename">✎</button><button class="nb-act" data-act="delete" title="Delete">🗑</button>';
    actions.addEventListener('click', (e) => {
        const b = e.target.closest('.nb-act');
        if (!b)
            return;
        e.stopPropagation();
        if (b.dataset.act === 'rename')
            startRenameNotebook(main, nb);
        else
            openNotebookMenu(b.getBoundingClientRect(), nb.id);
    });
    main.appendChild(actions);
    main.addEventListener('click', (e) => {
        if (e.target.closest('.nb-act'))
            return;
        goTo({ kind: 'notebook', notebookId: nb.id });
    });
    main.addEventListener('contextmenu', (e) => { e.preventDefault(); openNotebookMenu(e, nb.id); });
    main.addEventListener('dragover', (e) => { e.preventDefault(); row.classList.add('drop-ontop'); });
    main.addEventListener('dragleave', () => row.classList.remove('drop-ontop'));
    main.addEventListener('drop', (e) => { e.preventDefault(); row.classList.remove('drop-ontop'); onDropNotebook(e, nb.id); });
    row.appendChild(caret);
    row.appendChild(main);
    if (nb.expanded) {
        const sub = document.createElement('div');
        sub.className = 'nb-notes';
        const notes = (0, store_1.visibleNotes)({ kind: 'notebook', notebookId: nb.id }, (0, store_1.getState)().layout.sort).slice(0, 120);
        if (!notes.length) {
            const hint = document.createElement('div');
            hint.className = 'nb-note-item';
            hint.style.color = 'var(--text-faint)';
            hint.style.fontSize = '11.5px';
            hint.style.padding = '2px 4px';
            hint.textContent = 'Empty notebook';
            sub.appendChild(hint);
        }
        for (const n of notes) {
            sub.appendChild(buildNotebookNote(n));
        }
        row.appendChild(sub);
    }
    return row;
}
function buildNotebookNote(n) {
    const item = document.createElement('div');
    item.className = 'nb-note-item';
    const link = document.createElement('button');
    link.className = 'nb-note-link' + ((0, store_1.getState)().selectedNoteId === n.id ? ' active' : '');
    link.textContent = n.title || 'Untitled';
    link.title = n.title || 'Untitled';
    link.addEventListener('click', () => { (0, store_1.setView)({ kind: 'notebook', notebookId: n.notebookId }); selectNote(n.id); });
    const meta = document.createElement('span');
    meta.className = 'nb-note-meta';
    meta.textContent = n.pinned ? '★' : '';
    item.append(link, meta);
    return item;
}
function startRenameNotebook(main, nb) {
    const nameEl = main.querySelector('.nb-name');
    const name = nb.name;
    const input = document.createElement('input');
    input.className = 'rename-input';
    input.value = name;
    nameEl.replaceWith(input);
    input.focus();
    input.select();
    const finish = (commit) => {
        const v = input.value.trim();
        if (commit && v) {
            (0, store_1.renameNotebook)(nb.id, v);
            (0, store_1.save)();
        }
        renderAll();
    };
    input.addEventListener('keydown', (e) => {
        if (e.key === 'Enter')
            finish(true);
        else if (e.key === 'Escape')
            finish(false);
        e.stopPropagation();
    });
    input.addEventListener('blur', () => finish(true));
}
// ── notes list render ────────────────────────────────────────
function renderNotes() {
    const s = (0, store_1.getState)();
    const effectiveView = query.trim() ? { kind: 'all' } : s.view;
    const notes = (0, store_1.visibleNotes)(effectiveView, s.layout.sort, query.trim(), qtagFilter ? [qtagFilter] : []);
    $('#notes-head-title').textContent = query.trim() ? 'Search results' : titleOf(s.view);
    // tag filter bar
    const bar = $('#notes-toolbar');
    const label = $('#notes-tb-label');
    if (qtagFilter) {
        bar.hidden = false;
        label.innerHTML = '<span class="hash">#</span> ' + escapeHtml(qtagFilter) + ' · ' + notes.length + (notes.length === 1 ? ' note' : ' notes');
    }
    else if (query.trim()) {
        bar.hidden = false;
        label.textContent = '“' + query.trim() + '” · ' + notes.length + (notes.length === 1 ? ' match' : ' matches');
    }
    else {
        bar.hidden = true;
    }
    const list = $('#notes-list');
    const empty = $('#notes-empty');
    list.innerHTML = '';
    if (!notes.length) {
        empty.hidden = false;
        const q = query.trim();
        empty.innerHTML =
            '<div class="ne-ico"><svg viewBox="0 0 24 24" width="34" height="34" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 21l-4.3-4.3"/><circle cx="11" cy="11" r="7"/></svg></div>' +
                (q ? '<h3>No results for “' + escapeHtml(q) + '”</h3><p>Try a different search, or clear the filter.</p>'
                    : isTrashView() ? '<h3>Trash is empty</h3><p>Notes you delete will appear here.</p>'
                        : '<h3>Nothing here yet</h3><p>Create a new note to get started.</p>');
        return;
    }
    empty.hidden = true;
    for (const note of notes)
        list.appendChild(buildNoteItem(note));
}
function buildNoteItem(note) {
    const item = document.createElement('div');
    item.className = 'note-item' + ((0, store_1.getState)().selectedNoteId === note.id ? ' selected' : '') + (note.pinned ? ' pinned' : '');
    item.draggable = true;
    item.dataset.nid = note.id;
    const q = query.trim();
    const snip = (0, store_1.snippetFor)(note, q);
    // pins first (sort already handles), just render
    let titleHtml = escapeHtml(note.title || 'Untitled');
    if (q)
        titleHtml = highlight(titleHtml, note.title, q);
    let snipHtml = escapeHtml(snip.text);
    if (q && snip.ranges.length) {
        snipHtml = escapeHtml(snip.text);
        // rebuild with ranges
        let out = '';
        let last = 0;
        for (const [a, b] of snip.ranges) {
            out += escapeHtml(snip.text.slice(last, a)) + '<em class="match">' + escapeHtml(snip.text.slice(a, b)) + '</em>';
            last = b;
        }
        out += escapeHtml(snip.text.slice(last));
        snipHtml = out;
    }
    const tagsHtml = note.tags.map((t) => '<span class="ni-tag">#' + escapeHtml(t) + '</span>').join('');
    const toast = note.deletedAt !== null ? '<span class="trash-badge">Trash</span>' : '';
    item.innerHTML =
        '<div class="note-item-top">' +
            '<span class="ni-pin"><svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor"><path d="M16 3l5 5-4 1-4 4-1 5-4-4-3 3-1-1 3-3-4-4 5-1 4-4z"/></svg></span>' +
            '<span class="ni-title">' + titleHtml + '</span>' +
            '<button class="ni-pin-btn' + (note.pinned ? ' pinned' : '') + '" title="' + (note.pinned ? 'Unpin' : 'Pin') + '"><svg viewBox="0 0 24 24" width="13" height="13" fill="currentColor"><path d="M16 3l5 5-4 1-4 4-1 5-4-4-3 3-1-1 3-3-4-4 5-1 4-4z"/></svg></button>' +
            '</div>' +
            '<div class="ni-snippet">' + (snipHtml || '<span style="color:var(--text-faint)">No text</span>') + '</div>' +
            '<div class="ni-meta"><span>' + formatTime(note) + '</span><span class="dot"></span>' + (note.pinned ? '<span>Pinned</span>' + '<span class="dot"></span>' : '') + '<span class="ni-tags">' + tagsHtml + '</span>' + toast + '</div>';
    item.addEventListener('click', () => selectNote(note.id));
    const pinBtn = item.querySelector('.ni-pin-btn');
    pinBtn.addEventListener('click', (e) => { e.stopPropagation(); (0, store_1.setPinned)(note.id, !note.pinned); renderAll(); (0, store_1.save)(); });
    item.addEventListener('contextmenu', (e) => { e.preventDefault(); openNoteMenu(e, note.id); });
    item.addEventListener('dragstart', (e) => { onDragStart(e, note.id); });
    item.addEventListener('dragend', () => { document.querySelectorAll('.dragging-source').forEach((x) => x.classList.remove('dragging-source')); });
    return item;
}
function highlight(titleHtml, rawTitle, q) {
    const low = rawTitle.toLowerCase();
    const ql = q.toLowerCase();
    if (low.indexOf(ql) === -1)
        return titleHtml;
    let out = '';
    let last = 0;
    let idx = low.indexOf(ql);
    while (idx !== -1) {
        out += escapeHtml(rawTitle.slice(last, idx)) + '<em class="match">' + escapeHtml(rawTitle.slice(idx, idx + ql.length)) + '</em>';
        last = idx + ql.length;
        idx = low.indexOf(ql, last);
    }
    out += escapeHtml(rawTitle.slice(last));
    return out;
}
function formatTime(n) {
    const t = n.deletedAt !== null ? n.deletedAt : n.updatedAt;
    const diff = Date.now() - t;
    const d = new Date(t);
    const mins = Math.floor(diff / 60000);
    if (mins < 1)
        return 'just now';
    if (mins < 60)
        return mins + 'm ago';
    const hrs = Math.floor(mins / 60);
    if (hrs < 24)
        return hrs + 'h ago';
    const days = Math.floor(hrs / 24);
    if (days === 1)
        return 'yesterday';
    if (days < 7)
        return days + 'd ago';
    return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
}
// ── menus ────────────────────────────────────────────────────
function closeMenus() {
    document.querySelectorAll('.menu').forEach((m) => m.remove());
}
function placeMenu(menu, x, y) {
    document.body.appendChild(menu);
    const r = menu.getBoundingClientRect();
    let left = x, top = y;
    if (left + r.width > window.innerWidth - 8)
        left = window.innerWidth - r.width - 8;
    if (top + r.height > window.innerHeight - 8)
        top = window.innerHeight - r.height - 8;
    menu.style.left = Math.max(8, left) + 'px';
    menu.style.top = Math.max(8, top) + 'px';
}
function openNoteMenu(e, noteId) {
    closeMenus();
    const note = (0, store_1.findNote)(noteId);
    if (!note)
        return;
    const menu = document.createElement('div');
    menu.className = 'menu';
    const inTrash = note.deletedAt !== null;
    menu.innerHTML =
        '<div class="menu-sublabel">' + escapeHtml(note.title || 'Untitled') + '</div>' +
            (inTrash ?
                '<button class="menu-item" data-a="restore"><span>↩</span><span class="mi-label">Restore</span></button>' +
                    '<div class="menu-sep"></div>' +
                    '<button class="menu-item danger" data-a="purge"><span>🗑</span><span class="mi-label">Delete forever</span></button>'
                :
                    '<button class="menu-item" data-a="open"><span>✎</span><span class="mi-label">Open</span></button>' +
                        '<div class="menu-sublabel">Move to</div>' +
                        moveToItems(note) +
                        '<div class="menu-sep"></div>' +
                        '<button class="menu-item" data-a="pin"><span>📌</span><span class="mi-label">' + (note.pinned ? 'Unpin' : 'Pin') + '</span></button>' +
                        '<button class="menu-item" data-a="dupe"><span>⧉</span><span class="mi-label">Duplicate</span></button>' +
                        '<button class="menu-item" data-a="tags"><span>#</span><span class="mi-label">Add tag…</span></button>' +
                        '<div class="menu-sep"></div>' +
                        '<button class="menu-item danger" data-a="trash"><span>🗑</span><span class="mi-label">Delete</span><span class="mi-kbd">Del</span></button>');
    menu.addEventListener('click', (ev) => {
        const b = ev.target.closest('.menu-item');
        if (!b)
            return;
        const a = b.dataset.a;
        if (a === 'open')
            selectNote(noteId);
        else if (a === 'pin') {
            (0, store_1.setPinned)(noteId, !note.pinned);
            (0, store_1.save)();
            renderAll();
        }
        else if (a === 'dupe') {
            const c = (0, store_1.duplicateNote)(noteId);
            (0, store_1.save)();
            renderAll();
            selectNote(c.id);
        }
        else if (a === 'trash')
            trash();
        else if (a === 'restore') {
            (0, store_1.restoreNote)(noteId);
            (0, store_1.save)();
            renderAll();
        }
        else if (a === 'purge') {
            (0, store_1.permanentDelete)(noteId);
            (0, store_1.save)();
            renderAll();
            if ((0, editor_1.getCurrentNoteId)() === noteId) {
                (0, editor_1.clearNote)();
                showEditorEmpty(true);
            }
        }
        else if (a && a.startsWith('move:')) {
            (0, store_1.moveNoteTo)(noteId, a.slice(5) === '__none__' ? null : a.slice(5));
            (0, store_1.save)();
            renderAll();
        }
        else if (a === 'tags') {
            $('#tag-input')?.focus();
        }
        closeMenus();
    });
    placeMenu(menu, e.clientX, e.clientY);
    setTimeout(() => document.addEventListener('mousedown', onDocDown, { once: true }), 0);
}
function onDocDown(e) {
    if (!e.target.closest('.menu'))
        closeMenus();
}
function moveToItems(note) {
    const s = (0, store_1.getState)();
    let out = '';
    out += '<button class="menu-item" data-a="move:__none__"><span>🗂</span><span class="mi-label">All Notes</span>' + (note.notebookId === null ? ' <span class="check">✓</span>' : '') + '</button>';
    for (const nb of s.notebooks) {
        out += '<button class="menu-item" data-a="move:' + nb.id + '"><span>🗂</span><span class="mi-label">' + escapeHtml(nb.name) + '</span>' + (note.notebookId === nb.id ? ' <span class="check">✓</span>' : '') + '</button>';
    }
    // snippets
    return out;
}
function openNotebookMenu(e, notebookId) {
    closeMenus();
    const menu = document.createElement('div');
    menu.className = 'menu';
    const nb = (0, store_1.findNotebook)(notebookId);
    const name = nb ? nb.name : 'Notebook';
    menu.innerHTML =
        '<div class="menu-sublabel">' + escapeHtml(name) + '</div>' +
            '<button class="menu-item" data-a="newnote"><span>+</span><span class="mi-label">New note</span></button>' +
            '<button class="menu-item" data-a="rename"><span>✎</span><span class="mi-label">Rename</span></button>' +
            '<div class="menu-sep"></div>' +
            '<button class="menu-item danger" data-a="delete"><span>🗑</span><span class="mi-label">Delete notebook</span></button>';
    menu.addEventListener('click', (ev) => {
        const b = ev.target.closest('.menu-item');
        if (!b)
            return;
        closeMenus();
        const a = b.dataset.a;
        if (a === 'newnote') {
            const n = (0, store_1.createNote)(notebookId);
            (0, store_1.save)();
            renderAll();
            selectNote(n.id);
            focusTitle();
        }
        else if (a === 'rename') {
            const main = document.querySelector('.nb-row[data-id="' + notebookId + '"] .nb-main');
            if (main)
                startRenameNotebook(main, nb || { id: notebookId, name, expanded: true });
        }
        else if (a === 'delete') {
            (0, store_1.deleteNotebook)(notebookId);
            (0, store_1.save)();
            renderAll();
            if ((0, editor_1.getCurrentNoteId)()) {
                const cur = (0, store_1.findNote)((0, editor_1.getCurrentNoteId)());
                if (cur && cur.notebookId === notebookId) {
                    (0, editor_1.clearNote)();
                    showEditorEmpty(true);
                }
            }
        }
    });
    const x = e instanceof MouseEvent ? e.clientX : e.left;
    const y = e instanceof MouseEvent ? e.clientY : e.bottom + 4;
    placeMenu(menu, x, y);
    setTimeout(() => document.addEventListener('mousedown', onDocDown, { once: true }), 0);
}
function showSortMenu(anchor) {
    closeMenus();
    const menu = document.createElement('div');
    menu.className = 'menu sort-menu';
    const cur = (0, store_1.getState)().layout.sort;
    const items = [['modified', 'Date modified'], ['created', 'Date created'], ['alpha', 'Title A–Z']];
    for (const [ord, label] of items) {
        const b = document.createElement('button');
        b.className = 'menu-item';
        b.innerHTML = '<span class="check">' + (cur === ord ? '✓' : '') + '</span><span class="mi-label">' + label + '</span>';
        b.addEventListener('click', () => { (0, store_1.setSort)(ord); (0, store_1.save)(); renderAll(); closeMenus(); });
        menu.appendChild(b);
    }
    placeMenu(menu, anchor.getBoundingClientRect().right - 180, anchor.getBoundingClientRect().bottom + 4);
    setTimeout(() => document.addEventListener('mousedown', () => closeMenus(), { once: true }), 0);
}
// ── drag & drop: note → notebook / trash ─────────────────────
let dragNoteId = null;
function onDragStart(e, noteId) {
    dragNoteId = noteId;
    const note = (0, store_1.findNote)(noteId);
    if (!note)
        return;
    e.dataTransfer?.setData('text/plain', noteId);
    e.dataTransfer.effectAllowed = 'move';
    e.currentTarget.classList.add('dragging-source');
}
function onDropNotebook(e, notebookId) {
    const id = e.dataTransfer?.getData('text/plain') || dragNoteId;
    if (!id)
        return;
    const note = (0, store_1.findNote)(id);
    if (note) {
        if (note.deletedAt !== null)
            (0, store_1.restoreNote)(id);
        (0, store_1.moveNoteTo)(id, notebookId);
        (0, store_1.save)();
        renderAll();
        if ((0, editor_1.getCurrentNoteId)() === id) {
            const nn = (0, store_1.findNote)(id);
            if (nn)
                (0, editor_1.loadNote)(nn);
        }
    }
}
// ── toast ────────────────────────────────────────────────────
let toastTimer = 0;
function showToast(msg) {
    document.querySelectorAll('.toast').forEach((t) => t.remove());
    const t = document.createElement('div');
    t.className = 'toast';
    t.textContent = msg;
    document.body.appendChild(t);
    clearTimeout(toastTimer);
    toastTimer = window.setTimeout(() => t.remove(), 1800);
}
// ── editor empty state ───────────────────────────────────────
function showEditorEmpty(show) { $('#editor-empty').hidden = !show; }
function setQueryInput() { $('#global-search').value = query; }
// ── main render ──────────────────────────────────────────────
function renderAll() {
    renderSidebar();
    renderNotes();
}
// ── bind static UI ───────────────────────────────────────────
function bindUI(handlers) {
    // search
    const search = $('#global-search');
    search.addEventListener('input', () => {
        query = search.value.trim();
        qtagFilter = null;
        $('#search-clear').hidden = !query;
        renderNotes();
        renderSidebar();
    });
    $('#search-clear').addEventListener('click', () => { clearQuery(); });
    // nav
    $('#nav-all').addEventListener('click', () => goTo({ kind: 'all' }));
    $('#nav-trash').addEventListener('click', () => goTo({ kind: 'trash' }));
    // new notebook
    $('#new-notebook').addEventListener('click', () => {
        const nb = (0, store_1.createNotebook)('New notebook');
        (0, store_1.save)();
        renderAll();
        const main = document.querySelector('.nb-row[data-id="' + nb.id + '"] .nb-main');
        if (main)
            startRenameNotebook(main, nb);
    });
    // new note
    $('#new-note').addEventListener('click', () => handlers.onNewNote(currentNotebookForNew()));
    $('#empty-new').addEventListener('click', () => handlers.onNewNote(currentNotebookForNew()));
    // sort
    $('#sort-toggle').addEventListener('click', (e) => { e.stopPropagation(); showSortMenu($('#sort-toggle')); });
    // notes-toolbar clear
    $('#notes-tb-clear').addEventListener('click', () => { qtagFilter = null; renderAll(); });
    // global document menu close
    document.addEventListener('click', () => { });
}
function currentNotebookForNew() {
    const s = (0, store_1.getState)();
    if (s.view.kind === 'notebook')
        return s.view.notebookId || null;
    const cur = (0, store_1.findNote)(s.selectedNoteId);
    if (cur && cur.notebookId)
        return cur.notebookId;
    return s.notebooks[0]?.id || null;
}
function focusTitle() { $('#note-title').focus(); }
function trash() {
    const id = (0, editor_1.getCurrentNoteId)();
    if (!id)
        return;
    (0, store_1.trashNote)(id);
    (0, store_1.save)();
    (0, store_1.setSelectedNote)(null);
    (0, editor_1.clearNote)();
    showEditorEmpty(true);
    renderAll();
}
function deleteSelectedNote() { trash(); }
function escapeHtml(s) {
    return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
// exposed for main / editor
function getQtagFilter() { return qtagFilter; }
function setQtagFilter(t) { qtagFilter = t; (0, store_1.save)(); renderAll(); }
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"deepseek/deepseek-v4-flash-0731","provider":"openrouter","persona":"typescript","sessionId":"ef612721-439c-421f-9605-cc6a06f62b39","tokensIn":6165739,"tokensOut":81146,"tokensTotal":6246885,"cost":0.13793290200000002,"turns":83,"toolCalls":82,"failedToolCalls":0,"timestamp":"2026-08-01T16:41:25.207Z"} -->