← Notes results

Notes

Tiel Coder 35B A3B GGUF MTP · 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 77,157 bytes · SHA-256 0f8fc1441f1d
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Notes</title>
<style>
/* ============================================================
   TUNABLE CONSTANTS — colors, fonts, timing, lengths
   ============================================================ */
:root {
  /* Warm light theme */
  --bg: #f7f5f0;
  --panel: #ffffff;
  --panel-2: #fbf9f5;
  --sidebar-bg: #f1ede4;
  --border: #e7e2d7;
  --border-strong: #ddd6c7;
  --text: #2c2a24;
  --text-2: #5c574d;
  --text-muted: #9a9284;
  --accent: #c0703d;
  --accent-2: #a85a2c;
  --accent-soft: #f4e6d6;
  --accent-soft-2: #ead9c4;
  --hover: #efe9dd;
  --hover-2: #e8e1d3;
  --selected: #f0e9dc;
  --selected-border: #dcbf9a;
  --danger: #c0392b;
  --danger-soft: #f7e3e1;
  --pin: #d99a3f;
  --mark: #ffe18a;
  --mark-border: #f2c94c;
  --shadow: 0 1px 2px rgba(60,50,30,.08), 0 6px 20px rgba(60,50,30,.06);
  --radius: 9px;
  --radius-sm: 6px;
  --font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-edit: Georgia, "Times New Roman", "IPAGothic", serif;
  --autosave-debounce: 450;
  --snippet-length: 110;
  --sidebar-w: 256px;
  --list-w: 320px;
}
html[data-theme="dark"] {
  --bg: #1a1917;
  --panel: #24221f;
  --panel-2: #201f1c;
  --sidebar-bg: #1f1d1a;
  --border: #34312c;
  --border-strong: #413d36;
  --text: #e9e4da;
  --text-2: #b8b2a5;
  --text-muted: #7d776b;
  --accent: #e0955a;
  --accent-2: #ef9f63;
  --accent-soft: #3a2c1e;
  --accent-soft-2: #463423;
  --hover: #2c2a26;
  --hover-2: #33302b;
  --selected: #353029;
  --selected-border: #6b4d2e;
  --danger: #e2604f;
  --danger-soft: #3a2421;
  --pin: #e6b066;
  --mark: #7a6320;
  --mark-border: #9a7d28;
  --shadow: 0 1px 2px rgba(0,0,0,.4), 0 6px 20px rgba(0,0,0,.35);
}

* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
  font-family: var(--font-ui);
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow: hidden;
}
button, input, select { font-family: inherit; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 8px; border: 2px solid transparent; background-clip: padding-box; }
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); background-clip: padding-box; }
::selection { background: var(--accent-soft-2); color: var(--text); }

/* ============================================================
   LAYOUT
   ============================================================ */
#app {
  display: flex;
  height: 100vh;
  width: 100vw;
  overflow: hidden;
}
.pane { height: 100%; overflow: hidden; display: flex; flex-direction: column; }
#sidebar {
  width: var(--sidebar-w);
  flex-shrink: 0;
  min-width: 180px;
  background: var(--sidebar-bg);
  border-right: 1px solid var(--border);
  transition: width .22s cubic-bezier(.4,0,.2,1), opacity .2s ease;
}
#sidebar.collapsed { width: 0; opacity: 0; pointer-events: none; }
#sidebar-inner { width: 100%; height: 100%; display: flex; flex-direction: column; min-width: 180px; }
#listPanel {
  width: var(--list-w);
  flex-shrink: 0;
  min-width: 220px;
  background: var(--panel);
  border-right: 1px solid var(--border);
}
#editorPanel { flex: 1; min-width: 260px; background: var(--bg); }
.resize-handle {
  width: 5px;
  flex-shrink: 0;
  cursor: col-resize;
  background: transparent;
  position: relative;
  z-index: 20;
}
.resize-handle::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: 0; right: 0;
}
.resize-handle:hover { background: var(--accent); opacity: .35; }
.resize-handle.dim { visibility: hidden; }

/* ============================================================
   SIDEBAR
   ============================================================ */
.sidebar-top {
  padding: 16px 16px 12px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  border-bottom: 1px solid var(--border);
}
.app-brand { display: flex; align-items: center; justify-content: space-between; }
.app-logo { display: flex; align-items: center; gap: 9px; font-weight: 700; font-size: 17px; letter-spacing: -.2px; }
.app-logo .glyph {
  width: 26px; height: 26px; border-radius: 7px;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  display: grid; place-items: center; color: #fff; font-size: 15px; line-height: 1;
  box-shadow: 0 2px 6px rgba(168,90,44,.35);
}
.icon-btns { display: flex; gap: 4px; }
.icon-btn {
  width: 32px; height: 32px; border: none; border-radius: var(--radius-sm);
  background: transparent; color: var(--text-2); cursor: pointer;
  display: grid; place-items: center; font-size: 16px; transition: background .12s, color .12s;
}
.icon-btn:hover { background: var(--hover); color: var(--text); }
.icon-btn:active { transform: scale(.94); }

.new-note-btn {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  width: 100%; padding: 9px 12px; border: none; border-radius: var(--radius-sm);
  background: var(--accent); color: #fff; font-weight: 600; font-size: 13.5px;
  cursor: pointer; transition: background .12s, box-shadow .12s, transform .06s;
  box-shadow: 0 1px 2px rgba(168,90,44,.3);
}
.new-note-btn:hover { background: var(--accent-2); box-shadow: 0 2px 8px rgba(168,90,44,.35); }
.new-note-btn:active { transform: scale(.98); }

.search-box { position: relative; }
.search-box .s-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--text-muted); font-size: 13px; pointer-events: none; }
.search-box input {
  width: 100%; padding: 8px 30px 8px 30px; border: 1px solid var(--border);
  border-radius: var(--radius-sm); background: var(--panel-2); color: var(--text);
  font-size: 13px; outline: none; transition: border-color .12s, box-shadow .12s;
}
.search-box input::placeholder { color: var(--text-muted); }
.search-box input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.search-box .s-clear { position: absolute; right: 7px; top: 50%; transform: translateY(-50%); border: none; background: none; color: var(--text-muted); cursor: pointer; font-size: 14px; display: none; }
.search-box.has-text .s-clear { display: block; }

.sidebar-scroll { flex: 1; overflow-y: auto; padding: 10px 8px 20px; }

.nav-item {
  display: flex; align-items: center; gap: 9px; padding: 7px 9px; border-radius: var(--radius-sm);
  cursor: pointer; color: var(--text-2); font-size: 13.5px; user-select: none;
  transition: background .1s, color .1s; position: relative;
}
.nav-item:hover { background: var(--hover); color: var(--text); }
.nav-item.active { background: var(--accent-soft); color: var(--text); font-weight: 600; }
.nav-item.active .nav-ico { color: var(--accent); }
.nav-item .nav-ico { width: 16px; text-align: center; color: var(--text-muted); font-size: 13px; flex-shrink: 0; }
.nav-item .nav-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-item .nav-label[contenteditable="true"] { background: var(--panel); outline: 1px solid var(--accent); padding: 0 2px; }
.nav-item .nav-count { font-size: 11.5px; color: var(--text-muted); background: transparent; padding: 0 6px; border-radius: 10px; }
.nav-item .nav-acts { display: none; gap: 2px; }
.nav-item:hover .nav-acts { display: flex; }
.nav-act { border: none; background: none; color: var(--text-muted); cursor: pointer; font-size: 12px; padding: 2px 3px; border-radius: 4px; }
.nav-act:hover { background: var(--border); color: var(--text); }
.nav-section-label { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); padding: 12px 10px 5px; font-weight: 700; }
.chevron { width: 14px; text-align: center; color: var(--text-muted); font-size: 10px; transition: transform .16s ease; flex-shrink: 0; }
.notebook.collapsed .chevron { transform: rotate(-90deg); }
.notebook-notes { overflow: hidden; transition: height .2s ease; }
.notebook.collapsed .notebook-notes { height: 0 !important; overflow: hidden; }
.sub-note { padding-left: 34px; }
.sub-note .nav-item { font-size: 13px; }

/* ============================================================
   NOTE LIST
   ============================================================ */
.list-header {
  padding: 16px 16px 10px; border-bottom: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 10px; flex-shrink: 0; background: var(--panel);
}
.list-title-row { display: flex; align-items: center; justify-content: space-between; }
.list-title { font-size: 15px; font-weight: 700; letter-spacing: -.2px; display: flex; align-items: center; gap: 8px; }
.list-count { font-size: 12px; color: var(--text-muted); font-weight: 500; }
.list-sub { font-size: 12px; color: var(--text-muted); }
.sort-toggle {
  display: flex; border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden;
}
.sort-toggle button {
  border: none; background: transparent; color: var(--text-muted); cursor: pointer;
  padding: 5px 9px; font-size: 11.5px; font-weight: 600; transition: background .1s, color .1s;
}
.sort-toggle button:not(:last-child) { border-right: 1px solid var(--border); }
.sort-toggle button.active { background: var(--accent); color: #fff; }
.sort-toggle button:hover:not(.active) { background: var(--hover); color: var(--text); }

.note-list { flex: 1; overflow-y: auto; padding: 10px; }
.note-card {
  padding: 11px 13px; border-radius: var(--radius); cursor: pointer; margin-bottom: 6px;
  border: 1px solid transparent; background: var(--panel-2);
  transition: background .12s, border-color .12s, box-shadow .12s, transform .05s;
  position: relative;
}
.note-card:hover { background: var(--hover); border-color: var(--border); }
.note-card.active { border-color: var(--selected-border); background: var(--selected); box-shadow: var(--shadow); }
.note-card.drag-over { border-color: var(--accent); background: var(--accent-soft); box-shadow: inset 0 0 0 1px var(--accent); }
.note-card-title { font-size: 13.5px; font-weight: 600; margin: 0 0 4px; display: flex; align-items: center; gap: 6px; color: var(--text); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.note-card-title .pin-ico { color: var(--pin); font-size: 12px; flex-shrink: 0; }
.note-card-snippet { font-size: 12px; color: var(--text-muted); line-height: 1.45; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; margin-bottom: 7px; min-height: 17px; white-space: pre-line; }
.note-card-meta { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.note-card-time { font-size: 11px; color: var(--text-muted); }
.note-card-tags { display: flex; gap: 4px; flex-wrap: wrap; }
.tag-chip { font-size: 10.5px; padding: 2px 7px; border-radius: 10px; background: var(--accent-soft); color: var(--accent-2); cursor: pointer; transition: background .1s, color .1s; border: 1px solid transparent; }
.tag-chip:hover { background: var(--accent-soft-2); }
.tag-chip.active { border-color: var(--accent); }
.note-card .drag-ghost { display: none; }
mark { background: var(--mark); color: inherit; padding: 0 1px; border-radius: 2px; box-shadow: 0 0 0 1px var(--mark-border); }

/* ============================================================
   EDITOR
   ============================================================ */
.editor-header {
  padding: 12px 20px; border-bottom: 1px solid var(--border);
  display: flex; align-items: center; gap: 12px; flex-shrink: 0; background: var(--panel);
}
.breadcrumb { font-size: 12px; color: var(--text-muted); display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.breadcrumb span { cursor: pointer; }
.breadcrumb span:hover { color: var(--accent); }
.note-title-input {
  flex: 1; border: none; outline: none; background: transparent; color: var(--text);
  font-size: 17px; font-weight: 600; padding: 6px 4px; border-bottom: 2px solid transparent;
  transition: border-color .12s;
}
.note-title-input:focus { border-bottom-color: var(--accent-soft); }
.header-actions { display: flex; align-items: center; gap: 4px; position: relative; }
.save-indicator { font-size: 11px; color: var(--text-muted); margin-right: 6px; min-width: 56px; text-align: right; font-weight: 500; }
.save-indicator.saved { color: #4a9d5c; }
.save-indicator.saving { color: var(--pin); }
html[data-theme="dark"] .save-indicator.saved { color: #7fd08d; }

.toolbar {
  padding: 7px 14px; border-bottom: 1px solid var(--border);
  display: flex; align-items: center; gap: 3px; flex-wrap: wrap; flex-shrink: 0; background: var(--panel);
}
.tool-btn {
  min-width: 30px; height: 30px; padding: 0 7px; border: 1px solid transparent; border-radius: var(--radius-sm);
  background: transparent; color: var(--text-2); cursor: pointer; font-size: 13px; font-weight: 600;
  display: grid; place-items: center; transition: background .1s, color .1s, border-color .1s;
}
.tool-btn:hover { background: var(--hover); color: var(--text); }
.tool-btn:active { transform: scale(.93); }
.tool-btn.active { background: var(--accent-soft); border-color: var(--accent-soft-2); color: var(--accent-2); box-shadow: inset 0 1px 2px rgba(168,90,44,.12); }
.tool-btn:disabled { opacity: .4; cursor: default; }
.tool-sep { width: 1px; height: 20px; background: var(--border); margin: 0 5px; }
.tool-btn .kbd { font-size: 10px; opacity: .7; }

.editor-scroll { flex: 1; overflow-y: auto; }
.editor-wrap { max-width: 760px; margin: 0 auto; padding: 34px 40px 120px; }
#editor {
  font-family: var(--font-edit);
  font-size: 17px; line-height: 1.72; color: var(--text);
  outline: none; min-height: 60vh; letter-spacing: .01em;
  word-wrap: break-word; overflow-wrap: break-word;
}
#editor:empty:before {
  content: "Start writing…"; color: var(--text-muted); font-style: italic; pointer-events: none;
}
#editor h1 { font-size: 27px; font-weight: 700; margin: 22px 0 10px; line-height: 1.25; letter-spacing: -.3px; }
#editor h2 { font-size: 22px; font-weight: 700; margin: 20px 0 8px; line-height: 1.3; }
#editor h3 { font-size: 18px; font-weight: 700; margin: 18px 0 6px; line-height: 1.35; }
#editor p { margin: 0 0 12px; }
#editor ul, #editor ol { margin: 0 0 12px; padding-left: 26px; }
#editor li { margin-bottom: 5px; }
#editor blockquote {
  margin: 0 0 12px; padding: 4px 16px; border-left: 3px solid var(--accent);
  color: var(--text-2); font-style: italic; background: var(--panel-2); border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
#editor code {
  font-family: "SF Mono", Menlo, Consolas, monospace; font-size: 88%;
  background: var(--accent-soft); color: var(--accent-2); padding: 1.5px 5px; border-radius: 4px;
}
html[data-theme="dark"] #editor code { background: var(--accent-soft-2); }
#editor hr { border: none; border-top: 1px solid var(--border); margin: 22px 0; }
#editor a { color: var(--accent); text-decoration: underline; }
#editor mark { background: var(--mark); }

/* Checklist */
#editor ul.checklist { list-style: none; padding-left: 6px; }
#editor ul.checklist li { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 6px; }
.checkbox {
  --cb: 18px;
  width: var(--cb); height: var(--cb); flex-shrink: 0; margin-top: 2px;
  border: 1.8px solid var(--border-strong); border-radius: 5px;
  display: grid; place-items: center; cursor: pointer; background: var(--panel);
  color: #fff; font-size: 12px; line-height: 1; transition: background .12s, border-color .12s;
  user-select: none;
}
.checkbox:hover { border-color: var(--accent); }
.checkbox.checked { background: var(--accent); border-color: var(--accent); }
.check-text { flex: 1; outline: none; }
.checkbox.checked + .check-text { text-decoration: line-through; color: var(--text-muted); }

/* ============================================================
   EMPTY STATES
   ============================================================ */
.empty-state {
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
  color: var(--text-muted); text-align: center; padding: 40px; gap: 10px;
}
.empty-state .empty-glyph { font-size: 54px; opacity: .5; margin-bottom: 6px; }
.empty-state h2 { font-size: 18px; font-weight: 600; color: var(--text-2); margin: 0; }
.empty-state p { font-size: 13.5px; margin: 0; max-width: 320px; line-height: 1.5; }
.empty-state .empty-cta {
  margin-top: 8px; padding: 9px 18px; border: none; border-radius: var(--radius-sm);
  background: var(--accent); color: #fff; font-weight: 600; cursor: pointer; font-size: 13px;
}
.empty-state .empty-cta:hover { background: var(--accent-2); }
.list-empty { padding: 40px 24px; text-align: center; color: var(--text-muted); }
.list-empty .le-glyph { font-size: 40px; opacity: .4; margin-bottom: 8px; }

/* ============================================================
   DROPDOWN MENU
   ============================================================ */
.menu-wrap { position: relative; }
.dropdown {
  position: absolute; right: 0; top: calc(100% + 6px); min-width: 200px;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 5px; z-index: 100;
  display: none; animation: dd-in .12s ease;
}
.dropdown.open { display: block; }
@keyframes dd-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
.dd-item {
  display: flex; align-items: center; gap: 9px; width: 100%; padding: 8px 10px; border: none;
  background: none; color: var(--text-2); cursor: pointer; border-radius: var(--radius-sm);
  font-size: 13px; text-align: left; transition: background .1s, color .1s;
}
.dd-item:hover { background: var(--hover); color: var(--text); }
.dd-item.danger { color: var(--danger); }
.dd-item.danger:hover { background: var(--danger-soft); }
.dd-item .dd-ico { width: 16px; text-align: center; color: var(--text-muted); }
.dd-item.danger .dd-ico { color: var(--danger); }
.dd-sep { height: 1px; background: var(--border); margin: 5px 3px; }
.dd-sublabel { font-size: 11px; color: var(--text-muted); margin-left: auto; }
.dd-heading { padding: 7px 10px 4px; font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); font-weight: 700; }
.dd-item.has-sub { justify-content: space-between; }
.dd-item.has-sub .dd-arrow { color: var(--text-muted); }
.submenu {
  position: relative;
}
.submenu .submenu-pop {
  position: absolute; left: calc(100% + 3px); top: -3px; min-width: 170px;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 5px; display: none; z-index: 101;
}
.submenu:hover .submenu-pop { display: block; animation: dd-in .12s ease; }
.dd-item.active-notebook { background: var(--accent-soft); color: var(--text); font-weight: 600; }

/* Tag editor in editor */
.tag-editor {
  padding: 10px 20px 0; display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  border-bottom: 1px solid var(--border); background: var(--panel); flex-shrink: 0;
}
.tag-editor .te-label { font-size: 12px; color: var(--text-muted); font-weight: 600; }
.tag-editor .existing-tags { display: flex; gap: 5px; flex-wrap: wrap; }
.tag-editor .tag-pill {
  display: inline-flex; align-items: center; gap: 5px; font-size: 12px; padding: 3px 6px 3px 9px;
  background: var(--accent-soft); color: var(--accent-2); border-radius: 11px; cursor: default;
  transition: background .1s;
}
.tag-editor .tag-pill.clickable { cursor: pointer; }
.tag-editor .tag-pill.clickable:hover { background: var(--accent-soft-2); }
.tag-editor .tag-x { border: none; background: none; color: var(--accent-2); cursor: pointer; font-size: 13px; line-height: 1; padding: 0 1px; border-radius: 50%; }
.tag-editor .tag-x:hover { color: var(--danger); }
.tag-editor .tag-input {
  border: none; outline: none; background: transparent; color: var(--text); font-size: 12.5px; flex: 1; min-width: 100px; padding: 3px 0;
}
.tag-editor .tag-input::placeholder { color: var(--text-muted); }
.hidden { display: none !important; }

/* toast */
.toast {
  position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%) translateY(20px);
  background: var(--text); color: var(--panel); padding: 10px 18px; border-radius: var(--radius);
  font-size: 13px; box-shadow: var(--shadow); opacity: 0; transition: opacity .2s, transform .2s; z-index: 500;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
</style>
</head>
<body>
<div id="app">
  <!-- ===================== SIDEBAR ===================== -->
  <aside id="sidebar">
    <div id="sidebar-inner">
      <div class="sidebar-top">
        <div class="app-brand">
          <div class="app-logo"><span class="glyph">✎</span> Notes</div>
          <div class="icon-btns">
            <button class="icon-btn" id="themeToggle" title="Toggle theme" aria-label="Toggle theme">◐</button>
          </div>
        </div>
        <button class="new-note-btn" id="newNoteBtn">+ New note</button>
        <div class="search-box" id="searchBox">
          <span class="s-icon">⌕</span>
          <input type="text" id="globalSearch" placeholder="Search notes…" autocomplete="off" spellcheck="false">
          <button class="s-clear" id="searchClear">✕</button>
        </div>
      </div>
      <div class="sidebar-scroll">
        <div class="nav-item" id="navAll" data-view="all">
          <span class="nav-ico">📝</span><span class="nav-label">All Notes</span><span class="nav-count" id="countAll">0</span>
        </div>
        <div id="notebooks"><!-- notebooks injected here --></div>
        <div class="nav-section-label">Saved</div>
        <div class="nav-item" id="navTrash" data-view="trash">
          <span class="nav-ico">🗑</span><span class="nav-label">Trash</span><span class="nav-count" id="countTrash">0</span>
        </div>
      </div>
    </div>
  </aside>
  <div class="resize-handle" id="sidebarHandle"></div>

  <!-- ===================== NOTE LIST ===================== -->
  <div class="pane" id="listPanel">
    <div class="list-header">
      <div class="list-title-row">
        <div class="list-title" id="listTitle">All Notes</div>
      </div>
      <div class="sort-toggle" id="sortToggle">
        <button data-sort="modified" class="active">Modified</button>
        <button data-sort="created">Created</button>
        <button data-sort="alpha">A–Z</button>
      </div>
    </div>
    <div class="note-list" id="noteList"></div>
  </div>
  <div class="resize-handle" id="listHandle"></div>

  <!-- ===================== EDITOR ===================== -->
  <div class="pane" id="editorPanel">
    <div class="editor-header" id="editorHeader">
      <div class="breadcrumb" id="breadcrumb"></div>
      <input class="note-title-input" id="noteTitleInput" placeholder="Title" autocomplete="off" spellcheck="false">
      <div class="header-actions">
        <span class="save-indicator" id="saveIndicator">Saved</span>
        <button class="icon-btn" id="pinBtn" title="Pin note" aria-label="Pin note">☆</button>
        <button class="icon-btn" id="moveBtn" title="Move to notebook" aria-label="Move to notebook">⇶</button>
        <button class="icon-btn" id="dupBtn" title="Duplicate" aria-label="Duplicate">⧉</button>
        <button class="icon-btn" id="delBtn" title="Delete" aria-label="Delete">🗑</button>
        <div class="menu-wrap">
          <button class="icon-btn" id="moreBtn" title="More" aria-label="More">⋯</button>
          <div class="dropdown" id="moreMenu"></div>
        </div>
      </div>
    </div>
    <div class="toolbar" id="toolbar">
      <button class="tool-btn" data-cmd="undo" title="Undo (Ctrl+Z)">↶</button>
      <button class="tool-btn" data-cmd="redo" title="Redo (Ctrl+Shift+Z)">↷</button>
      <span class="tool-sep"></span>
      <button class="tool-btn" data-cmd="bold" title="Bold (Ctrl+B)"><b>B</b></button>
      <button class="tool-btn" data-cmd="italic" title="Italic (Ctrl+I)"><i>I</i></button>
      <button class="tool-btn" data-cmd="underline" title="Underline (Ctrl+U)"><u>U</u></button>
      <button class="tool-btn" data-cmd="strikethrough" title="Strikethrough"><s>S</s></button>
      <button class="tool-btn" data-cmd="code" title="Inline code">&lt;/&gt;</button>
      <span class="tool-sep"></span>
      <button class="tool-btn" data-cmd="h1" title="Heading 1">H1</button>
      <button class="tool-btn" data-cmd="h2" title="Heading 2">H2</button>
      <button class="tool-btn" data-cmd="h3" title="Heading 3">H3</button>
      <button class="tool-btn" data-cmd="quote" title="Quote">❝</button>
      <span class="tool-sep"></span>
      <button class="tool-btn" data-cmd="bullet" title="Bulleted list">≣</button>
      <button class="tool-btn" data-cmd="numbered" title="Numbered list">1.</button>
      <button class="tool-btn" data-cmd="checklist" title="Checklist">☑</button>
      <span class="tool-sep"></span>
      <button class="tool-btn" data-cmd="hr" title="Horizontal rule">―</button>
    </div>
    <div class="tag-editor hidden" id="tagEditor">
      <span class="te-label">Tags</span>
      <span class="existing-tags" id="existingTags"></span>
      <input class="tag-input" id="tagInput" placeholder="Add tag + Enter">
    </div>
    <div class="editor-scroll">
      <div class="editor-wrap">
        <div class="empty-state" id="emptyState">
          <div class="empty-glyph">📄</div>
          <h2>Select or create a note</h2>
          <p>Choose a note from the list on the left, or create a new one to start writing.</p>
          <button class="empty-cta" id="emptyNewBtn">+ New note</button>
        </div>
        <div class="hidden" id="editorContainer">
          <div id="editor" contenteditable="true" spellcheck="false"></div>
        </div>
      </div>
    </div>
  </div>
</div>
<div class="toast" id="toast"></div>
<script>
/* ============================================================
   NOTES APPLICATION — vanilla JS
   Sections: (1) Tunable constants (2) Utilities (3) Data model
   (4) Persistence (5) Sanitizer (6) Rendering (7) Toolbar/selection
   (8) Editor events + markdown shortcuts (9) Search (10) Actions
   (11) Drag-drop (12) Resize (13) Init
   ============================================================ */
'use strict';

/* ---------- (1) TUNABLE CONSTANTS ---------- */
const CONFIG = {
  STORAGE_KEY: 'notesapp.v1',
  AUTOSAVE_DEBOUNCE: 450,
  SNIPPET_LENGTH: 110,
  SORT_DEFAULT: 'modified',
  ALLOWED_TAGS: new Set([
    'h1','h2','h3','p','br','ul','ol','li','strong','em','b','i','u','s','strike',
    'code','blockquote','hr','a','mark','span','div','del','ins'
  ]),
  ALLOWED_ATTR: new Set(['class','data-checked']),
  CHECKLIST_UL_CLASS: 'checklist',
};

/* ---------- (2) UTILITIES ---------- */
const $ = (sel, ctx = document) => ctx.querySelector(sel);
const $$ = (sel, ctx = document) => Array.from(ctx.querySelectorAll(sel));
const uid = () => 'id_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 8);
const clamp = (n, lo, hi) => Math.max(lo, Math.min(hi, n));

function escapeHtml(s) {
  return String(s).replace(/[&<>"']/g, c => ({
    '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
  }[c]));
}
// Plain-text extraction: strip tags, decode entities. Used for snippets + search.
function extractText(html) {
  if (!html) return '';
  // DOM node: return its text directly
  if (html.nodeType) return (html.textContent || html.innerText || '');
  // String: parse as HTML and extract text
  const div = document.createElement('div');
  div.innerHTML = html;
  return div.textContent || div.innerText || '';
}
function decodeEntities(t) {
  const div = document.createElement('div');
  div.innerHTML = t;
  return div.textContent || div.innerText || '';
}
// Build a snippet for the note list: join block-level elements with newlines so
// list items / paragraphs don't run together, then truncate.
function extractSnippet(html, len) {
  if (!html) return '';
  const div = document.createElement('div');
  div.innerHTML = html;
  const blockTags = new Set(['P','H1','H2','H3','H4','H5','H6','LI','BLOCKQUOTE','BR','DIV','HR','FIGCAPTION','FIGURE']);
  const parts = [];
  const walk = (node) => {
    node.childNodes.forEach(child => {
      if (child.nodeType === 1) {
        const tag = child.tagName;
        if (tag === 'BR') { parts.push('\n'); }
        else if (tag === 'HR') { parts.push('—'); }
        else if (blockTags.has(tag)) {
          const txt = child.textContent;
          if (txt.trim()) parts.push(txt.trim());
        } else {
          walk(child);
        }
      } else if (child.nodeType === 3) {
        parts.push(child.textContent);
      }
    });
  };
  walk(div);
  let text = parts.join('\n');
  // Collapse multiple blank lines
  text = text.replace(/\n{2,}/g, '\n').trim();
  const limit = len || CONFIG.SNIPPET_LENGTH || 110;
  if (text.length > limit) {
    text = text.slice(0, limit - 1).trimEnd() + '…';
  }
  return text;
}
function fmtTime(ts) {
  const d = new Date(ts);
  const now = new Date();
  const sameDay = d.toDateString() === now.toDateString();
  if (sameDay) {
    return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
  }
  const oneWeek = 7 * 24 * 60 * 60 * 1000;
  if (now - d < oneWeek) {
    return d.toLocaleDateString([], { weekday: 'short' });
  }
  return d.toLocaleDateString([], { month: 'short', day: 'numeric', year: d.getFullYear() === now.getFullYear() ? undefined : 'numeric' });
}
let toastTimer;
function toast(msg) {
  const el = $('#toast');
  el.textContent = msg;
  el.classList.add('show');
  clearTimeout(toastTimer);
  toastTimer = setTimeout(() => el.classList.remove('show'), 2200);
}

/* ---------- (3) DATA MODEL (single source of truth) ---------- */
// The DOM is a projection of this model. Never write DOM -> model directly.
const DB = {
  notebooks: [],   // { id, name, collapsed, order }
  notes: [],       // { id, notebookId, title, body (HTML), tags: [], pinned, deleted, createdAt, updatedAt }
  sort: CONFIG.SORT_DEFAULT,
  selectedNoteId: null,
};

function createNotebook(name) {
  const nb = { id: uid(), name, collapsed: false, order: DB.notebooks.length };
  DB.notebooks.push(nb);
  return nb;
}
function createNote(notebookId) {
  const now = Date.now();
  const note = {
    id: uid(),
    notebookId: notebookId || (DB.notebooks[0] && DB.notebooks[0].id),
    title: '',
    body: '',
    tags: [],
    pinned: false,
    deleted: false,
    createdAt: now,
    updatedAt: now,
  };
  DB.notes.push(note);
  return note;
}
function findNote(id) { return DB.notes.find(n => n.id === id); }
function findNotebook(id) { return DB.notebooks.find(n => n.id === id); }
function allNotes() { return DB.notes; }
function getTags() {
  const set = new Set();
  DB.notes.forEach(n => n.tags.forEach(t => set.add(t)));
  return Array.from(set);
}

/* ---------- (4) PERSISTENCE ---------- */
function save() {
  try {
    localStorage.setItem(CONFIG.STORAGE_KEY, JSON.stringify(DB));
  } catch (e) { console.warn('Persist failed', e); }
}
// Debounced auto-save
let saveTimer;
function scheduleSave() {
  clearTimeout(saveTimer);
  saveTimer = setTimeout(() => { save(); }, CONFIG.AUTOSAVE_DEBOUNCE);
}
// Debounced content-specific save handled by editor layer.
function load() {
  let raw = null;
  try { raw = localStorage.getItem(CONFIG.STORAGE_KEY); } catch (e) {}
  if (!raw) return false;
  try {
    const data = JSON.parse(raw);
    if (data && Array.isArray(data.notebooks) && Array.isArray(data.notes)) {
      DB.notebooks = data.notebooks;
      DB.notes = data.notes;
      DB.sort = data.sort || CONFIG.SORT_DEFAULT;
      DB.selectedNoteId = data.selectedNoteId || null;
      return true;
    }
  } catch (e) { console.warn('Load parse failed', e); }
  return false;
}

/* ---------- (5) SANITIZER ---------- */
// Pure sanitizer: strips disallowed tags/attrs/scripts. Returns safe HTML string.
function sanitize(html) {
  if (!html) return '';
  const doc = new DOMParser().parseFromString('<div>' + html + '</div>', 'text/html');
  const root = doc.body.firstElementChild || doc.body;

  // Remove style, script, template, object, embed, iframe, form tags plus event handlers and javascript: urls
  const dangerTags = new Set(['STYLE','SCRIPT','TEMPLATE','OBJECT','EMBED','IFRAME','FORM','INPUT','LINK','META','NOSCRIPT']);
  const walk = (node) => {
    const children = Array.from(node.childNodes);
    children.forEach(child => {
      if (child.nodeType === 1) {
        const tag = child.tagName;
        if (dangerTags.has(tag)) { child.remove(); return; }
        // Remove dangerous attributes
        const attrs = Array.from(child.attributes);
        attrs.forEach(a => {
          const name = a.name.toLowerCase();
          if (name.startsWith('on')) { child.removeAttribute(a.name); return; }
          if (name === 'style') {
            // allow minimal styling only if present; strip to be safe (no urls)
            const css = (a.value || '').toLowerCase();
            if (css.includes('expression') || css.includes('javascript:') || css.includes('url(')) {
              child.removeAttribute('style');
            }
            return;
          }
          if (name === 'href' || name === 'src') {
            const val = (a.value || '').trim().toLowerCase();
            if (val.startsWith('javascript:') || val.startsWith('data:') || val.startsWith('vbscript:')) {
              child.removeAttribute(a.name);
            }
            return;
          }
          if (name === 'class' && !CONFIG.ALLOWED_ATTR.has('class')) {
            child.removeAttribute(a.name);
            return;
          }
          if (!CONFIG.ALLOWED_ATTR.has(name)) {
            child.removeAttribute(a.name);
          }
        });
        if (dangerTags.has(tag)) return;
        walk(child);
      } else if (child.nodeType === 3) {
        // text node — keep
      } else {
        child.remove();
      }
    });
  };
  walk(root);
  return root.innerHTML;
}
// Convert bold/italic/underline <b>/<i>/<u> to strong/em/em for consistency? Keep as-is; browser round-trips fine.

/* ---------- (6) RENDERING ---------- */
// Shared DOM refs
const els = {};
function cacheEls() {
  ['sidebar','sidebarHandle','listPanel','listHandle','editorPanel',
   'themeToggle','newNoteBtn','searchBox','globalSearch','searchClear',
   'navAll','countAll','notebooks','navTrash','countTrash','listTitle',
   'sortToggle','noteList','editorHeader','breadcrumb','noteTitleInput',
   'saveIndicator','pinBtn','moveBtn','dupBtn','delBtn','moreBtn','moreMenu',
   'toolbar','tagEditor','existingTags','tagInput','emptyState','emptyNewBtn',
   'editorContainer','editor','toast'].forEach(id => { els[id] = document.getElementById(id); });
}
// Cache elements immediately so top-level listeners below have refs.
cacheEls();

// Global state for rendering
const state = {
  view: 'all',          // 'all' | 'trash' | notebook id
  selectedNoteId: null,
  sort: CONFIG.SORT_DEFAULT,
  search: '',
  tagFilter: null,      // tag string to filter by
};

/* ---- Sidebar (notebooks) ---- */
function renderSidebar() {
  // All Notes count
  const active = state.view === 'all' ? 'active' : '';
  els.navAll.classList.toggle('active', !!active);
  const allCount = DB.notes.filter(n => !n.deleted).length;
  els.countAll.textContent = allCount;

  // Trash
  const trashActive = state.view === 'trash' ? 'active' : '';
  els.navTrash.classList.toggle('active', !!trashActive);
  const trashCount = DB.notes.filter(n => n.deleted).length;
  els.countTrash.textContent = trashCount;

  // Notebooks
  const wrap = els.notebooks;
  const collapsedNotebookIds = new Set();
  wrap.querySelectorAll('.notebook').forEach(n => {
    const nbId = n.dataset.notebookId;
    const data = findNotebook(nbId);
    if (data) {
      n.classList.toggle('collapsed', !!data.collapsed);
      n.dataset.tag = data.tags || '';
    }
  });

  const sortedNB = DB.notebooks.slice().sort((a,b) => a.order - b.order);
  wrap.innerHTML = sortedNB.map(nb => {
    const isActive = state.view === nb.id ? 'active' : '';
    const collapsed = nb.collapsed ? 'collapsed' : '';
    const count = DB.notes.filter(n => n.notebookId === nb.id && !n.deleted).length;
    return `
      <div class="nav-item notebook-row ${collapsed}" data-notebook-row="${nb.id}">
        <span class="chevron">▾</span>
        <span class="nav-ico">📓</span>
        <span class="nav-label" data-notebook-label="${nb.id}">${escapeHtml(nb.name)}</span>
        <span class="nav-acts">
          <button class="nav-act" data-nb-add="${nb.id}" title="New note here">+</button>
          <button class="nav-act" data-nb-rename="${nb.id}" title="Rename">✎</button>
          <button class="nav-act" data-nb-del="${nb.id}" title="Delete notebook">🗑</button>
        </span>
        <span class="nav-count">${count}</span>
      </div>
      <div class="notebook-notes" data-notebook-notes="${nb.id}">
        ${DB.notes.filter(n => n.notebookId === nb.id && !n.deleted)
          .sort((a,b)=>a.order - b.order)
          .map(n => renderSubNavItem(n))
          .join('')}
      </div>`;
  }).join('');

  // Attach collapse + label events
  wrap.querySelectorAll('.notebook-row').forEach(row => {
    const nbId = row.dataset.notebookRow;
    const nb = findNotebook(nbId);
    const notesEl = wrap.querySelector(`[data-notebook-notes="${nbId}"]`);
    row.addEventListener('click', (e) => {
      if (e.target.closest('.nav-acts')) return;
      if (e.target.dataset.notebookLabel) {
        selectNotebook(nbId);
        return;
      }
      if (nb) {
        nb.collapsed = !nb.collapsed;
        if (nb.collapsed) {
          notesEl.style.height = notesEl.scrollHeight + 'px';
          requestAnimationFrame(() => { notesEl.style.height = '0px'; });
        } else {
          notesEl.style.height = 'auto';
        }
        nb.classList && nb.classList.toggle('collapsed', nb.collapsed);
        row.classList.toggle('collapsed', nb.collapsed);
        save();
      }
    });
  });
  // Sub-note item clicks
  wrap.querySelectorAll('[data-sub-note]').forEach(item => {
    item.addEventListener('click', () => selectNote(item.dataset.subNote));
  });
  // Actions
  wrap.querySelectorAll('[data-nb-add]').forEach(b => b.addEventListener('click', (e) => {
    e.stopPropagation();
    const nb = findNotebook(b.dataset.nbAdd);
    const n = createNote(nb.id);
    noteOrder(n);
    renderNoteList();
    selectNote(n.id);
    save();
  }));
  wrap.querySelectorAll('[data-nb-rename]').forEach(b => b.addEventListener('click', (e) => {
    e.stopPropagation();
    renameNotebookInline(b.dataset.nbRename);
  }));
  wrap.querySelectorAll('[data-nb-del]').forEach(b => b.addEventListener('click', (e) => {
    e.stopPropagation();
    deleteNotebook(b.dataset.nbDel);
  }));
}
function renderSubNavItem(note) {
  const isActive = state.selectedNoteId === note.id ? 'active' : '';
  return `<div class="nav-item sub-note ${isActive}" data-sub-note="${note.id}">
    <span class="nav-ico" style="width:16px;text-align:center;color:var(--text-muted);font-size:11px">${note.pinned ? '📌' : '•'}</span>
    <span class="nav-label">${escapeHtml(note.title || 'Untitled')}</span>
  </div>`;
}

function selectNotebook(nbId) {
  state.view = nbId;
  state.tagFilter = null;
  const nb = findNotebook(nbId);
  els.listTitle.textContent = nb ? nb.name : 'Notebook';
  renderSidebar();
  renderNoteList();
}

function renameNotebookInline(nbId) {
  const nb = findNotebook(nbId);
  if (!nb) return;
  const label = els.notebooks.querySelector(`[data-notebook-label="${nbId}"]`);
  if (!label) return;
  label.setAttribute('contenteditable', 'true');
  label.focus();
  document.getSelection().selectAllChildren(label);
  const done = (commit) => {
    label.removeAttribute('contenteditable');
    const newName = label.textContent.trim() || nb.name;
    nb.name = newName;
    if (state.view === nbId) els.listTitle.textContent = nb.name;
    save();
    renderNoteList();
    label.textContent = nb.name;
  };
  label.addEventListener('blur', () => done(true), { once: true });
  label.addEventListener('keydown', (e) => {
    if (e.key === 'Enter') { e.preventDefault(); label.blur(); }
    if (e.key === 'Escape') { e.preventDefault(); label.blur(); }
  });
}

function deleteNotebook(nbId) {
  const nb = findNotebook(nbId);
  if (!nb) return;
  const noteCount = DB.notes.filter(n => n.notebookId === nbId).length;
  const msg = noteCount
    ? `Delete notebook "${nb.name}"? ${noteCount} notes will be moved to the first notebook.`
    : `Delete notebook "${nb.name}"?`;
  if (!confirm(msg)) return;
  const firstOther = DB.notebooks.find(n => n.id !== nbId) || createNotebook('Notes');
  DB.notes.filter(n => n.notebookId === nbId).forEach(n => n.notebookId = firstOther.id);
  DB.notebooks = DB.notebooks.filter(n => n.id !== nbId);
  if (state.view === nbId) {
    state.view = 'all';
    els.listTitle.textContent = 'All Notes';
  }
  save();
  renderSidebar();
  renderNoteList();
}

/* ---- Note List ---- */
function getVisibleNotes() {
  let notes;
  if (state.view === 'trash') {
    notes = DB.notes.filter(n => n.deleted);
  } else if (state.view === 'all') {
    notes = DB.notes.filter(n => !n.deleted);
  } else {
    notes = DB.notes.filter(n => !n.deleted && n.notebookId === state.view);
  }
  // tag filter
  if (state.tagFilter) {
    notes = notes.filter(n => n.tags.includes(state.tagFilter));
  }
  // text search across title + body text (not raw HTML)
  if (state.search && state.search.trim()) {
    const q = state.search.trim().toLowerCase();
    notes = notes.filter(n => {
      const title = (n.title || '').toLowerCase();
      const body = extractText(n.body).toLowerCase();
      return title.includes(q) || body.includes(q);
    });
  }
  return notes;
}

function sortNotes(notes) {
  const arr = notes.slice();
  // pinned always float to top
  arr.sort((a, b) => {
    if (a.pinned !== b.pinned) return a.pinned ? -1 : 1;
    if (state.sort === 'created') return b.createdAt - a.createdAt;
    if (state.sort === 'alpha') {
      const t = (a.title || 'Untitled').localeCompare(b.title || 'Untitled');
      return t !== 0 ? t : b.updatedAt - a.updatedAt;
    }
    return b.updatedAt - a.updatedAt; // modified
  });
  return arr;
}

function renderNoteList() {
  const notes = sortNotes(getVisibleNotes());
  const list = els.noteList;

  // update counts in sidebar
  if (state.view === 'all') els.countAll.textContent = DB.notes.filter(n => !n.deleted).length;
  els.countTrash.textContent = DB.notes.filter(n => n.deleted).length;

  if (notes.length === 0) {
    list.innerHTML = `<div class="list-empty">
      <div class="le-glyph">${state.search ? '🔍' : (state.view === 'trash' ? '🗑' : '📭')}</div>
      <p>${state.search ? 'No notes match your search.' : (state.view === 'trash' ? 'Trash is empty.' : 'No notes here yet.')}</p>
    </div>`;
    return;
  }

  const q = state.search.trim().toLowerCase();

  list.innerHTML = notes.map(n => {
    const isActive = state.selectedNoteId === n.id ? 'active' : '';
    const snippet = extractSnippet(n.body);
    const snippetHtml = highlightText(escapeHtml(snippet), q) || '<em style="opacity:.6">No content</em>';
    const time = fmtTime(n.updatedAt);
    const tags = n.tags.slice(0, 4).map(t =>
      `<span class="tag-chip ${state.tagFilter === t ? 'active' : ''}" data-tag="${escapeHtml(t)}">#${escapeHtml(t)}</span>`
    ).join('');
    return `
      <div class="note-card ${isActive}" data-note="${n.id}" draggable="true">
        <div class="note-card-title">
          ${n.pinned ? '<span class="pin-ico">📌</span>' : ''}
          <span>${escapeHtml(n.title || 'Untitled')}</span>
        </div>
        <div class="note-card-snippet">${snippetHtml}</div>
        <div class="note-card-meta">
          <span class="note-card-time">${time}</span>
          <div class="note-card-tags">${tags}</div>
        </div>
      </div>`;
  }).join('');

  // Card clicks
  list.querySelectorAll('.note-card').forEach(card => {
    card.addEventListener('click', (e) => {
      if (e.target.dataset.tag) {
        state.tagFilter = e.target.dataset.tag;
        els.listTitle.textContent = '#' + state.tagFilter;
        renderSidebar();
        renderNoteList();
        return;
      }
      selectNote(card.dataset.note);
    });
    card.addEventListener('dragstart', (e) => {
      els.dragNoteId = card.dataset.note;
      e.dataTransfer.effectAllowed = 'move';
      e.dataTransfer.setData('text/plain', card.dataset.note);
    });
    card.addEventListener('dragend', () => { els.dragNoteId = null; });
  });
}

// Highlight matched text within an already-escaped string.
function highlightText(html, q) {
  if (!q) return html;
  const words = q.split(/\s+/).filter(Boolean);
  if (words.length === 0) return html;
  const escaped = words.map(w => w.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
  const re = new RegExp('(' + escaped + ')', 'gi');
  return html.replace(re, '<mark>$1</mark>');
}

/* ---- Sort toggle ---- */
function renderSortButtons() {
  $$('#sortToggle button').forEach(b => {
    b.classList.toggle('active', b.dataset.sort === state.sort);
  });
}
$$('#sortToggle button').forEach(b => {
  b.addEventListener('click', () => {
    state.sort = b.dataset.sort;
    DB.sort = state.sort;
    save();
    renderSortButtons();
    renderNoteList();
  });
});

/* ---- Breadcrumb ---- */
function renderBreadcrumb() {
  const note = findNote(state.selectedNoteId);
  if (!note) { els.breadcrumb.innerHTML = ''; return; }
  const nb = findNotebook(note.notebookId);
  const all = `<span data-view="all">All Notes</span>`;
  const nbSpan = nb ? `<span data-view="${nb.id}">${escapeHtml(nb.name)}</span>` : '';
  els.breadcrumb.innerHTML = `${all}${nbSpan ? ' / ' + nbSpan : ''} / <span>${escapeHtml(note.title || 'Untitled')}</span>`;
  els.breadcrumb.querySelectorAll('[data-view]').forEach(s => {
    s.addEventListener('click', () => {
      if (s.dataset.view === 'all') { state.view = 'all'; els.listTitle.textContent = 'All Notes'; }
      else selectNotebook(s.dataset.view);
      renderSidebar();
      renderNoteList();
    });
  });
}

/* ---------- NOTE SELECTION + EDITOR ---------- */
let activeNote = null;

function selectNote(noteId) {
  state.selectedNoteId = noteId;
  DB.selectedNoteId = noteId;
  activeNote = findNote(noteId);
  // Update sidebar active state
  $$('#notebooks .sub-note').forEach(el => el.classList.toggle('active', el.dataset.subNote === noteId));
  // Show editor, hide empty state
  els.emptyState.classList.add('hidden');
  els.editorContainer.classList.remove('hidden');
  els.editorHeader.classList.remove('hidden');
  els.tagEditor.classList.remove('hidden');

  if (!activeNote) {
    els.emptyState.classList.remove('hidden');
    els.editorContainer.classList.add('hidden');
    return;
  }

  els.noteTitleInput.value = activeNote.title;
  els.editor.innerHTML = activeNote.body;
  renderBreadcrumb();
  renderTags();
  saveIndicator('saved');
  updateToolbar();
  renderNoteList(); // reflect selection highlight
  els.editor.focus();
}

function saveIndicator(status) {
  els.saveIndicator.textContent = status === 'saved' ? 'Saved' : 'Saving…';
  els.saveIndicator.classList.toggle('saved', status === 'saved');
  els.saveIndicator.classList.toggle('saving', status === 'saving');
}

/* ---- Tags ---- */
function renderTags() {
  if (!activeNote) { els.tagEditor.classList.add('hidden'); return; }
  const wrap = els.existingTags;
  wrap.innerHTML = activeNote.tags.map(t =>
    `<span class="tag-pill clickable" data-tag="${escapeHtml(t)}">#${escapeHtml(t)}<button class="tag-x" data-tagx="${escapeHtml(t)}">×</button></span>`
  ).join('');
  wrap.querySelectorAll('[data-tagx]').forEach(b => b.addEventListener('click', () => {
    activeNote.tags = activeNote.tags.filter(t => t !== b.dataset.tagx);
    activeNote.updatedAt = Date.now();
    save(); scheduleSave();
    renderTags();
    renderNoteList();
  }));
  wrap.querySelectorAll('[data-tag]').forEach(chip => chip.addEventListener('click', () => {
    state.tagFilter = chip.dataset.tag;
    els.listTitle.textContent = '#' + state.tagFilter;
    renderSidebar();
    renderNoteList();
  }));
}
function addTag(tag) {
  if (!activeNote) return;
  tag = tag.trim().replace(/^#+/, '').toLowerCase();
  if (!tag) return;
  if (!activeNote.tags.includes(tag)) {
    activeNote.tags.push(tag);
    activeNote.updatedAt = Date.now();
    save(); scheduleSave();
    renderTags();
    renderNoteList();
  }
}
els.tagInput.addEventListener('keydown', (e) => {
  if (e.key === 'Enter') {
    e.preventDefault();
    addTag(els.tagInput.value);
    els.tagInput.value = '';
  }
});

/* ---- Editor change handling + autosave ---- */
let saveTimer2;
function editorChange() {
  if (!activeNote) return;
  // Normalize list styling: ensure checklist ul has class
  normalizeChecklists();
  const body = sanitize(els.editor.innerHTML);
  activeNote.body = body;
  activeNote.updatedAt = Date.now();
  saveIndicator('saving');
  clearTimeout(saveTimer2);
  saveTimer2 = setTimeout(() => {
    save();
    saveIndicator('saved');
    renderNoteList();
  }, CONFIG.AUTOSAVE_DEBOUNCE);
}
els.editor.addEventListener('input', editorChange);

/* title input */
let titleTimer;
els.noteTitleInput.addEventListener('input', () => {
  if (!activeNote) return;
  activeNote.title = els.noteTitleInput.value;
  activeNote.updatedAt = Date.now();
  clearTimeout(titleTimer);
  titleTimer = setTimeout(() => {
    save();
    renderBreadcrumb();
    renderNoteList();
    renderSidebar();
  }, CONFIG.AUTOSAVE_DEBOUNCE);
});

/* ---- Checklist normalization: ensure checked state persists ---- */
function normalizeChecklists() {
  const uls = $$('#editor ul.checklist');
  uls.forEach(ul => {
    const lis = ul.querySelectorAll('li');
    lis.forEach(li => {
      let cb = li.querySelector(':scope > .checkbox');
      if (!cb) {
        cb = document.createElement('span');
        cb.className = 'checkbox';
        cb.setAttribute('contenteditable', 'false');
        li.insertBefore(cb, li.firstChild);
      }
      const checked = cb.classList.contains('checked');
      cb.setAttribute('data-checked', checked ? 'true' : 'false');
      cb.textContent = checked ? '✓' : '';
      // Ensure text node is the check-text
      let txt = li.querySelector(':scope > .check-text');
      if (!txt) {
        txt = document.createElement('span');
        txt.className = 'check-text';
        txt.setAttribute('contenteditable', 'true');
        li.appendChild(txt);
      }
      txt.setAttribute('contenteditable', 'true');
    });
  });
}

/* ---------- TOOLBAR + FORMATTING COMMANDS ---------- */
function isFocusedInEditor() {
  return document.activeElement === els.editor || els.editor.contains(document.activeElement);
}

function execCmd(cmd, arg) {
  els.editor.focus();
  document.execCommand(cmd, false, arg || null);
  updateToolbar();
}

// Heading conversion uses formatBlock
function applyHeading(tag) {
  els.editor.focus();
  document.execCommand('formatBlock', false, tag);
  updateToolbar();
}

// Apply inline code to selection by wrapping in <code>
function applyCode() {
  els.editor.focus();
  const sel = window.getSelection();
  if (!sel.rangeCount || sel.isCollapsed) return;
  const range = sel.getRangeAt(0);
  const selectedText = range.toString();
  if (!selectedText.trim()) return;
  const code = document.createElement('code');
  code.textContent = selectedText;
  range.deleteContents();
  range.insertNode(code);
  // Collapse selection to end of the code span
  range.setStartAfter(code);
  range.collapse(true);
  sel.removeAllRanges();
  sel.addRange(range);
  updateToolbar();
}

function applyHr() {
  els.editor.focus();
  document.execCommand('insertHorizontalRule', false);
}

// List commands: use formatBlock ul/ol then normalize, or create new list
function applyList(type) {
  els.editor.focus();
  const sel = window.getSelection();
  const atLineStart = isAtLineStart();
  // If at line start with empty/short content, use formatBlock
  if (atLineStart) {
    document.execCommand('formatBlock', false, type === 'numbered' ? 'ol' : 'ul');
  } else {
    // Wrap current block into a list
    const block = getBlockElement();
    if (block) {
      const list = document.createElement(type === 'numbered' ? 'ol' : 'ul');
      const li = document.createElement('li');
      li.innerHTML = block.innerHTML || block.textContent;
      list.appendChild(li);
      block.replaceWith(list);
    }
  }
  if (type === 'checklist') {
    // Convert the last ul into checklist
    const uls = $$('#editor ul');
    const last = uls[uls.length - 1];
    if (last) {
      last.classList.add('checklist');
      const li = last.querySelector(':scope > li:last-child');
      if (li && !li.querySelector('.checkbox')) {
        const cb = document.createElement('span');
        cb.className = 'checkbox';
        cb.setAttribute('contenteditable', 'false');
        li.insertBefore(cb, li.firstChild);
        let txt = li.querySelector('.check-text');
        if (!txt) {
          txt = document.createElement('span');
          txt.className = 'check-text';
          txt.setAttribute('contenteditable', 'true');
          li.appendChild(txt);
        }
        if (!txt.textContent) txt.focus();
      }
    }
  }
  updateToolbar();
}

// Toggle helpers for list state
function currentBlockIs(tagName) {
  let node = window.getSelection().anchorNode;
  while (node && node !== els.editor) {
    if (node.nodeType === 1 && node.tagName.toLowerCase() === tagName) return true;
    node = node.parentNode;
  }
  return false;
}

function updateToolbar() {
  const active = new Set();
  if (isFocusedInEditor()) {
    const checks = {
      bold: () => document.queryCommandState('bold'),
      italic: () => document.queryCommandState('italic'),
      underline: () => document.queryCommandState('underline'),
      strikethrough: () => document.queryCommandState('strikethrough'),
    };
    Object.keys(checks).forEach(k => { if (checks[k]()) active.add(k); });
    if (currentBlockIs('H1')) active.add('h1');
    if (currentBlockIs('H2')) active.add('h2');
    if (currentBlockIs('H3')) active.add('h3');
    if (currentBlockIs('BLOCKQUOTE')) active.add('quote');
    if (currentBlockIs('UL')) active.add('bullet');
    if (currentBlockIs('OL')) active.add('numbered');
    const sel = window.getSelection();
    let n = sel.anchorNode;
    while (n && n !== els.editor) {
      if (n.nodeType === 1 && n.tagName.toLowerCase() === 'CODE') active.add('code');
      n = n.parentNode;
    }
  }
  els.toolbar.querySelectorAll('.tool-btn[data-cmd]').forEach(btn => {
    btn.classList.toggle('active', active.has(btn.dataset.cmd));
  });
}

function toggleChecklistItem(li) {
  const cb = li.querySelector('.checkbox');
  if (!cb) return;
  const checked = cb.classList.toggle('checked');
  cb.setAttribute('data-checked', checked ? 'true' : 'false');
  cb.textContent = checked ? '✓' : '';
  if (!checked) cb.textContent = '';
  saveIndicator('saving');
  clearTimeout(saveTimer2);
  saveTimer2 = setTimeout(() => { save(); }, 200);
  updateToolbar();
}

/* ---------- LINE HELPERS ---------- */
function getBlockElement() {
  let node = window.getSelection().anchorNode;
  node = node || els.editor;
  while (node && node !== els.editor) {
    if (node.nodeType === 1) return node;
    node = node.parentNode;
  }
  return els.editor;
}
function isAtLineStart() {
  const sel = window.getSelection();
  if (!sel.rangeCount) return true;
  const range = sel.getRangeAt(0);
  const block = getBlockElement();
  // Compute offset within block
  let offset = 0;
  const walker = document.createTreeWalker(block, NodeFilter.SHOW_TEXT);
  let textNode;
  let prevLen = 0;
  const pos = range.startContainer;
  if (pos === block) {
    // selection directly in block (empty)
    return true;
  }
  // Find previous sibling text content length
  let before = 0;
  let node = block.firstChild;
  while (node) {
    if (node === range.startContainer) { before += range.startOffset; break; }
    if (node.nodeType === 3) before += node.textContent.length;
    node = node.nextSibling;
  }
  // Check that everything before on this line is whitespace
  const text = (range.startContainer.textContent || '');
  const fullBefore = text.slice(0, range.startOffset);
  // get text from block start
  const blockText = extractText(block);
  // Simpler: check the current line's leading portion
  const idx = blockText.indexOf('\n');
  // find the line containing the cursor
  let lineStart = 0;
  let posInBlock = before;
  // rebuild position
  let accumulated = 0;
  let lineStartIndex = 0;
  const tw = document.createTreeWalker(block, NodeFilter.SHOW_TEXT);
  let tn;
  while ((tn = tw.nextNode())) {
    const len = tn.textContent.length;
    if (accumulated + len >= posInBlock) {
      lineStartIndex = accumulated;
      break;
    }
    accumulated += len;
  }
  const lineBefore = blockText.slice(lineStartIndex, before);
  return /^\s*$/.test(lineBefore);
}

/* ---------- EDITOR KEY HANDLING (markdown shortcuts, lists, etc.) ---------- */
// Regex for markdown-style shortcuts. Anchored to line start.
const MD_PATTERNS = [
  { type: 'checklist', re: /^\s*\[\s?\]\s(.*)$/i },
  { type: 'bullet', re: /^\s*[-*+]\s(.*)$/ },
  { type: 'numbered', re: /^\s*(\d+)\.\s(.*)$/ },
  { type: 'quote', re: /^\s*>\s?(.*)$/ },
  { type: 'h1', re: /^#{1}\s(.*)$/ },
  { type: 'h2', re: /^#{2}\s(.*)$/ },
  { type: 'h3', re: /^#{3}\s(.*)$/ },
];

els.editor.addEventListener('keydown', (e) => {
  if (e.ctrlKey || e.metaKey) return; // let browser handle undo/redo/shift+undo

  const key = e.key;
  // Enter: handle list continuation + markdown shortcut + empty-item exit
  if (key === 'Enter') {
    const sel = window.getSelection();
    const atStart = isAtLineStart();
    const block = getBlockElement();
    const blockTag = block.tagName ? block.tagName.toLowerCase() : '';

    // Markdown shortcut on space handled by input event; here handle Enter shortcuts
    if (atStart) {
      const text = extractText(block);
      // Try to match a markdown shortcut at the start
      const matched = matchShortcut(text);
      if (matched) {
        // Defer: let the input event handle it (space triggers conversion)
        // For Enter with partial text we do nothing special here.
      }
    }

    // List continuation / empty item exit
    if (blockTag === 'li') {
      const ul = block.parentElement;
      const isEmpty = extractText(block).trim() === '';

      if (isEmpty) {
        // Empty list item: Enter exits the list
        e.preventDefault();
        exitList(block);
        return;
      } else {
        // Continue the list
        e.preventDefault();
        continueList(block, ul);
        return;
      }
    }
  }

  // Backspace on empty list item: exit list
  if (key === 'Backspace') {
    const sel = window.getSelection();
    const block = getBlockElement();
    const blockTag = block.tagName ? block.tagName.toLowerCase() : '';
    if (blockTag === 'li' && extractText(block).trim() === '' && isAtLineStart()) {
      const ul = block.parentElement;
      e.preventDefault();
      exitList(block);
      return;
    }
  }
});

function matchShortcut(text) {
  const t = text.replace(/^/, '');
  for (const p of MD_PATTERNS) {
    const m = p.re.exec(text);
    if (m) return { type: p.type, rest: m[1] };
  }
  return null;
}

/* ---------- EDITOR INPUT (markdown shortcut conversion on space) ---------- */
els.editor.addEventListener('input', (e) => {
  normalizeChecklists();
  const sel = window.getSelection();
  if (sel.rangeCount === 0) return;
  const range = sel.getRangeAt(0);
  const block = getBlockElement();
  if (block !== els.editor && block.tagName && !['UL','OL','LI','P','H1','H2','H3','BLOCKQUOTE','DIV'].includes(block.tagName.toUpperCase())) {
    return;
  }
  const text = extractText(block);
  // Match a markdown shortcut anchored to line start (mid-sentence "#"/">" etc. won't match).
  const matched = matchShortcut(text);
  if (!matched) return;
  // Only convert when the caret sits right after the trailing space of the marker.
  const markerEnd = text.indexOf(' ', 1) === -1 ? text.length : text.indexOf(' ', 1);
  const caretAtEnd = range.startOffset >= markerEnd;
  if (!caretAtEnd) return;
  convertShortcut(block, matched.type);
  e.stopPropagation();
});

function convertShortcut(block, type) {
  const text = extractText(block);
  let newTag, content;
  if (type === 'bullet') {
    newTag = 'ul'; content = text.replace(/^\s*[-*+]\s/, '');
  } else if (type === 'numbered') {
    newTag = 'ol'; content = text.replace(/^\s*\d+\.\s/, '');
  } else if (type === 'quote') {
    newTag = 'blockquote'; content = text.replace(/^\s*>\s/, '');
  } else if (type === 'h1') {
    newTag = 'h1'; content = text.replace(/^#\s/, '');
  } else if (type === 'h2') {
    newTag = 'h2'; content = text.replace(/^##\s/, '');
  } else if (type === 'h3') {
    newTag = 'h3'; content = text.replace(/^###\s/, '');
  } else if (type === 'checklist') {
    newTag = 'ul'; content = text.replace(/^\s*\[\s?\]\s/, '');
  }
  if (content === undefined) return;
  const newBlock = document.createElement(newTag);
  if (newTag === 'ul') newBlock.className = 'checklist';
  newBlock.innerHTML = escapeHtml(content).length ? escapeHtml(content) : '';
  // Preserve caret position roughly
  const caretPos = extractText(block).length;
  const old = block;
  old.replaceWith(newBlock);
  restoreCaret(newBlock, Math.min(caretPos, extractText(newBlock).length));
  if (newTag === 'ul') {
    // add checklist checkbox
    const li = newBlock.querySelector(':scope > li');
    if (li) {
      const cb = document.createElement('span');
      cb.className = 'checkbox';
      cb.setAttribute('contenteditable', 'false');
      li.insertBefore(cb, li.firstChild);
      let txt = li.querySelector('.check-text');
      if (!txt) {
        txt = document.createElement('span');
        txt.className = 'check-text';
        txt.setAttribute('contenteditable', 'true');
        li.appendChild(txt);
      }
      if (txt.textContent) txt.focus();
    }
  }
  normalizeChecklists();
  updateToolbar();
}

function restoreCaret(block, offset) {
  const range = document.createRange();
  const textNode = block.lastChild;
  if (textNode && textNode.nodeType === 3) {
    offset = Math.min(offset, textNode.textContent.length);
    range.setStart(textNode, offset);
  } else {
    range.setStart(block, 0);
  }
  range.collapse(true);
  const sel = window.getSelection();
  sel.removeAllRanges();
  sel.addRange(range);
}

/* ---------- LIST CONTINUE / EXIT ---------- */
function continueList(li, ul) {
  const newLi = document.createElement('li');
  newLi.innerHTML = '';
  const listType = ul.tagName.toLowerCase();
  if (listType === 'ul' && ul.classList.contains('checklist')) {
    const cb = document.createElement('span');
    cb.className = 'checkbox';
    cb.setAttribute('contenteditable', 'false');
    newLi.appendChild(cb);
    const txt = document.createElement('span');
    txt.className = 'check-text';
    txt.setAttribute('contenteditable', 'true');
    newLi.appendChild(txt);
    txt.focus();
  } else {
    newLi.focus();
  }
  ul.insertBefore(newLi, li.nextSibling);
  setCaretAtStart(newLi);
  normalizeChecklists();
  updateToolbar();
}
function exitList(li) {
  const ul = li.parentElement;
  const p = document.createElement('p');
  p.innerHTML = '';
  li.replaceWith(p);
  setCaretAtStart(p);
  // Remove empty list if now empty
  if (!ul.querySelector('li')) ul.remove();
  updateToolbar();
}
function setCaretAtStart(node) {
  const range = document.createRange();
  const textNode = node.firstChild;
  if (textNode && textNode.nodeType === 3 && textNode.textContent.length) {
    range.setStart(textNode, Math.min(1, textNode.textContent.length));
  } else {
    range.setStart(node, 0);
  }
  range.collapse(true);
  const sel = window.getSelection();
  sel.removeAllRanges();
  sel.addRange(range);
  node.focus();
}

/* ---------- PASTE SANITIZATION ---------- */
els.editor.addEventListener('paste', (e) => {
  const html = e.clipboardData.getData('text/html');
  const plain = e.clipboardData.getData('text/plain');
  if (html) {
    e.preventDefault();
    const safe = sanitize(html);
    document.execCommand('insertHTML', false, safe);
  } else {
    // plain text: insert as escaped paragraphs
    e.preventDefault();
    const safe = escapeHtml(plain).replace(/\n\n+/g, '</p><p>').replace(/\n/g, '<br>');
    document.execCommand('insertHTML', false, '<p>' + safe + '</p>');
  }
});

/* ---------- TOOLBAR CLICKS ---------- */
els.toolbar.querySelectorAll('.tool-btn[data-cmd]').forEach(btn => {
  btn.addEventListener('click', () => {
    const cmd = btn.dataset.cmd;
    switch (cmd) {
      case 'bold': execCmd('bold'); break;
      case 'italic': execCmd('italic'); break;
      case 'underline': execCmd('underline'); break;
      case 'strikethrough': execCmd('strikethrough'); break;
      case 'code': applyCode(); break;
      case 'h1': applyHeading('h1'); break;
      case 'h2': applyHeading('h2'); break;
      case 'h3': applyHeading('h3'); break;
      case 'quote': applyHeading('blockquote'); break;
      case 'hr': applyHr(); break;
      case 'bullet': applyList('bullet'); break;
      case 'numbered': applyList('numbered'); break;
      case 'checklist': applyList('checklist'); break;
      case 'undo': execCmd('undo'); break;
      case 'redo': execCmd('redo'); break;
    }
  });
});

/* Clicking checklist toggles it */
els.editor.addEventListener('click', (e) => {
  const cb = e.target.closest('.checkbox');
  if (cb) {
    e.preventDefault();
    e.stopPropagation();
    toggleChecklistItem(cb.closest('li'));
  }
});

/* ---------- (9) SEARCH ---------- */
let searchTimer;
els.globalSearch.addEventListener('input', () => {
  clearTimeout(searchTimer);
  const val = els.globalSearch.value;
  els.searchBox.classList.toggle('has-text', val.length > 0);
  searchTimer = setTimeout(() => {
    state.search = val;
    renderNoteList();
  }, 120);
});
els.searchClear.addEventListener('click', () => {
  els.globalSearch.value = '';
  els.searchBox.classList.remove('has-text');
  state.search = '';
  renderNoteList();
  els.globalSearch.focus();
});

/* ---------- (10) ACTIONS ---------- */
function createNoteFromButton() {
  let notebookId;
  if (state.view && state.view !== 'all' && state.view !== 'trash') {
    notebookId = state.view;
  } else if (state.selectedNoteId) {
    notebookId = findNote(state.selectedNoteId).notebookId;
  } else {
    notebookId = DB.notebooks[0] && DB.notebooks[0].id;
    if (!notebookId) notebookId = createNotebook('Notes').id;
  }
  const n = createNote(notebookId);
  noteOrder(n);
  renderSidebar();
  renderNoteList();
  selectNote(n.id);
  els.noteTitleInput.focus();
}
function noteOrder(note) {
  const siblings = DB.notes.filter(n => n.notebookId === note.notebookId);
  note.order = siblings.length;
}

els.newNoteBtn.addEventListener('click', createNoteFromButton);
els.emptyNewBtn.addEventListener('click', createNoteFromButton);

function pinToggle() {
  if (!activeNote) return;
  activeNote.pinned = !activeNote.pinned;
  activeNote.updatedAt = Date.now();
  save();
  saveIndicator('saved');
  updatePinBtn();
  renderSidebar();
  renderNoteList();
}
function updatePinBtn() {
  if (activeNote) {
    els.pinBtn.textContent = activeNote.pinned ? '★' : '☆';
    els.pinBtn.style.color = activeNote.pinned ? 'var(--pin)' : '';
  }
}
els.pinBtn.addEventListener('click', pinToggle);

function duplicateNote() {
  if (!activeNote) return;
  const copy = JSON.parse(JSON.stringify(activeNote));
  const newId = uid();
  const idx = DB.notes.indexOf(activeNote);
  const dup = {
    ...copy,
    id: newId,
    title: activeNote.title + ' (copy)',
    createdAt: Date.now(),
    updatedAt: Date.now(),
    pinned: false,
    deleted: false,
  };
  DB.notes.splice(idx + 1, 0, dup);
  save();
  renderSidebar();
  renderNoteList();
  selectNote(newId);
  toast('Note duplicated');
}
els.dupBtn.addEventListener('click', duplicateNote);

function deleteNote() {
  if (!activeNote) return;
  const wasTrash = state.view === 'trash';
  if (wasTrash) {
    if (!confirm('Permanently delete this note? This cannot be undone.')) return;
    DB.notes = DB.notes.filter(n => n.id !== activeNote.id);
    toast('Permanently deleted');
  } else {
    if (!confirm('Move this note to Trash?')) return;
    activeNote.deleted = true;
    activeNote.updatedAt = Date.now();
    toast('Moved to Trash');
  }
  const idx = DB.notes.indexOf(activeNote);
  DB.selectedNoteId = null;
  state.selectedNoteId = null;
  activeNote = null;
  els.emptyState.classList.remove('hidden');
  els.editorContainer.classList.add('hidden');
  els.tagEditor.classList.add('hidden');
  els.breadcrumb.innerHTML = '';
  save();
  renderSidebar();
  renderNoteList();
  if (idx > 0) {
    const prev = DB.notes[idx - 1];
    if (prev && !prev.deleted) selectNote(prev.id);
  }
}
els.delBtn.addEventListener('click', deleteNote);

/* ---- Move menu ---- */
function buildMoveMenu() {
  const items = DB.notebooks.filter(nb => nb.id !== (activeNote && activeNote.notebookId))
    .sort((a,b) => a.order - b.order);
  els.moreMenu.innerHTML = `
    <div class="dd-heading">Move to</div>
    ${items.length ? items.map(nb =>
      `<button class="dd-item ${activeNote && activeNote.notebookId === nb.id ? 'active-notebook' : ''}" data-move="${nb.id}">
        <span class="dd-ico">📓</span>${escapeHtml(nb.name)}
      </button>`).join('') : '<div class="dd-item" disabled>No notebooks</div>'}
    <div class="dd-sep"></div>
    <button class="dd-item" data-move-current><span class="dd-ico">📓</span>Current notebook</button>
    <div class="dd-sep"></div>
    <button class="dd-item danger" data-delete-note><span class="dd-ico">🗑</span>${state.view === 'trash' ? 'Permanently delete' : 'Delete to Trash'}</button>
  `;
  els.moreMenu.querySelectorAll('[data-move]').forEach(b => b.addEventListener('click', () => {
    if (!activeNote) return;
    activeNote.notebookId = b.dataset.move;
    activeNote.updatedAt = Date.now();
    save();
    toast('Moved to notebook');
    renderSidebar();
    renderNoteList();
    renderBreadcrumb();
    closeMoveMenu();
  }));
  els.moreMenu.querySelectorAll('[data-move-current]').forEach(b => b.addEventListener('click', () => {
    closeMoveMenu();
  }));
  els.moreMenu.querySelectorAll('[data-delete-note]').forEach(b => b.addEventListener('click', () => {
    closeMoveMenu();
    deleteNote();
  }));
}
function closeMoveMenu() { els.moreMenu.classList.remove('open'); }
els.moveBtn.addEventListener('click', (e) => {
  e.stopPropagation();
  buildMoveMenu();
  els.moreMenu.classList.toggle('open');
});
els.moreBtn.addEventListener('click', (e) => {
  e.stopPropagation();
  buildMoveMenu();
  els.moreMenu.classList.toggle('open');
});
document.addEventListener('click', (e) => {
  if (!e.target.closest('.menu-wrap')) closeMoveMenu();
});
els.moreMenu.addEventListener('mousedown', (e) => e.stopPropagation());

/* ---------- (11) DRAG-DROP between sidebar notebooks ---------- */
els.notebooks.addEventListener('dragover', (e) => {
  const row = e.target.closest('[data-notebook-row]');
  $$('#notebooks .notebook-row').forEach(r => r.classList.remove('drag-over'));
  if (row && !e.target.closest('.sub-note')) {
    row.classList.add('drag-over');
    e.preventDefault();
  }
});
els.notebooks.addEventListener('dragleave', (e) => {
  if (!els.notebooks.contains(e.relatedTarget)) {
    $$('#notebooks .notebook-row').forEach(r => r.classList.remove('drag-over'));
  }
});
els.notebooks.addEventListener('drop', (e) => {
  const row = e.target.closest('[data-notebook-row]');
  $$('#notebooks .notebook-row').forEach(r => r.classList.remove('drag-over'));
  if (!row) return;
  const nbId = row.dataset.notebookRow;
  if (state.view === 'trash' || state.view === nbId) return;
  const noteId = e.dataTransfer.getData('text/plain') || els.dragNoteId;
  const note = findNote(noteId);
  if (!note) return;
  const wasTrash = (state.view === 'trash');
  if (wasTrash) {
    note.deleted = false;
  }
  note.notebookId = nbId;
  note.updatedAt = Date.now();
  save();
  renderSidebar();
  renderNoteList();
  toast('Note moved');
});

/* Trash drop: restore */
els.navTrash.addEventListener('dragover', (e) => {
  e.preventDefault();
});
els.navTrash.addEventListener('drop', (e) => {
  e.preventDefault();
  const noteId = e.dataTransfer.getData('text/plain') || els.dragNoteId;
  const note = findNote(noteId);
  if (!note) return;
  note.deleted = true;
  note.updatedAt = Date.now();
  save();
  renderSidebar();
  renderNoteList();
  toast('Moved to Trash');
});

/* Double-click notebook row to create note there */
els.notebooks.addEventListener('dblclick', (e) => {
  const row = e.target.closest('[data-notebook-row]');
  if (!row) return;
  const nbId = row.dataset.notebookRow;
  const n = createNote(nbId);
  noteOrder(n);
  renderSidebar();
  renderNoteList();
  selectNote(n.id);
});

/* ---------- (13) INIT ---------- */
function updateThemeIcon() {
  const dark = document.documentElement.getAttribute('data-theme') === 'dark';
  els.themeToggle.textContent = dark ? '☀' : '☾';
}
els.themeToggle.addEventListener('click', () => {
  const current = document.documentElement.getAttribute('data-theme');
  const next = current === 'dark' ? 'light' : 'dark';
  document.documentElement.setAttribute('data-theme', next);
  localStorage.setItem('notesapp.theme', next);
  updateThemeIcon();
});
function init() {
  cacheEls();
  renderSortButtons();
  let loaded = load();
  if (!loaded) {
    // seed demo data
    const nb = createNotebook('Getting Started');
    createDemoNotes(nb.id);
    renderSidebar();
    renderNoteList();
    save();
    return;
  }
  // restore theme
  const theme = localStorage.getItem('notesapp.theme') || 'light';
  document.documentElement.setAttribute('data-theme', theme);
  updateThemeIcon();

  // restore view
  if (state.view && state.view !== 'all') {
    if (findNotebook(state.view)) {
      els.listTitle.textContent = findNotebook(state.view).name;
    } else {
      state.view = 'all';
      els.listTitle.textContent = 'All Notes';
    }
  } else if (state.view === 'trash') {
    els.listTitle.textContent = 'Trash';
  } else {
    els.listTitle.textContent = 'All Notes';
  }

  renderSidebar();
  renderNoteList();
  if (state.selectedNoteId) selectNote(state.selectedNoteId);
}

function createDemoNotes(nbId) {
  const n1 = createNote(nbId);
  n1.title = 'Welcome to Notes';
  n1.body = '<h1>Welcome 👋</h1><p>This is a <strong>polished</strong> notes app. Try <em>formatting</em>, checklists, and <code>inline code</code>.</p><ul><li>Click a heading, or type <code>#</code> at line start</li><li>Type <code>-</code> for a bullet list</li><li>Type <code>[]</code> for a checklist</li></ul><p>Everything saves automatically to your browser.</p>';
  n1.tags = ['welcome', 'tutorial'];
  n1.pinned = true;

  const n2 = createNote(nbId);
  n2.title = 'Markdown shortcuts';
  n2.body = '<h2>Try these</h2><ul><li><span class="checkbox" data-checked="false" contenteditable="false"></span><span class="check-text" contenteditable="true">Type <code>[]</code> then space for a checklist</span></li><li><span class="checkbox" data-checked="true" contenteditable="false">✓</span><span class="check-text" contenteditable="true">Checkboxes persist on reload</span></li><li><span class="checkbox" data-checked="false" contenteditable="false"></span><span class="check-text" contenteditable="true">Type <code>&gt;</code> for a blockquote</span></li></ul><p>Start writing your own notes here.</p>';
  n2.tags = ['tutorial'];

  const n3 = createNote(nbId);
  n3.title = 'Shopping list';
  n3.body = '<h2>For the weekend</h2><ul><li><span class="checkbox" data-checked="false" contenteditable="false"></span><span class="check-text" contenteditable="true">Coffee beans</span></li><li><span class="checkbox" data-checked="false" contenteditable="false"></span><span class="check-text" contenteditable="true">Oat milk</span></li><li><span class="checkbox" data-checked="true" contenteditable="false">✓</span><span class="check-text" contenteditable="true">Sourdough bread</span></li></ul>';
  n3.tags = ['personal'];
}

init();
</script>
</body>
</html>
<!-- agent-meta {"model":"peculiar-ragdoll/Tiel-Coder-35B-A3B-GGUF-MTP","provider":"brigid","persona":"full","sessionId":"57a15267-e70b-4b37-ad54-4321e232a6db","tokensIn":10654024,"tokensOut":77406,"tokensTotal":10731430,"cost":0,"turns":119,"toolCalls":118,"failedToolCalls":3,"timestamp":"2026-09-04T20:41:40.213Z"} -->