← Notes results

Notes

muse spark 1.2 contributor · 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 63,520 bytes · SHA-256 3d735bc6697f
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Inkwell — Notes</title>
<style>
/* ========== TUNABLE CONSTANTS ========== */
:root{
  --bg:#fdfbf7;
  --bg-sidebar:#f6f1e8;
  --bg-notelist:#fcf9f4;
  --bg-editor:#ffffff;
  --bg-hover:#efe8d8;
  --bg-active:#e8ddd0;
  --border:#e8ddd0;
  --border-strong:#d6c9b6;
  --text:#1a1a1a;
  --text-secondary:#6b6560;
  --text-muted:#9a9590;
  --accent:#c07a3a;
  --accent-hover:#a8682e;
  --accent-light:#fdf0e0;
  --accent-bg:#f5e6d3;
  --radius:8px;
  --radius-sm:6px;
  --shadow:0 1px 3px rgba(0,0,0,0.06), 0 4px 12px rgba(0,0,0,0.04);
  --shadow-lg:0 8px 24px rgba(0,0,0,0.08);
  --font-sans:'Inter', system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
  --font-serif:'Georgia', 'Times New Roman', serif;
  --font-mono:'SF Mono', 'Cascadia Code', monospace;
  --sidebar-w:260px;
  --notelist-w:340px;
}
[data-theme="dark"]{
  --bg:#1a1a1e;
  --bg-sidebar:#232326;
  --bg-notelist:#1e1e21;
  --bg-editor:#252529;
  --bg-hover:#2e2e32;
  --bg-active:#36363c;
  --border:#2e2e32;
  --border-strong:#3a3a40;
  --text:#e8e6e3;
  --text-secondary:#9a9590;
  --text-muted:#6b6560;
  --accent:#d49a5a;
  --accent-hover:#e0a86a;
  --accent-light:#2a2318;
  --accent-bg:#2e2618;
  --shadow:0 1px 3px rgba(0,0,0,0.3);
  --shadow-lg:0 8px 24px rgba(0,0,0,0.4);
}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:var(--font-sans);background:var(--bg);color:var(--text);height:100vh;overflow:hidden;display:flex;flex-direction:column;-webkit-font-smoothing:antialiased}
button{font-family:inherit;cursor:pointer}
input{font-family:inherit}

/* Header */
.app-header{height:44px;display:flex;align-items:center;gap:12px;padding:0 16px;border-bottom:1px solid var(--border);background:var(--bg-sidebar);flex-shrink:0}
.app-logo{display:flex;align-items:center;gap:10px;font-weight:700;font-size:15px;letter-spacing:-0.02em}
.app-logo-mark{width:28px;height:28px;background:var(--accent);border-radius:7px;display:grid;place-items:center;color:white;font-size:14px}
.header-actions{margin-left:auto;display:flex;align-items:center;gap:8px}
.icon-btn{width:32px;height:32px;border:1px solid var(--border);background:var(--bg-editor);border-radius:8px;display:grid;place-items:center;color:var(--text-secondary);transition:all .15s}
.icon-btn:hover{background:var(--bg-hover);color:var(--text);border-color:var(--border-strong)}
.search-wrap{position:relative}
.search-wrap input{width:220px;height:32px;padding:0 32px 0 32px;border:1px solid var(--border);border-radius:20px;background:var(--bg-editor);font-size:13px;outline:none;transition:border-color .15s}
.search-wrap input:focus{border-color:var(--accent)}
.search-wrap svg{position:absolute;left:10px;top:50%;transform:translateY(-50%);color:var(--text-muted);pointer-events:none}
.search-clear{position:absolute;right:8px;top:50%;transform:translateY(-50%);width:20px;height:20px;border:none;background:var(--bg-hover);border-radius:50%;display:grid;place-items:center;font-size:12px;color:var(--text-muted);cursor:pointer}
.theme-toggle{width:32px;height:32px;border-radius:50%;border:1px solid var(--border);background:var(--bg-editor);display:grid;place-items:center;font-size:14px}

/* Main layout */
.main{flex:1;display:flex;overflow:hidden;min-height:0}
.pane{flex-shrink:0;overflow:hidden;display:flex;flex-direction:column;min-width:0}
#sidebar{width:var(--sidebar-w);background:var(--bg-sidebar);border-right:1px solid var(--border)}
#notelist{width:var(--notelist-w);background:var(--bg-notelist);border-right:1px solid var(--border)}
#editor{flex:1;background:var(--bg-editor);min-width:320px}
.drag-handle{width:4px;flex-shrink:0;background:transparent;cursor:col-resize;position:relative;z-index:5;transition:background .15s}
.drag-handle:hover,.drag-handle.dragging{background:var(--accent);opacity:0.6}
.drag-handle::after{content:'';position:absolute;inset:0 -4px 0 -4px}

/* Sidebar */
.sidebar-top{padding:16px 12px 8px;display:flex;align-items:center;justify-content:space-between}
.sidebar-title{font-size:11px;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;color:var(--text-muted)}
.btn-new-nb{font-size:12px;padding:5px 10px;border-radius:20px;border:1px solid var(--border);background:var(--bg-editor);color:var(--text-secondary);display:flex;align-items:center;gap:4px}
.btn-new-nb:hover{border-color:var(--accent);color:var(--accent)}
.sidebar-nav{padding:8px;flex:1;overflow-y:auto}
.nav-item{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:8px;cursor:pointer;font-size:13.5px;color:var(--text-secondary);transition:all .12s;user-select:none;position:relative}
.nav-item:hover{background:var(--bg-hover);color:var(--text)}
.nav-item.active{background:var(--accent-bg);color:var(--text);font-weight:600}
.nav-item.active .nav-icon{color:var(--accent)}
.nav-icon{width:20px;height:20px;display:grid;place-items:center;font-size:13px;flex-shrink:0}
.nav-count{margin-left:auto;font-size:11px;background:var(--bg-editor);border:1px solid var(--border);padding:1px 6px;border-radius:10px;color:var(--text-muted);min-width:20px;text-align:center}
.nav-item.active .nav-count{background:var(--bg-editor)}
.notebook-row{display:flex;align-items:center;gap:8px;padding:7px 10px;border-radius:8px;cursor:pointer;font-size:13.5px;color:var(--text-secondary);transition:all .12s}
.notebook-row:hover{background:var(--bg-hover)}
.notebook-row.active{background:var(--bg-active);color:var(--text);font-weight:600}
.nb-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.nb-actions{margin-left:auto;display:flex;gap:2px;opacity:0;transition:opacity .12s}
.notebook-row:hover .nb-actions{opacity:1}
.nb-actions button{width:22px;height:22px;border:none;background:transparent;border-radius:5px;display:grid;place-items:center;color:var(--text-muted);font-size:12px}
.nb-actions button:hover{background:var(--bg-editor);color:var(--text)}
.sidebar-bottom{padding:12px;border-top:1px solid var(--border);display:flex;align-items:center;gap:8px}
.collapse-btn{flex:1;font-size:12px;padding:7px;border:1px solid var(--border);background:var(--bg-editor);border-radius:8px;color:var(--text-secondary);display:flex;align-items:center;justify-content:center;gap:6px}
.storage-info{font-size:11px;color:var(--text-muted);text-align:center;padding:8px}

/* Collapsed sidebar */
.main.sidebar-collapsed #sidebar{width:0 !important;overflow:hidden;border:none;opacity:0;pointer-events:none}
.main.sidebar-collapsed #sidebar *{pointer-events:none}

/* Note list */
.notelist-header{padding:14px 14px 10px;border-bottom:1px solid var(--border);background:var(--bg-notelist);flex-shrink:0}
.notelist-title-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
.notelist-title{font-size:15px;font-weight:700;letter-spacing:-0.02em}
.notelist-count{font-size:12px;color:var(--text-muted);background:var(--bg-editor);border:1px solid var(--border);padding:2px 8px;border-radius:10px}
.notelist-controls{display:flex;gap:8px;align-items:center}
.btn-new-note{flex:1;height:36px;background:var(--accent);color:white;border:none;border-radius:8px;font-weight:600;font-size:13px;display:flex;align-items:center;justify-content:center;gap:6px;transition:background .15s}
.btn-new-note:hover{background:var(--accent-hover)}
.sort-select{height:36px;padding:0 8px;border:1px solid var(--border);border-radius:8px;background:var(--bg-editor);font-size:12px;color:var(--text-secondary);outline:none;cursor:pointer}
.note-list{flex:1;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:6px}
.note-card{padding:12px 12px 10px;background:var(--bg-editor);border:1px solid var(--border);border-radius:10px;cursor:pointer;transition:all .15s;position:relative}
.note-card:hover{border-color:var(--border-strong);box-shadow:var(--shadow)}
.note-card.selected{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-light);background:var(--bg-editor)}
.note-card.pinned{border-left:3px solid var(--accent)}
.note-card.dragging{opacity:0.5}
.note-card-top{display:flex;align-items:center;gap:6px;margin-bottom:4px}
.pin-icon{color:var(--accent);font-size:11px}
.note-card-title{font-size:13.5px;font-weight:600;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1}
.note-card-snippet{font-size:12.5px;color:var(--text-secondary);line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;min-height:1.4em;margin-bottom:6px;word-break:break-word}
.note-card-snippet mark{background:#fde68a;color:inherit;padding:0 2px;border-radius:3px}
[data-theme="dark"] .note-card-snippet mark{background:#854d0e;color:#fef3c7}
.note-card-meta{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--text-muted)}
.note-card-tags{display:flex;gap:4px;flex-wrap:wrap;margin-top:6px}
.mini-tag{font-size:10px;padding:2px 6px;background:var(--bg-hover);border:1px solid var(--border);border-radius:10px;color:var(--text-secondary)}
.note-card-actions{position:absolute;top:8px;right:8px;display:flex;gap:2px;opacity:0;transition:opacity .12s}
.note-card:hover .note-card-actions{opacity:1}
.note-card-actions button{width:24px;height:24px;border:none;background:var(--bg-hover);border-radius:6px;display:grid;place-items:center;font-size:11px;color:var(--text-muted)}
.note-card-actions button:hover{background:var(--bg-active);color:var(--text)}
.empty-list{padding:40px 20px;text-align:center;color:var(--text-muted)}
.empty-list p{font-size:13px;margin-top:8px}

/* Editor */
.editor-empty{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;color:var(--text-muted);text-align:center}
.editor-empty-icon{width:64px;height:64px;border:1px dashed var(--border-strong);border-radius:16px;display:grid;place-items:center;font-size:24px;margin-bottom:16px;background:var(--bg-notelist)}
.editor-empty h3{font-size:16px;font-weight:600;color:var(--text);margin-bottom:6px}
.editor-empty p{font-size:13px;max-width:280px;line-height:1.5}
.editor-empty button{margin-top:16px;padding:8px 16px;background:var(--accent);color:white;border:none;border-radius:8px;font-weight:600;font-size:13px}
.editor-wrap{flex:1;display:flex;flex-direction:column;overflow:hidden;min-height:0}
.editor-toolbar{height:44px;display:flex;align-items:center;gap:2px;padding:0 16px;border-bottom:1px solid var(--border);background:var(--bg-editor);flex-shrink:0;overflow-x:auto;scrollbar-width:none}
.editor-toolbar::-webkit-scrollbar{display:none}
.tb-btn{width:30px;height:30px;border:none;background:transparent;border-radius:6px;display:grid;place-items:center;font-size:13px;color:var(--text-secondary);flex-shrink:0;transition:all .12s}
.tb-btn:hover{background:var(--bg-hover);color:var(--text)}
.tb-btn.active{background:var(--accent-bg);color:var(--accent);font-weight:700}
.tb-sep{width:1px;height:20px;background:var(--border);margin:0 4px;flex-shrink:0}
.editor-meta{padding:14px 32px 0;display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.editor-title{flex:1;min-width:200px;font-size:22px;font-weight:700;border:none;outline:none;background:transparent;color:var(--text);letter-spacing:-0.02em;line-height:1.2;padding:4px 0}
.editor-title::placeholder{color:var(--text-muted)}
.editor-actions{display:flex;gap:6px;align-items:center}
.editor-actions button{height:28px;padding:0 10px;border:1px solid var(--border);background:var(--bg-editor);border-radius:6px;font-size:12px;color:var(--text-secondary);display:flex;align-items:center;gap:4px}
.editor-actions button:hover{border-color:var(--border-strong);color:var(--text)}
.editor-actions .btn-danger{color:#b45309;border-color:#fde68a;background:#fffbeb}
[data-theme="dark"] .editor-actions .btn-danger{background:#2a1f0f;color:#fbbf24;border-color:#78350f}
.saved-ind{font-size:11px;color:var(--text-muted);display:flex;align-items:center;gap:4px;transition:opacity .3s}
.saved-ind.dot::before{content:'';width:6px;height:6px;background:#22c55e;border-radius:50%;display:inline-block}
.tag-bar{display:flex;align-items:center;gap:6px;padding:8px 32px;flex-wrap:wrap}
.tag-pill{font-size:11px;padding:4px 8px;background:var(--accent-light);border:1px solid var(--border);border-radius:20px;display:flex;align-items:center;gap:4px;color:var(--text-secondary)}
.tag-pill button{border:none;background:transparent;color:var(--text-muted);cursor:pointer;font-size:12px;line-height:1}
.tag-input{border:none;outline:none;background:transparent;font-size:12px;min-width:80px;color:var(--text)}
.tag-input::placeholder{color:var(--text-muted)}
.editor-content{flex:1;overflow-y:auto;padding:8px 32px 40px}
#editorEl{outline:none;min-height:200px;font-family:var(--font-serif);font-size:15.5px;line-height:1.7;color:var(--text);max-width:720px}
#editorEl:empty:before{content:attr(data-placeholder);color:var(--text-muted);pointer-events:none}
#editorEl h1{font-family:var(--font-sans);font-size:28px;font-weight:800;letter-spacing:-0.03em;margin:20px 0 8px;line-height:1.2}
#editorEl h2{font-family:var(--font-sans);font-size:22px;font-weight:700;letter-spacing:-0.02em;margin:18px 0 6px;line-height:1.3}
#editorEl h3{font-family:var(--font-sans);font-size:17px;font-weight:700;margin:16px 0 6px;line-height:1.3;color:var(--text-secondary)}
#editorEl p{margin:8px 0}
#editorEl blockquote{border-left:3px solid var(--accent);padding:6px 16px;margin:12px 0;background:var(--accent-light);border-radius:0 8px 8px 0;color:var(--text-secondary);font-style:italic}
#editorEl ul, #editorEl ol{margin:8px 0 8px 24px}
#editorEl li{margin:4px 0}
#editorEl code{background:var(--bg-hover);border:1px solid var(--border);padding:1px 5px;border-radius:4px;font-family:var(--font-mono);font-size:13px}
#editorEl pre{background:var(--bg-hover);border:1px solid var(--border);padding:12px 16px;border-radius:8px;overflow-x:auto;margin:12px 0}
#editorEl pre code{background:none;border:none;padding:0}
#editorEl hr{border:none;border-top:1px solid var(--border);margin:16px 0}
#editorEl .checklist{list-style:none;margin-left:0;padding:0}
#editorEl .checklist li{display:flex;align-items:flex-start;gap:8px;padding:3px 0}
#editorEl .checklist li input[type="checkbox"]{margin-top:6px;width:16px;height:16px;accent-color:var(--accent);flex-shrink:0;cursor:pointer}
#editorEl .checklist li.checked span{ text-decoration:line-through;color:var(--text-muted)}

/* Dropdown / modal */
.dropdown{position:absolute;top:100%;right:0;margin-top:6px;background:var(--bg-editor);border:1px solid var(--border);border-radius:10px;box-shadow:var(--shadow-lg);padding:6px;min-width:180px;z-index:50;display:none}
.dropdown.open{display:block}
.dropdown button{width:100%;text-align:left;padding:8px 10px;border:none;background:transparent;border-radius:6px;font-size:13px;color:var(--text);display:flex;align-items:center;gap:8px}
.dropdown button:hover{background:var(--bg-hover)}
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.35);display:none;align-items:center;justify-content:center;z-index:100}
.modal-overlay.open{display:flex}
.modal{background:var(--bg-editor);border-radius:12px;box-shadow:var(--shadow-lg);padding:20px;min-width:360px;max-width:90vw}
.modal h3{font-size:15px;margin-bottom:12px}
.modal input{width:100%;height:36px;padding:0 12px;border:1px solid var(--border);border-radius:8px;outline:none;font-size:13px;background:var(--bg);margin-bottom:12px}
.modal input:focus{border-color:var(--accent)}
.modal-actions{display:flex;gap:8px;justify-content:flex-end}
.btn-primary{padding:7px 14px;background:var(--accent);color:white;border:none;border-radius:8px;font-weight:600;font-size:13px}
.btn-ghost{padding:7px 14px;background:transparent;border:1px solid var(--border);border-radius:8px;font-size:13px;color:var(--text-secondary)}

/* Context menu */
.ctx-menu{position:fixed;background:var(--bg-editor);border:1px solid var(--border);border-radius:10px;box-shadow:var(--shadow-lg);padding:6px;min-width:180px;z-index:90;display:none}
.ctx-menu.open{display:block}
.ctx-menu button{width:100%;text-align:left;padding:7px 10px;border:none;background:transparent;border-radius:6px;font-size:12.5px;color:var(--text);display:flex;align-items:center;gap:8px}
.ctx-menu button:hover{background:var(--bg-hover)}
.ctx-menu .sep{height:1px;background:var(--border);margin:4px 0}

/* Responsive */
@media(max-width:900px){
  #sidebar{position:absolute;z-index:20;height:100%;box-shadow:var(--shadow-lg)}
  .main.sidebar-collapsed #sidebar{transform:translateX(-100%)}
  #notelist{width:280px}
  .search-wrap input{width:140px}
}
::-webkit-scrollbar{width:6px;height:6px}
::-webkit-scrollbar-thumb{background:var(--border-strong);border-radius:10px}
::-webkit-scrollbar-track{background:transparent}
</style>
</head>
<body>
<header class="app-header">
  <div class="app-logo"><div class="app-logo-mark">✦</div> Inkwell</div>
  <button class="icon-btn" id="toggleSidebar" title="Toggle sidebar">☰</button>
  <div class="header-actions">
    <div class="search-wrap">
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
      <input id="searchInput" placeholder="Search notes..." autocomplete="off">
      <button class="search-clear" id="searchClear" style="display:none">×</button>
    </div>
    <button class="theme-toggle" id="themeToggle" title="Toggle theme">◐</button>
  </div>
</header>

<div class="main" id="mainLayout">
  <div class="pane" id="sidebar">
    <div class="sidebar-top">
      <span class="sidebar-title">Notebooks</span>
      <button class="btn-new-nb" id="btnNewNotebook">+ New</button>
    </div>
    <div class="sidebar-nav" id="sidebarNav"></div>
    <div class="sidebar-bottom">
      <button class="collapse-btn" id="btnCollapse">‹ Collapse</button>
    </div>
    <div class="storage-info" id="storageInfo"></div>
  </div>
  <div class="drag-handle" id="handle1"></div>
  <div class="pane" id="notelist">
    <div class="notelist-header">
      <div class="notelist-title-row">
        <span class="notelist-title" id="notelistTitle">All Notes</span>
        <span class="notelist-count" id="notelistCount">0</span>
      </div>
      <div class="notelist-controls">
        <button class="btn-new-note" id="btnNewNote">+ New note</button>
        <select class="sort-select" id="sortSelect">
          <option value="updated">Modified</option>
          <option value="created">Created</option>
          <option value="alpha">A — Z</option>
        </select>
      </div>
    </div>
    <div class="note-list" id="noteList"></div>
  </div>
  <div class="drag-handle" id="handle2"></div>
  <div class="pane" id="editor">
    <div class="editor-empty" id="editorEmpty">
      <div class="editor-empty-icon">📝</div>
      <h3>Select or create a note</h3>
      <p>Choose a note from the list or create a new one to start writing. Your notes auto-save as you type.</p>
      <button id="btnEmptyNew">Create new note</button>
    </div>
    <div class="editor-wrap" id="editorWrap" style="display:none">
      <div class="editor-toolbar" id="toolbar">
        <button class="tb-btn" data-cmd="bold" title="Bold (Ctrl+B)"><b>B</b></button>
        <button class="tb-btn" data-cmd="italic" title="Italic (Ctrl+I)"><i>I</i></button>
        <button class="tb-btn" data-cmd="underline" title="Underline (Ctrl+U)"><u>U</u></button>
        <button class="tb-btn" data-cmd="strikeThrough" title="Strikethrough"><s>S</s></button>
        <button class="tb-btn" data-cmd="code" title="Inline code">{ }</button>
        <div class="tb-sep"></div>
        <button class="tb-btn" data-cmd="h1" title="Heading 1">H1</button>
        <button class="tb-btn" data-cmd="h2" title="Heading 2">H2</button>
        <button class="tb-btn" data-cmd="h3" title="Heading 3">H3</button>
        <div class="tb-sep"></div>
        <button class="tb-btn" data-cmd="insertUnorderedList" title="Bullet list">•</button>
        <button class="tb-btn" data-cmd="insertOrderedList" title="Numbered list">1.</button>
        <button class="tb-btn" data-cmd="checklist" title="Checklist">☑</button>
        <button class="tb-btn" data-cmd="blockquote" title="Quote">❝</button>
        <button class="tb-btn" data-cmd="hr" title="Horizontal rule">—</button>
      </div>
      <div class="editor-meta">
        <input class="editor-title" id="editorTitle" placeholder="Untitled">
        <div class="editor-actions">
          <span class="saved-ind dot" id="savedInd">Saved</span>
          <div style="position:relative">
            <button id="btnMoveNote">Move ▾</button>
            <div class="dropdown" id="moveDropdown"></div>
          </div>
          <button id="btnPinNote" title="Pin">📌</button>
          <button id="btnDupNote" title="Duplicate">⧉</button>
          <button id="btnDelNote" class="btn-danger" title="Delete">Delete</button>
        </div>
      </div>
      <div class="tag-bar" id="tagBar">
        <span style="font-size:11px;color:var(--text-muted)">Tags:</span>
        <div id="tagPills" style="display:flex;gap:6px;flex-wrap:wrap;align-items:center"></div>
        <input class="tag-input" id="tagInput" placeholder="Add tag + Enter">
      </div>
      <div class="editor-content"><div id="editorEl" contenteditable="true" data-placeholder="Start writing… Type # for heading, - for list, [] for checklist, > for quote"></div></div>
    </div>
  </div>
</div>

<!-- Notebook modal -->
<div class="modal-overlay" id="nbModal">
  <div class="modal">
    <h3 id="nbModalTitle">New Notebook</h3>
    <input id="nbModalInput" placeholder="Notebook name">
    <div class="modal-actions">
      <button class="btn-ghost" id="nbModalCancel">Cancel</button>
      <button class="btn-primary" id="nbModalOk">Create</button>
    </div>
  </div>
</div>

<div class="ctx-menu" id="ctxMenu"></div>

<script>
/* =========================================================
   INKWELL — Single-file Notes App
   ========================================================= */

/* ---------- Tunable constants ---------- */
const CONFIG = {
  STORAGE_KEY: 'inkwell_v2',
  AUTOSAVE_DEBOUNCE: 350,
  SNIPPET_LENGTH: 120,
  DEFAULT_NOTEBOOK_COLORS: ['#c07a3a','#5b8fb9','#6a9a6a','#9a6ab5','#d45d5d','#4a9a9a','#b58a3a'],
  FONTS: { sans: 'Inter, system-ui, sans-serif', serif: 'Georgia, serif', mono: 'SF Mono, monospace' }
};

/* ---------- Helpers ---------- */
const $ = s => document.querySelector(s);
const uid = () => Date.now().toString(36)+Math.random().toString(36).slice(2,6);
const now = () => Date.now();
const fmtDate = ts => {
  const d=new Date(ts); const diff=Date.now()-ts;
  if(diff<60000) return 'Just now';
  if(diff<3600000) return Math.floor(diff/60000)+'m ago';
  if(diff<86400000) return Math.floor(diff/3600000)+'h ago';
  if(diff<604800000) return Math.floor(diff/86400000)+'d ago';
  return d.toLocaleDateString(undefined,{month:'short',day:'numeric',year: d.getFullYear()!==new Date().getFullYear()?'numeric':undefined});
};
function escapeHtml(s){ const d=document.createElement('div'); d.textContent=s; return d.innerHTML; }

/* ---------- Text extraction & sanitization ---------- */
function extractText(html){
  const tmp=document.createElement('div');
  tmp.innerHTML=html||'';
  // remove script/style
  tmp.querySelectorAll('script,style').forEach(e=>e.remove());
  return (tmp.textContent||tmp.innerText||'').replace(/\s+/g,' ').trim();
}
function snippetFromHtml(html){
  const t=extractText(html);
  return t.length>CONFIG.SNIPPET_LENGTH ? t.slice(0,CONFIG.SNIPPET_LENGTH)+'…' : t;
}
function sanitizeHtml(html){
  const tmp=document.createElement('div');
  tmp.innerHTML=html;
  // strip disallowed
  tmp.querySelectorAll('script,style,iframe,object,embed,link,meta,form').forEach(e=>e.remove());
  // remove event handlers & javascript: urls
  const walker=document.createTreeWalker(tmp, NodeFilter.SHOW_ELEMENT);
  const els=[];
  while(walker.nextNode()) els.push(walker.currentNode);
  const ALLOWED_TAGS=new Set(['P','BR','B','STRONG','I','EM','U','S','STRIKE','DEL','CODE','PRE','H1','H2','H3','UL','OL','LI','BLOCKQUOTE','HR','DIV','SPAN','A','INPUT','LABEL']);
  els.forEach(el=>{
    // strip attributes
    [...el.attributes].forEach(attr=>{
      const n=attr.name.toLowerCase();
      if(n.startsWith('on')) el.removeAttribute(attr.name);
      else if(n==='style') el.removeAttribute(attr.name);
      else if(n==='href' && attr.value.trim().toLowerCase().startsWith('javascript:')) el.removeAttribute(attr.name);
      else if(!['href','type','checked','class','data-checked','data-checklist'].includes(n)) {
        // keep class only for checklist
        if(n==='class' && el.classList.contains('checklist')) return;
        if(n!=='class') el.removeAttribute(attr.name);
      }
    });
    if(!ALLOWED_TAGS.has(el.tagName)){
      // unwrap
      const parent=el.parentNode;
      while(el.firstChild) parent.insertBefore(el.firstChild, el);
      parent.removeChild(el);
    }
  });
  // ensure checklist inputs are checkbox
  tmp.querySelectorAll('input').forEach(inp=>{
    if(inp.type!=='checkbox'){ inp.type='checkbox'; }
  });
  return tmp.innerHTML;
}

/* ---------- Data Model ---------- */
let store = {
  notebooks: [],
  notes: [],
  ui: {
    selectedNotebookId: 'all',
    selectedNoteId: null,
    sortOrder: 'updated',
    searchQuery: '',
    tagFilter: null,
    sidebarCollapsed: false,
    paneWidths: { sidebar: 260, notelist: 340 },
    theme: 'light'
  }
};

function defaultData(){
  const nb1={id:uid(), name:'Personal', color:CONFIG.DEFAULT_NOTEBOOK_COLORS[0]};
  const nb2={id:uid(), name:'Work', color:CONFIG.DEFAULT_NOTEBOOK_COLORS[1]};
  const nb3={id:uid(), name:'Ideas', color:CONFIG.DEFAULT_NOTEBOOK_COLORS[2]};
  const n1={id:uid(), notebookId:nb1.id, title:'Welcome to Inkwell', content:'<h1>Welcome to Inkwell ✦</h1><p>A calm, focused place for your thoughts. This editor supports <b>bold</b>, <i>italic</i>, <u>underline</u>, <code>inline code</code>, and more.</p><blockquote>“Good notes are a garden, not a filing cabinet.”</blockquote><p>Try the shortcuts:</p><ul><li>Type <code># </code> at line start for a heading</li><li>Type <code>- </code> for a bullet list</li><li>Type <code>[] </code> for a checklist</li><li>Type <code>&gt; </code> for a quote</li></ul><ul class="checklist"><li><input type="checkbox"> <span>Explore the app</span></li><li><input type="checkbox" checked> <span>Create your first note</span></li></ul><p>Everything saves automatically.</p>', tags:['welcome','getting-started'], pinned:true, createdAt:now()-86400000, updatedAt:now()-3600000, deleted:false};
  const n2={id:uid(), notebookId:nb2.id, title:'Meeting notes — Q3 planning', content:'<h2>Q3 Planning — 12 Aug</h2><p><b>Attendees:</b> Alex, Sam, Priya</p><ul><li>Ship onboarding revamp by Sep 15</li><li>Research <i>user retention</i> patterns</li><li>Budget review next Friday</li></ul><hr><p>Follow-ups: send deck to team.</p>', tags:['work','planning'], pinned:false, createdAt:now()-172800000, updatedAt:now()-7200000, deleted:false};
  const n3={id:uid(), notebookId:nb3.id, title:'Book ideas', content:'<h3>Novel seeds</h3><ol><li>The library that remembers every book never written</li><li>A translator who discovers languages change the past</li></ol><p>Tags help you find things later — try clicking a tag below.</p>', tags:['writing','ideas'], pinned:false, createdAt:now()-259200000, updatedAt:now()-10000000, deleted:false};
  return { notebooks:[nb1,nb2,nb3], notes:[n1,n2,n3], ui:{ selectedNotebookId:'all', selectedNoteId:n1.id, sortOrder:'updated', searchQuery:'', tagFilter:null, sidebarCollapsed:false, paneWidths:{sidebar:260, notelist:340}, theme:'light'} };
}

function loadStore(){
  try{
    const raw=localStorage.getItem(CONFIG.STORAGE_KEY);
    if(raw){
      const parsed=JSON.parse(raw);
      // migrate: ensure fields
      store.notebooks=parsed.notebooks||[];
      store.notes=parsed.notes||[];
      store.ui={...defaultData().ui, ...(parsed.ui||{})};
      // ensure deleted flag
      store.notes.forEach(n=>{ if(n.deleted===undefined) n.deleted=false; if(!n.tags) n.tags=[]; });
      if(store.notebooks.length===0 && store.notes.length===0) store=defaultData();
    } else {
      store=defaultData();
      saveStore();
    }
  }catch(e){ store=defaultData(); }
  applyTheme();
  // restore pane widths
  if(store.ui.paneWidths){
    $('#sidebar').style.width=store.ui.paneWidths.sidebar+'px';
    $('#notelist').style.width=store.ui.paneWidths.notelist+'px';
  }
  if(store.ui.sidebarCollapsed) $('#mainLayout').classList.add('sidebar-collapsed');
}
function saveStore(){
  localStorage.setItem(CONFIG.STORAGE_KEY, JSON.stringify(store));
}
let saveTimer=null;
function debouncedSave(){
  $('#savedInd').textContent='Saving…'; $('#savedInd').style.opacity='1';
  clearTimeout(saveTimer);
  saveTimer=setTimeout(()=>{ saveStore(); $('#savedInd').textContent='Saved'; setTimeout(()=>$('#savedInd').style.opacity='0.7',800); }, CONFIG.AUTOSAVE_DEBOUNCE);
}
function applyTheme(){
  document.documentElement.setAttribute('data-theme', store.ui.theme);
  $('#themeToggle').textContent = store.ui.theme==='dark' ? '☀' : '◐';
}

/* ---------- Rendering ---------- */
function renderAll(){
  renderSidebar();
  renderNoteList();
  renderEditor();
  updateStorageInfo();
}
function renderSidebar(){
  const nav=$('#sidebarNav');
  const allCount=store.notes.filter(n=>!n.deleted).length;
  const trashCount=store.notes.filter(n=>n.deleted).length;
  let html='';
  html+=`<div class="nav-item ${store.ui.selectedNotebookId==='all'?'active':''}" data-nb="all"><span class="nav-icon">◧</span> All Notes <span class="nav-count">${allCount}</span></div>`;
  store.notebooks.forEach(nb=>{
    const cnt=store.notes.filter(n=>!n.deleted && n.notebookId===nb.id).length;
    const active=store.ui.selectedNotebookId===nb.id?'active':'';
    html+=`<div class="notebook-row ${active}" data-nb="${nb.id}" draggable="true">
      <span class="nb-dot" style="background:${nb.color}"></span>
      <span style="flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${escapeHtml(nb.name)}</span>
      <span class="nav-count">${cnt}</span>
      <span class="nb-actions">
        <button data-act="rename" data-id="${nb.id}" title="Rename">✎</button>
        <button data-act="delete" data-id="${nb.id}" title="Delete">×</button>
      </span>
    </div>`;
  });
  html+=`<div class="nav-item ${store.ui.selectedNotebookId==='trash'?'active':''}" data-nb="trash" style="margin-top:8px"><span class="nav-icon">🗑</span> Trash <span class="nav-count">${trashCount}</span></div>`;
  if(store.ui.tagFilter){
    html+=`<div style="margin-top:12px;padding:8px 10px;background:var(--accent-light);border:1px solid var(--border);border-radius:8px;display:flex;align-items:center;gap:8px;font-size:12px"><span>Tag: <b>${escapeHtml(store.ui.tagFilter)}</b></span><button id="clearTagFilter" style="margin-left:auto;border:none;background:var(--bg-editor);border-radius:50%;width:20px;height:20px;display:grid;place-items:center">×</button></div>`;
  }
  nav.innerHTML=html;
  // events
  nav.querySelectorAll('[data-nb]').forEach(el=>{
    el.addEventListener('click', e=>{
      if(e.target.closest('[data-act]')) return;
      store.ui.selectedNotebookId=el.dataset.nb;
      store.ui.selectedNoteId=null;
      // auto select first note in that view
      const list=getFilteredNotes();
      if(list.length) store.ui.selectedNoteId=list[0].id;
      saveStore(); renderAll();
    });
    // drag over for moving notes
    el.addEventListener('dragover', e=>{ e.preventDefault(); el.style.background='var(--accent-light)'; });
    el.addEventListener('dragleave', ()=> el.style.background='');
    el.addEventListener('drop', e=>{
      e.preventDefault(); el.style.background='';
      const nid=e.dataTransfer.getData('text/plain');
      const note=store.notes.find(n=>n.id===nid);
      if(note && el.dataset.nb!=='all' && el.dataset.nb!=='trash'){
        note.notebookId=el.dataset.nb; note.updatedAt=now(); saveStore(); renderAll();
      }
    });
  });
  nav.querySelectorAll('[data-act="rename"]').forEach(b=> b.addEventListener('click', e=>{ e.stopPropagation(); openNbModal('rename', b.dataset.id); }));
  nav.querySelectorAll('[data-act="delete"]').forEach(b=> b.addEventListener('click', e=>{ e.stopPropagation(); deleteNotebook(b.dataset.id); }));
  const clearBtn=$('#clearTagFilter');
  if(clearBtn) clearBtn.addEventListener('click', ()=>{ store.ui.tagFilter=null; saveStore(); renderAll(); });
}

function getFilteredNotes(){
  let notes=store.notes.filter(n=>{
    if(store.ui.selectedNotebookId==='trash') return n.deleted;
    if(n.deleted) return false;
    if(store.ui.selectedNotebookId!=='all' && n.notebookId!==store.ui.selectedNotebookId) return false;
    if(store.ui.tagFilter && !n.tags.includes(store.ui.tagFilter)) return false;
    if(store.ui.searchQuery){
      const q=store.ui.searchQuery.toLowerCase();
      const hay=(n.title+' '+extractText(n.content)).toLowerCase();
      if(!hay.includes(q)) return false;
    }
    return true;
  });
  // sort: pinned first, then by sortOrder
  const order=store.ui.sortOrder;
  notes.sort((a,b)=>{
    if(a.pinned!==b.pinned) return b.pinned - a.pinned;
    if(order==='alpha') return a.title.localeCompare(b.title);
    if(order==='created') return b.createdAt - a.createdAt;
    return b.updatedAt - a.updatedAt;
  });
  return notes;
}

function highlightText(text, query){
  if(!query) return escapeHtml(text);
  const idx=text.toLowerCase().indexOf(query.toLowerCase());
  if(idx===-1) return escapeHtml(text);
  return escapeHtml(text.slice(0,idx))+'<mark>'+escapeHtml(text.slice(idx, idx+query.length))+'</mark>'+escapeHtml(text.slice(idx+query.length));
}

function renderNoteList(){
  const list=getFilteredNotes();
  const titleMap={all:'All Notes', trash:'Trash'};
  const nb=store.notebooks.find(x=>x.id===store.ui.selectedNotebookId);
  $('#notelistTitle').textContent = titleMap[store.ui.selectedNotebookId] || (nb? nb.name : 'Notes');
  $('#notelistCount').textContent=list.length;
  $('#sortSelect').value=store.ui.sortOrder;
  const container=$('#noteList');
  if(list.length===0){
    const msg=store.ui.searchQuery? 'No matches for "'+escapeHtml(store.ui.searchQuery)+'"' : (store.ui.selectedNotebookId==='trash'?'Trash is empty':'No notes yet');
    container.innerHTML=`<div class="empty-list"><div style="font-size:24px">📄</div><p>${msg}</p>${store.ui.selectedNotebookId!=='trash'?'<button class="btn-primary" style="margin-top:12px" id="emptyNewBtn">New note</button>':''}</div>`;
    const b=$('#emptyNewBtn'); if(b) b.addEventListener('click', ()=> createNote());
    return;
  }
  container.innerHTML=list.map(n=>{
    const snippet=snippetFromHtml(n.content);
    const isSelected=n.id===store.ui.selectedNoteId;
    const q=store.ui.searchQuery;
    let titleHtml=escapeHtml(n.title||'Untitled');
    let snippetHtml=escapeHtml(snippet);
    if(q){
      // highlight in title/snippet using plain text
      const t=n.title||'Untitled';
      const low=q.toLowerCase();
      if(t.toLowerCase().includes(low)) titleHtml=highlightText(t,q);
      if(snippet.toLowerCase().includes(low)) snippetHtml=highlightText(snippet,q);
    }
    const tagsHtml=n.tags.slice(0,3).map(t=>`<span class="mini-tag">#${escapeHtml(t)}</span>`).join('');
    return `<div class="note-card ${isSelected?'selected':''} ${n.pinned?'pinned':''}" data-id="${n.id}" draggable="true">
      <div class="note-card-actions">
        <button data-card-act="pin" title="${n.pinned?'Unpin':'Pin'}">${n.pinned?'📌':'📍'}</button>
        <button data-card-act="dup" title="Duplicate">⧉</button>
        <button data-card-act="del" title="${store.ui.selectedNotebookId==='trash'?'Delete forever':'Delete'}">🗑</button>
      </div>
      <div class="note-card-top">${n.pinned?'<span class="pin-icon">📌</span>':''}<div class="note-card-title">${titleHtml}</div></div>
      <div class="note-card-snippet">${snippetHtml||'<span style="color:var(--text-muted);font-style:italic">No content</span>'}</div>
      <div class="note-card-meta"><span>${fmtDate(n.updatedAt)}</span> ${n.tags.length? '· <span>'+n.tags.length+' tags</span>':''}</div>
      ${tagsHtml?'<div class="note-card-tags">'+tagsHtml+'</div>':''}
    </div>`;
  }).join('');
  container.querySelectorAll('.note-card').forEach(card=>{
    card.addEventListener('click', e=>{
      if(e.target.closest('[data-card-act]')) return;
      store.ui.selectedNoteId=card.dataset.id;
      saveStore(); renderAll();
    });
    card.addEventListener('dragstart', e=>{ e.dataTransfer.setData('text/plain', card.dataset.id); card.classList.add('dragging'); });
    card.addEventListener('dragend', ()=> card.classList.remove('dragging'));
    card.querySelectorAll('[data-card-act]').forEach(btn=>{
      btn.addEventListener('click', e=>{
        e.stopPropagation();
        const id=card.dataset.id;
        if(btn.dataset.cardAct==='pin') togglePin(id);
        if(btn.dataset.cardAct==='dup') duplicateNote(id);
        if(btn.dataset.cardAct==='del') deleteNote(id);
      });
    });
    // right click
    card.addEventListener('contextmenu', e=>{
      e.preventDefault();
      showCtxMenu(e.clientX, e.clientY, card.dataset.id);
    });
  });
}

let suppressEditorInput=false;
let historyStack=[]; let historyIndex=-1;
function pushHistory(html){
  // trim future
  historyStack=historyStack.slice(0, historyIndex+1);
  historyStack.push(html);
  if(historyStack.length>80) historyStack.shift();
  else historyIndex++;
  if(historyStack.length>80) historyIndex=historyStack.length-1;
}
function renderEditor(){
  const note=store.notes.find(n=>n.id===store.ui.selectedNoteId);
  const empty=$('#editorEmpty');
  const wrap=$('#editorWrap');
  if(!note || (store.ui.selectedNotebookId==='trash' && !note.deleted) ){
    empty.style.display='flex'; wrap.style.display='none';
    return;
  }
  // if trash view but note not deleted, shouldn't happen; but handle
  empty.style.display='none'; wrap.style.display='flex';
  suppressEditorInput=true;
  $('#editorTitle').value=note.title;
  $('#editorTitle').disabled=note.deleted;
  $('#editorEl').contentEditable = note.deleted ? 'false' : 'true';
  $('#editorEl').innerHTML=note.content || '';
  if(note.deleted) $('#editorEl').style.opacity='0.6'; else $('#editorEl').style.opacity='1';
  // tags
  const pills=$('#tagPills');
  pills.innerHTML=note.tags.map(t=>`<span class="tag-pill">#${escapeHtml(t)} <button data-tag="${escapeHtml(t)}">×</button></span>`).join('');
  pills.querySelectorAll('button').forEach(b=> b.addEventListener('click', ()=>{
    note.tags=note.tags.filter(x=>x!==b.dataset.tag);
    note.updatedAt=now(); saveStore(); renderEditor(); renderNoteList();
  }));
  $('#tagInput').style.display= note.deleted? 'none':'block';
  // pin button state
  $('#btnPinNote').textContent=note.pinned?'Unpin 📌':'Pin 📍';
  $('#btnPinNote').style.background=note.pinned?'var(--accent-bg)':'';
  // move dropdown
  const dd=$('#moveDropdown');
  dd.innerHTML=store.notebooks.map(nb=>`<button data-move="${nb.id}"><span class="nb-dot" style="background:${nb.color}"></span> ${escapeHtml(nb.name)} ${nb.id===note.notebookId?'✓':''}</button>`).join('');
  dd.querySelectorAll('[data-move]').forEach(b=> b.addEventListener('click', ()=>{
    note.notebookId=b.dataset.move; note.updatedAt=now(); dd.classList.remove('open'); saveStore(); renderAll();
  }));
  // delete button label
  $('#btnDelNote').textContent= note.deleted ? 'Delete forever' : 'Delete';
  $('#btnDupNote').style.display= note.deleted? 'none':'';
  $('#btnMoveNote').style.display= note.deleted? 'none':'';
  $('#btnPinNote').style.display= note.deleted? 'none':'';
  // show restore button if deleted
  let restoreBtn=$('#btnRestore');
  if(note.deleted){
    if(!restoreBtn){
      const btn=document.createElement('button');
      btn.id='btnRestore'; btn.textContent='Restore'; btn.style.background='var(--accent)'; btn.style.color='white'; btn.style.border='none';
      btn.addEventListener('click', ()=> restoreNote(note.id));
      $('.editor-actions').prepend(btn);
    }
  } else {
    if(restoreBtn) restoreBtn.remove();
  }
  // history init
  historyStack=[note.content||'']; historyIndex=0;
  suppressEditorInput=false;
  updateToolbarState();
  // focus if new note empty
  if(!note.content || note.content==='<p><br></p>'){
    setTimeout(()=> $('#editorEl').focus(), 50);
  }
}

function updateStorageInfo(){
  try{
    const s=JSON.stringify(store).length;
    $('#storageInfo').textContent=(s/1024).toFixed(1)+' KB stored · '+store.notes.filter(n=>!n.deleted).length+' notes';
  }catch(e){}
}

/* ---------- Actions ---------- */
function createNote(){
  const nbId= store.ui.selectedNotebookId==='all' || store.ui.selectedNotebookId==='trash' ? (store.notebooks[0]?.id || null) : store.ui.selectedNotebookId;
  if(!nbId){ alert('Create a notebook first'); return; }
  const n={id:uid(), notebookId:nbId, title:'Untitled', content:'<p><br></p>', tags:[], pinned:false, createdAt:now(), updatedAt:now(), deleted:false, originalNotebookId:null};
  store.notes.unshift(n);
  store.ui.selectedNotebookId = nbId==='trash' ? 'all' : store.ui.selectedNotebookId;
  // if we were in trash, switch to all
  if(store.ui.selectedNotebookId==='trash') store.ui.selectedNotebookId='all';
  store.ui.selectedNoteId=n.id;
  saveStore(); renderAll();
  setTimeout(()=>{ $('#editorTitle').focus(); $('#editorTitle').select(); }, 80);
}
function duplicateNote(id){
  const src=store.notes.find(n=>n.id===id); if(!src) return;
  const copy={...src, id:uid(), title:src.title+' (copy)', createdAt:now(), updatedAt:now(), deleted:false};
  copy.tags=[...src.tags];
  store.notes.unshift(copy);
  store.ui.selectedNoteId=copy.id;
  saveStore(); renderAll();
}
function deleteNote(id){
  const n=store.notes.find(x=>x.id===id); if(!n) return;
  if(n.deleted){
    if(!confirm('Permanently delete this note?')) return;
    store.notes=store.notes.filter(x=>x.id!==id);
    if(store.ui.selectedNoteId===id) store.ui.selectedNoteId=null;
  } else {
    n.deleted=true; n.originalNotebookId=n.notebookId; n.updatedAt=now();
    if(store.ui.selectedNoteId===id) store.ui.selectedNoteId=null;
  }
  saveStore(); renderAll();
}
function restoreNote(id){
  const n=store.notes.find(x=>x.id===id); if(!n) return;
  n.deleted=false;
  if(n.originalNotebookId && store.notebooks.some(b=>b.id===n.originalNotebookId)) n.notebookId=n.originalNotebookId;
  n.updatedAt=now();
  saveStore(); renderAll();
}
function togglePin(id){
  const n=store.notes.find(x=>x.id===id); if(!n) return;
  n.pinned=!n.pinned; n.updatedAt=now(); saveStore(); renderAll();
}
function deleteNotebook(id){
  const cnt=store.notes.filter(n=>!n.deleted && n.notebookId===id).length;
  if(cnt>0 && !confirm(`Delete notebook and move ${cnt} note(s) to Trash?`)) return;
  // move notes to trash or delete notebook
  store.notes.forEach(n=>{ if(n.notebookId===id && !n.deleted){ n.deleted=true; n.originalNotebookId=id; }});
  store.notebooks=store.notebooks.filter(b=>b.id!==id);
  if(store.ui.selectedNotebookId===id) store.ui.selectedNotebookId='all';
  saveStore(); renderAll();
}

/* ---------- Notebook modal ---------- */
let nbModalMode='create', nbModalId=null;
function openNbModal(mode, id){
  nbModalMode=mode; nbModalId=id;
  $('#nbModalTitle').textContent= mode==='rename' ? 'Rename Notebook' : 'New Notebook';
  $('#nbModalOk').textContent= mode==='rename' ? 'Save' : 'Create';
  $('#nbModalInput').value= mode==='rename' ? (store.notebooks.find(b=>b.id===id)?.name||'') : '';
  $('#nbModal').classList.add('open');
  setTimeout(()=> $('#nbModalInput').focus(), 50);
}
function closeNbModal(){ $('#nbModal').classList.remove('open'); }
$('#nbModalOk').addEventListener('click', ()=>{
  const name=$('#nbModalInput').value.trim();
  if(!name) return;
  if(nbModalMode==='create'){
    const nb={id:uid(), name, color:CONFIG.DEFAULT_NOTEBOOK_COLORS[store.notebooks.length % CONFIG.DEFAULT_NOTEBOOK_COLORS.length]};
    store.notebooks.push(nb);
    store.ui.selectedNotebookId=nb.id; store.ui.selectedNoteId=null;
  } else {
    const nb=store.notebooks.find(b=>b.id===nbModalId);
    if(nb) nb.name=name;
  }
  saveStore(); renderAll(); closeNbModal();
});
$('#nbModalCancel').addEventListener('click', closeNbModal);
$('#nbModal').addEventListener('click', e=>{ if(e.target===$('#nbModal')) closeNbModal(); });
$('#nbModalInput').addEventListener('keydown', e=>{ if(e.key==='Enter') $('#nbModalOk').click(); if(e.key==='Escape') closeNbModal(); });

/* ---------- Context menu ---------- */
function showCtxMenu(x,y, noteId){
  const n=store.notes.find(v=>v.id===noteId); if(!n) return;
  const m=$('#ctxMenu');
  m.innerHTML=`
    <button data-ctx="pin">${n.pinned?'Unpin':'Pin'}</button>
    <button data-ctx="dup">Duplicate</button>
    <button data-ctx="move">Move to…</button>
    <div class="sep"></div>
    <button data-ctx="del" style="color:#b45309">${n.deleted?'Delete forever':'Delete'}</button>
    ${n.deleted?'<button data-ctx="restore">Restore</button>':''}
  `;
  m.style.left=x+'px'; m.style.top=y+'px'; m.classList.add('open');
  m.querySelectorAll('[data-ctx]').forEach(b=> b.addEventListener('click', ()=>{
    m.classList.remove('open');
    if(b.dataset.ctx==='pin') togglePin(noteId);
    if(b.dataset.ctx==='dup') duplicateNote(noteId);
    if(b.dataset.ctx==='del') deleteNote(noteId);
    if(b.dataset.ctx==='restore') restoreNote(noteId);
    if(b.dataset.ctx==='move'){
      // show move dropdown near
      store.ui.selectedNoteId=noteId; renderEditor();
      $('#moveDropdown').classList.add('open');
    }
  }));
}
document.addEventListener('click', ()=> $('#ctxMenu').classList.remove('open'));

/* ---------- Editor events ---------- */
const editorEl=$('#editorEl');
const titleEl=$('#editorTitle');

titleEl.addEventListener('input', ()=>{
  const n=store.notes.find(x=>x.id===store.ui.selectedNoteId); if(!n) return;
  n.title=titleEl.value; n.updatedAt=now();
  debouncedSave(); renderNoteList();
});
titleEl.addEventListener('keydown', e=>{
  if(e.key==='Enter'){ e.preventDefault(); editorEl.focus(); }
});

editorEl.addEventListener('input', ()=>{
  if(suppressEditorInput) return;
  const n=store.notes.find(x=>x.id===store.ui.selectedNoteId); if(!n) return;
  // markdown shortcuts check
  handleMarkdownShortcuts();
  // sanitize checklist clicks handled separately
  const html=sanitizeHtml(editorEl.innerHTML);
  // avoid loop if sanitize changed nothing significant
  n.content=editorEl.innerHTML; // keep as is but sanitized on save
  n.updatedAt=now();
  // history
  if(historyStack[historyIndex]!==editorEl.innerHTML) pushHistory(editorEl.innerHTML);
  debouncedSave();
  // update snippet live
  renderNoteList();
  updateToolbarState();
});
editorEl.addEventListener('keydown', e=>{
  // undo/redo
  if((e.ctrlKey||e.metaKey) && e.key.toLowerCase()==='z'){
    e.preventDefault();
    if(e.shiftKey) redo(); else undo();
    return;
  }
  if((e.ctrlKey||e.metaKey) && e.key.toLowerCase()==='y'){ e.preventDefault(); redo(); return; }
  // bold/italic/underline
  if((e.ctrlKey||e.metaKey) && ['b','i','u'].includes(e.key.toLowerCase())){
    e.preventDefault();
    const map={b:'bold',i:'italic',u:'underline'};
    document.execCommand(map[e.key.toLowerCase()], false, null);
    updateToolbarState();
    triggerSaveFromEditor();
    return;
  }
  // Enter handling for lists
  if(e.key==='Enter'){
    const sel=window.getSelection();
    if(!sel.rangeCount) return;
    const range=sel.getRangeAt(0);
    let li=range.startContainer;
    while(li && li!==editorEl && li.tagName!=='LI') li=li.parentNode;
    if(li && li.tagName==='LI'){
      const text=li.textContent.trim();
      const ul=li.parentNode;
      const isChecklist=ul.classList.contains('checklist');
      if(text===''){
        e.preventDefault();
        // exit list
        const p=document.createElement('p');
        p.innerHTML='<br>';
        if(isChecklist){
          // remove empty li
          li.remove();
          if(ul.children.length===0) ul.remove();
        } else {
          // unwrap
          const newP=document.createElement('p');
          newP.innerHTML='<br>';
          ul.parentNode.insertBefore(newP, ul.nextSibling);
          li.remove();
          if(ul.children.length===0) ul.remove();
          placeCaretAt(newP,0);
          triggerSaveFromEditor();
          return;
        }
        // place after ul
        const after=document.createElement('p');
        after.innerHTML='<br>';
        const parent=ul.parentNode;
        parent.insertBefore(after, ul.nextSibling);
        placeCaretAt(after,0);
        triggerSaveFromEditor();
        return;
      } else {
        // let browser handle continuation, but for checklist ensure new li has checkbox
        if(isChecklist){
          // allow default then fix next li
          setTimeout(()=>{
            const sel2=window.getSelection();
            let newLi=sel2.anchorNode;
            while(newLi && newLi.tagName!=='LI') newLi=newLi.parentNode;
            if(newLi && !newLi.querySelector('input')){
              const cb=document.createElement('input'); cb.type='checkbox';
              cb.addEventListener('change', onCheckToggle);
              newLi.insertBefore(cb, newLi.firstChild);
              // ensure span wrapper
              if(!newLi.querySelector('span')){
                const span=document.createElement('span');
                // move text nodes into span
                const nodes=[...newLi.childNodes].filter(n=> n!==cb);
                nodes.forEach(n=> span.appendChild(n));
                newLi.appendChild(span);
              }
            }
            triggerSaveFromEditor();
          }, 0);
        }
      }
    }
  }
  // Escape handling already via browser
});

function triggerSaveFromEditor(){
  const n=store.notes.find(x=>x.id===store.ui.selectedNoteId); if(!n) return;
  n.content=editorEl.innerHTML; n.updatedAt=now();
  pushHistory(editorEl.innerHTML);
  debouncedSave(); renderNoteList();
}

function placeCaretAt(el, offset){
  const range=document.createRange(); const sel=window.getSelection();
  range.setStart(el, offset); range.collapse(true); sel.removeAllRanges(); sel.addRange(range);
}

function undo(){
  if(historyIndex<=0) return;
  historyIndex--;
  const html=historyStack[historyIndex];
  editorEl.innerHTML=html;
  const n=store.notes.find(x=>x.id===store.ui.selectedNoteId); if(n) n.content=html;
  saveStore(); renderNoteList();
}
function redo(){
  if(historyIndex>=historyStack.length-1) return;
  historyIndex++;
  const html=historyStack[historyIndex];
  editorEl.innerHTML=html;
  const n=store.notes.find(x=>x.id===store.ui.selectedNoteId); if(n) n.content=html;
  saveStore(); renderNoteList();
}

/* Markdown shortcuts: only at line start */
function handleMarkdownShortcuts(){
  const sel=window.getSelection();
  if(!sel.rangeCount) return;
  const range=sel.getRangeAt(0);
  if(!range.collapsed) return;
  // find current block
  let node=range.startContainer;
  // if text node, get its parent block
  let block=node;
  while(block && block!==editorEl && !['P','DIV','H1','H2','H3','LI','BLOCKQUOTE'].includes(block.tagName)) block=block.parentNode;
  if(!block || block===editorEl) {
    // try to find containing p/div
    block=node.parentNode;
    while(block && block!==editorEl && block.tagName!=='DIV' && block.tagName!=='P') block=block.parentNode;
  }
  if(!block || block===editorEl) return;
  // get text up to cursor
  const textBefore = getTextBeforeCursor(block, range);
  // Only trigger if at line start: textBefore is exactly the shortcut + space and block starts with it
  // Check patterns
  const patterns=[
    { regex:/^# $/, action:()=> convertBlock(block,'H1') },
    { regex:/^## $/, action:()=> convertBlock(block,'H2') },
    { regex:/^### $/, action:()=> convertBlock(block,'H3') },
    { regex:/^- $/, action:()=> convertToList(block,'UL',false) },
    { regex:/^\* $/, action:()=> convertToList(block,'UL',false) },
    { regex:/^\[\] $/, action:()=> convertToList(block,'UL',true) },
    { regex:/^\[ \] $/, action:()=> convertToList(block,'UL',true) },
    { regex:/^> $/, action:()=> convertToBlockquote(block) },
    { regex:/^1\. $/, action:()=> convertToList(block,'OL',false) },
  ];
  for(const p of patterns){
    if(p.regex.test(textBefore)){
      // ensure it's at start of block (block text starts with that)
      const blockText=(block.textContent||'');
      if(blockText.startsWith(textBefore.trimEnd()) || blockText.trimStart().startsWith(textBefore.trim())){
        p.action();
        break;
      }
    }
  }
}
function getTextBeforeCursor(block, range){
  const r=document.createRange();
  r.selectNodeContents(block);
  r.setEnd(range.startContainer, range.startOffset);
  const frag=r.cloneContents();
  const div=document.createElement('div'); div.appendChild(frag);
  return div.textContent;
}
function convertBlock(block, tag){
  const newEl=document.createElement(tag);
  newEl.innerHTML='<br>';
  block.parentNode.replaceChild(newEl, block);
  placeCaretAt(newEl,0);
  triggerSaveFromEditor();
}
function convertToList(block, listTag, isChecklist){
  const list=document.createElement(listTag);
  if(isChecklist) list.className='checklist';
  const li=document.createElement('li');
  if(isChecklist){
    const cb=document.createElement('input'); cb.type='checkbox';
    cb.addEventListener('change', onCheckToggle);
    const span=document.createElement('span'); span.innerHTML='<br>';
    li.appendChild(cb); li.appendChild(span);
  } else {
    li.innerHTML='<br>';
  }
  list.appendChild(li);
  block.parentNode.replaceChild(list, block);
  // focus li span or li
  if(isChecklist) placeCaretAt(li.querySelector('span'),0);
  else placeCaretAt(li,0);
  triggerSaveFromEditor();
}
function convertToBlockquote(block){
  const bq=document.createElement('blockquote');
  bq.innerHTML='<br>';
  block.parentNode.replaceChild(bq, block);
  placeCaretAt(bq,0);
  triggerSaveFromEditor();
}
function onCheckToggle(e){
  const li=e.target.closest('li');
  if(li){
    if(e.target.checked) li.classList.add('checked'); else li.classList.remove('checked');
    triggerSaveFromEditor();
  }
}
// delegate checkbox changes
editorEl.addEventListener('change', e=>{
  if(e.target.type==='checkbox') onCheckToggle(e);
});
editorEl.addEventListener('click', e=>{
  if(e.target.type==='checkbox'){
    // toggle class already handled via change, but ensure
    setTimeout(()=> onCheckToggle({target:e.target}),0);
  }
});

/* Toolbar */
function updateToolbarState(){
  const cmds=['bold','italic','underline','strikeThrough'];
  document.querySelectorAll('.tb-btn[data-cmd]').forEach(btn=>{
    const cmd=btn.dataset.cmd;
    let active=false;
    try{
      if(['bold','italic','underline','strikeThrough'].includes(cmd)) active=document.queryCommandState(cmd);
      else if(cmd==='h1') active=isCursorIn('H1');
      else if(cmd==='h2') active=isCursorIn('H2');
      else if(cmd==='h3') active=isCursorIn('H3');
    }catch(e){}
    btn.classList.toggle('active', active);
  });
}
function isCursorIn(tag){
  const sel=window.getSelection(); if(!sel.rangeCount) return false;
  let n=sel.anchorNode;
  while(n && n!==editorEl){ if(n.tagName===tag) return true; n=n.parentNode; }
  return false;
}
document.addEventListener('selectionchange', ()=>{ if(document.activeElement===editorEl) updateToolbarState(); });
$('#toolbar').addEventListener('click', e=>{
  const btn=e.target.closest('.tb-btn'); if(!btn) return;
  const cmd=btn.dataset.cmd;
  editorEl.focus();
  if(cmd==='code'){
    // wrap selection in code
    const sel=window.getSelection();
    if(!sel.rangeCount) return;
    const range=sel.getRangeAt(0);
    if(range.collapsed){
      document.execCommand('insertHTML', false, '<code>code</code>');
    } else {
      const content=range.extractContents();
      const code=document.createElement('code');
      code.appendChild(content);
      range.insertNode(code);
      // select after
      range.setStartAfter(code); range.collapse(true);
      sel.removeAllRanges(); sel.addRange(range);
    }
    triggerSaveFromEditor();
  } else if(cmd==='h1' || cmd==='h2' || cmd==='h3'){
    const tag=cmd.toUpperCase();
    // toggle: if already in that heading, convert to p
    if(isCursorIn(tag)){
      document.execCommand('formatBlock', false, 'p');
    } else {
      document.execCommand('formatBlock', false, tag);
    }
    triggerSaveFromEditor();
  } else if(cmd==='checklist'){
    convertToListAtCursor(true);
  } else if(cmd==='blockquote'){
    document.execCommand('formatBlock', false, 'blockquote');
    triggerSaveFromEditor();
  } else if(cmd==='hr'){
    document.execCommand('insertHorizontalRule', false, null);
    triggerSaveFromEditor();
  } else {
    document.execCommand(cmd, false, null);
    triggerSaveFromEditor();
  }
  updateToolbarState();
});
function convertToListAtCursor(isChecklist){
  const sel=window.getSelection(); if(!sel.rangeCount) return;
  let block=sel.anchorNode;
  while(block && block!==editorEl && !['P','DIV','H1','H2','H3'].includes(block.tagName)) block=block.parentNode;
  if(!block || block===editorEl) {
    document.execCommand(isChecklist?'insertUnorderedList': 'insertUnorderedList', false, null);
    if(isChecklist){
      const ul=editorEl.querySelector('ul:not(.checklist)');
      if(ul) ul.classList.add('checklist');
      ul?.querySelectorAll('li').forEach(li=>{
        if(!li.querySelector('input')){
          const cb=document.createElement('input'); cb.type='checkbox';
          li.insertBefore(cb, li.firstChild);
        }
      });
    }
  } else {
    convertToList(block, 'UL', isChecklist);
  }
  triggerSaveFromEditor();
}

/* Paste sanitization */
editorEl.addEventListener('paste', e=>{
  e.preventDefault();
  const text=(e.clipboardData||window.clipboardData).getData('text/html') || (e.clipboardData||window.clipboardData).getData('text/plain');
  // if html contains tags, sanitize; else plain text
  let html=text;
  // if plain text without html tags, convert newlines to <br> or paragraphs
  const hasHtml = /<[a-z][\s\S]*>/i.test(text) && (e.clipboardData.getData('text/html'));
  if(!hasHtml){
    const plain=(e.clipboardData||window.clipboardData).getData('text/plain');
    html=escapeHtml(plain).replace(/\n/g,'<br>');
  }
  const clean=sanitizeHtml(html);
  document.execCommand('insertHTML', false, clean);
  triggerSaveFromEditor();
});

/* Search */
$('#searchInput').addEventListener('input', e=>{
  store.ui.searchQuery=e.target.value;
  $('#searchClear').style.display= store.ui.searchQuery ? 'grid' : 'none';
  saveStore(); renderNoteList();
});
$('#searchClear').addEventListener('click', ()=>{
  store.ui.searchQuery=''; $('#searchInput').value=''; $('#searchClear').style.display='none';
  saveStore(); renderNoteList();
});

/* Sort */
$('#sortSelect').addEventListener('change', e=>{
  store.ui.sortOrder=e.target.value; saveStore(); renderNoteList();
});

/* Tags */
$('#tagInput').addEventListener('keydown', e=>{
  if(e.key==='Enter'){
    e.preventDefault();
    const val=e.target.value.trim().replace(/^#/,'').toLowerCase();
    if(!val) return;
    const n=store.notes.find(x=>x.id===store.ui.selectedNoteId); if(!n) return;
    if(!n.tags.includes(val)){ n.tags.push(val); n.updatedAt=now(); saveStore(); renderEditor(); renderNoteList(); }
    e.target.value='';
  }
  if(e.key==='Backspace' && !e.target.value){
    const n=store.notes.find(x=>x.id===store.ui.selectedNoteId); if(n && n.tags.length){ n.tags.pop(); saveStore(); renderEditor(); renderNoteList(); }
  }
});
// clicking tag in note list filters
document.addEventListener('click', e=>{
  const pill=e.target.closest('.mini-tag');
  if(pill){
    const tag=pill.textContent.replace('#','').trim();
    store.ui.tagFilter=tag; saveStore(); renderAll();
  }
  const tp=e.target.closest('.tag-pill');
  if(tp && !e.target.closest('button')){
    const tag=tp.textContent.replace('#','').replace('×','').trim();
    store.ui.tagFilter=tag; saveStore(); renderAll();
  }
});

/* Move dropdown toggle */
$('#btnMoveNote').addEventListener('click', e=>{
  e.stopPropagation();
  $('#moveDropdown').classList.toggle('open');
});
document.addEventListener('click', ()=> $('#moveDropdown').classList.remove('open'));

/* Pin / dup / del in editor */
$('#btnPinNote').addEventListener('click', ()=>{ if(store.ui.selectedNoteId) togglePin(store.ui.selectedNoteId); });
$('#btnDupNote').addEventListener('click', ()=>{ if(store.ui.selectedNoteId) duplicateNote(store.ui.selectedNoteId); });
$('#btnDelNote').addEventListener('click', ()=>{ if(store.ui.selectedNoteId) deleteNote(store.ui.selectedNoteId); });

/* New note buttons */
$('#btnNewNote').addEventListener('click', createNote);
$('#btnEmptyNew').addEventListener('click', createNote);
$('#btnNewNotebook').addEventListener('click', ()=> openNbModal('create'));

/* Sidebar collapse */
$('#toggleSidebar').addEventListener('click', ()=>{
  $('#mainLayout').classList.toggle('sidebar-collapsed');
  store.ui.sidebarCollapsed=$('#mainLayout').classList.contains('sidebar-collapsed');
  saveStore();
});
$('#btnCollapse').addEventListener('click', ()=>{
  $('#mainLayout').classList.add('sidebar-collapsed');
  store.ui.sidebarCollapsed=true; saveStore();
});

/* Theme */
$('#themeToggle').addEventListener('click', ()=>{
  store.ui.theme= store.ui.theme==='light' ? 'dark' : 'light';
  applyTheme(); saveStore();
});

/* Resizable panes */
function makeResizable(handle, pane, key){
  let startX, startW;
  handle.addEventListener('mousedown', e=>{
    e.preventDefault();
    startX=e.clientX; startW=pane.getBoundingClientRect().width;
    handle.classList.add('dragging');
    function onMove(ev){
      const dx=ev.clientX - startX;
      let nw=startW + dx;
      nw=Math.max(180, Math.min(420, nw));
      pane.style.width=nw+'px';
      store.ui.paneWidths[key]=nw;
    }
    function onUp(){
      handle.classList.remove('dragging');
      document.removeEventListener('mousemove', onMove);
      document.removeEventListener('mouseup', onUp);
      saveStore();
    }
    document.addEventListener('mousemove', onMove);
    document.addEventListener('mouseup', onUp);
  });
}
makeResizable($('#handle1'), $('#sidebar'), 'sidebar');
makeResizable($('#handle2'), $('#notelist'), 'notelist');

/* Keyboard: Ctrl+N new note */
document.addEventListener('keydown', e=>{
  if((e.ctrlKey||e.metaKey) && e.key.toLowerCase()==='n'){
    e.preventDefault(); createNote();
  }
});

/* Init */
loadStore();
renderAll();
// restore search input
$('#searchInput').value=store.ui.searchQuery;
$('#searchClear').style.display= store.ui.searchQuery ? 'grid' : 'none';

// expose for probe tests
window.__inkwell={ store, extractText, sanitizeHtml, getFilteredNotes, saveStore, loadStore, CONFIG };
</script>
</body>
</html>
<!-- agent-meta {"model":"meta/muse-spark-1.2-contributor","provider":"openrouter","persona":"full","sessionId":"954d0d94-26d5-4945-a113-ba4e58818a30","tokensIn":162038,"tokensOut":21092,"tokensTotal":183130,"cost":0.007316268,"turns":7,"toolCalls":6,"failedToolCalls":0,"timestamp":"2026-08-23T00:55:50.640Z"} -->