← Notes results

Notes

muse spark 1.2 · basic

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 54,084 bytes · SHA-256 7230e7435d40
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title> Folio — Notes</title>
<style>
/* ===== TUNABLE CONSTANTS (CSS) ===== */
:root{
  --bg:#FDF8F0;
  --bg-2:#F6EFE3;
  --sidebar-bg:#F3EBDA;
  --sidebar-border:#E8DDC6;
  --middle-bg:#FEFCF8;
  --middle-border:#EDE6D6;
  --editor-bg:#FDF8F0;
  --text:#2B241E;
  --text-soft:#7A7066;
  --text-faint:#A99E92;
  --accent:#C07A3A;
  --accent-2:#E8A25D;
  --accent-bg:#FFF1E0;
  --accent-border:#E9C9A0;
  --card-bg:#FFFFFF;
  --card-border:#EDE6D6;
  --card-selected-bg:#FFF6E8;
  --card-selected-border:#E9C9A0;
  --toolbar-bg:#FFFFFF;
  --toolbar-border:#EDE6D6;
  --hover:#F0E6D3;
  --hover-2:#EFE7D6;
  --danger:#C94A3A;
  --radius:10px;
  --radius-sm:8px;
  --shadow:0 4px 24px rgba(60,40,20,0.07);
  --shadow-sm:0 1px 8px rgba(60,40,20,0.06);
  --font-ui: 'Inter', ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
  --font-serif: 'Georgia', 'Times New Roman', serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
[data-theme="dark"]{
  --bg:#1A1816;
  --bg-2:#23201D;
  --sidebar-bg:#1E1C1A;
  --sidebar-border:#2E2B28;
  --middle-bg:#1F1D1B;
  --middle-border:#2E2B28;
  --editor-bg:#1A1816;
  --text:#EDE8E0;
  --text-soft:#A8A09A;
  --text-faint:#7A7470;
  --accent:#E8A25D;
  --accent-bg:#2A241E;
  --accent-border:#4A3F30;
  --card-bg:#25221F;
  --card-border:#2E2B28;
  --card-selected-bg:#2E281F;
  --card-selected-border:#4A3F30;
  --toolbar-bg:#25221F;
  --toolbar-border:#2E2B28;
  --hover:#2A2724;
  --hover-2:#2E2B28;
}
*{box-sizing:border-box}
html,body{height:100%;margin:0}
body{
  font-family:var(--font-ui);
  background:var(--bg);
  color:var(--text);
  overflow:hidden;
  -webkit-font-smoothing:antialiased;
}
/* Top bar */
.topbar{
  height:48px; display:flex; align-items:center; gap:12px;
  padding:0 14px; border-bottom:1px solid var(--sidebar-border);
  background:var(--sidebar-bg); position:relative; z-index:10;
}
.brand{display:flex;align-items:center;gap:10px;min-width:0}
.brand-mark{
  width:30px;height:30px;border-radius:8px;background:linear-gradient(135deg,#E8A25D,#C07A3A);
  display:grid;place-items:center;color:white;font-weight:800;font-size:14px;letter-spacing:-0.5px
}
.brand-name{font-weight:700;letter-spacing:-0.02em;font-size:15px}
.brand-sub{font-size:11px;color:var(--text-soft);margin-top:1px}
.search-wrap{flex:1;max-width:560px;margin:0 auto;display:flex;align-items:center;position:relative}
.search-wrap input{
  width:100%;height:34px;border-radius:999px;border:1px solid var(--middle-border);
  background:var(--card-bg);color:var(--text);padding:0 14px 0 36px;font-size:13.5px;outline:none;
}
.search-wrap input::placeholder{color:var(--text-faint)}
.search-wrap svg{position:absolute;left:12px;color:var(--text-faint);pointer-events:none}
.top-actions{display:flex;align-items:center;gap:8px}
.icon-btn{
  width:32px;height:32px;border-radius:8px;border:1px solid transparent;background:transparent;
  display:grid;place-items:center;color:var(--text-soft);cursor:pointer;transition:0.15s;
}
.icon-btn:hover{background:var(--hover);color:var(--text);border-color:var(--middle-border)}
.icon-btn.active{background:var(--text);color:var(--bg)}
/* App layout */
.app{height:calc(100% - 48px);display:flex;overflow:hidden}
.pane{height:100%;display:flex;flex-direction:column;overflow:hidden;flex-shrink:0}
#sidebar{width:260px;background:var(--sidebar-bg);border-right:1px solid var(--sidebar-border)}
#notelist{width:360px;background:var(--middle-bg);border-right:1px solid var(--middle-border)}
#editor{flex:1;background:var(--editor-bg);min-width:320px}
.resizer{width:6px;cursor:col-resize;background:transparent;position:relative;flex-shrink:0}
.resizer:hover{background:rgba(192,122,58,0.12)}
.resizer::after{content:"";position:absolute;left:2px;top:0;bottom:0;width:1px;background:var(--middle-border)}
.collapsed #sidebar{width:0!important;overflow:hidden;border:none}
.collapsed #resizer1{display:none}
/* Sidebar */
.sidebar-head{padding:14px 12px 8px;display:flex;align-items:center;justify-content:space-between}
.section-label{font-size:10px;letter-spacing:0.14em;color:var(--text-faint);font-weight:700;text-transform:uppercase}
.small-btn{
  height:26px;padding:0 10px;border-radius:8px;border:1px solid var(--sidebar-border);
  background:var(--card-bg);font-size:12px;font-weight:600;color:var(--text-soft);cursor:pointer
}
.small-btn:hover{border-color:var(--accent-border);color:var(--text)}
.nav{padding:6px 8px;display:flex;flex-direction:column;gap:4px}
.nav-item{
  display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:10px;cursor:pointer;
  color:var(--text-soft);font-size:13.5px;transition:0.15s;border:1px solid transparent
}
.nav-item:hover{background:var(--hover);color:var(--text)}
.nav-item.active{background:var(--card-bg);border-color:var(--card-border);color:var(--text);box-shadow:var(--shadow-sm)}
.nav-item .count{margin-left:auto;background:var(--hover-2);padding:2px 7px;border-radius:999px;font-size:11px;font-weight:700}
.nav-item.active .count{background:var(--accent-bg);border:1px solid var(--accent-border);color:var(--accent)}
.notebook-list{padding:4px 8px;overflow:auto;flex:1}
.nb{border-radius:10px;padding:6px 6px 6px 8px;display:flex;flex-direction:column;gap:4px;margin-bottom:2px;border:1px solid transparent}
.nb.active{background:rgba(255,255,255,0.6);border-color:var(--card-border)}
[data-theme="dark"] .nb.active{background:var(--hover)}
.nb-head{display:flex;align-items:center;gap:8px;cursor:pointer}
.nb-title{font-size:13px;font-weight:600;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.nb-meta{font-size:11px;color:var(--text-faint)}
.nb-actions{display:flex;gap:2px;opacity:0;transition:0.15s}
.nb:hover .nb-actions{opacity:1}
.mini{width:24px;height:24px;border-radius:6px;border:1px solid transparent;background:transparent;display:grid;place-items:center;cursor:pointer;color:var(--text-faint)}
.mini:hover{background:var(--card-bg);border-color:var(--card-border);color:var(--text)}
.trash{margin-top:auto;border-top:1px solid var(--sidebar-border);padding:8px}
.drop-target{outline:2px dashed var(--accent);outline-offset:-4px;border-radius:10px;background:var(--accent-bg)!important}
/* Notelist */
.list-head{padding:12px 12px 10px;border-bottom:1px solid var(--middle-border);background:var(--middle-bg)}
.list-head-top{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:10px}
.list-title{font-family:var(--font-serif);font-size:18px;font-weight:700;letter-spacing:-0.02em}
.sort{height:28px;border-radius:8px;border:1px solid var(--middle-border);background:var(--card-bg);padding:0 8px;font-size:12px;color:var(--text-soft)}
.new-note-btn{
  width:100%;height:36px;border-radius:10px;border:1px solid var(--accent-border);
  background:linear-gradient(180deg,#E8A25D,#C07A3A);color:white;font-weight:700;font-size:13px;cursor:pointer;
  display:flex;align-items:center;justify-content:center;gap:8px;box-shadow:0 6px 16px rgba(192,122,58,0.25)
}
.new-note-btn:hover{filter:brightness(1.04)}
.list{overflow:auto;flex:1;padding:10px 10px 24px;display:flex;flex-direction:column;gap:8px}
.note-card{
  background:var(--card-bg);border:1px solid var(--card-border);border-radius:12px;
  padding:12px 12px 10px;cursor:pointer;transition:0.15s;position:relative
}
.note-card:hover{border-color:var(--accent-border);box-shadow:var(--shadow-sm);transform:translateY(-1px)}
.note-card.selected{background:var(--card-selected-bg);border-color:var(--card-selected-border);box-shadow:var(--shadow)}
.note-card.pinned{border-left:3px solid var(--accent)}
.note-card-head{display:flex;gap:8px;align-items:flex-start}
.note-card-title{font-size:13.5px;font-weight:700;line-height:1.3;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pin{color:var(--accent);font-size:12px}
.note-card-snippet{font-size:12.5px;color:var(--text-soft);line-height:1.45;margin-top:6px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;min-height:34px}
.note-card-foot{display:flex;align-items:center;gap:6px;margin-top:8px}
.time{font-size:11px;color:var(--text-faint)}
.tag{font-size:10px;font-weight:700;letter-spacing:0.02em;padding:3px 7px;border-radius:999px;background:var(--accent-bg);border:1px solid var(--accent-border);color:var(--accent)}
.hl{background:rgba(232,162,93,0.32);padding:0 2px;border-radius:3px}
/* Editor */
.editor-head{padding:12px 12px 0}
.toolbar{
  display:flex;flex-wrap:wrap;gap:6px;padding:8px;background:var(--toolbar-bg);border:1px solid var(--toolbar-border);
  border-radius:12px;box-shadow:var(--shadow-sm);align-items:center
}
.tbtn{
  min-width:30px;height:30px;padding:0 8px;border-radius:8px;border:1px solid transparent;background:transparent;
  display:inline-grid;place-items:center;font-size:13px;font-weight:700;color:var(--text-soft);cursor:pointer
}
.tbtn:hover{background:var(--hover);color:var(--text);border-color:var(--middle-border)}
.tbtn.active{background:var(--text);color:var(--bg);border-color:var(--text)}
.tsep{width:1px;height:22px;background:var(--middle-border);margin:0 2px}
.title-input{
  width:100%;margin-top:14px;background:transparent;border:none;outline:none;
  font-family:var(--font-serif);font-size:30px;font-weight:800;letter-spacing:-0.03em;color:var(--text);line-height:1.15
}
.title-input::placeholder{color:var(--text-faint)}
.meta{display:flex;align-items:center;gap:10px;margin-top:8px;flex-wrap:wrap}
.meta-dot{width:4px;height:4px;border-radius:50%;background:var(--text-faint)}
.meta-text{font-size:11px;color:var(--text-faint)}
.tags{display:flex;gap:6px;flex-wrap:wrap;align-items:center}
.tag-input{
  height:24px;border-radius:999px;border:1px dashed var(--card-border);background:var(--card-bg);
  padding:0 10px;font-size:11px;color:var(--text-soft);outline:none;min-width:120px
}
.tag-pill{
  display:inline-flex;align-items:center;gap:6px;padding:4px 8px;border-radius:999px;
  background:var(--accent-bg);border:1px solid var(--accent-border);color:var(--accent);font-size:11px;font-weight:700
}
.tag-pill button{border:none;background:transparent;color:inherit;cursor:pointer;font-weight:800}
.editor-actions{display:flex;gap:6px;margin-left:auto}
.chip-btn{
  height:26px;padding:0 10px;border-radius:999px;border:1px solid var(--middle-border);
  background:var(--card-bg);font-size:12px;font-weight:600;color:var(--text-soft);cursor:pointer
}
.chip-btn:hover{border-color:var(--accent-border)}
.chip-btn.danger{color:var(--danger);border-color:#E9B8B0}
.editable-wrap{flex:1;overflow:auto;padding:14px 22px 40px}
.editable{
  max-width:720px;margin:0 auto;outline:none;min-height:300px;
  font-family:var(--font-serif);font-size:15.5px;line-height:1.75;color:var(--text);
  caret-color:var(--accent)
}
.editable:empty:before{content:attr(data-placeholder);color:var(--text-faint)}
.editable h1{font-size:28px;line-height:1.2;margin:18px 0 8px;letter-spacing:-0.03em}
.editable h2{font-size:22px;line-height:1.25;margin:16px 0 8px;letter-spacing:-0.02em}
.editable h3{font-size:16px;letter-spacing:0.06em;text-transform:uppercase;margin:14px 0 6px;color:var(--text-soft)}
.editable p{margin:8px 0}
.editable blockquote{border-left:3px solid var(--accent);padding:6px 14px;margin:10px 0;background:var(--accent-bg);border-radius:0 8px 8px 0;color:var(--text-soft);font-style:italic}
.editable ul, .editable ol{margin:8px 0 8px 22px}
.editable li{margin:4px 0}
.editable hr{border:none;border-top:1px solid var(--middle-border);margin:18px 0}
.editable code{font-family:var(--font-mono);font-size:13px;background:var(--accent-bg);border:1px solid var(--accent-border);padding:1px 5px;border-radius:6px}
.editable pre{background:var(--card-bg);border:1px solid var(--card-border);padding:12px;border-radius:10px;overflow:auto}
.check-item{display:flex;align-items:flex-start;gap:8px;margin:6px 0}
.check-item input{margin-top:5px;accent-color:var(--accent);width:16px;height:16px}
.check-item span{flex:1}
.check-item.checked span{color:var(--text-faint);text-decoration:line-through}
.empty{
  flex:1;display:grid;place-items:center;padding:24px;text-align:center
}
.empty-card{
  max-width:420px;background:var(--card-bg);border:1px solid var(--card-border);border-radius:16px;padding:28px;box-shadow:var(--shadow)
}
.empty-icon{width:48px;height:48px;border-radius:12px;display:grid;place-items:center;margin:0 auto 12px;background:var(--accent-bg);border:1px solid var(--accent-border);color:var(--accent)}
.empty h3{font-family:var(--font-serif);margin:0 0 6px;font-size:18px}
.empty p{margin:0;color:var(--text-soft);font-size:13px;line-height:1.6}
.footer{height:28px;display:flex;align-items:center;justify-content:space-between;padding:0 14px;border-top:1px solid var(--middle-border);color:var(--text-faint);font-size:11px;background:var(--middle-bg)}
.saved{color:#2E7D32;display:flex;align-items:center;gap:6px;font-weight:600}
.saved.dot::before{content:"";width:6px;height:6px;border-radius:50%;background:#2E7D32;display:inline-block}
@media(max-width:900px){
  #sidebar{position:absolute;left:0;top:48px;bottom:0;z-index:20;box-shadow:var(--shadow)}
  #notelist{width:46vw}
}
</style>
</head>
<body>
<div class="topbar">
  <div class="brand">
    <div class="brand-mark">F</div>
    <div>
      <div class="brand-name">Folio</div>
      <div class="brand-sub">A calm place to write</div>
    </div>
    <button class="icon-btn" id="collapseBtn" title="Toggle sidebar" style="margin-left:6px">
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M3 12h18M3 18h18"/></svg>
    </button>
  </div>
  <label class="search-wrap">
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M20 20l-3.5-3.5"/></svg>
    <input id="search" placeholder="Search notes — titles, content, tags">
  </label>
  <div class="top-actions">
    <button class="icon-btn" id="themeBtn" title="Toggle theme">
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
    </button>
  </div>
</div>

<div class="app" id="app">
  <aside class="pane" id="sidebar">
    <div class="sidebar-head">
      <div class="section-label">Notebooks</div>
      <button class="small-btn" id="newNotebookBtn">+ New</button>
    </div>
    <div class="nav" id="nav">
      <div class="nav-item active" data-nb="all"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg> All Notes <span class="count" id="allCount">0</span></div>
    </div>
    <div class="notebook-list" id="notebookList"></div>
    <div class="trash">
      <div class="nav-item" data-nb="trash" id="trashNav"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1L5 6"/><path d="M10 11v6M14 11v6"/></svg> Trash <span class="count" id="trashCount">0</span></div>
      <div style="display:flex;gap:6px;margin-top:8px">
        <button class="small-btn" id="emptyTrashBtn" style="flex:1">Empty Trash</button>
      </div>
    </div>
  </aside>
  <div class="resizer" id="resizer1"></div>

  <section class="pane" id="notelist">
    <div class="list-head">
      <div class="list-head-top">
        <div class="list-title" id="listTitle">All Notes</div>
        <select class="sort" id="sortSelect" title="Sort">
          <option value="modified">Modified</option>
          <option value="created">Created</option>
          <option value="alpha">A → Z</option>
        </select>
      </div>
      <button class="new-note-btn" id="newNoteBtn">+ New Note</button>
      <div id="tagFilterBar" style="display:none;margin-top:10px;align-items:center;gap:6px">
        <span style="font-size:11px;color:var(--text-faint)">Filtering:</span>
        <span class="tag-pill" id="activeTag"></span>
        <button class="chip-btn" id="clearTag">Clear</button>
      </div>
    </div>
    <div class="list" id="noteList"></div>
    <div class="footer"><span id="noteCount">0 notes</span><span id="savedIndicator" class="saved dot">Saved</span></div>
  </section>

  <div class="resizer" id="resizer2"></div>

  <section class="pane" id="editor">
    <div id="editorEmpty" class="empty">
      <div class="empty-card">
        <div class="empty-icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z"/></svg></div>
        <h3>Select or create a note</h3>
        <p>Your thoughts deserve a quiet, beautiful place. Choose a note from the list or create a new one to start writing.</p>
        <button class="new-note-btn" id="emptyNewBtn" style="margin-top:14px">Create your first note</button>
        <p style="margin-top:12px;font-size:11px;color:var(--text-faint)">Tip: Type <code># </code> for heading, <code>- </code> for list, <code>[] </code> for checklist</p>
      </div>
    </div>
    <div id="editorContent" style="display:none;flex:1;flex-direction:column;overflow:hidden">
      <div class="editor-head">
        <div class="toolbar" id="toolbar">
          <button class="tbtn" data-cmd="bold" title="Bold (Ctrl+B)"><b>B</b></button>
          <button class="tbtn" data-cmd="italic" title="Italic (Ctrl+I)"><i>I</i></button>
          <button class="tbtn" data-cmd="underline" title="Underline (Ctrl+U)"><u>U</u></button>
          <button class="tbtn" data-cmd="strikeThrough" title="Strikethrough">S</button>
          <button class="tbtn" data-cmd="code" title="Inline code">&lt;&gt;</button>
          <span class="tsep"></span>
          <button class="tbtn" data-block="h1" title="Heading 1">H1</button>
          <button class="tbtn" data-block="h2" title="Heading 2">H2</button>
          <button class="tbtn" data-block="h3" title="Heading 3">H3</button>
          <span class="tsep"></span>
          <button class="tbtn" data-block="ul" title="Bullet list">•</button>
          <button class="tbtn" data-block="ol" title="Numbered list">1.</button>
          <button class="tbtn" data-block="check" title="Checklist">☐</button>
          <button class="tbtn" data-block="quote" title="Quote">❝</button>
          <button class="tbtn" data-block="hr" title="Horizontal rule">—</button>
          <span class="tsep"></span>
          <button class="tbtn" id="undoBtn" title="Undo (Ctrl+Z)">↩</button>
          <button class="tbtn" id="redoBtn" title="Redo (Ctrl+Shift+Z)">↪</button>
        </div>
        <input id="titleInput" class="title-input" placeholder="Untitled">
        <div class="meta">
          <span class="meta-text" id="metaText"></span>
          <span class="meta-dot"></span>
          <div class="tags" id="tags"></div>
          <input id="tagInput" class="tag-input" placeholder="Add tag + Enter">
          <div class="editor-actions">
            <button class="chip-btn" id="pinBtn">Pin</button>
            <button class="chip-btn" id="duplicateBtn">Duplicate</button>
            <button class="chip-btn" id="moveBtn">Move</button>
            <button class="chip-btn danger" id="deleteBtn">Delete</button>
          </div>
        </div>
      </div>
      <div class="editable-wrap"><div id="editable" class="editable" contenteditable="true" data-placeholder="Start writing…"></div></div>
      <div class="footer"><span id="wordCount">0 words</span><span id="saveState" class="saved dot">Saved</span></div>
    </div>
  </section>
</div>

<!-- Move menu -->
<dialog id="moveDialog" style="border:1px solid var(--card-border);border-radius:12px;padding:16px;max-width:360px;width:90%">
  <h4 style="margin:0 0 10px;font-family:var(--font-serif)">Move note</h4>
  <div id="moveList" style="display:flex;flex-direction:column;gap:6px"></div>
  <div style="display:flex;justify-content:flex-end;gap:8px;margin-top:12px"><button class="chip-btn" onclick="document.getElementById('moveDialog').close()">Cancel</button></div>
</dialog>

<script>
/* ================= TUNABLE CONSTANTS ================= */
const CONSTANTS = {
  STORAGE_KEY: 'folio_notes_v2',
  AUTOSAVE_DEBOUNCE: 350,
  SNIPPET_LENGTH: 120,
  DEFAULT_NOTEBOOKS: ['Personal','Work','Ideas'],
  COLORS: { accent:'#C07A3A' }
};

/* ================= STATE ================= */
const uid = () => Math.random().toString(36).slice(2,9) + Date.now().toString(36).slice(-4);
const now = () => Date.now();
function defaultNote(nbId, title='Untitled'){
  return { id:uid(), notebookId:nbId, title, html:'<p></p>', tags:[], pinned:false, created:now(), updated:now(), trashed:false, originalNotebookId:null }
}
let state = {
  notebooks: [],
  notes: [],
  selectedNotebookId:'all',
  selectedNoteId:null,
  sortOrder:'modified',
  search:'', tagFilter:null,
  sidebarWidths:{ side:260, mid:360 },
  sidebarCollapsed:false,
  theme:'light',
  trash:[]
};
let undoStacks = new Map(); // noteId -> {undo:[], redo:[]}

/* ================= PERSISTENCE ================= */
function load(){
  try{
    const raw = localStorage.getItem(CONSTANTS.STORAGE_KEY);
    if(raw){
      const p = JSON.parse(raw);
      state = Object.assign(state, p);
      // ensure defaults
      if(!state.notebooks.length){
        state.notebooks = CONSTANTS.DEFAULT_NOTEBOOKS.map(n=>({id:uid(), name:n}));
      }
    } else {
      state.notebooks = CONSTANTS.DEFAULT_NOTEBOOKS.map(n=>({id:uid(), name:n}));
      const nb = state.notebooks[0].id;
      state.notes = [
        { id:uid(), notebookId:nb, title:'Welcome to Folio', html:'<h1>Welcome to Folio</h1><p>A calm, warm place for your thoughts. Try <b>bold</b>, <i>italic</i>, or type <code># </code> to create a heading.</p><blockquote>“Writing is the way to think clearly.”</blockquote><ul><li>Press <b>Ctrl+B</b> for bold</li><li>Type <code>- </code> for bullets</li><li>Type <code>[] </code> for checklists</li></ul><div class="check-item"><input type="checkbox"><span>Try checking this item</span></div><div class="check-item checked"><input type="checkbox" checked><span>Checked items persist after reload</span></div>', tags:['welcome','getting-started'], pinned:true, created:now()-100000, updated:now()-5000, trashed:false, originalNotebookId:null },
        { id:uid(), notebookId:state.notebooks[1]?.id||nb, title:'Project ideas', html:'<h2>Ideas to explore</h2><ol><li>Minimal writing app</li><li>Garden journal</li></ol><p>Tag your notes and click a tag to filter.</p>', tags:['ideas'], pinned:false, created:now()-80000, updated:now()-20000, trashed:false, originalNotebookId:null },
        { id:uid(), notebookId:nb, title:'Shopping — checklist demo', html:'<p>Groceries</p><div class="check-item"><input type="checkbox"><span>Oat milk</span></div><div class="check-item checked"><input type="checkbox" checked><span>Bread</span></div><div class="check-item"><input type="checkbox"><span>Honey</span></div>', tags:['personal'], pinned:false, created:now()-60000, updated:now()-10000, trashed:false, originalNotebookId:null }
      ];
      state.selectedNoteId = state.notes[0].id;
    }
  } catch(e){ console.error(e) }
  if(!state.notebooks.length) state.notebooks = CONSTANTS.DEFAULT_NOTEBOOKS.map(n=>({id:uid(),name:n}));
  // migrate html sanitization
  state.notes.forEach(n=>{ if(!n.html) n.html='<p></p>'; });
}
function save(){
  localStorage.setItem(CONSTANTS.STORAGE_KEY, JSON.stringify(state));
}
const debounceSave = (()=>{ let t; return ()=>{ clearTimeout(t); document.getElementById('saveState').textContent='Saving…'; document.getElementById('saveState').classList.remove('dot'); t=setTimeout(()=>{ save(); document.getElementById('saveState').textContent='Saved'; document.getElementById('saveState').classList.add('dot'); document.getElementById('savedIndicator').textContent='Saved'; }, CONSTANTS.AUTOSAVE_DEBOUNCE); } })();

/* ================= HELPERS ================= */
function sanitizeHTML(dirty){
  const template = document.createElement('template');
  template.innerHTML = dirty;
  const allowed = new Set(['P','BR','B','STRONG','I','EM','U','S','STRIKE','CODE','H1','H2','H3','UL','OL','LI','BLOCKQUOTE','HR','DIV','SPAN','A']);
  function clean(node){
    [...node.childNodes].forEach(child=>{
      if(child.nodeType===1){
        const tag = child.tagName;
        if(tag==='SCRIPT' || tag==='STYLE' || tag==='IFRAME' || tag==='IMG'){
          child.remove(); return;
        }
        if(tag==='INPUT'){
          // allow only checkbox inside check-item
          if(child.type!=='checkbox'){ child.remove(); return; }
        } else if(!allowed.has(tag)){
          // unwrap
          while(child.firstChild) node.insertBefore(child.firstChild, child);
          child.remove(); return;
        }
        // strip attributes except checked, href, class for check-item
        [...child.attributes].forEach(attr=>{
          const n = attr.name.toLowerCase();
          if(tag==='A' && n==='href') return;
          if(tag==='INPUT' && (n==='type' || n==='checked')) return;
          if(tag==='DIV' && (n==='class' && attr.value.includes('check-item'))) return;
          if(tag==='DIV' && n==='class' && attr.value.includes('check')) return;
          child.removeAttribute(attr.name);
        });
        if(tag==='DIV' && child.classList.contains('check-item')){
          // ensure checkbox exists
          if(!child.querySelector('input[type="checkbox"]')){
            const cb=document.createElement('input'); cb.type='checkbox'; child.prepend(cb);
          }
        }
        clean(child);
      } else if(child.nodeType===8){ child.remove(); }
    });
  }
  clean(template.content);
  // remove empty class attrs
  return template.innerHTML;
}
function textOf(html){
  const d=document.createElement('div'); d.innerHTML=html;
  // include checkbox states? not needed
  return (d.textContent||'').replace(/\s+/g,' ').trim();
}
function snippetOf(html){
  const t=textOf(html); return t.slice(0, CONSTANTS.SNIPPET_LENGTH) + (t.length>CONSTANTS.SNIPPET_LENGTH?'…':'');
}
function getSelectedNote(){ return state.notes.find(n=>n.id===state.selectedNoteId) || null }
function filteredNotes(){
  let notes = state.notes.filter(n=> !n.trashed);
  if(state.selectedNotebookId!=='all' && state.selectedNotebookId!=='trash'){
    notes = notes.filter(n=> n.notebookId===state.selectedNotebookId);
  }
  if(state.selectedNotebookId==='trash'){
    notes = state.notes.filter(n=> n.trashed);
  }
  if(state.tagFilter){
    notes = notes.filter(n=> n.tags.includes(state.tagFilter));
  }
  if(state.search){
    const q=state.search.toLowerCase();
    notes = notes.filter(n=>{
      const hay = (n.title + ' ' + textOf(n.html) + ' ' + n.tags.join(' ')).toLowerCase();
      return hay.includes(q);
    });
  }
  // sort pinned first
  notes.sort((a,b)=>{
    if(a.pinned!==b.pinned) return a.pinned?-1:1;
    if(state.sortOrder==='modified') return b.updated - a.updated;
    if(state.sortOrder==='created') return b.created - a.created;
    if(state.sortOrder==='alpha') return a.title.localeCompare(b.title);
    return 0;
  });
  return notes;
}

/* ================= RENDER ================= */
const els={
  notebookList: document.getElementById('notebookList'),
  noteList: document.getElementById('noteList'),
  listTitle: document.getElementById('listTitle'),
  noteCount: document.getElementById('noteCount'),
  allCount: document.getElementById('allCount'),
  trashCount: document.getElementById('trashCount'),
  editable: document.getElementById('editable'),
  titleInput: document.getElementById('titleInput'),
  metaText: document.getElementById('metaText'),
  tags: document.getElementById('tags'),
  tagInput: document.getElementById('tagInput'),
  editorEmpty: document.getElementById('editorEmpty'),
  editorContent: document.getElementById('editorContent'),
  wordCount: document.getElementById('wordCount')
};
function render(){
  // counts
  els.allCount.textContent = state.notes.filter(n=>!n.trashed).length;
  els.trashCount.textContent = state.notes.filter(n=>n.trashed).length;
  // notebooks
  els.notebookList.innerHTML='';
  state.notebooks.forEach(nb=>{
    const active = state.selectedNotebookId===nb.id;
    const count = state.notes.filter(n=>!n.trashed && n.notebookId===nb.id).length;
    const div=document.createElement('div');
    div.className='nb'+(active?' active':'');
    div.draggable=true;
    div.dataset.id=nb.id;
    div.innerHTML=`<div class="nb-head"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg><span class="nb-title">${nb.name}</span><span class="nb-meta">${count}</span><span class="nb-actions"><button class="mini rename" title="Rename">✎</button><button class="mini del" title="Delete">×</button></span></div>`;
    div.querySelector('.nb-head').addEventListener('click', ()=>{ state.selectedNotebookId=nb.id; state.selectedNoteId=null; save(); render(); });
    div.querySelector('.rename').addEventListener('click',(e)=>{ e.stopPropagation(); const name=prompt('Rename notebook', nb.name); if(name){ nb.name=name.trim(); save(); render(); }});
    div.querySelector('.del').addEventListener('click',(e)=>{ e.stopPropagation(); if(confirm('Delete notebook? Notes will move to first notebook.')){ const fallback=state.notebooks[0]?.id; state.notes.forEach(n=>{ if(n.notebookId===nb.id) n.notebookId=fallback}); state.notebooks=state.notebooks.filter(x=>x.id!==nb.id); if(state.selectedNotebookId===nb.id) state.selectedNotebookId='all'; save(); render(); }});
    // drag over for dropping notes
    div.addEventListener('dragover', e=>{ e.preventDefault(); div.classList.add('drop-target'); });
    div.addEventListener('dragleave', ()=> div.classList.remove('drop-target'));
    div.addEventListener('drop', e=>{
      e.preventDefault(); div.classList.remove('drop-target');
      const noteId=e.dataTransfer.getData('text/plain'); const note=state.notes.find(n=>n.id===noteId); if(note){ note.notebookId=nb.id; note.updated=now(); save(); render(); }
    });
    els.notebookList.appendChild(div);
  });
  // nav active
  document.querySelectorAll('.nav-item').forEach(el=>{
    el.classList.toggle('active', el.dataset.nb===state.selectedNotebookId);
  });
  // list title
  if(state.selectedNotebookId==='all') els.listTitle.textContent='All Notes';
  else if(state.selectedNotebookId==='trash') els.listTitle.textContent='Trash';
  else els.listTitle.textContent = state.notebooks.find(n=>n.id===state.selectedNotebookId)?.name || 'Notes';

  // tag filter bar
  const bar=document.getElementById('tagFilterBar');
  if(state.tagFilter){ bar.style.display='flex'; document.getElementById('activeTag').innerHTML=`#${state.tagFilter} <button onclick="clearTagFilter()" style="border:none;background:transparent;cursor:pointer">×</button>`; } else bar.style.display='none';

  // note list
  const notes=filteredNotes();
  els.noteCount.textContent = `${notes.length} ${notes.length===1?'note':'notes'}`;
  els.noteList.innerHTML='';
  if(!notes.length){
    els.noteList.innerHTML=`<div style="text-align:center;padding:40px 16px;color:var(--text-faint);font-size:13px">No notes here.<br>Create a new one.</div>`;
  } else {
    notes.forEach(n=>{
      const card=document.createElement('div');
      card.className='note-card'+(n.id===state.selectedNoteId?' selected':'')+(n.pinned?' pinned':'');
      card.draggable=true;
      card.dataset.id=n.id;
      const time = new Date(n.updated).toLocaleDateString(undefined,{month:'short', day:'numeric'}) + ' · ' + new Date(n.updated).toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'});
      let title = n.title || 'Untitled';
      let snip = snippetOf(n.html) || 'No additional text';
      // highlighting
      if(state.search){
        const q=state.search; const esc=q.replace(/[.*+?^${}()|[\]\\]/g,'\\$&'); const re=new RegExp(`(${esc})`,'gi');
        title = title.replace(re,'<span class="hl">$1</span>');
        snip = snip.replace(re,'<span class="hl">$1</span>');
      }
      card.innerHTML=`<div class="note-card-head"><div class="note-card-title">${title}</div>${n.pinned?'<span class="pin">📌</span>':''}</div><div class="note-card-snippet">${snip}</div><div class="note-card-foot"><span class="time">${time}</span>${n.tags.slice(0,2).map(t=>`<span class="tag">#${t}</span>`).join('')}</div>`;
      card.addEventListener('click', ()=>{ state.selectedNoteId=n.id; save(); renderEditor(); render(); });
      card.addEventListener('dragstart', e=>{ e.dataTransfer.setData('text/plain', n.id); });
      els.noteList.appendChild(card);
    });
  }
  renderEditor();
  // widths
  document.getElementById('sidebar').style.width = state.sidebarWidths.side+'px';
  document.getElementById('notelist').style.width = state.sidebarWidths.mid+'px';
  document.getElementById('app').classList.toggle('collapsed', state.sidebarCollapsed);
  document.documentElement.setAttribute('data-theme', state.theme);
  document.getElementById('sortSelect').value=state.sortOrder;
}
function renderEditor(){
  const note=getSelectedNote();
  const isTrashView = state.selectedNotebookId==='trash';
  if(!note || isTrashView && note && !note.trashed && false){}
  if(!note){
    els.editorEmpty.style.display='grid';
    els.editorContent.style.display='none';
    // if trash has selection but no notes, show trash empty? handled
    if(state.selectedNotebookId==='trash'){
      const trashNotes=state.notes.filter(n=>n.trashed);
      if(trashNotes.length && !note){
        // select first trash note automatically? keep empty
      }
      // override empty message
      if(trashNotes.length===0){
        els.editorEmpty.querySelector('h3').textContent='Trash is empty';
        els.editorEmpty.querySelector('p').textContent='Deleted notes appear here. You can restore or permanently delete them.';
        els.editorEmpty.querySelector('#emptyNewBtn').style.display='none';
      } else {
        els.editorEmpty.querySelector('h3').textContent='Select a deleted note';
        els.editorEmpty.querySelector('p').textContent='Restore it to its notebook or delete permanently.';
        els.editorEmpty.querySelector('#emptyNewBtn').style.display='none';
      }
    } else {
      els.editorEmpty.querySelector('h3').textContent='Select or create a note';
      els.editorEmpty.querySelector('p').textContent='Your thoughts deserve a quiet, beautiful place. Choose a note from the list or create a new one to start writing.';
      els.editorEmpty.querySelector('#emptyNewBtn').style.display='inline-flex';
    }
    return;
  }
  if(note.trashed){
    // show trash view editor with restore
    els.editorEmpty.style.display='none';
    els.editorContent.style.display='flex';
    els.titleInput.value = note.title;
    els.titleInput.disabled=true;
    els.editable.innerHTML = sanitizeHTML(note.html);
    els.editable.contentEditable='false';
    els.metaText.textContent = 'Deleted · ' + new Date(note.updated).toLocaleString();
    els.wordCount.textContent = textOf(note.html).split(/\s+/).filter(Boolean).length + ' words';
    els.tags.innerHTML = note.tags.map(t=>`<span class="tag-pill">#${t}</span>`).join('');
    els.tagInput.style.display='none';
    document.getElementById('pinBtn').style.display='none';
    document.getElementById('duplicateBtn').style.display='none';
    document.getElementById('moveBtn').style.display='none';
    document.getElementById('deleteBtn').textContent='Delete Permanently';
    document.getElementById('deleteBtn').onclick=()=>{ if(confirm('Permanently delete?')){ state.notes=state.notes.filter(n=>n.id!==note.id); state.selectedNoteId=null; save(); render(); } };
    // add restore button if not present
    let restore=document.getElementById('restoreBtn');
    if(!restore){
      restore=document.createElement('button'); restore.id='restoreBtn'; restore.className='chip-btn'; restore.textContent='Restore';
      document.querySelector('.editor-actions').prepend(restore);
    }
    restore.style.display='inline-flex';
    restore.onclick=()=>{
      note.trashed=false; note.notebookId = note.originalNotebookId || state.notebooks[0]?.id; note.originalNotebookId=null; note.updated=now(); save(); render();
    };
    els.editable.style.opacity='0.7';
    return;
  }
  // normal note
  els.editorEmpty.style.display='none';
  els.editorContent.style.display='flex';
  els.titleInput.disabled=false;
  els.editable.contentEditable='true';
  els.editable.style.opacity='1';
  // avoid overwriting if focused
  if(document.activeElement!==els.titleInput) els.titleInput.value=note.title;
  if(document.activeElement!==els.editable) els.editable.innerHTML = sanitizeHTML(note.html);
  els.metaText.textContent = 'Updated ' + new Date(note.updated).toLocaleString();
  const words = textOf(note.html).split(/\s+/).filter(Boolean).length;
  els.wordCount.textContent = words + ' words';
  els.tags.innerHTML = note.tags.map(t=>`<span class="tag-pill">#${t} <button data-t="${t}">×</button></span>`).join('');
  els.tags.querySelectorAll('button').forEach(b=> b.addEventListener('click', ()=>{ note.tags=note.tags.filter(x=>x!==b.dataset.t); note.updated=now(); save(); renderEditor(); render(); debounceSave(); }));
  els.tagInput.style.display='inline-block';
  document.getElementById('pinBtn').style.display='inline-flex';
  document.getElementById('pinBtn').textContent = note.pinned?'Unpin':'Pin';
  document.getElementById('duplicateBtn').style.display='inline-flex';
  document.getElementById('moveBtn').style.display='inline-flex';
  document.getElementById('deleteBtn').textContent='Delete';
  document.getElementById('deleteBtn').onclick=deleteCurrent;
  let restore=document.getElementById('restoreBtn'); if(restore) restore.style.display='none';
  // toolbar state
  updateToolbarState();
}

/* ================= EDITOR LOGIC ================= */
function updateToolbarState(){
  const sel=window.getSelection();
  const active={};
  try{ active.bold=document.queryCommandState('bold'); active.italic=document.queryCommandState('italic'); active.underline=document.queryCommandState('underline'); active.strike=document.queryCommandState('strikeThrough'); }catch{}
  document.querySelectorAll('.tbtn[data-cmd]').forEach(b=>{
    const cmd=b.dataset.cmd;
    const on = (cmd==='bold'&&active.bold)||(cmd==='italic'&&active.italic)||(cmd==='underline'&&active.underline)||(cmd==='strikeThrough'&&active.strike);
    b.classList.toggle('active', !!on);
  });
}
function exec(cmd, val=null){
  document.execCommand(cmd,false,val);
  pushUndo(); triggerSave();
}
function triggerSave(){
  const note=getSelectedNote(); if(!note) return;
  note.title = els.titleInput.value.trim() || 'Untitled';
  note.html = sanitizeHTML(els.editable.innerHTML);
  note.updated=now();
  els.metaText.textContent='Updated ' + new Date(note.updated).toLocaleString();
  els.wordCount.textContent = textOf(note.html).split(/\s+/).filter(Boolean).length + ' words';
  save(); render(); debounceSave();
}
function pushUndo(){
  const note=getSelectedNote(); if(!note) return;
  if(!undoStacks.has(note.id)) undoStacks.set(note.id,{undo:[],redo:[]});
  const stack=undoStacks.get(note.id);
  const html=els.editable.innerHTML;
  if(stack.undo[stack.undo.length-1]!==html){ stack.undo.push(html); if(stack.undo.length>80) stack.undo.shift(); stack.redo=[]; }
}
function undo(){
  const note=getSelectedNote(); if(!note) return;
  const st=undoStacks.get(note.id); if(!st||!st.undo.length) return;
  const cur=els.editable.innerHTML; st.redo.push(cur);
  // pop until different
  let prev=st.undo.pop();
  // if prev is current, pop again
  if(prev===cur && st.undo.length) prev=st.undo.pop();
  if(prev!==undefined){ els.editable.innerHTML=prev; note.html=sanitizeHTML(prev); save(); render(); }
}
function redo(){
  const note=getSelectedNote(); if(!note) return;
  const st=undoStacks.get(note.id); if(!st||!st.redo.length) return;
  const next=st.redo.pop(); st.undo.push(els.editable.innerHTML); els.editable.innerHTML=next; note.html=sanitizeHTML(next); save(); render();
}

/* Markdown shortcuts at line start */
function handleMarkdownShortcut(e){
  if(e.key!==' ') return;
  const sel=window.getSelection(); if(!sel.rangeCount) return;
  const range=sel.getRangeAt(0); if(!range.collapsed) return;
  // get block element
  let block = range.startContainer;
  if(block.nodeType===3) block=block.parentElement;
  while(block && block!==els.editable && !['P','DIV','H1','H2','H3','BLOCKQUOTE','LI'].includes(block.tagName)) block=block.parentElement;
  if(!block || block===els.editable) block = range.startContainer;
  // get text before cursor in block
  const preRange = range.cloneRange(); preRange.selectNodeContents(block); preRange.setEnd(range.startContainer, range.startOffset);
  const textBefore = preRange.toString();
  // must be at line start: textBefore is exactly trigger without preceding chars (allow trimming? must be exactly)
  // Check triggers
  if(textBefore==='#'){ e.preventDefault(); document.execCommand('formatBlock',false,'H1'); // remove '#'
    // remove '#'
    const node=range.startContainer; if(node.nodeType===3){ node.textContent = node.textContent.replace(/^#\s*$/,''); } else { block.textContent = block.textContent.replace(/^#\s*$/,''); }
    // create H1
    const h1=document.createElement('h1'); h1.innerHTML='<br>'; block.replaceWith(h1); setCursor(h1,0);
    pushUndo(); return;
  }
  if(textBefore==='##'){ e.preventDefault(); const h2=document.createElement('h2'); h2.innerHTML='<br>'; block.replaceWith(h2); if(block.textContent) block.textContent=''; setCursor(h2,0); pushUndo(); return; }
  if(textBefore==='###'){ e.preventDefault(); const h3=document.createElement('h3'); h3.innerHTML='<br>'; block.replaceWith(h3); setCursor(h3,0); pushUndo(); return; }
  if(textBefore==='-' || textBefore==='*' || textBefore==='•'){ e.preventDefault(); document.execCommand('insertUnorderedList',false,null); // remove dash
    const li=window.getSelection().anchorNode; // cleanup dash text
    if(block.textContent.trim()==='-'||block.textContent.trim()==='*'||block.textContent.trim()==='•') block.textContent='';
    pushUndo(); return;
  }
  if(textBefore==='[]'){ e.preventDefault(); // checklist
    const item=document.createElement('div'); item.className='check-item'; item.innerHTML='<input type="checkbox"><span><br></span>';
    if(block && block.parentElement===els.editable) block.replaceWith(item); else { els.editable.appendChild(item); }
    const span=item.querySelector('span'); setCursor(span,0);
    // cleanup [] text if present
    pushUndo(); return;
  }
  if(textBefore==='>'){ e.preventDefault(); document.execCommand('formatBlock',false,'BLOCKQUOTE'); if(block.textContent.trim()==='>') block.textContent=''; pushUndo(); return; }
}
function setCursor(el, offset){
  const range=document.createRange(); const sel=window.getSelection();
  if(el.firstChild && el.firstChild.nodeType===3){ range.setStart(el.firstChild, Math.min(offset, el.firstChild.length)); }
  else { range.selectNodeContents(el); range.collapse(true); }
  sel.removeAllRanges(); sel.addRange(range);
}

/* List handling */
function handleListEnter(e){
  if(e.key!=='Enter') return;
  const sel=window.getSelection(); if(!sel.rangeCount) return;
  const range=sel.getRangeAt(0);
  let li = range.startContainer;
  if(li.nodeType===3) li=li.parentElement;
  li = li.closest('li');
  if(li){
    const text = li.textContent.trim();
    if(text==='' && !li.querySelector('input')){
      e.preventDefault();
      document.execCommand('insertUnorderedList',false,null); // toggle off? simpler: outdent
      document.execCommand('outdent',false,null);
      // if still in list, remove list
      pushUndo();
    } else {
      // let browser continue list, but push undo soon
      setTimeout(pushUndo,0);
    }
    return;
  }
  // checklist handling
  let check = range.startContainer;
  if(check.nodeType===3) check=check.parentElement;
  check = check.closest('.check-item');
  if(check){
    e.preventDefault();
    const span=check.querySelector('span');
    const txt = span ? span.textContent.trim() : check.textContent.trim();
    if(txt===''){
      // exit checklist: create p
      const p=document.createElement('p'); p.innerHTML='<br>';
      check.after(p); check.remove(); setCursor(p,0);
    } else {
      const next=document.createElement('div'); next.className='check-item'; next.innerHTML='<input type="checkbox"><span><br></span>';
      check.after(next); setCursor(next.querySelector('span'),0);
    }
    pushUndo();
  }
}

/* ================= EVENTS ================= */
document.getElementById('newNotebookBtn').addEventListener('click', ()=>{
  const name=prompt('Notebook name'); if(!name) return;
  state.notebooks.push({id:uid(), name:name.trim()}); save(); render();
});
document.getElementById('newNoteBtn').addEventListener('click', createNote);
document.getElementById('emptyNewBtn').addEventListener('click', createNote);
function createNote(){
  let nbId = state.selectedNotebookId;
  if(nbId==='all' || nbId==='trash') nbId = state.notebooks[0]?.id;
  const n=defaultNote(nbId);
  state.notes.unshift(n); state.selectedNoteId=n.id; state.selectedNotebookId = (state.selectedNotebookId==='trash'?'all':state.selectedNotebookId);
  undoStacks.set(n.id,{undo:[n.html],redo:[]});
  save(); render();
  setTimeout(()=> els.titleInput.focus(), 50);
}
document.getElementById('search').addEventListener('input', e=>{ state.search=e.target.value; render(); });
document.getElementById('sortSelect').addEventListener('change', e=>{ state.sortOrder=e.target.value; save(); render(); });
document.getElementById('collapseBtn').addEventListener('click', ()=>{ state.sidebarCollapsed=!state.sidebarCollapsed; save(); render(); });
document.getElementById('themeBtn').addEventListener('click', ()=>{ state.theme = state.theme==='light'?'dark':'light'; save(); render(); });
document.querySelector('[data-nb="all"]').addEventListener('click', ()=>{ state.selectedNotebookId='all'; state.selectedNoteId=null; save(); render(); });
document.getElementById('trashNav').addEventListener('click', ()=>{ state.selectedNotebookId='trash'; state.selectedNoteId=null; save(); render(); });
document.getElementById('emptyTrashBtn').addEventListener('click', ()=>{ if(confirm('Permanently delete all trash?')){ state.notes=state.notes.filter(n=>!n.trashed); save(); render(); }});
function clearTagFilter(){ state.tagFilter=null; render(); }
document.getElementById('clearTag').addEventListener('click', clearTagFilter);
window.clearTagFilter=clearTagFilter;

// title
els.titleInput.addEventListener('input', ()=>{ triggerSave(); });
els.titleInput.addEventListener('keydown', e=>{ if(e.key==='Enter'){ e.preventDefault(); els.editable.focus(); }});

// tag input
els.tagInput.addEventListener('keydown', e=>{
  if(e.key==='Enter' || e.key===','){
    e.preventDefault();
    const val=els.tagInput.value.trim().replace(/,/g,'').replace(/^#/,''); if(!val) return;
    const note=getSelectedNote(); if(!note) return;
    if(!note.tags.includes(val)) note.tags.push(val);
    els.tagInput.value=''; note.updated=now(); save(); renderEditor(); render();
  }
});

// toolbar
document.querySelectorAll('.tbtn[data-cmd]').forEach(b=>{
  b.addEventListener('click', ()=>{
    const cmd=b.dataset.cmd;
    if(cmd==='code'){
      // wrap selection in code
      const sel=window.getSelection();
      if(sel.rangeCount){ const range=sel.getRangeAt(0); const frag=range.extractContents(); const code=document.createElement('code'); code.appendChild(frag); range.insertNode(code); }
      pushUndo(); triggerSave();
    } else {
      exec(cmd);
    }
  });
});
document.querySelectorAll('.tbtn[data-block]').forEach(b=>{
  b.addEventListener('click', ()=>{
    const blk=b.dataset.block;
    if(blk==='h1') exec('formatBlock','H1');
    else if(blk==='h2') exec('formatBlock','H2');
    else if(blk==='h3') exec('formatBlock','H3');
    else if(blk==='ul') exec('insertUnorderedList');
    else if(blk==='ol') exec('insertOrderedList');
    else if(blk==='quote') exec('formatBlock','BLOCKQUOTE');
    else if(blk==='hr'){ exec('insertHorizontalRule'); }
    else if(blk==='check'){
      const item=document.createElement('div'); item.className='check-item'; item.innerHTML='<input type="checkbox"><span>Checklist item</span>';
      els.editable.appendChild(item); pushUndo(); triggerSave();
    }
  });
});
document.getElementById('undoBtn').addEventListener('click', undo);
document.getElementById('redoBtn').addEventListener('click', redo);

// editor events
els.editable.addEventListener('keydown', e=>{
  if((e.ctrlKey||e.metaKey) && e.key.toLowerCase()==='b'){ e.preventDefault(); exec('bold'); }
  if((e.ctrlKey||e.metaKey) && e.key.toLowerCase()==='i'){ e.preventDefault(); exec('italic'); }
  if((e.ctrlKey||e.metaKey) && e.key.toLowerCase()==='u'){ e.preventDefault(); exec('underline'); }
  if((e.ctrlKey||e.metaKey) && e.key.toLowerCase()==='z' && !e.shiftKey){ e.preventDefault(); undo(); }
  if((e.ctrlKey||e.metaKey) && (e.key.toLowerCase()==='z' && e.shiftKey || e.key.toLowerCase()==='y')){ e.preventDefault(); redo(); }
  handleMarkdownShortcut(e);
  handleListEnter(e);
  // esc? nothing
});
els.editable.addEventListener('keyup', updateToolbarState);
els.editable.addEventListener('mouseup', updateToolbarState);
els.editable.addEventListener('input', ()=>{
  pushUndo(); triggerSave(); updateToolbarState();
});
els.editable.addEventListener('paste', e=>{
  e.preventDefault();
  const html = e.clipboardData.getData('text/html');
  const text = e.clipboardData.getData('text/plain');
  let clean = html ? sanitizeHTML(html) : '';
  // if no html, use text with line breaks
  if(!clean || clean.trim()===''){
    clean = text.split('\n').map(l=>`<p>${l.replace(/</g,'&lt;')}</p>`).join('');
    clean = sanitizeHTML(clean);
  }
  document.execCommand('insertHTML', false, clean);
  pushUndo(); triggerSave();
});
els.editable.addEventListener('click', e=>{
  if(e.target.matches('.check-item input[type="checkbox"]')){
    const item=e.target.closest('.check-item');
    item.classList.toggle('checked', e.target.checked);
    e.target.checked ? item.classList.add('checked') : item.classList.remove('checked');
    // persist checked attr
    if(e.target.checked) e.target.setAttribute('checked',''); else e.target.removeAttribute('checked');
    triggerSave();
  }
});
// also handle tag clicks in note list? delegate
document.addEventListener('click', e=>{
  if(e.target.classList.contains('tag')){
    const t=e.target.textContent.replace('#',''); state.tagFilter=t; render();
  }
});

// pin/duplicate/move/delete
document.getElementById('pinBtn').addEventListener('click', ()=>{
  const n=getSelectedNote(); if(!n) return; n.pinned=!n.pinned; n.updated=now(); save(); render();
});
document.getElementById('duplicateBtn').addEventListener('click', ()=>{
  const n=getSelectedNote(); if(!n) return; const copy={...n, id:uid(), title:n.title+' (copy)', created:now(), updated:now(), trashed:false}; state.notes.unshift(copy); state.selectedNoteId=copy.id; save(); render();
});
document.getElementById('moveBtn').addEventListener('click', ()=>{
  const dlg=document.getElementById('moveDialog'); const list=document.getElementById('moveList'); list.innerHTML='';
  state.notebooks.forEach(nb=>{
    const b=document.createElement('button'); b.className='chip-btn'; b.style.textAlign='left'; b.textContent=nb.name;
    b.addEventListener('click', ()=>{ const n=getSelectedNote(); if(n){ n.notebookId=nb.id; n.updated=now(); save(); render(); dlg.close(); }});
    list.appendChild(b);
  });
  dlg.showModal();
});
function deleteCurrent(){
  const n=getSelectedNote(); if(!n) return;
  n.trashed=true; n.originalNotebookId=n.notebookId; n.updated=now();
  state.selectedNoteId=null; save(); render();
}
document.getElementById('deleteBtn').addEventListener('click', deleteCurrent);

// resizers
(function(){
  const r1=document.getElementById('resizer1'), r2=document.getElementById('resizer2');
  function make(r, key){
    let dragging=false;
    r.addEventListener('mousedown', e=>{ dragging=true; document.body.style.cursor='col-resize'; e.preventDefault(); });
    window.addEventListener('mousemove', e=>{
      if(!dragging) return;
      if(key==='side'){
        const rect=document.getElementById('app').getBoundingClientRect();
        let w=e.clientX - rect.left; w=Math.max(180, Math.min(380, w)); state.sidebarWidths.side=w; document.getElementById('sidebar').style.width=w+'px';
      } else {
        const sideW=state.sidebarWidths.side;
        let w=e.clientX - sideW - 6; w=Math.max(240, Math.min(560, w)); state.sidebarWidths.mid=w; document.getElementById('notelist').style.width=w+'px';
      }
    });
    window.addEventListener('mouseup', ()=>{ if(dragging){ dragging=false; document.body.style.cursor=''; save(); }});
  }
  make(r1,'side'); make(r2,'mid');
})();

// init
load();
render();
if(getSelectedNote()){ const n=getSelectedNote(); if(!undoStacks.has(n.id)) undoStacks.set(n.id,{undo:[n.html],redo:[]}); }

// expose for self-check probing
window.__folio = { state, sanitizeHTML, textOf, getSelectedNote, els, triggerSave };

</script>
</body>
</html>
<!-- agent-meta {"model":"meta/muse-spark-1.2","provider":"openrouter","persona":"basic","sessionId":"65055f10-12e3-4562-b340-4f73a4b50fa8","tokensIn":1180,"tokensOut":15852,"tokensTotal":17032,"cost":0.0686513,"turns":1,"toolCalls":0,"failedToolCalls":0,"timestamp":"2026-08-06T15:13:12.146Z"} -->