← Notes results

Notes

Deepseek v4.1 Flash · full

Initial view of Notes

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

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

Generation prompt · exact text unavailable

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

Inspect original source 104,771 bytes · SHA-256 fa9539d7d5a2
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Notary — Notes</title>
<style>
/* ============================================================================
   1. DESIGN TOKENS
   ========================================================================== */
:root{
  --ui-font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --read-font: Georgia, "Iowan Old Style", "Palatino Linotype", "Times New Roman", serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --bg: #ebe5db;
  --sidebar-bg: #f6f1e9;
  --panel: #fffdf9;
  --panel-2: #f8f4ed;
  --hover: rgba(60,45,30,.055);
  --hover-strong: rgba(60,45,30,.095);
  --border: #e4dccf;
  --border-strong: #d2c7b5;
  --text: #2d2823;
  --text-soft: #565049;
  --muted: #8d8478;
  --accent: #c2683f;
  --accent-strong: #a4532e;
  --accent-soft: #f7e5da;
  --mark: #ffe39b;
  --code-bg: #f1ebe1;
  --shadow-sm: 0 1px 2px rgba(60,45,30,.07), 0 1px 3px rgba(60,45,30,.05);
  --shadow-md: 0 6px 20px rgba(60,45,30,.10), 0 2px 6px rgba(60,45,30,.06);
  --shadow-lg: 0 18px 48px rgba(40,30,20,.22), 0 4px 12px rgba(40,30,20,.12);
  --radius: 10px;
}
[data-theme="dark"]{
  --bg:#0f0e0d;
  --sidebar-bg:#171614;
  --panel:#232120;
  --panel-2:#1b1a18;
  --hover: rgba(255,255,255,.055);
  --hover-strong: rgba(255,255,255,.10);
  --border:#2e2b28;
  --border-strong:#3e3a34;
  --text:#e9e3d9;
  --text-soft:#c5beb3;
  --muted:#8e867b;
  --accent:#e08e64;
  --accent-strong:#f2ab82;
  --accent-soft:#33251d;
  --mark:#5d4a1c;
  --code-bg:#2a2724;
  --shadow-sm:0 1px 2px rgba(0,0,0,.45);
  --shadow-md:0 6px 20px rgba(0,0,0,.45);
  --shadow-lg:0 18px 48px rgba(0,0,0,.62);
}

/* ============================================================================
   2. BASE
   ========================================================================== */
*,*::before,*::after{box-sizing:border-box}
html,body{height:100%;margin:0}
body{
  font-family:var(--ui-font);font-size:13.5px;line-height:1.45;
  background:var(--bg);color:var(--text);overflow:hidden;
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
  text-rendering:optimizeLegibility;
}
button{font:inherit;color:inherit}
input{font:inherit;color:inherit}
svg{width:16px;height:16px;display:block;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
[hidden]{display:none!important}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:6px}
::selection{background:var(--accent-soft);color:var(--accent-strong)}
[data-theme="dark"] ::selection{background:#4a3226;color:#f6d9c5}

.scroll{overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--border-strong) transparent}
.scroll::-webkit-scrollbar{width:11px;height:11px}
.scroll::-webkit-scrollbar-thumb{background:var(--border);border:3px solid transparent;background-clip:content-box;border-radius:8px}
.scroll::-webkit-scrollbar-thumb:hover{background:var(--border-strong);background-clip:content-box}
.scroll::-webkit-scrollbar-track{background:transparent}

/* ============================================================================
   3. APP SHELL / PANES
   ========================================================================== */
.app{
  --sidebar-w: 268px;
  --list-w: 322px;
  display:flex;height:100vh;height:100dvh;overflow:hidden;background:var(--bg);
}
.sidebar{
  flex:0 0 var(--sidebar-w);width:var(--sidebar-w);overflow:hidden;
  background:var(--sidebar-bg);border-right:1px solid var(--border);
  transition:flex-basis .26s cubic-bezier(.22,.8,.24,1), width .26s cubic-bezier(.22,.8,.24,1);
}
.sidebar-inner{
  width:var(--sidebar-w);min-width:var(--sidebar-w);height:100%;
  display:flex;flex-direction:column;transition:opacity .18s ease;
}
.sidebar.collapsed{flex-basis:0;width:0;border-right-color:transparent}
.sidebar.collapsed .sidebar-inner{opacity:0}
.resizer{
  flex:0 0 6px;margin:0 -3px;z-index:5;cursor:col-resize;position:relative;background:transparent;
}
.resizer::after{
  content:'';position:absolute;top:0;bottom:0;left:2px;width:2px;background:transparent;transition:background .15s;
}
.resizer:hover::after,.resizer.dragging::after{background:var(--accent)}
.resizer.dragging{cursor:col-resize}
body.resizing{cursor:col-resize;user-select:none}

/* ============================================================================
   4. SIDEBAR
   ========================================================================== */
.brand{display:flex;align-items:center;gap:10px;padding:18px 16px 12px}
.brand-mark{
  width:28px;height:28px;border-radius:9px;flex:0 0 auto;
  background:linear-gradient(150deg,var(--accent),#8d4327);
  color:#fff;font-weight:700;font-size:15px;display:grid;place-items:center;
  font-family:var(--read-font);box-shadow:var(--shadow-sm);letter-spacing:-.02em;
}
.brand-text{min-width:0;flex:1}
.brand-name{font-weight:650;font-size:14.5px;letter-spacing:-.01em}
.brand-sub{font-size:11px;color:var(--muted);margin-top:1px}
.icon-btn{
  width:28px;height:28px;border-radius:8px;border:none;background:transparent;color:var(--muted);
  display:grid;place-items:center;cursor:pointer;transition:background .15s,color .15s;flex:0 0 auto;
}
.icon-btn:hover{background:var(--hover-strong);color:var(--text)}
.btn-new{
  margin:2px 14px 12px;height:34px;border-radius:9px;border:1px solid var(--border-strong);
  background:var(--panel);color:var(--text);display:flex;align-items:center;gap:8px;
  padding:0 11px;cursor:pointer;font-weight:520;box-shadow:var(--shadow-sm);
  transition:transform .12s,box-shadow .15s,border-color .15s,background .15s;
}
.btn-new:hover{border-color:var(--accent);color:var(--accent-strong);transform:translateY(-1px);box-shadow:var(--shadow-md)}
.btn-new:active{transform:translateY(0)}
.btn-new .kbd{margin-left:auto;font-size:10.5px;color:var(--muted);border:1px solid var(--border);border-radius:5px;padding:1px 5px;background:var(--panel-2)}
.nav-scroll{flex:1;overflow-y:auto;padding:0 10px 14px;scrollbar-width:thin;scrollbar-color:var(--border-strong) transparent}
.nav-scroll::-webkit-scrollbar{width:9px}
.nav-scroll::-webkit-scrollbar-thumb{background:var(--border);border:3px solid transparent;background-clip:content-box;border-radius:8px}
.nav-group{margin-bottom:14px}
.group-head{display:flex;align-items:center;justify-content:space-between;padding:0 6px 5px}
.group-title{font-size:10.5px;font-weight:650;letter-spacing:.09em;text-transform:uppercase;color:var(--muted)}
.mini-btn{
  width:20px;height:20px;border-radius:6px;border:none;background:transparent;color:var(--muted);
  cursor:pointer;display:grid;place-items:center;transition:background .15s,color .15s;
}
.mini-btn:hover{background:var(--hover-strong);color:var(--accent)}
.nav-item{
  display:flex;align-items:center;gap:9px;width:100%;padding:6px 8px;border-radius:8px;border:none;
  background:transparent;cursor:pointer;text-align:left;transition:background .13s,color .13s;
  color:var(--text-soft);position:relative;
}
.nav-item:hover{background:var(--hover)}
.nav-item .nav-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.nav-item .count{
  font-size:10.5px;color:var(--muted);font-variant-numeric:tabular-nums;
  padding:1px 6px;border-radius:20px;background:var(--hover-strong);
}
.nav-item.active{background:var(--accent-soft);color:var(--accent-strong);font-weight:600}
.nav-item.active .count{background:rgba(194,104,63,.18);color:var(--accent-strong)}
.nav-item.active svg{stroke-width:2}
.nav-item.drop-target{background:var(--accent-soft);box-shadow:inset 0 0 0 1.5px var(--accent)}
.nav-item svg{flex:0 0 auto;opacity:.85}
.nav-actions{display:flex;gap:2px;opacity:0;transition:opacity .12s}
.nav-item:hover .nav-actions,.nav-item.active .nav-actions{opacity:1}
.nav-actions .mini-btn{width:22px;height:22px}
.notebook-row{display:flex;align-items:center}
.caret{
  width:18px;height:18px;flex:0 0 auto;display:grid;place-items:center;border-radius:5px;border:none;
  background:transparent;color:var(--muted);cursor:pointer;transition:transform .18s ease,background .15s;
}
.caret svg{width:11px;height:11px;stroke-width:2.4}
.caret.open{transform:rotate(90deg)}
.caret:hover{background:var(--hover-strong);color:var(--text)}
.sub-notes{margin:1px 0 4px 22px;padding-left:8px;border-left:1px solid var(--border);display:flex;flex-direction:column;gap:1px}
.sub-note{
  display:block;width:100%;text-align:left;border:none;background:transparent;cursor:pointer;
  padding:4px 7px;border-radius:7px;color:var(--muted);font-size:12.5px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:background .13s,color .13s;
}
.sub-note:hover{background:var(--hover);color:var(--text)}
.sub-note.active{background:var(--panel);color:var(--accent-strong);font-weight:550;box-shadow:var(--shadow-sm)}
.sub-empty{padding:5px 7px;font-size:12px;color:var(--muted);font-style:italic}
.tag-list{display:flex;flex-wrap:wrap;gap:5px;padding:2px 6px}
.tag-pill{
  border:1px solid var(--border);background:var(--panel);border-radius:20px;padding:2.5px 9px;
  font-size:11.5px;color:var(--text-soft);cursor:pointer;display:inline-flex;align-items:center;gap:5px;
  transition:background .13s,border-color .13s,color .13s,transform .12s;
}
.tag-pill:hover{border-color:var(--accent);color:var(--accent-strong);transform:translateY(-1px)}
.tag-pill.active{background:var(--accent);border-color:var(--accent);color:#fff}
.tag-pill .tag-n{opacity:.6;font-size:10.5px;font-variant-numeric:tabular-nums}
.empty-hint{font-size:12px;color:var(--muted);padding:3px 7px;font-style:italic}
.sidebar-foot{padding:8px 10px 12px;border-top:1px solid var(--border);background:var(--sidebar-bg)}

/* ============================================================================
   5. NOTE LIST PANE
   ========================================================================== */
.list-pane{
  flex:0 0 var(--list-w);width:var(--list-w);background:var(--panel-2);
  border-right:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden;min-width:0;
}
.list-head{padding:13px 14px 9px;border-bottom:1px solid var(--border)}
.list-title-row{display:flex;align-items:center;gap:6px;margin-bottom:10px}
.list-title{font-size:14.5px;font-weight:650;letter-spacing:-.01em;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.collapse-btn{
  width:26px;height:26px;border-radius:7px;border:none;background:transparent;color:var(--muted);
  display:grid;place-items:center;cursor:pointer;transition:background .15s,color .15s;
}
.collapse-btn:hover{background:var(--hover-strong);color:var(--text)}
.collapse-btn.collapsed svg{transform:rotate(180deg)}
.search-wrap{position:relative;display:flex;align-items:center}
.search-wrap svg{position:absolute;left:9px;color:var(--muted);width:15px;height:15px;pointer-events:none}
#searchInput{
  width:100%;height:32px;border-radius:8px;border:1px solid var(--border);background:var(--panel);
  padding:0 28px 0 29px;font-size:13px;transition:border-color .15s,box-shadow .15s,background .15s;
}
#searchInput::placeholder{color:var(--muted)}
#searchInput:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft);background:var(--panel)}
.search-clear{
  position:absolute;right:5px;width:20px;height:20px;border-radius:6px;border:none;background:transparent;
  color:var(--muted);cursor:pointer;display:grid;place-items:center;
}
.search-clear:hover{background:var(--hover-strong);color:var(--text)}
.list-sub{margin-top:8px;display:flex;align-items:center;gap:8px;min-height:20px}
.result-note{font-size:11.5px;color:var(--muted);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sort-btn{
  border:1px solid transparent;background:transparent;color:var(--muted);border-radius:7px;cursor:pointer;
  height:22px;padding:0 7px;font-size:11.5px;display:inline-flex;align-items:center;gap:5px;white-space:nowrap;
  transition:background .15s,color .15s,border-color .15s;
}
.sort-btn:hover{background:var(--hover-strong);color:var(--text);border-color:var(--border)}
.sort-btn svg{width:12px;height:12px}
.note-list{flex:1;overflow-y:auto;padding:8px 9px 24px;scrollbar-width:thin;scrollbar-color:var(--border-strong) transparent}
.note-list::-webkit-scrollbar{width:11px}
.note-list::-webkit-scrollbar-thumb{background:var(--border);border:3px solid transparent;background-clip:content-box;border-radius:8px}
.note-card{
  position:relative;padding:10px 11px 10px 13px;border-radius:10px;cursor:pointer;
  border:1px solid transparent;transition:background .14s,border-color .14s,box-shadow .14s,transform .12s;
  margin-bottom:3px;
}
.note-card:hover{background:var(--hover)}
.note-card.active{background:var(--panel);border-color:var(--border);box-shadow:var(--shadow-sm)}
.note-card.active::before{
  content:'';position:absolute;left:4px;top:11px;bottom:11px;width:3px;border-radius:3px;background:var(--accent);
}
.note-card.dragging{opacity:.45}
.note-card-top{display:flex;align-items:center;gap:6px}
.note-card-title{
  font-weight:600;font-size:13.2px;letter-spacing:-.005em;flex:1;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text);
}
.note-card.active .note-card-title{color:var(--accent-strong)}
.pin-badge{color:var(--accent);flex:0 0 auto;display:grid;place-items:center}
.pin-badge svg{width:12px;height:12px;fill:currentColor;stroke-width:1.4}
.note-card-snippet{
  margin-top:3px;font-size:12.3px;color:var(--muted);line-height:1.5;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word;
}
.note-card-snippet .meta-dot{margin:0 5px;opacity:.5}
.note-card-foot{display:flex;align-items:center;gap:6px;margin-top:7px}
.note-card-time{font-size:11px;color:var(--muted);font-variant-numeric:tabular-nums}
.note-card-tags{display:flex;gap:4px;overflow:hidden;flex:1}
.mini-tag{font-size:10.5px;color:var(--muted);background:var(--hover-strong);border-radius:20px;padding:1px 6px;white-space:nowrap}
.card-menu{
  position:absolute;right:6px;top:6px;width:24px;height:24px;border-radius:7px;border:none;
  background:var(--panel);color:var(--muted);cursor:pointer;display:grid;place-items:center;opacity:0;
  box-shadow:var(--shadow-sm);transition:opacity .14s,background .14s,color .14s;
}
.note-card:hover .card-menu,.card-menu.open{opacity:1}
.card-menu:hover{color:var(--accent)}
mark{background:var(--mark);color:inherit;border-radius:3px;padding:0 1px;font-weight:550}

/* ============================================================================
   6. EDITOR PANE
   ========================================================================== */
.editor-pane{flex:1 1 auto;min-width:0;background:var(--panel);display:flex;flex-direction:column;overflow:hidden;position:relative}
.editor-wrap{display:flex;flex-direction:column;height:100%;min-height:0;animation:fade .22s ease}
@keyframes fade{from{opacity:0;transform:translateY(3px)}to{opacity:1;transform:none}}
.editor-empty{
  position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:4px;text-align:center;padding:30px;animation:fade .3s ease;color:var(--muted);
}
.empty-art{
  width:78px;height:78px;border-radius:22px;display:grid;place-items:center;margin-bottom:14px;
  background:var(--accent-soft);color:var(--accent);box-shadow:var(--shadow-sm);
}
.empty-art svg{width:34px;height:34px;stroke-width:1.5}
.editor-empty h2{margin:0;font-size:17px;font-weight:640;color:var(--text);letter-spacing:-.01em;font-family:var(--read-font)}
.editor-empty p{margin:6px 0 18px;font-size:13px;max-width:290px;line-height:1.6}
.btn-primary{
  height:34px;padding:0 15px;border-radius:9px;border:none;background:var(--accent);color:#fff;
  font-weight:560;cursor:pointer;display:inline-flex;align-items:center;gap:7px;box-shadow:var(--shadow-sm);
  transition:background .15s,transform .12s,box-shadow .15s;
}
.btn-primary:hover{background:var(--accent-strong);transform:translateY(-1px);box-shadow:var(--shadow-md)}
.btn-primary:active{transform:none}
.editor-head{padding:20px max(34px,calc(50% - 360px)) 0;border-bottom:1px solid var(--border)}
.title-input{
  width:100%;border:none;background:transparent;font-family:var(--read-font);font-size:26px;font-weight:700;
  letter-spacing:-.018em;padding:0 0 6px;color:var(--text);
}
.title-input::placeholder{color:var(--border-strong);font-weight:600}
.title-input:focus{outline:none}
.trash-bar{
  display:flex;align-items:center;gap:8px;margin-bottom:13px;padding:6px 8px 6px 10px;
  background:var(--accent-soft);border:1px solid rgba(194,104,63,.32);border-radius:9px;
  font-size:12px;color:var(--accent-strong);animation:fade .2s ease;
}
.trash-bar svg{width:14px;height:14px;flex:0 0 auto}
.trash-bar button{
  height:23px;padding:0 9px;border-radius:7px;border:1px solid rgba(194,104,63,.4);
  background:var(--panel);color:var(--accent-strong);cursor:pointer;font-size:11.5px;font-weight:530;
  transition:background .14s,border-color .14s;
}
.trash-bar button:hover{background:#fff;border-color:var(--accent)}
[data-theme="dark"] .trash-bar button:hover{background:#2c2320}
.trash-bar button.danger{color:#c0392b;border-color:rgba(192,57,43,.35)}
.trash-bar button.danger:hover{background:rgba(192,57,43,.08)}
.trash-bar .flex-spacer{flex:1}
.meta-row{display:flex;align-items:center;gap:7px;padding:2px 0 12px;flex-wrap:wrap;min-height:28px}
.meta-chip{
  height:23px;padding:0 9px;border-radius:20px;border:1px solid var(--border);background:var(--panel-2);
  color:var(--text-soft);font-size:11.5px;cursor:pointer;display:inline-flex;align-items:center;gap:5px;
  transition:background .14s,border-color .14s,color .14s;max-width:220px;
}
.meta-chip span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.meta-chip:hover{border-color:var(--accent);color:var(--accent-strong);background:var(--accent-soft)}
.meta-chip.ghost{border-style:dashed;color:var(--muted);background:transparent}
.meta-chip svg{width:12px;height:12px}
.tags-inline{display:flex;gap:5px;flex-wrap:wrap}
.tag-chip{
  height:23px;padding:0 6px 0 9px;border-radius:20px;background:var(--accent-soft);color:var(--accent-strong);
  font-size:11.5px;display:inline-flex;align-items:center;gap:4px;font-weight:520;
}
.tag-chip button{border:none;background:transparent;color:inherit;cursor:pointer;opacity:.5;width:15px;height:15px;border-radius:50%;display:grid;place-items:center;font-size:13px;line-height:1;padding:0}
.tag-chip button:hover{opacity:1;background:rgba(194,104,63,.2)}
.flex-spacer{flex:1}
.save-state{
  font-size:11.5px;color:var(--muted);display:inline-flex;align-items:center;gap:5px;
  transition:opacity .4s,color .2s;font-variant-numeric:tabular-nums;
}
.save-state svg{width:12px;height:12px;color:var(--accent);transition:transform .2s}
.save-state.saving svg{transform:scale(.7);opacity:.5}
.toolbar{
  display:flex;align-items:center;gap:2px;padding:7px max(32px,calc(50% - 362px));border-bottom:1px solid var(--border);
  background:var(--panel-2);flex-wrap:wrap;position:relative;z-index:3;
}
.tb{
  height:29px;min-width:29px;padding:0 7px;border-radius:7px;border:1px solid transparent;background:transparent;
  color:var(--text-soft);font-size:12.5px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;
  transition:background .13s,color .13s,border-color .13s;font-weight:520;
}
.tb:hover{background:var(--hover-strong);color:var(--text)}
.tb:active{transform:scale(.94)}
.tb.active{background:var(--accent-soft);color:var(--accent-strong);border-color:rgba(194,104,63,.28)}
.tb[disabled]{opacity:.35;cursor:default}
.tb[disabled]:hover{background:transparent;color:var(--text-soft)}
.tb-sep{width:1px;height:17px;background:var(--border);margin:0 5px}
.editor-scroll{flex:1;overflow-y:auto;padding:26px 34px 0;scrollbar-width:thin;scrollbar-color:var(--border-strong) transparent}
.editor-scroll::-webkit-scrollbar{width:12px}
.editor-scroll::-webkit-scrollbar-thumb{background:var(--border);border:4px solid transparent;background-clip:content-box;border-radius:8px}
.editor{
  max-width:720px;margin:0 auto;font-family:var(--read-font);font-size:17.5px;line-height:1.72;
  color:var(--text);outline:none;min-height:56vh;padding-bottom:12px;word-break:break-word;
}
.editor:empty::before{content:attr(data-placeholder);color:var(--border-strong);pointer-events:none;font-style:italic}
.editor p{margin:0 0 .62em}
.editor div{margin:0 0 .5em}
.editor h1,.editor h2,.editor h3{font-weight:700;letter-spacing:-.018em;line-height:1.25;margin:1.15em 0 .38em}
.editor h1{font-size:1.72em}
.editor h2{font-size:1.4em}
.editor h3{font-size:1.16em}
.editor h1:first-child,.editor h2:first-child,.editor h3:first-child{margin-top:.1em}
.editor h1:empty::after,.editor h2:empty::after,.editor h3:empty::after,
.editor blockquote:empty::after{content:'\200b'}
.editor li:empty::after,.editor p:empty::after{content:'\200b'}
.editor ul,.editor ol{margin:.45em 0 .8em;padding-left:1.5em}
.editor ul{list-style:disc}
.editor ol{list-style:decimal}
.editor li{margin:.2em 0}
.editor li::marker{color:var(--accent);font-weight:600}
.editor blockquote{
  margin:.85em 0;padding:.15em 0 .15em 17px;border-left:3px solid var(--accent);
  color:var(--text-soft);font-style:italic;
}
.editor code{
  font-family:var(--mono);font-size:.85em;background:var(--code-bg);
  padding:.13em .38em;border-radius:5px;border:1px solid var(--border);white-space:pre-wrap;
}
.editor hr{border:none;border-top:1.5px solid var(--border);margin:1.6em 0}
.editor ul.checklist{list-style:none;padding-left:2px;margin:.4em 0 .7em}
.editor li.chk{display:flex;align-items:flex-start;gap:9px;margin:.18em 0}
.chk-box{
  width:17px;height:17px;flex:0 0 auto;margin-top:.34em;border:1.6px solid var(--border-strong);
  border-radius:5px;cursor:pointer;position:relative;background:var(--panel);
  transition:background .16s,border-color .16s,transform .12s;
}
.chk-box:hover{border-color:var(--accent);transform:scale(1.08)}
li.chk[data-checked="true"] .chk-box{background:var(--accent);border-color:var(--accent)}
li.chk[data-checked="true"] .chk-box::after{
  content:'';position:absolute;left:4.6px;top:1.3px;width:5px;height:9px;
  border:solid #fff;border-width:0 2.1px 2.1px 0;transform:rotate(45deg);
}
.chk-text{flex:1;min-width:0;transition:color .18s}
.chk-text:empty::after{content:'\200b'}
li.chk[data-checked="true"] .chk-text{color:var(--muted);text-decoration:line-through;text-decoration-thickness:1px}
.editor a{color:var(--accent-strong)}
.editor-foot{
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:7px max(34px,calc(50% - 360px));border-top:1px solid var(--border);background:var(--panel);
  font-size:11.5px;color:var(--muted);font-variant-numeric:tabular-nums;
}

/* ============================================================================
   7. MENUS / MODALS / TOAST
   ========================================================================== */
.menu{
  position:fixed;z-index:60;min-width:196px;background:var(--panel);border:1px solid var(--border);
  border-radius:11px;box-shadow:var(--shadow-lg);padding:5px;animation:pop .13s cubic-bezier(.2,.9,.3,1.2);
  transform-origin:top left;
}
@keyframes pop{from{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:none}}
.menu-label{font-size:10px;font-weight:650;letter-spacing:.09em;text-transform:uppercase;color:var(--muted);padding:7px 9px 4px}
.menu-item{
  display:flex;align-items:center;gap:9px;width:100%;padding:7px 9px;border-radius:8px;border:none;
  background:transparent;cursor:pointer;text-align:left;font-size:13px;color:var(--text);transition:background .12s,color .12s;
}
.menu-item:hover{background:var(--hover-strong)}
.menu-item.danger{color:#c0392b}
[data-theme="dark"] .menu-item.danger{color:#e88a7d}
.menu-item.danger:hover{background:rgba(192,57,43,.1)}
.menu-item .menu-check{width:15px;display:grid;place-items:center;flex:0 0 auto}
.menu-item .menu-key{margin-left:auto;font-size:11px;color:var(--muted)}
.menu-sep{height:1px;background:var(--border);margin:5px 3px}
.backdrop{
  position:fixed;inset:0;background:rgba(30,22,14,.32);backdrop-filter:blur(2px);z-index:70;
  display:grid;place-items:center;animation:fade .16s ease;padding:20px;
}
[data-theme="dark"] .backdrop{background:rgba(0,0,0,.55)}
.modal{
  width:min(430px,100%);background:var(--panel);border:1px solid var(--border);border-radius:15px;
  box-shadow:var(--shadow-lg);padding:20px 20px 16px;animation:pop .18s cubic-bezier(.2,.9,.3,1.1);
}
.modal h3{margin:0 0 6px;font-size:16px;font-weight:660;letter-spacing:-.01em}
.modal p{margin:0 0 15px;font-size:13px;color:var(--text-soft);line-height:1.55}
.modal-input{
  width:100%;height:36px;border-radius:9px;border:1px solid var(--border-strong);background:var(--panel-2);
  padding:0 11px;margin-bottom:15px;font-size:13.5px;
}
.modal-input:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.modal-actions{display:flex;justify-content:flex-end;gap:8px}
.btn{
  height:33px;padding:0 14px;border-radius:9px;border:1px solid var(--border-strong);background:var(--panel-2);
  cursor:pointer;font-weight:530;transition:background .14s,border-color .14s,transform .12s;
}
.btn:hover{background:var(--hover-strong)}
.btn.primary{background:var(--accent);border-color:var(--accent);color:#fff}
.btn.primary:hover{background:var(--accent-strong);border-color:var(--accent-strong)}
.btn.danger{background:#c0392b;border-color:#c0392b;color:#fff}
.btn.danger:hover{background:#a93226;border-color:#a93226}
.toast{
  position:fixed;left:50%;bottom:26px;transform:translateX(-50%);z-index:90;
  background:var(--text);color:var(--panel);border-radius:10px;padding:9px 15px;font-size:12.5px;
  box-shadow:var(--shadow-lg);display:flex;align-items:center;gap:10px;animation:rise .22s cubic-bezier(.2,.9,.3,1.2);
}
@keyframes rise{from{opacity:0;transform:translate(-50%,10px)}to{opacity:1;transform:translate(-50%,0)}}
.toast button{background:transparent;border:none;color:var(--accent);cursor:pointer;font-weight:620;font-size:12.5px;padding:0}
.drag-ghost{position:fixed;pointer-events:none;z-index:100}

@media (max-width:860px){
  .app{--sidebar-w:230px}
  .editor-head,.toolbar,.editor-scroll,.editor-foot{padding-left:18px;padding-right:18px}
}
@media (max-width:700px){
  .app{--list-w:140px}
  .list-pane{flex:0 0 140px}
  .editor-head,.toolbar,.editor-scroll,.editor-foot{padding-left:12px;padding-right:12px}
  .editor{font-size:15.5px}
  .title-input{font-size:20px}
  .brand-sub,.btn-new .kbd{display:none}
}
</style>
</head>
<body>
<div class="app" id="app">

  <!-- ===================== SIDEBAR ===================== -->
  <aside class="sidebar" id="sidebar" aria-label="Notebooks">
    <div class="sidebar-inner" id="sidebarInner">
      <header class="brand">
        <div class="brand-mark">N</div>
        <div class="brand-text">
          <div class="brand-name">Notary</div>
          <div class="brand-sub">Notes, nicely kept</div>
        </div>
        <button class="icon-btn" id="themeToggle" title="Toggle theme (dark / light)" aria-label="Toggle theme"></button>
      </header>

      <button class="btn-new" id="newNoteBtn">
        <svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>
        New note
        <span class="kbd">⌘N</span>
      </button>

      <div class="nav-scroll" id="navScroll">
        <div class="nav-group">
          <button class="nav-item" data-nav="all">
            <svg viewBox="0 0 24 24"><path d="M4 5.5A1.5 1.5 0 0 1 5.5 4H10l2 2h6.5A1.5 1.5 0 0 1 20 7.5v11A1.5 1.5 0 0 1 18.5 20h-13A1.5 1.5 0 0 1 4 18.5z"/></svg>
            <span class="nav-label">All notes</span>
            <span class="count" id="countAll">0</span>
          </button>
        </div>

        <div class="nav-group">
          <div class="group-head">
            <span class="group-title">Notebooks</span>
            <button class="mini-btn" id="newNotebookBtn" title="New notebook" aria-label="New notebook">
              <svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>
            </button>
          </div>
          <div id="notebookList"></div>
        </div>

        <div class="nav-group" id="tagsGroup">
          <div class="group-head"><span class="group-title">Tags</span></div>
          <div class="tag-list" id="tagList"></div>
        </div>
      </div>

      <footer class="sidebar-foot">
        <button class="nav-item" data-nav="trash">
          <svg viewBox="0 0 24 24"><path d="M4 7h16M9.5 7V5.2A1.2 1.2 0 0 1 10.7 4h2.6a1.2 1.2 0 0 1 1.2 1.2V7M6.5 7l.8 11.4A1.6 1.6 0 0 0 8.9 20h6.2a1.6 1.6 0 0 0 1.6-1.6L17.5 7"/></svg>
          <span class="nav-label">Trash</span>
          <span class="count" id="countTrash">0</span>
        </button>
      </footer>
    </div>
  </aside>

  <div class="resizer" id="rz1" title="Drag to resize sidebar"></div>

  <!-- ===================== NOTE LIST ===================== -->
  <section class="list-pane" id="listPane" aria-label="Note list">
    <div class="list-head">
      <div class="list-title-row">
        <button class="collapse-btn" id="collapseBtn" title="Collapse sidebar" aria-label="Collapse sidebar">
          <svg viewBox="0 0 24 24"><path d="M15 6l-6 6 6 6"/><path d="M4 4v16"/></svg>
        </button>
        <div class="list-title" id="listTitle">All notes</div>
        <button class="icon-btn" id="listNewBtn" title="New note">
          <svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>
        </button>
      </div>
      <div class="search-wrap">
        <svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="6.5"/><path d="M20 20l-4-4"/></svg>
        <input id="searchInput" type="text" placeholder="Search all notes…" autocomplete="off" spellcheck="false">
        <button class="search-clear" id="searchClear" title="Clear search" hidden>
          <svg viewBox="0 0 24 24"><path d="M6 6l12 12M18 6L6 18"/></svg>
        </button>
      </div>
      <div class="list-sub">
        <span class="result-note" id="resultNote"></span>
        <button class="sort-btn" id="sortBtn" title="Change sort order"></button>
      </div>
    </div>
    <div class="note-list" id="noteList"></div>
  </section>

  <div class="resizer" id="rz2" title="Drag to resize note list"></div>

  <!-- ===================== EDITOR ===================== -->
  <section class="editor-pane" id="editorPane" aria-label="Editor">

    <div class="editor-empty" id="editorEmpty">
      <div class="empty-art">
        <svg viewBox="0 0 24 24"><path d="M6 4.5h7.5L18 9v10.5H6z"/><path d="M13.5 4.5V9H18"/><path d="M9 13h6M9 16.2h3.6"/></svg>
      </div>
      <h2>Select or create a note</h2>
      <p>Choose a note from the list, or start something new. Everything saves as you type.</p>
      <button class="btn-primary" id="emptyNewBtn">
        <svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>
        New note
      </button>
    </div>

    <div class="editor-wrap" id="editorWrap" hidden>
      <header class="editor-head">
        <div class="trash-bar" id="trashBar" hidden>
          <svg viewBox="0 0 24 24"><path d="M4 7h16M9.5 7V5.2A1.2 1.2 0 0 1 10.7 4h2.6a1.2 1.2 0 0 1 1.2 1.2V7M6.5 7l.8 11.4A1.6 1.6 0 0 0 8.9 20h6.2a1.6 1.6 0 0 0 1.6-1.6L17.5 7"/></svg>
          <span>This note is in the Trash.</span>
          <span class="flex-spacer"></span>
          <button id="restoreBtn">Restore</button>
          <button id="purgeBtn" class="danger">Delete forever</button>
        </div>
        <input id="titleInput" class="title-input" placeholder="Untitled note" autocomplete="off" spellcheck="false">
        <div class="meta-row">
          <button class="meta-chip" id="notebookChip" title="Move to notebook"></button>
          <span id="tagChips" class="tags-inline"></span>
          <button class="meta-chip ghost" id="addTagBtn">
            <svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>
            <span>Tag</span>
          </button>
          <div class="flex-spacer"></div>
          <span class="save-state" id="saveState">
            <svg viewBox="0 0 24 24"><path d="M5 13l4 4L19 7"/></svg>
            <span id="saveStateText">Saved</span>
          </span>
          <button class="icon-btn" id="noteMenuBtn" title="Note options">
            <svg viewBox="0 0 24 24"><circle cx="5.5" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="18.5" cy="12" r="1.4" fill="currentColor" stroke="none"/></svg>
          </button>
        </div>
      </header>

      <div class="toolbar" id="toolbar">
        <button class="tb" data-cmd="undo" title="Undo (⌘Z)"><svg viewBox="0 0 24 24"><path d="M9 14L4 9l5-5"/><path d="M4 9h9a6 6 0 0 1 0 12h-3"/></svg></button>
        <button class="tb" data-cmd="redo" title="Redo (⌘⇧Z)"><svg viewBox="0 0 24 24"><path d="M15 14l5-5-5-5"/><path d="M20 9h-9a6 6 0 0 0 0 12h3"/></svg></button>
        <span class="tb-sep"></span>
        <button class="tb" data-cmd="bold" title="Bold (⌘B)"><b>B</b></button>
        <button class="tb" data-cmd="italic" title="Italic (⌘I)"><i>I</i></button>
        <button class="tb" data-cmd="underline" title="Underline (⌘U)"><u>U</u></button>
        <button class="tb" data-cmd="strikeThrough" title="Strikethrough"><s>S</s></button>
        <button class="tb" data-cmd="code" title="Inline code (⌘E)"><span style="font-family:var(--mono);font-size:11.5px">&lt;/&gt;</span></button>
        <span class="tb-sep"></span>
        <button class="tb" data-cmd="h1" title="Heading 1">H1</button>
        <button class="tb" data-cmd="h2" title="Heading 2">H2</button>
        <button class="tb" data-cmd="h3" title="Heading 3">H3</button>
        <span class="tb-sep"></span>
        <button class="tb" data-cmd="ul" title="Bulleted list"><svg viewBox="0 0 24 24"><path d="M9 6.5h11M9 12h11M9 17.5h11"/><circle cx="4.6" cy="6.5" r="1.4" fill="currentColor" stroke="none"/><circle cx="4.6" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="4.6" cy="17.5" r="1.4" fill="currentColor" stroke="none"/></svg></button>
        <button class="tb" data-cmd="ol" title="Numbered list"><svg viewBox="0 0 24 24"><path d="M9.5 6.5h11M9.5 12h11M9.5 17.5h11"/><path d="M3.6 4.8h1.3v3.1M3.4 11.2c.9-.9 2.1-.5 2.1.4 0 .9-2 1.3-2 2.2h2.2M3.5 16.4c1.4 0 2 .7 2 1.4 0 .9-.8 1.5-1.6 1.4M4 19.2c1 0 1.7.5 1.7 1.3"/></svg></button>
        <button class="tb" data-cmd="checklist" title="Checklist item"><svg viewBox="0 0 24 24"><rect x="3.4" y="3.6" width="7" height="7" rx="2"/><path d="M5.2 7.1l1.4 1.4 2.3-2.7"/><path d="M13.5 7h7M13.5 12h7M13.5 17h7"/><path d="M5.4 14.6h4.2M7.5 12.5v4.2"/></svg></button>
        <span class="tb-sep"></span>
        <button class="tb" data-cmd="blockquote" title="Blockquote"><svg viewBox="0 0 24 24"><path d="M5 5v14"/><path d="M10.5 8h8M10.5 12h8M10.5 16h5"/></svg></button>
        <button class="tb" data-cmd="hr" title="Divider"><svg viewBox="0 0 24 24"><path d="M4 12h16"/></svg></button>
      </div>

      <div class="editor-scroll" id="editorScroll">
        <div class="editor" id="editor" contenteditable="true" spellcheck="true" data-placeholder="Start writing…"></div>
      </div>

      <footer class="editor-foot">
        <span id="wordCount">0 words</span>
        <span id="footDate"></span>
      </footer>
    </div>
  </section>
</div>
<script>
"use strict";
/* ============================================================================
   1. TUNABLE CONSTANTS
   ========================================================================== */
const CFG = {
  STORAGE_KEY:      'notary.notes.v1',
  AUTOSAVE_MS:      450,      // debounce before the data model is written to localStorage
  HISTORY_COMMIT_MS:450,      // typing burst window for undo grouping
  HISTORY_LIMIT:    150,
  SNIPPET_LEN:      150,      // characters of body text shown in the note list
  MIN_SIDEBAR_W:    186,
  MAX_SIDEBAR_W:    440,
  MIN_LIST_W:       232,
  MAX_LIST_W:       560,
  MIN_EDITOR_W:     400,
  COLLAPSE_AT:      720,      // viewport width under which the sidebar auto-collapses
  SORT: {
    modified: { key: 'modified', label: 'Last modified' },
    created:  { key: 'created',  label: 'Date created' },
    alpha:    { key: 'alpha',    label: 'Alphabetical'  }
  },
  DEFAULT_NOTEBOOK: 'My Notebook'
};

/* ============================================================================
   2. ICONS (inline SVG, feather-ish)
   ========================================================================== */
const ICONS = {
  notebook: '<svg viewBox="0 0 24 24"><path d="M6 4.5A1.5 1.5 0 0 1 7.5 3H17a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H7.5A1.5 1.5 0 0 1 6 19.5z"/><path d="M6 17.5h12M9.5 3v18"/></svg>',
  chevron:  '<svg viewBox="0 0 24 24"><path d="M9 5l7 7-7 7"/></svg>',
  pin:      '<svg viewBox="0 0 24 24"><path d="M9 4h6l-.7 4.2 2.7 2.6H7l2.7-2.6z"/><path d="M12 10.8V20"/></svg>',
  more:     '<svg viewBox="0 0 24 24"><circle cx="5.5" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="18.5" cy="12" r="1.4" fill="currentColor" stroke="none"/></svg>',
  plus:     '<svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>',
  pencil:   '<svg viewBox="0 0 24 24"><path d="M16.5 4.6l2.9 2.9L8.4 18.4l-3.4.5.5-3.4z"/></svg>',
  copy:     '<svg viewBox="0 0 24 24"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M15 6.5V5.8A1.8 1.8 0 0 0 13.2 4H5.8A1.8 1.8 0 0 0 4 5.8v7.4A1.8 1.8 0 0 0 5.8 15h.7"/></svg>',
  trash:    '<svg viewBox="0 0 24 24"><path d="M4 7h16M9.5 7V5.2A1.2 1.2 0 0 1 10.7 4h2.6a1.2 1.2 0 0 1 1.2 1.2V7M6.5 7l.8 11.4A1.6 1.6 0 0 0 8.9 20h6.2a1.6 1.6 0 0 0 1.6-1.6L17.5 7"/></svg>',
  restore:  '<svg viewBox="0 0 24 24"><path d="M4 10a8 8 0 1 1 2.3 5.6"/><path d="M4 4.5V10h5.5"/></svg>',
  folder:   '<svg viewBox="0 0 24 24"><path d="M4 6.5A1.5 1.5 0 0 1 5.5 5H10l2 2h6.5A1.5 1.5 0 0 1 20 8.5v9A1.5 1.5 0 0 1 18.5 19h-13A1.5 1.5 0 0 1 4 17.5z"/></svg>',
  arrowR:   '<svg viewBox="0 0 24 24"><path d="M5 12h14M13 6l6 6-6 6"/></svg>',
  check:    '<svg viewBox="0 0 24 24"><path d="M5 13l4 4L19 7"/></svg>',
  undo:     '<svg viewBox="0 0 24 24"><path d="M9 14L4 9l5-5"/><path d="M4 9h9a6 6 0 0 1 0 12h-3"/></svg>',
  sun:      '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="4.2"/><path d="M12 2.6v2.2M12 19.2v2.2M4.2 12H2M22 12h-2.2M5.8 5.8L4.2 4.2M19.8 19.8l-1.6-1.6M18.2 5.8l1.6-1.6M4.2 19.8l1.6-1.6"/></svg>',
  moon:     '<svg viewBox="0 0 24 24"><path d="M20 14.2A8.2 8.2 0 0 1 9.8 4a8.4 8.4 0 1 0 10.2 10.2z"/></svg>',
  divider:  '<svg viewBox="0 0 24 24"><path d="M4 12h16"/></svg>',
  sortAsc:  '<svg viewBox="0 0 24 24"><path d="M7 20V5M7 5l-3 3M7 5l3 3M13 7h7M13 12h5M13 17h3"/></svg>'
};

/* ============================================================================
   3. UTILITIES
   ========================================================================== */
const $  = (sel, root) => (root || document).querySelector(sel);
const $$ = (sel, root) => Array.prototype.slice.call((root || document).querySelectorAll(sel));

function uid(prefix) {
  return (prefix || 'id') + '_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
}
function debounce(fn, ms) {
  let t = null;
  const wrapped = function () {
    const args = arguments;
    clearTimeout(t);
    t = setTimeout(() => { t = null; fn.apply(null, args); }, ms);
  };
  wrapped.flush = function () { if (t) { clearTimeout(t); t = null; fn.apply(null, arguments); } };
  wrapped.cancel = function () { clearTimeout(t); t = null; };
  return wrapped;
}
function escapeHtml(s) {
  return String(s == null ? '' : s).replace(/[&<>"']/g, c => (
    { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]
  ));
}
function escapeRegExp(s) { return String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }

/* Human-friendly timestamps --------------------------------------------------*/
function formatStamp(ts) {
  if (!ts) return '';
  const d = new Date(ts), now = new Date();
  const sameDay = d.toDateString() === now.toDateString();
  const yest = new Date(now); yest.setDate(now.getDate() - 1);
  if (sameDay) return d.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
  if (d.toDateString() === yest.toDateString()) return 'Yesterday';
  if (d.getFullYear() === now.getFullYear()) return d.toLocaleDateString([], { month: 'short', day: 'numeric' });
  return d.toLocaleDateString([], { month: 'short', day: 'numeric', year: 'numeric' });
}
function formatFull(ts) {
  if (!ts) return '';
  return new Date(ts).toLocaleString([], { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit' });
}

/* HTML -> plain text (single source of truth for search + snippets) ----------*/
const TEXT_BLOCKS = 'p,div,h1,h2,h3,li,blockquote,tr';
function htmlToText(html, opts) {
  const o = opts || {};
  const doc = new DOMParser().parseFromString(String(html == null ? '' : html), 'text/html');
  doc.querySelectorAll('br').forEach(br => br.replaceWith(doc.createTextNode('\n')));
  doc.querySelectorAll('hr').forEach(hr => hr.replaceWith(doc.createTextNode(' ')));
  doc.querySelectorAll(TEXT_BLOCKS).forEach(el => {
    if (el.lastChild && el.lastChild.nodeType === 1 && el.lastChild.tagName === 'BR') el.lastChild.remove();
    el.appendChild(doc.createTextNode('\n'));
  });
  let text = (doc.body.textContent || '').replace(/\u00a0/g, ' ').replace(/\u200b/g, '');
  text = text.replace(/[ \t]*\n[ \t]*/g, '\n').replace(/\n{3,}/g, '\n\n');
  return o.keepNewlines ? text.trim() : text.replace(/\s+/g, ' ').trim();
}

/* HTML sanitizer: keeps only the supported formatting subset ----------------*/
const ALLOWED_TAGS = {
  B: 1, STRONG: 1, I: 1, EM: 1, U: 1, S: 1, STRIKE: 1, DEL: 1, CODE: 1,
  P: 1, DIV: 1, BR: 1, H1: 1, H2: 1, H3: 1,
  UL: 1, OL: 1, LI: 1, BLOCKQUOTE: 1, HR: 1, SPAN: 1, MARK: 1, A: 1
};
const FORBIDDEN_TAGS = { SCRIPT: 1, STYLE: 1, IFRAME: 1, OBJECT: 1, EMBED: 1, LINK: 1, META: 1, TITLE: 1, NOSCRIPT: 1, TEMPLATE: 1, FORM: 1, INPUT: 1, SVG: 1, IMG: 1, VIDEO: 1, AUDIO: 1, HEAD: 1 };
const TAG_REMAP = { STRONG: 'b', EM: 'i', STRIKE: 's', DEL: 's', MARK: 'span', B: 'b', I: 'i', U: 'u', S: 's' };

function sanitizeNode(node, doc) {
  if (node.nodeType === 3) return doc.createTextNode(node.nodeValue);
  if (node.nodeType !== 1) return doc.createDocumentFragment();
  const tag = node.tagName;
  if (FORBIDDEN_TAGS[tag]) return doc.createDocumentFragment();
  const frag = doc.createDocumentFragment();
  Array.prototype.forEach.call(node.childNodes, child => frag.appendChild(sanitizeNode(child, doc)));
  if (!ALLOWED_TAGS[tag]) return frag;                       // unwrap unknown wrappers
  const out = doc.createElement(TAG_REMAP[tag] || tag.toLowerCase());
  /* checklist spans are the only permitted structural classes */
  const cls = node.getAttribute('class') || '';
  if (tag === 'SPAN' && /(^|\s)chk-(box|text)(\s|$)/.test(cls)) {
    const isBox = /(^|\s)chk-box(\s|$)/.test(cls);
    out.setAttribute('class', isBox ? 'chk-box' : 'chk-text');
    if (isBox) out.setAttribute('contenteditable', 'false');
  }
  if (tag === 'A') {
    const href = (node.getAttribute('href') || '').trim();
    if (/^https?:|^mailto:/i.test(href)) out.setAttribute('href', href);
  }
  out.appendChild(frag);
  return out;
}
function sanitizeHtml(html) {
  const doc = new DOMParser().parseFromString(String(html == null ? '' : html), 'text/html');
  const holder = doc.createElement('div');
  Array.prototype.forEach.call(doc.body.childNodes, n => holder.appendChild(sanitizeNode(n, doc)));
  return holder.innerHTML;
}
function textToHtml(text) {
  return escapeHtml(text).split(/\n{2,}/).map(block =>
    '<p>' + block.split('\n').map(l => escapeHtml(l)).join('<br>') + '</p>'
  ).join('');
}

/* ============================================================================
   4. DATA MODEL  (single source of truth — the DOM is only a projection)
   ========================================================================== */
/* notebook: { id, name, createdAt }
   note:     { id, notebookId, title, html, tags[], pinned, trashed, trashedFrom,
               createdAt, updatedAt }                                             */
let state = {
  notebooks: [],
  notes: [],
  ui: {
    view: 'all',            // 'all' | 'trash' | <notebookId>
    tagFilter: null,
    selectedNoteId: null,
    search: '',
    sort: 'modified',
    theme: 'light',
    sidebarW: CFG.MIN_SIDEBAR_W + 82,
    listW: 322,
    sidebarCollapsed: false,
    expanded: {}
  }
};

/* --- persistence -----------------------------------------------------------*/
const persist = debounce(() => {
  try {
    localStorage.setItem(CFG.STORAGE_KEY, JSON.stringify({
      v: 1, notebooks: state.notebooks, notes: state.notes, ui: state.ui
    }));
    setSaveState('saved');
  } catch (e) {
    console.warn('Save failed', e);
    setSaveState('error');
  }
}, CFG.AUTOSAVE_MS);

function saveNow() {
  persist.cancel();
  try {
    localStorage.setItem(CFG.STORAGE_KEY, JSON.stringify({
      v: 1, notebooks: state.notebooks, notes: state.notes, ui: state.ui
    }));
    setSaveState('saved');
  } catch (e) { /* ignore */ }
}
function loadState() {
  let raw = null;
  try { raw = localStorage.getItem(CFG.STORAGE_KEY); } catch (e) { raw = null; }
  if (raw) {
    try {
      const data = JSON.parse(raw);
      if (data && Array.isArray(data.notebooks) && Array.isArray(data.notes)) {
        state.notebooks = data.notebooks.map(nb => ({ id: nb.id, name: nb.name, createdAt: nb.createdAt || Date.now() }));
        state.notes = data.notes.map(n => ({
          id: n.id || uid('note'),
          notebookId: n.notebookId,
          title: typeof n.title === 'string' ? n.title : '',
          html: typeof n.html === 'string' ? n.html : '',
          tags: Array.isArray(n.tags) ? n.tags.filter(t => typeof t === 'string') : [],
          pinned: !!n.pinned, trashed: !!n.trashed,
          trashedFrom: n.trashedFrom || null,
          createdAt: n.createdAt || Date.now(),
          updatedAt: n.updatedAt || n.createdAt || Date.now()
        }));
        Object.assign(state.ui, data.ui || {});
        /* repair notes orphaned by a deleted/unknown notebook */
        const ids = {};
        state.notebooks.forEach(nb => { ids[nb.id] = true; });
        state.notes.forEach(n => { if (!ids[n.notebookId]) n.notebookId = state.notebooks[0].id; });
        return true;
      }
    } catch (e) { console.warn('Corrupt save, starting fresh', e); }
  }
  seed();
  return false;
}

/* --- seed content ----------------------------------------------------------*/
function seed() {
  const nbId = uid('nb');
  state.notebooks = [
    { id: nbId, name: CFG.DEFAULT_NOTEBOOK, createdAt: Date.now() }
  ];
  const ideasId = uid('nb');
  state.notebooks.push({ id: ideasId, name: 'Ideas', createdAt: Date.now() });
  const now = Date.now();
  state.notes = [
    {
      id: uid('note'), notebookId: nbId, title: 'Welcome to Notary',
      html: '<p>Notary is a quiet place to keep your notes. Everything you type is <b>saved as you go</b> — no save button, no ceremony.</p>' +
            '<h2>Try a few things</h2>' +
            '<ul class="checklist">' +
            '<li class="chk" data-checked="true"><span class="chk-box" contenteditable="false"></span><span class="chk-text">Click a checkbox to toggle it</span></li>' +
            '<li class="chk" data-checked="false"><span class="chk-box" contenteditable="false"></span><span class="chk-text">Type <code># </code> for a heading, <code>- </code> for a list, <code>[] </code> for a task</span></li>' +
            '<li class="chk" data-checked="false"><span class="chk-box" contenteditable="false"></span><span class="chk-text">Drag a note onto a notebook to move it</span></li>' +
            '</ul>' +
            '<blockquote>Search finds words inside your formatted notes — even the <i>italic</i> ones.</blockquote>' +
            '<p>Use <code>⌘B</code>, <code>⌘I</code> and <code>⌘U</code> for quick formatting, or reach for the toolbar above.</p>',
      tags: ['getting started'], pinned: true, trashed: false, trashedFrom: null,
      createdAt: now, updatedAt: now
    },
    {
      id: uid('note'), notebookId: ideasId, title: 'Reading list',
      html: '<p>Books worth the shelf space:</p><ul><li>The Shape of Design — Frank Chimero</li>' +
            '<li><i>Thinking in Systems</i> — Donella Meadows</li><li>Several Short Sentences About Writing</li></ul>',
      tags: ['reading'], pinned: false, trashed: false, trashedFrom: null,
      createdAt: now - 86400000, updatedAt: now - 43200000
    }
  ];
  state.ui.selectedNoteId = state.notes[0].id;
}
</script>
<script>
"use strict";
/* ============================================================================
   5. MODEL SELECTORS  (derived, read-only views of `state`)
   ========================================================================== */
function getNote(id) { return state.notes.find(n => n.id === id) || null; }
function getNotebook(id) { return state.notebooks.find(nb => nb.id === id) || null; }
function notebookName(id) { const nb = getNotebook(id); return nb ? nb.name : 'Notebook'; }
function allTags() {
  const map = new Map();
  state.notes.forEach(n => {
    if (n.trashed) return;
    n.tags.forEach(t => map.set(t, (map.get(t) || 0) + 1));
  });
  return Array.from(map.entries()).map(([name, count]) => ({ name, count })).sort((a, b) => a.name.localeCompare(b.name));
}
function displayTitle(note) {
  if (!note) return 'Untitled';
  const t = (note.title || '').trim();
  if (t) return t;
  const first = htmlToText(note.html).split('\n')[0].trim();
  return first ? first.slice(0, 90) : 'Untitled';
}
function noteText(note) { return htmlToText(note.html); }

/* The list currently in view: honours view, tag filter, search and sort. ----*/
function visibleNotes() {
  const ui = state.ui;
  const q = ui.search.trim().toLowerCase();
  let list = state.notes.filter(n => (ui.view === 'trash' ? n.trashed : !n.trashed));
  if (ui.view !== 'all' && ui.view !== 'trash') list = list.filter(n => n.notebookId === ui.view);
  if (ui.tagFilter) list = list.filter(n => n.tags.indexOf(ui.tagFilter) !== -1);
  if (q) {
    list = list.filter(n => (displayTitle(n) + '\n' + noteText(n)).toLowerCase().indexOf(q) !== -1);
  }
  list.sort((a, b) => {
    if (a.pinned !== b.pinned) return a.pinned ? -1 : 1;
    if (ui.sort === 'alpha') return displayTitle(a).localeCompare(displayTitle(b), undefined, { sensitivity: 'base' });
    if (ui.sort === 'created') return b.createdAt - a.createdAt;
    return b.updatedAt - a.updatedAt;
  });
  return list;
}

/* Snippet + match highlighting (operates on extracted TEXT, never on tags) --*/
function buildSnippet(note, query) {
  const text = noteText(note);
  if (!text) return '';
  const q = (query || '').trim();
  let start = 0;
  if (q) {
    const idx = text.toLowerCase().indexOf(q.toLowerCase());
    if (idx > -1) start = Math.max(0, idx - Math.floor(CFG.SNIPPET_LEN / 3));
  }
  let out = text.slice(start, start + CFG.SNIPPET_LEN);
  if (start > 0) out = '…' + out;
  if (start + CFG.SNIPPET_LEN < text.length) out += '…';
  let html = escapeHtml(out);
  if (q) html = html.replace(new RegExp('(' + escapeRegExp(escapeHtml(q)) + ')', 'ig'), '<mark>$1</mark>');
  return html;
}
function highlightTitle(title, query) {
  const q = (query || '').trim();
  if (!q) return escapeHtml(title);
  return escapeHtml(title).replace(new RegExp('(' + escapeRegExp(q) + ')', 'ig'), '<mark>$1</mark>');
}

/* ============================================================================
   6. SMALL UI PRIMITIVES — menus, modal, toast, save state
   ========================================================================== */
let openMenuEl = null;
function closeMenu() {
  if (openMenuEl) { openMenuEl.remove(); openMenuEl = null; document.removeEventListener('mousedown', onDocDownForMenu, true); document.removeEventListener('keydown', onKeyForMenu, true); }
}
function onDocDownForMenu(e) { if (openMenuEl && !openMenuEl.contains(e.target)) closeMenu(); }
function onKeyForMenu(e) { if (e.key === 'Escape') { e.stopPropagation(); closeMenu(); } }

function showMenu(anchor, items) {
  closeMenu();
  const menu = document.createElement('div');
  menu.className = 'menu';
  items.forEach(item => {
    if (item.separator) { const s = document.createElement('div'); s.className = 'menu-sep'; menu.appendChild(s); return; }
    if (item.label) { const l = document.createElement('div'); l.className = 'menu-label'; l.textContent = item.label; menu.appendChild(l); return; }
    const btn = document.createElement('button');
    btn.className = 'menu-item' + (item.danger ? ' danger' : '');
    btn.innerHTML = '<span class="menu-check">' + (item.checked ? ICONS.check : '') + '</span>' +
                    '<span class="menu-icon" style="display:grid;place-items:center;color:inherit;opacity:.75">' + (item.icon || '') + '</span>' +
                    '<span>' + escapeHtml(item.text) + '</span>' +
                    (item.key ? '<span class="menu-key">' + escapeHtml(item.key) + '</span>' : '');
    btn.addEventListener('click', ev => { ev.stopPropagation(); closeMenu(); item.onClick && item.onClick(); });
    menu.appendChild(btn);
  });
  document.body.appendChild(menu);
  const r = anchor.getBoundingClientRect();
  const mw = menu.offsetWidth, mh = menu.offsetHeight;
  let x = r.left, y = r.bottom + 6;
  if (x + mw > window.innerWidth - 10) x = Math.max(10, r.right - mw);
  if (y + mh > window.innerHeight - 10) y = Math.max(10, r.top - mh - 6);
  if (items.some(i => i.alignRight) || false) x = Math.max(10, r.right - mw);
  menu.style.left = x + 'px';
  menu.style.top = y + 'px';
  openMenuEl = menu;
  setTimeout(() => {
    document.addEventListener('mousedown', onDocDownForMenu, true);
    document.addEventListener('keydown', onKeyForMenu, true);
  }, 0);
  return menu;
}

function askDialog(opts) {
  return new Promise(resolve => {
    const back = document.createElement('div');
    back.className = 'backdrop';
    const modal = document.createElement('div');
    modal.className = 'modal';
    modal.innerHTML =
      '<h3>' + escapeHtml(opts.title || '') + '</h3>' +
      (opts.message ? '<p>' + escapeHtml(opts.message) + '</p>' : '') +
      (opts.input ? '<input class="modal-input" type="text" spellcheck="false" />' : '') +
      '<div class="modal-actions">' +
      '<button class="btn" data-act="cancel">' + escapeHtml(opts.cancelText || 'Cancel') + '</button>' +
      '<button class="btn ' + (opts.danger ? 'danger' : 'primary') + '" data-act="ok">' + escapeHtml(opts.confirmText || 'Create') + '</button>' +
      '</div>';
    back.appendChild(modal);
    document.body.appendChild(back);
    const input = $('.modal-input', modal);
    if (input) input.value = opts.value || '';
    setTimeout(() => { if (input) { input.focus(); input.select(); } else { $('[data-act="ok"]', modal).focus(); } }, 30);

    function done(val) { back.remove(); document.removeEventListener('keydown', onKey, true); resolve(val); }
    function onKey(e) {
      if (e.key === 'Escape') { e.stopPropagation(); done(null); }
      else if (e.key === 'Enter' && (opts.input || true)) { e.preventDefault(); done(input ? input.value : true); }
    }
    $('[data-act="cancel"]', modal).addEventListener('click', () => done(null));
    $('[data-act="ok"]', modal).addEventListener('click', () => done(input ? input.value : true));
    back.addEventListener('mousedown', e => { if (e.target === back) done(null); });
    document.addEventListener('keydown', onKey, true);
  });
}
function confirmDialog(title, message, confirmText) {
  return askDialog({ title, message, confirmText: confirmText || 'Delete', danger: true }).then(v => !!v);
}

let toastTimer = null;
function toast(message, actionLabel, action) {
  $$('.toast').forEach(t => t.remove());
  const el = document.createElement('div');
  el.className = 'toast';
  el.innerHTML = '<span>' + escapeHtml(message) + '</span>';
  if (actionLabel) {
    const b = document.createElement('button');
    b.textContent = actionLabel;
    b.addEventListener('click', () => { el.remove(); action && action(); });
    el.appendChild(b);
  }
  document.body.appendChild(el);
  clearTimeout(toastTimer);
  toastTimer = setTimeout(() => el.remove(), actionLabel ? 6000 : 2400);
}

const setSaveState = (() => {
  const el = () => $('#saveStateText');
  const icon = () => $('#saveState');
  return function (mode) {
    const t = el(); if (!t) return;
    const wrap = icon();
    if (mode === 'saving') { t.textContent = 'Saving…'; wrap && wrap.classList.add('saving'); }
    else if (mode === 'saved') { t.textContent = 'Saved'; wrap && wrap.classList.remove('saving'); }
    else { t.textContent = 'Not saved'; wrap && wrap.classList.remove('saving'); }
  };
})();

/* ============================================================================
   7. RENDER — SIDEBAR
   ========================================================================== */
function renderSidebar() {
  const ui = state.ui;
  /* All notes + trash counts */
  const live = state.notes.filter(n => !n.trashed);
  $('#countAll').textContent = live.length;
  $('#countTrash').textContent = state.notes.filter(n => n.trashed).length;
  $$('.nav-item[data-nav]').forEach(el => {
    el.classList.toggle('active', !ui.tagFilter && ui.view === el.getAttribute('data-nav') && !ui.search);
  });

  /* Notebooks */
  const list = $('#notebookList');
  list.innerHTML = '';
  state.notebooks.forEach(nb => {
    const count = live.filter(n => n.notebookId === nb.id).length;
    const isOpen = !!ui.expanded[nb.id];
    const row = document.createElement('div');
    row.className = 'notebook-row';

    const caret = document.createElement('button');
    caret.className = 'caret' + (isOpen ? ' open' : '');
    caret.innerHTML = ICONS.chevron;
    caret.title = isOpen ? 'Collapse' : 'Expand';
    caret.addEventListener('click', e => {
      e.stopPropagation();
      ui.expanded[nb.id] = !ui.expanded[nb.id];
      renderSidebar(); persist();
    });
    row.appendChild(caret);

    const item = document.createElement('button');
    item.className = 'nav-item';
    item.setAttribute('data-notebook', nb.id);
    item.classList.toggle('active', ui.view === nb.id && !ui.tagFilter && !ui.search);
    item.innerHTML = ICONS.notebook + '<span class="nav-label"></span><span class="count">' + count + '</span>' +
      '<span class="nav-actions"><span class="mini-btn" data-act="menu" title="Notebook options">' + ICONS.more + '</span></span>';
    $('.nav-label', item).textContent = nb.name;
    item.addEventListener('click', e => {
      if (e.target.closest('[data-act="menu"]')) { notebookMenu(e.target.closest('[data-act="menu"]'), nb); return; }
      selectNotebook(nb.id);
    });
    item.addEventListener('dblclick', () => renameNotebook(nb.id));
    /* drag & drop target */
    item.addEventListener('dragover', e => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; item.classList.add('drop-target'); });
    item.addEventListener('dragleave', () => item.classList.remove('drop-target'));
    item.addEventListener('drop', e => {
      e.preventDefault(); item.classList.remove('drop-target');
      const id = e.dataTransfer.getData('text/plain');
      if (id && getNote(id)) { moveNote(id, nb.id); toast('Moved to “' + nb.name + '”'); }
    });
    row.appendChild(item);
    list.appendChild(row);

    if (isOpen) {
      const sub = document.createElement('div');
      sub.className = 'sub-notes';
      const notes = live.filter(n => n.notebookId === nb.id).sort((a, b) => (b.pinned - a.pinned) || (b.updatedAt - a.updatedAt));
      if (!notes.length) {
        const e = document.createElement('div');
        e.className = 'sub-empty'; e.textContent = 'No notes yet';
        sub.appendChild(e);
      } else {
        notes.forEach(n => {
          const b = document.createElement('button');
          b.className = 'sub-note' + (ui.selectedNoteId === n.id ? ' active' : '');
          b.textContent = displayTitle(n);
          b.title = displayTitle(n);
          b.addEventListener('click', () => { ui.view = nb.id; ui.tagFilter = null; openNote(n.id); });
          sub.appendChild(b);
        });
      }
      list.appendChild(sub);
    }
  });

  /* Tags */
  const tagList = $('#tagList');
  const tags = allTags();
  tagList.innerHTML = '';
  if (!tags.length) {
    const e = document.createElement('div');
    e.className = 'empty-hint'; e.textContent = 'No tags yet';
    tagList.appendChild(e);
  }
  tags.forEach(t => {
    const b = document.createElement('button');
    b.className = 'tag-pill' + (ui.tagFilter === t.name ? ' active' : '');
    b.innerHTML = '<span>#' + escapeHtml(t.name) + '</span><span class="tag-n">' + t.count + '</span>';
    b.addEventListener('click', () => {
      ui.tagFilter = ui.tagFilter === t.name ? null : t.name;
      if (ui.view === 'trash') ui.view = 'all';
      renderSidebar(); renderList();
    });
    tagList.appendChild(b);
  });
}

function notebookMenu(anchor, nb) {
  showMenu(anchor, [
    { text: 'Rename notebook', icon: ICONS.pencil, onClick: () => renameNotebook(nb.id) },
    { text: 'New note here', icon: ICONS.plus, onClick: () => newNote(nb.id) },
    { separator: true },
    {
      text: 'Delete notebook', icon: ICONS.trash, danger: true, onClick: async () => {
        const ok = await confirmDialog('Delete “' + nb.name + '”?',
          'Notes in this notebook are moved to Trash. You can restore them later.', 'Delete notebook');
        if (ok) deleteNotebook(nb.id);
      }
    }
  ]);
}

/* ============================================================================
   8. RENDER — NOTE LIST
   ========================================================================== */
function currentListTitle() {
  const ui = state.ui;
  if (ui.search.trim()) return 'Search';
  if (ui.tagFilter) return '#' + ui.tagFilter;
  if (ui.view === 'all') return 'All notes';
  if (ui.view === 'trash') return 'Trash';
  return notebookName(ui.view);
}
function renderList() {
  const ui = state.ui;
  const q = ui.search.trim();
  const notes = visibleNotes();

  $('#listTitle').textContent = currentListTitle();
  $('#searchInput').value = ui.search;
  $('#searchClear').hidden = !ui.search;
  $('#collapseBtn').classList.toggle('collapsed', ui.sidebarCollapsed);
  $('#collapseBtn').title = ui.sidebarCollapsed ? 'Show sidebar' : 'Hide sidebar';

  const sortLabels = { modified: 'Last modified', created: 'Date created', alpha: 'Alphabetical' };
  $('#sortBtn').innerHTML = ICONS.sortAsc + '<span>' + sortLabels[ui.sort] + '</span>';

  const noteEl = $('#resultNote');
  if (ui.search.trim()) noteEl.textContent = notes.length + (notes.length === 1 ? ' result' : ' results') + ' for “' + q + '”';
  else if (ui.view === 'trash') noteEl.textContent = notes.length ? notes.length + ' deleted' : 'Trash is empty';
  else noteEl.textContent = notes.length + (notes.length === 1 ? ' note' : ' notes');

  const wrap = $('#noteList');
  wrap.innerHTML = '';
  if (!notes.length) {
    const empty = document.createElement('div');
    empty.style.cssText = 'padding:34px 16px;text-align:center;color:var(--muted);font-size:12.5px;line-height:1.6';
    empty.innerHTML = ui.search.trim()
      ? '<div style="font-size:22px;margin-bottom:8px;opacity:.5">🔍</div>No notes match “' + escapeHtml(q) + '”'
      : (ui.view === 'trash'
        ? '<div style="font-size:22px;margin-bottom:8px;opacity:.5">🗑️</div>Nothing in the trash'
        : '<div style="font-size:22px;margin-bottom:8px;opacity:.5">📝</div>No notes here yet<br><span style="opacity:.75">Press ⌘N to start one</span>');
    wrap.appendChild(empty);
    return;
  }

  notes.forEach(note => {
    const card = document.createElement('div');
    card.className = 'note-card' + (ui.selectedNoteId === note.id ? ' active' : '');
    card.setAttribute('data-note', note.id);
    card.draggable = true;
    const snippet = buildSnippet(note, q);
    const tagsHtml = note.tags.slice(0, 3).map(t => '<span class="mini-tag">#' + escapeHtml(t) + '</span>').join('');
    card.innerHTML =
      '<div class="note-card-top">' +
        (note.pinned ? '<span class="pin-badge" title="Pinned">' + ICONS.pin + '</span>' : '') +
        '<div class="note-card-title">' + highlightTitle(displayTitle(note), q) + '</div>' +
      '</div>' +
      '<div class="note-card-snippet">' +
        (snippet || '<span style="opacity:.6">Empty note</span>') +
      '</div>' +
      '<div class="note-card-foot">' +
        '<span class="note-card-time">' +
          (ui.view === 'trash' ? 'Deleted ' + formatStamp(note.updatedAt) : formatStamp(note.updatedAt)) +
        '</span>' +
        '<span class="note-card-tags">' + tagsHtml + '</span>' +
      '</div>' +
      '<button class="card-menu" title="Options">' + ICONS.more + '</button>';

    card.addEventListener('click', e => {
      if (e.target.closest('.card-menu')) { noteCardMenu(e.target.closest('.card-menu'), note); return; }
      openNote(note.id);
    });
    card.addEventListener('dragstart', e => {
      card.classList.add('dragging');
      e.dataTransfer.setData('text/plain', note.id);
      e.dataTransfer.effectAllowed = 'move';
    });
    card.addEventListener('dragend', () => card.classList.remove('dragging'));
    wrap.appendChild(card);
  });
}
function noteCardMenu(anchor, note) {
  const items = [];
  if (note.trashed) {
    items.push({ text: 'Restore note', icon: ICONS.restore, onClick: () => restoreNote(note.id) });
    items.push({ separator: true });
    items.push({
      text: 'Delete forever', icon: ICONS.trash, danger: true, onClick: async () => {
        const ok = await confirmDialog('Delete forever?', '“' + displayTitle(note) + '” will be permanently removed. This cannot be undone.', 'Delete forever');
        if (ok) deleteForever(note.id);
      }
    });
  } else {
    items.push({ text: note.pinned ? 'Unpin' : 'Pin to top', icon: ICONS.pin, onClick: () => togglePin(note.id) });
    items.push({ text: 'Duplicate', icon: ICONS.copy, onClick: () => duplicateNote(note.id) });
    items.push({ text: 'Rename', icon: ICONS.pencil, onClick: () => renameNote(note.id) });
    items.push({ separator: true });
    items.push({ label: 'Move to' });
    state.notebooks.forEach(nb => items.push({
      text: nb.name, checked: nb.id === note.notebookId,
      onClick: () => { moveNote(note.id, nb.id); toast('Moved to “' + nb.name + '”'); }
    }));
    items.push({ separator: true });
    items.push({ text: 'Move to trash', icon: ICONS.trash, danger: true, onClick: () => trashNote(note.id) });
  }
  showMenu(anchor, items);
}

/* ============================================================================
   9. RENDER — EDITOR CHROME
   ========================================================================== */
let renderedNoteId = null;
function renderEditor() {
  const note = getNote(state.ui.selectedNoteId);
  const wrap = $('#editorWrap');
  const empty = $('#editorEmpty');
  if (!note) {
    commitEditorToModel();
    wrap.hidden = true;
    empty.hidden = false;
    renderedNoteId = null;
    return;
  }
  wrap.hidden = false;
  empty.hidden = true;

  if (renderedNoteId !== note.id) {
    commitEditorToModel();               /* outgoing note keeps the caret's work */
    renderedNoteId = note.id;
    $('#titleInput').value = note.title;
    $('#editor').innerHTML = note.html || '';
    const h = hist.get(note.id);
    if (h) h.last = note.html; else hist.set(note.id, { undo: [], redo: [], last: note.html || '', timer: null });
  }
  renderEditorMeta();
  updateWordCount();
  refreshToolbar();
}
function renderEditorMeta() {
  const note = getNote(state.ui.selectedNoteId);
  if (!note) return;
  $('#notebookChip').innerHTML = ICONS.folder + '<span>' + escapeHtml(notebookName(note.notebookId)) + '</span>';
  const chips = $('#tagChips');
  chips.innerHTML = '';
  note.tags.forEach(t => {
    const c = document.createElement('span');
    c.className = 'tag-chip';
    c.innerHTML = '<span>#' + escapeHtml(t) + '</span><button title="Remove tag" aria-label="Remove tag">×</button>';
    $('button', c).addEventListener('click', e => { e.stopPropagation(); removeTag(note.id, t); });
    chips.appendChild(c);
  });
  $('#footDate').textContent = 'Edited ' + formatFull(note.updatedAt);
  $('#trashBar').hidden = !note.trashed;
  $('#noteMenuBtn').title = note.pinned ? 'Pinned — note options' : 'Note options';
}
function updateWordCount() {
  const text = htmlToText($('#editor').innerHTML);
  const words = text ? text.split(/\s+/).filter(Boolean).length : 0;
  const chars = text.length;
  $('#wordCount').textContent = words + (words === 1 ? ' word' : ' words') + ' · ' + chars + ' characters';
}
</script>
<script>
"use strict";
/* ============================================================================
   10. MODEL MUTATIONS
   ========================================================================== */
function createNotebook(name) {
  const nb = { id: uid('nb'), name: (name || 'Notebook').trim() || 'Notebook', createdAt: Date.now() };
  state.notebooks.push(nb);
  state.ui.expanded[nb.id] = true;
  persist();
  return nb;
}
async function renameNotebook(id) {
  const nb = getNotebook(id); if (!nb) return;
  const name = await askDialog({ title: 'Rename notebook', input: true, value: nb.name, confirmText: 'Rename' });
  if (name == null) return;
  const clean = String(name).trim();
  if (!clean) return;
  nb.name = clean;
  persist(); renderSidebar(); renderList(); renderEditorMeta();
}
async function deleteNotebook(id) {
  const nb = getNotebook(id); if (!nb) return;
  const notes = state.notes.filter(n => n.notebookId === id && !n.trashed);
  notes.forEach(n => { n.trashed = true; n.trashedFrom = n.notebookId; n.updatedAt = Date.now(); });
  state.notebooks = state.notebooks.filter(n => n.id !== id);
  if (!state.notebooks.length) state.notebooks.push({ id: uid('nb'), name: CFG.DEFAULT_NOTEBOOK, createdAt: Date.now() });
  if (state.ui.view === id) state.ui.view = 'all';
  ensureSelection();
  persist(); renderAll();
  if (notes.length) toast(notes.length + ' note' + (notes.length > 1 ? 's' : '') + ' moved to Trash');
}
function newNote(notebookId) {
  let nbId = notebookId;
  if (!nbId && state.ui.view !== 'all' && state.ui.view !== 'trash') nbId = state.ui.view;
  if (!nbId) nbId = (state.notebooks[0] || createNotebook(CFG.DEFAULT_NOTEBOOK)).id;
  const now = Date.now();
  const note = {
    id: uid('note'), notebookId: nbId, title: '', html: '<p><br></p>', tags: [],
    pinned: false, trashed: false, trashedFrom: null, createdAt: now, updatedAt: now
  };
  state.notes.unshift(note);
  if (state.ui.view !== 'all') state.ui.view = nbId;
  state.ui.tagFilter = null;
  state.ui.search = '';
  state.ui.selectedNoteId = note.id;
  getHist(note.id).last = note.html;
  saveNow(); renderAll();
  setTimeout(() => { const t = $('#titleInput'); if (t) { t.focus(); t.select(); } }, 40);
  return note;
}
function openNote(id) {
  const note = getNote(id); if (!note) return;
  if (renderedNoteId && renderedNoteId !== id) flushHistory(renderedNoteId);
  if (note.trashed !== (state.ui.view === 'trash') && !state.ui.search.trim()) {
    if (note.trashed) state.ui.view = 'trash'; else if (state.ui.view === 'trash') state.ui.view = 'all';
  }
  state.ui.selectedNoteId = id;
  commitEditorToModel();
  renderAll();
}
function selectNotebook(id) {
  state.ui.view = id;
  state.ui.tagFilter = null;
  state.ui.search = '';
  ensureSelection();
  renderAll();
}
function trashNote(id) {
  const note = getNote(id); if (!note || note.trashed) return;
  flushHistory(id);
  note.trashed = true;
  note.trashedFrom = note.notebookId;
  note.updatedAt = Date.now();
  if (state.ui.selectedNoteId === id) state.ui.selectedNoteId = null;
  ensureSelection();
  persist(); renderAll();
  toast('Note moved to Trash', 'Undo', () => restoreNote(id));
}
function restoreNote(id) {
  const note = getNote(id); if (!note) return;
  note.trashed = false;
  if (!getNotebook(note.notebookId)) {
    note.notebookId = (note.trashedFrom && getNotebook(note.trashedFrom)) ? note.trashedFrom
      : (state.notebooks[0] && state.notebooks[0].id);
  }
  note.updatedAt = Date.now();
  if (state.ui.view === 'trash') { state.ui.selectedNoteId = null; ensureSelection(); }
  persist(); renderAll();
  toast('Restored to “' + notebookName(note.notebookId) + '”');
}
async function deleteForever(id) {
  const note = getNote(id); if (!note) return;
  state.notes = state.notes.filter(n => n.id !== id);
  hist.delete(id);
  if (state.ui.selectedNoteId === id) { state.ui.selectedNoteId = null; ensureSelection(); }
  persist(); renderAll();
}
function togglePin(id) {
  const note = getNote(id); if (!note) return;
  note.pinned = !note.pinned;
  persist(); renderList(); renderSidebar(); renderEditorMeta();
  toast(note.pinned ? 'Pinned to top' : 'Unpinned');
}
function duplicateNote(id) {
  const note = getNote(id); if (!note) return;
  const copy = JSON.parse(JSON.stringify(note));
  copy.id = uid('note');
  copy.title = displayTitle(note) + ' copy';
  copy.pinned = false;
  copy.trashed = false;
  copy.trashedFrom = null;
  copy.createdAt = copy.updatedAt = Date.now();
  state.notes.unshift(copy);
  state.ui.selectedNoteId = copy.id;
  state.ui.search = '';
  if (state.ui.view === 'trash') state.ui.view = copy.notebookId;
  persist(); renderAll();
  toast('Note duplicated');
}
function moveNote(id, notebookId) {
  const note = getNote(id); if (!note || !getNotebook(notebookId)) return;
  note.notebookId = notebookId;
  note.updatedAt = Date.now();
  if (state.ui.view !== 'all' && state.ui.view !== 'trash' && state.ui.view !== notebookId) {
    state.ui.selectedNoteId = null; ensureSelection();
  }
  persist(); renderAll();
}
async function renameNote(id) {
  const note = getNote(id); if (!note) return;
  const name = await askDialog({ title: 'Rename note', input: true, value: note.title || displayTitle(note), confirmText: 'Rename' });
  if (name == null) return;
  note.title = String(name).trim();
  note.updatedAt = Date.now();
  if (state.ui.selectedNoteId === id) { $('#titleInput').value = note.title; }
  persist(); renderAll();
}
function addTag(id, raw) {
  const note = getNote(id); if (!note) return;
  const tag = String(raw || '').trim().replace(/^#+/, '').replace(/\s+/g, '-').toLowerCase().slice(0, 32);
  if (!tag || note.tags.indexOf(tag) !== -1) { renderEditorMeta(); return; }
  note.tags.push(tag);
  note.updatedAt = Date.now();
  persist(); renderEditorMeta(); renderSidebar(); renderList();
}
function removeTag(id, tag) {
  const note = getNote(id); if (!note) return;
  note.tags = note.tags.filter(t => t !== tag);
  note.updatedAt = Date.now();
  if (state.ui.tagFilter === tag && !state.notes.some(n => !n.trashed && n.tags.indexOf(tag) !== -1)) state.ui.tagFilter = null;
  persist(); renderEditorMeta(); renderSidebar(); renderList();
}
function commitEditorToModel() {
  const id = renderedNoteId;
  const note = getNote(id);
  const e = ed();
  if (!note || !e || $('#editorWrap').hidden) return;
  if (note.html !== e.innerHTML) {
    note.html = e.innerHTML;
    note.updatedAt = Date.now();
    persist();
  }
}

/* ============================================================================
   11. UNDO / REDO — snapshot history per note
   ========================================================================== */
const hist = new Map();
function getHist(id) {
  if (!hist.has(id)) {
    const note = getNote(id);
    hist.set(id, { undo: [], redo: [], last: note ? note.html : '', timer: null });
  }
  return hist.get(id);
}
function commitHistory(id) {
  const h = hist.get(id); if (!h) return;
  clearTimeout(h.timer); h.timer = null;
  const note = getNote(id); if (!note) return;
  if (note.html !== h.last) {
    h.undo.push(h.last);
    if (h.undo.length > CFG.HISTORY_LIMIT) h.undo.shift();
    h.redo.length = 0;
    h.last = note.html;
  }
}
function recordHistory(id) {
  const h = getHist(id);
  clearTimeout(h.timer);
  h.timer = setTimeout(() => commitHistory(id), CFG.HISTORY_COMMIT_MS);
}
function flushHistory(id) { const h = hist.get(id); if (h && h.timer) commitHistory(id); }
function undo() {
  const id = state.ui.selectedNoteId; if (!getNote(id)) return;
  commitHistory(id);
  const h = getHist(id);
  if (!h.undo.length) { toast('Nothing to undo'); return; }
  h.redo.push(h.last);
  h.last = h.undo.pop();
  applyHtml(h.last);
}
function redo() {
  const id = state.ui.selectedNoteId; if (!getNote(id)) return;
  const h = getHist(id);
  if (!h.redo.length) return;
  h.undo.push(h.last);
  h.last = h.redo.pop();
  applyHtml(h.last);
}
function applyHtml(html) {
  const note = getNote(state.ui.selectedNoteId); if (!note) return;
  const ed = $('#editor');
  ed.innerHTML = html;
  note.html = html;
  note.updatedAt = Date.now();
  caretToEnd(ed);
  ed.focus();
  setSaveState('saving');
  persist();
  updateWordCount(); renderEditorMeta(); refreshToolbar(); scheduleListRefresh();
}

/* ============================================================================
   12. EDITOR — DOM / CARET HELPERS
   ========================================================================== */
function ed() { return $('#editor'); }
function inEditor(node) { const e = ed(); return !!(node && (node === e || e.contains(node))); }
function closestTag(node, tagName) {
  const e = ed();
  if (!inEditor(node)) return null;
  let n = node && node.nodeType === 3 ? node.parentNode : node;
  while (n && n !== e) {
    if (n.nodeType === 1 && n.tagName === tagName) return n;
    n = n.parentNode;
  }
  return null;
}
function closestIn(node, test) {
  const e = ed();
  if (!inEditor(node)) return null;
  let n = node && node.nodeType === 3 ? node.parentNode : node;
  while (n && n !== e) {
    if (n.nodeType === 1 && test(n)) return n;
    n = n.parentNode;
  }
  return null;
}
const BLOCK_RE = /^(P|DIV|H1|H2|H3|LI|BLOCKQUOTE)$/;
function lineBlockFor(node) {
  const e = ed();
  if (!inEditor(node)) return null;
  let n = node && node.nodeType === 3 ? node.parentNode : node;
  if (n === e) return null;
  while (n && n !== e) {
    if (n.nodeType === 1 && BLOCK_RE.test(n.tagName)) return n;
    n = n.parentNode;
  }
  return null;
}
function currentBlockEl() {
  const sel = window.getSelection();
  if (!sel || !sel.rangeCount) return null;
  return lineBlockFor(sel.anchorNode);
}
function caretToStart(el) {
  const r = document.createRange();
  r.setStart(el, 0); r.collapse(true);
  const s = window.getSelection(); s.removeAllRanges(); s.addRange(r);
}
function caretToEnd(el) {
  const r = document.createRange();
  r.selectNodeContents(el); r.collapse(false);
  const s = window.getSelection(); s.removeAllRanges(); s.addRange(r);
}
function linePrefix(node, offset) {
  if (!node || node.nodeType !== 3) return { text: '', block: null };
  const block = lineBlockFor(node);
  if (block) {
    let out = '';
    const w = document.createTreeWalker(block, NodeFilter.SHOW_TEXT);
    let t;
    while ((t = w.nextNode())) {
      if (t === node) { out += t.data.slice(0, offset); return { text: out, block: block }; }
      out += t.data;
    }
    return { text: out, block: block };
  }
  return { text: node.data.slice(0, offset), block: null };
}
/* Paste: sanitised content is spliced at the caret. Block-level pastes split the
   current paragraph instead of nesting blocks inside it. */
function insertPastedHtml(html) {
  const sel = window.getSelection();
  const e = ed();
  if (!sel || !sel.rangeCount || !inEditor(sel.anchorNode)) { e.insertAdjacentHTML('beforeend', html); return; }
  const range = sel.getRangeAt(0);
  const block = lineBlockFor(range.startContainer);
  const temp = document.createElement('div');
  temp.innerHTML = html;
  const nodes = Array.prototype.slice.call(temp.childNodes);
  const hasBlocks = nodes.some(n => n.nodeType === 1 && /^(P|DIV|H1|H2|H3|UL|OL|LI|BLOCKQUOTE|HR)$/.test(n.tagName));
  if (!block || !hasBlocks) {
    range.deleteContents();
    const frag = document.createDocumentFragment();
    nodes.forEach(n => frag.appendChild(n));
    if (!frag.childNodes.length) return;
    const last = frag.lastChild;
    range.insertNode(frag);
    const r = document.createRange();
    r.setStartAfter(last); r.collapse(true);
    sel.removeAllRanges(); sel.addRange(r);
    return;
  }
  /* keep whatever sat after the caret and push it below the pasted blocks */
  const tail = range.cloneRange();
  tail.setEnd(block, block.childNodes.length);
  const tailFrag = tail.extractContents();
  let ref = block;
  nodes.forEach(n => { ref.after(n); ref = n; });
  const tailText = (tailFrag.textContent || '').replace(/[\s\u200b]/g, '');
  if (tailText || tailFrag.querySelector('*')) {
    const holder = document.createElement('p');
    holder.appendChild(tailFrag);
    ref.after(holder);
    caretToStart(holder);
  } else {
    const r = document.createRange();
    r.setStartAfter(ref); r.collapse(true);
    sel.removeAllRanges(); sel.addRange(r);
  }
}
function insertHtmlAtCaret(html) {
  const sel = window.getSelection();
  const e = ed();
  if (!sel || !sel.rangeCount || !inEditor(sel.anchorNode)) {
    e.insertAdjacentHTML('beforeend', html);
    return;
  }
  const range = sel.getRangeAt(0);
  range.deleteContents();
  const temp = document.createElement('div');
  temp.innerHTML = html;
  const frag = document.createDocumentFragment();
  while (temp.firstChild) frag.appendChild(temp.firstChild);
  if (!frag.childNodes.length) return;
  const last = frag.lastChild;
  range.insertNode(frag);
  const r = document.createRange();
  r.setStartAfter(last); r.collapse(true);
  sel.removeAllRanges(); sel.addRange(r);
}

/* ============================================================================
   13. EDITOR — INLINE / BLOCK FORMATTING
   ========================================================================== */
function pushEditorChange() {
  const note = getNote(state.ui.selectedNoteId); if (!note) return;
  note.html = ed().innerHTML;
  note.updatedAt = Date.now();
  recordHistory(note.id);
  setSaveState('saving');
  persist();
  updateWordCount();
  renderEditorMeta();
  refreshToolbar();
  scheduleListRefresh();
}
const scheduleListRefresh = debounce(() => { renderList(); renderSidebar(); }, 250);

function toggleInline(cmd) {
  try {
    document.execCommand('styleWithCSS', false, false);
    document.execCommand(cmd, false, null);
  } catch (err) { /* no-op */ }
  pushEditorChange();
}
function toggleInlineCode() {
  const sel = window.getSelection();
  if (!sel || !sel.rangeCount || !inEditor(sel.anchorNode)) return;
  const existing = closestTag(sel.anchorNode, 'CODE');
  if (existing) {
    const parent = existing.parentNode;
    while (existing.firstChild) parent.insertBefore(existing.firstChild, existing);
    existing.remove();
  } else {
    const range = sel.getRangeAt(0);
    if (range.collapsed) return;
    const code = document.createElement('code');
    try { range.surroundContents(code); }
    catch (err) { code.appendChild(range.extractContents()); range.insertNode(code); }
    caretToEnd(code);
  }
  pushEditorChange();
}
function setBlock(tag) {
  const cur = currentBlockEl();
  if (cur && cur.tagName === tag.toUpperCase()) {
    document.execCommand('formatBlock', false, 'P');
  } else {
    document.execCommand('formatBlock', false, '<' + tag.toUpperCase() + '>');
  }
  pushEditorChange();
}
function toggleListCmd(kind) {
  const cmd = kind === 'ul' ? 'insertUnorderedList' : 'insertOrderedList';
  try { document.execCommand(cmd, false, null); } catch (e) { /* no-op */ }
  pushEditorChange();
}
function makeChkItem(text) {
  const li = document.createElement('li');
  li.className = 'chk';
  li.setAttribute('data-checked', 'false');
  const box = document.createElement('span');
  box.className = 'chk-box';
  box.setAttribute('contenteditable', 'false');
  const span = document.createElement('span');
  span.className = 'chk-text';
  span.textContent = '\u200b';
  if (text) span.textContent = text;
  li.appendChild(box);
  li.appendChild(span);
  return li;
}
function chkTextOf(li) { return $('.chk-text', li) || li; }
function moveBlockIntoChk(li, block) {
  const span = chkTextOf(li);
  let moved = false;
  while (block.firstChild) {
    const c = block.firstChild;
    if (c.nodeType === 3 && c.nodeValue === '') { c.remove(); continue; }
    span.appendChild(c);
    moved = true;
  }
  /* Chrome purges inline elements that hold nothing but empty text nodes, so the
     wrapper must always carry at least one (invisible) character. */
  if (!moved) span.textContent = '\u200b';
  return span;
}
function convertBlockToList(block, tag) {
  const li = document.createElement('li');
  while (block.firstChild) li.appendChild(block.firstChild);
  if (!li.firstChild) li.appendChild(document.createElement('br'));
  const list = document.createElement(tag);
  list.appendChild(li);
  block.replaceWith(list);
  caretToEnd(li);
}
function convertBlockToChecklist(block) {
  const li = makeChkItem('');
  moveBlockIntoChk(li, block);
  const ul = document.createElement('ul');
  ul.className = 'checklist';
  ul.appendChild(li);
  block.replaceWith(ul);
  caretToEnd(chkTextOf(li));
}
function unwrapChkItem(li) {
  const list = li.parentElement;
  const p = document.createElement('div');
  while (li.firstChild) {
    const c = li.firstChild;
    if (c.nodeType === 1 && c.classList && c.classList.contains('chk-box')) { c.remove(); continue; }
    if (c.nodeType === 1 && c.classList && c.classList.contains('chk-text')) {
      while (c.firstChild) p.appendChild(c.firstChild);
      c.remove();
      continue;
    }
    p.appendChild(c);
  }
  li.remove();
  if (!list.querySelector('li')) list.replaceWith(p); else list.after(p);
  caretToEnd(p);
}
function toggleChecklist() {
  const sel = window.getSelection();
  if (!sel || !sel.rangeCount || !inEditor(sel.anchorNode)) return;
  const li = closestTag(sel.anchorNode, 'LI');
  if (li && li.classList.contains('chk')) { unwrapChkItem(li); pushEditorChange(); return; }
  const block = currentBlockEl();
  if (!block) return;
  if (block.tagName === 'LI') {
    const ul = document.createElement('ul');
    ul.className = 'checklist';
    const nli = makeChkItem('');
    moveBlockIntoChk(nli, block);
    ul.appendChild(nli);
    block.replaceWith(ul);
    caretToEnd(chkTextOf(nli));
  } else {
    convertBlockToChecklist(block);
  }
  pushEditorChange();
}
function insertDivider() {
  const before = currentBlockEl();
  try { document.execCommand('insertHorizontalRule', false, null); } catch (e) { /* no-op */ }
  const e = ed();
  const hrs = $$('hr', e);
  const hr = hrs[hrs.length - 1];
  if (hr && !hr.nextElementSibling) {
    const p = document.createElement('p');
    p.innerHTML = '<br>';
    hr.after(p);
    caretToStart(p);
  }
  pushEditorChange();
}

/* Markdown-style input shortcuts --------------------------------------------*/
const MD_PATTERNS = [
  [/^###$/, 'h3'], [/^##$/, 'h2'], [/^#$/, 'h1'],
  [/^[-*+]$/, 'ul'], [/^\[\]$/, 'checklist'], [/^\[ ?\]$/, 'checklist'],
  [/^>$/, 'blockquote']
];
function detectShortcut() {
  const sel = window.getSelection();
  if (!sel || !sel.rangeCount) return null;
  const r = sel.getRangeAt(0);
  if (!r.collapsed) return null;
  const ctx = linePrefix(r.startContainer, r.startOffset);
  if (ctx.text == null) return null;
  for (let i = 0; i < MD_PATTERNS.length; i++) {
    if (MD_PATTERNS[i][0].test(ctx.text)) {
      return { kind: MD_PATTERNS[i][1], block: ctx.block, node: r.startContainer, offset: r.startOffset };
    }
  }
  return null;
}
function applyShortcut(sc) {
  const del = document.createRange();
  del.setStart(sc.node, 0);
  del.setEnd(sc.node, sc.offset);
  del.deleteContents();
  let target = sc.block;
  if (!target) {
    const p = document.createElement('div');
    sc.node.parentNode.insertBefore(p, sc.node);
    p.appendChild(sc.node);
    target = p;
  }
  if (target.tagName === 'LI') {
    if (sc.kind === 'ul' || sc.kind === 'checklist') { pushEditorChange(); return; }
    const li = target;
    const wrap = document.createElement('div');
    while (li.firstChild) wrap.appendChild(li.firstChild);
    li.replaceWith(wrap);
    target = wrap;
  }
  if (sc.kind === 'ul') convertBlockToList(target, 'ul');
  else if (sc.kind === 'checklist') convertBlockToChecklist(target);
  else if (sc.kind === 'blockquote') setBlockTag(target, 'blockquote');
  else setBlockTag(target, sc.kind);
  pushEditorChange();
}
function setBlockTag(block, tag) {
  if (block.tagName === tag.toUpperCase()) return;
  const el = document.createElement(tag);
  while (block.firstChild) el.appendChild(block.firstChild);
  block.replaceWith(el);
  caretToEnd(el);
}

/* Enter / list continuation --------------------------------------------------*/
function handleEnter(e) {
  if (e.shiftKey) return;
  const sel = window.getSelection();
  if (!sel || !sel.rangeCount || !inEditor(sel.anchorNode)) return;
  const li = closestTag(sel.anchorNode, 'LI');
  if (!li) return;
  const isChk = li.classList.contains('chk');
  const text = li.textContent.replace(/\u200b/g, '').trim();
  const list = li.parentElement;
  if (!isChk) {
    if (text) return;               // let the browser continue the plain list
  }
  e.preventDefault();
  if (text) {
    const nli = makeChkItem('');
    li.after(nli);
    caretToEnd(chkTextOf(nli));
    pushEditorChange();
    return;
  }
  /* empty item -> leave the list and start a paragraph */
  const p = document.createElement('div');
  p.innerHTML = '<br>';
  const siblings = Array.prototype.slice.call(list.children).filter(c => c !== li);
  const after = [];
  let n = li.nextElementSibling;
  while (n) { after.push(n); n = n.nextElementSibling; }
  if (!siblings.length) {
    list.replaceWith(p);
  } else if (!after.length) {
    list.after(p);
    li.remove();
  } else {
    const newList = document.createElement(list.tagName.toLowerCase());
    if (list.className) newList.className = list.className;
    after.forEach(a => newList.appendChild(a));
    list.after(p);
    p.after(newList);
    li.remove();
  }
  caretToStart(p);
  pushEditorChange();
}

/* Toolbar state --------------------------------------------------------------*/
let tbRaf = null;
function refreshToolbar() {
  if (tbRaf) return;
  tbRaf = requestAnimationFrame(() => {
    tbRaf = null;
    const sel = window.getSelection();
    const anchor = sel && sel.rangeCount ? sel.anchorNode : null;
    const qs = cmd => { try { return document.queryCommandState(cmd); } catch (e) { return false; } };
    const block = currentBlockEl();
    const li = closestTag(anchor, 'LI');
    $$('#toolbar .tb').forEach(btn => {
      const cmd = btn.getAttribute('data-cmd');
      let active = false;
      if (cmd === 'bold' || cmd === 'italic' || cmd === 'underline' || cmd === 'strikeThrough') active = qs(cmd);
      else if (cmd === 'code') active = !!closestTag(anchor, 'CODE');
      else if (cmd === 'ul') active = qs('insertUnorderedList') || !!(li && li.parentElement.tagName === 'UL' && !li.classList.contains('chk'));
      else if (cmd === 'ol') active = qs('insertOrderedList') || !!(li && li.parentElement.tagName === 'OL');
      else if (cmd === 'checklist') active = !!(li && li.classList.contains('chk'));
      else if (cmd === 'h1' || cmd === 'h2' || cmd === 'h3') active = !!(block && block.tagName === cmd.toUpperCase());
      else if (cmd === 'blockquote') active = !!(block && block.tagName === 'BLOCKQUOTE');
      btn.classList.toggle('active', active);
    });
    const h = hist.get(state.ui.selectedNoteId);
    const u = $('#toolbar .tb[data-cmd="undo"]'), r = $('#toolbar .tb[data-cmd="redo"]');
    if (u) u.disabled = !(h && h.undo.length);
    if (r) r.disabled = !(h && h.redo.length);
  });
}

/* ============================================================================
   14. EVENTS
   ========================================================================== */
function ensureSelection() {
  const sel = getNote(state.ui.selectedNoteId);
  const wantTrash = state.ui.view === 'trash';
  if (sel && (!!sel.trashed === wantTrash)) return;
  const list = visibleNotes();
  state.ui.selectedNoteId = list.length ? list[0].id : null;
}
/* At boot we keep an explicit "nothing selected" so the empty state can show. */
function validateSelection() {
  const sel = getNote(state.ui.selectedNoteId);
  const wantTrash = state.ui.view === 'trash';
  if (!sel || (!!sel.trashed !== wantTrash)) state.ui.selectedNoteId = null;
}
function renderAll() { renderSidebar(); renderList(); renderEditor(); }

function bindEvents() {
  const tool = $('#toolbar');
  tool.addEventListener('mousedown', e => { if (e.target.closest('.tb')) e.preventDefault(); });
  tool.addEventListener('click', e => {
    const btn = e.target.closest('.tb'); if (!btn) return;
    const cmd = btn.getAttribute('data-cmd');
    ed().focus();
    if (cmd === 'bold' || cmd === 'italic' || cmd === 'underline' || cmd === 'strikeThrough') toggleInline(cmd);
    else if (cmd === 'code') toggleInlineCode();
    else if (cmd === 'h1' || cmd === 'h2' || cmd === 'h3') setBlock(cmd);
    else if (cmd === 'ul' || cmd === 'ol') toggleListCmd(cmd);
    else if (cmd === 'checklist') toggleChecklist();
    else if (cmd === 'blockquote') setBlock('blockquote');
    else if (cmd === 'hr') insertDivider();
    else if (cmd === 'undo') undo();
    else if (cmd === 'redo') redo();
  });

  const e = ed();
  e.addEventListener('input', pushEditorChange);
  e.addEventListener('keydown', e => {
    const mod = e.metaKey || e.ctrlKey;
    if (mod && !e.altKey) {
      const k = (e.key || '').toLowerCase();
      if (k === 'b' || k === 'i' || k === 'u') { e.preventDefault(); toggleInline(k === 'b' ? 'bold' : k === 'i' ? 'italic' : 'underline'); return; }
      if (k === 'e') { e.preventDefault(); toggleInlineCode(); return; }
      if (k === 'z') { e.preventDefault(); e.shiftKey ? redo() : undo(); return; }
      if (k === 'y') { e.preventDefault(); redo(); return; }
    }
    if (e.key === ' ' && !e.shiftKey) {
      const sc = detectShortcut();
      if (sc) { e.preventDefault(); applyShortcut(sc); return; }
    }
    if (e.key === 'Enter') { handleEnter(e); return; }
    if (e.key === 'Tab') { e.preventDefault(); try { document.execCommand(e.shiftKey ? 'outdent' : 'indent', false, null); } catch (err) { } pushEditorChange(); return; }
  });
  e.addEventListener('keyup', refreshToolbar);
  e.addEventListener('mouseup', refreshToolbar);
  e.addEventListener('mousedown', e => {
    const box = e.target.closest ? e.target.closest('.chk-box') : null;
    if (!box) return;
    e.preventDefault();
    const li = box.closest('li.chk');
    if (!li) return;
    li.setAttribute('data-checked', li.getAttribute('data-checked') === 'true' ? 'false' : 'true');
    if (!inEditor(document.activeElement)) ed().focus();
    pushEditorChange();
  });
  e.addEventListener('paste', ev => {
    ev.preventDefault();
    let html = '', text = '';
    try {
      const cd = ev.clipboardData || window.clipboardData;
      if (cd) { html = cd.getData('text/html') || ''; text = cd.getData('text/plain') || ''; }
    } catch (err) { /* ignore */ }
    const clean = html ? sanitizeHtml(html) : textToHtml(text);
    insertPastedHtml(clean);
    pushEditorChange();
  });
  e.addEventListener('drop', ev => { /* let the browser handle in-editor drops */ });

  document.addEventListener('selectionchange', () => { if (inEditor(document.activeElement) || (window.getSelection() && inEditor(window.getSelection().anchorNode))) refreshToolbar(); });

  $('#editorScroll').addEventListener('mousedown', ev => {
    if (ev.target !== $('#editorScroll')) return;
    ev.preventDefault();
    ed().focus();
    caretToEnd(ed());
  });

  /* title */
  const title = $('#titleInput');
  title.addEventListener('input', () => {
    const note = getNote(state.ui.selectedNoteId); if (!note) return;
    note.title = title.value;
    note.updatedAt = Date.now();
    setSaveState('saving'); persist();
    scheduleListRefresh();
  });
  title.addEventListener('keydown', ev => {
    if (ev.key === 'Enter') { ev.preventDefault(); ed().focus(); caretToEnd(ed()); }
    else if (ev.key === 'Escape') title.blur();
  });

  /* search + sort */
  const search = $('#searchInput');
  search.addEventListener('input', () => {
    state.ui.search = search.value;
    state.ui.tagFilter = null;
    if (search.value.trim() && state.ui.view === 'trash') state.ui.view = 'all';
    persist(); renderList(); renderSidebar();
  });
  search.addEventListener('keydown', ev => { if (ev.key === 'Escape') { search.value = ''; state.ui.search = ''; search.blur(); renderList(); renderSidebar(); } });
  $('#searchClear').addEventListener('click', () => { search.value = ''; state.ui.search = ''; search.focus(); persist(); renderList(); renderSidebar(); });
  $('#sortBtn').addEventListener('click', ev => {
    const items = Object.keys(CFG.SORT).map(k => ({
      text: CFG.SORT[k].label, checked: state.ui.sort === k,
      onClick: () => { state.ui.sort = k; persist(); renderList(); }
    }));
    showMenu(ev.currentTarget, items);
  });

  /* sidebar nav */
  $$('.nav-item[data-nav]').forEach(el => el.addEventListener('click', () => {
    state.ui.view = el.getAttribute('data-nav');
    state.ui.tagFilter = null;
    state.ui.search = '';
    ensureSelection(); persist(); renderAll();
  }));
  $('#newNoteBtn').addEventListener('click', () => newNote());
  $('#listNewBtn').addEventListener('click', () => newNote());
  $('#emptyNewBtn').addEventListener('click', () => newNote());
  $('#newNotebookBtn').addEventListener('click', async () => {
    const name = await askDialog({ title: 'New notebook', input: true, value: '', confirmText: 'Create' });
    if (name == null) return;
    createNotebook(name);
    renderSidebar();
  });
  $('#collapseBtn').addEventListener('click', () => {
    state.ui.sidebarCollapsed = !state.ui.sidebarCollapsed;
    $('#sidebar').classList.toggle('collapsed', state.ui.sidebarCollapsed);
    $('#collapseBtn').classList.toggle('collapsed', state.ui.sidebarCollapsed);
    $('#collapseBtn').title = state.ui.sidebarCollapsed ? 'Show sidebar' : 'Hide sidebar';
    persist();
  });
  $('#themeToggle').addEventListener('click', () => {
    state.ui.theme = state.ui.theme === 'dark' ? 'light' : 'dark';
    applyTheme(); persist();
  });

  /* editor meta */
  $('#notebookChip').addEventListener('click', ev => {
    const note = getNote(state.ui.selectedNoteId); if (!note) return;
    showMenu(ev.currentTarget, state.notebooks.map(nb => ({
      text: nb.name, checked: nb.id === note.notebookId,
      onClick: () => { moveNote(note.id, nb.id); toast('Moved to “' + nb.name + '”'); }
    })));
  });
  $('#addTagBtn').addEventListener('click', async () => {
    const note = getNote(state.ui.selectedNoteId); if (!note) return;
    const tag = await askDialog({ title: 'Add a tag', input: true, value: '', confirmText: 'Add tag' });
    if (tag == null) return;
    addTag(note.id, tag);
  });
  $('#noteMenuBtn').addEventListener('click', ev => {
    const note = getNote(state.ui.selectedNoteId); if (!note) return;
    noteCardMenu(ev.currentTarget, note);
  });
  $('#restoreBtn').addEventListener('click', () => {
    const note = getNote(state.ui.selectedNoteId); if (note) restoreNote(note.id);
  });
  $('#purgeBtn').addEventListener('click', async () => {
    const note = getNote(state.ui.selectedNoteId); if (!note) return;
    const ok = await confirmDialog('Delete forever?', '“' + displayTitle(note) + '” will be permanently removed. This cannot be undone.', 'Delete forever');
    if (ok) deleteForever(note.id);
  });

  /* resizers */
  setupResizer($('#rz1'), 'sidebar');
  setupResizer($('#rz2'), 'list');

  /* global keys */
  document.addEventListener('keydown', ev => {
    const mod = ev.metaKey || ev.ctrlKey;
    if (!mod) return;
    const k = (ev.key || '').toLowerCase();
    if (k === 'n') { ev.preventDefault(); newNote(); }
    else if (k === 'f') { ev.preventDefault(); $('#searchInput').focus(); $('#searchInput').select(); }
    else if (k === 's') { ev.preventDefault(); saveNow(); toast('Saved'); }
  });

  window.addEventListener('resize', () => {
    const app = $('#app');
    const maxList = window.innerWidth - state.ui.sidebarW - CFG.MIN_EDITOR_W;
    if (state.ui.listW > maxList) { state.ui.listW = Math.max(CFG.MIN_LIST_W, maxList); app.style.setProperty('--list-w', state.ui.listW + 'px'); }
  });
  window.addEventListener('beforeunload', () => { commitEditorToModel(); saveNow(); });
}

function setupResizer(el, which) {
  el.addEventListener('mousedown', ev => {
    ev.preventDefault();
    const startX = ev.clientX;
    const startW = which === 'sidebar' ? state.ui.sidebarW : state.ui.listW;
    const app = $('#app');
    el.classList.add('dragging');
    document.body.classList.add('resizing');
    function move(e2) {
      const d = e2.clientX - startX;
      if (which === 'sidebar') {
        const w = Math.max(CFG.MIN_SIDEBAR_W, Math.min(CFG.MAX_SIDEBAR_W, startW + d));
        setSidebarWidth(w);
      } else {
        const maxByEditor = window.innerWidth - state.ui.sidebarW - CFG.MIN_EDITOR_W;
        const w = Math.max(CFG.MIN_LIST_W, Math.min(CFG.MAX_LIST_W, Math.min(startW + d, Math.max(CFG.MIN_LIST_W, maxByEditor))));
        state.ui.listW = w;
        app.style.setProperty('--list-w', w + 'px');
      }
    }
    function up() {
      document.removeEventListener('mousemove', move);
      document.removeEventListener('mouseup', up);
      el.classList.remove('dragging');
      document.body.classList.remove('resizing');
      persist();
    }
    document.addEventListener('mousemove', move);
    document.addEventListener('mouseup', up);
  });
}
function setSidebarWidth(w) {
  state.ui.sidebarW = w;
  $('#app').style.setProperty('--sidebar-w', w + 'px');
  $('#sidebarInner').style.minWidth = w + 'px';
}
function applyTheme() {
  document.documentElement.setAttribute('data-theme', state.ui.theme === 'dark' ? 'dark' : 'light');
  $('#themeToggle').innerHTML = state.ui.theme === 'dark' ? ICONS.sun : ICONS.moon;
  $('#themeToggle').title = state.ui.theme === 'dark' ? 'Switch to light theme' : 'Switch to dark theme';
}

/* ============================================================================
   15. INIT
   ========================================================================== */
function init() {
  loadState();
  applyTheme();
  applyLayout();
  if (window.innerWidth < CFG.COLLAPSE_AT) state.ui.sidebarCollapsed = true;
  $('#sidebar').classList.toggle('collapsed', state.ui.sidebarCollapsed);
  $('#collapseBtn').classList.toggle('collapsed', state.ui.sidebarCollapsed);
  validateSelection();
  bindEvents();
  renderAll();
  /* keep the save indicator honest on first paint */
  setTimeout(() => setSaveState('saved'), 260);
}
function applyLayout() {
  setSidebarWidth(state.ui.sidebarW || 268);
  $('#app').style.setProperty('--list-w', (state.ui.listW || 322) + 'px');
}

/* expose a small surface for testing / debugging */
window.__app = {
  get state() { return state; },
  CFG, ICONS,
  htmlToText, sanitizeHtml, textToHtml, displayTitle, visibleNotes, noteText, buildSnippet,
  getNote, getNotebook, notebookName, allTags,
  newNote, openNote, selectNotebook, trashNote, restoreNote, deleteForever, togglePin,
  duplicateNote, moveNote, renameNote, createNotebook, addTag, removeTag,
  saveNow, renderAll, renderList, renderSidebar, renderEditor, undo, redo, applyShortcut, detectShortcut, commitHistory, flushHistory,
  editor: () => $('#editor'),
  caretToEnd, caretToStart, pushEditorChange, getHist,
  setEditorHtml(html) { const e = ed(); e.innerHTML = html; pushEditorChange(); }
};

init();
</script>
</body>
</html>
<!-- agent-meta {"model":"deepseek/deepseek-v4.1-flash","provider":"openrouter","persona":"full","sessionId":"f90f5bc5-3fc6-4287-aebf-74f5f4d832ea","tokensIn":13336385,"tokensOut":117714,"tokensTotal":13454099,"cost":0.13422355799999997,"turns":76,"toolCalls":75,"failedToolCalls":0,"timestamp":"2026-09-10T17:36:03.531Z"} -->