← Notes results

Notes

GLM 5.2 · middle

Initial view of Notes

Browser check: Artifact runtime error. This is a technical check, not a quality review.

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 56,397 bytes · SHA-256 e85c19776828
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vellum — Notes</title>
<style>
/* ============ RESET & BASE ============ */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; overflow: hidden; }
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: var(--text);
  background: var(--bg);
  font-size: 15px;
  line-height: 1.6;
}
button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; }
input { font-family: inherit; }
::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--scroll); border-radius: 6px; border: 2px solid transparent; background-clip: padding-box; }
::-webkit-scrollbar-thumb:hover { background: var(--scroll-hover); background-clip: padding-box; }

/* ============ THEME ============ */
:root {
  --bg: #ffffff;
  --bg-list: #fbfbfa;
  --bg-sidebar: #f6f5f2;
  --bg-hover: #eeede9;
  --bg-active: #e7e5df;
  --bg-elevated: #ffffff;
  --border: #eceae5;
  --border-strong: #dbd8d1;
  --text: #2f2e2c;
  --text-secondary: #6f6d68;
  --text-tertiary: #a3a09a;
  --accent: #6d5efc;
  --accent-hover: #5a49f0;
  --accent-soft: #ece8ff;
  --accent-text: #ffffff;
  --star: #f0b400;
  --danger: #e5484d;
  --danger-soft: #fde8e8;
  --scroll: #d8d5cf;
  --scroll-hover: #c2beb6;
  --code-bg: #f3f2ef;
  --shadow: 0 4px 14px rgba(20,18,15,.08);
  --shadow-lg: 0 12px 40px rgba(20,18,15,.14);
  --radius: 9px;
  --radius-sm: 6px;
  --side-w: 252px;
  --list-w: 320px;
}
[data-theme="dark"] {
  --bg: #1c1b1a;
  --bg-list: #20201f;
  --bg-sidebar: #171716;
  --bg-hover: #2a2928;
  --bg-active: #34322f;
  --bg-elevated: #28272a;
  --border: #2c2b2a;
  --border-strong: #3a3937;
  --text: #e8e6e2;
  --text-secondary: #9c9a94;
  --text-tertiary: #6a6863;
  --accent: #9381ff;
  --accent-hover: #a594ff;
  --accent-soft: #2b2540;
  --star: #ffce4d;
  --danger: #ff6166;
  --danger-soft: #3a2326;
  --scroll: #3a3937;
  --scroll-hover: #4a4946;
  --code-bg: #2a292b;
  --shadow: 0 4px 14px rgba(0,0,0,.4);
  --shadow-lg: 0 12px 40px rgba(0,0,0,.55);
}

/* ============ LAYOUT ============ */
.app { display: flex; height: 100vh; width: 100vw; }

/* ============ SIDEBAR ============ */
.sidebar {
  width: var(--side-w);
  flex: 0 0 var(--side-w);
  background: var(--bg-sidebar);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  transition: margin-left .28s cubic-bezier(.4,0,.2,1);
  z-index: 40;
}
.sidebar-head {
  display: flex; align-items: center; gap: 8px;
  padding: 16px 16px 12px;
}
.brand { display: flex; align-items: center; gap: 9px; flex: 1; min-width: 0; }
.brand-logo {
  width: 26px; height: 26px; border-radius: 7px;
  background: linear-gradient(135deg, var(--accent), #b06bff);
  display: grid; place-items: center; flex: 0 0 auto;
  box-shadow: 0 2px 6px rgba(109,94,252,.4);
}
.brand-logo svg { width: 15px; height: 15px; color: #fff; }
.brand-name { font-weight: 700; font-size: 15px; letter-spacing: -.2px; }
.icon-btn {
  width: 30px; height: 30px; border-radius: 7px; flex: 0 0 auto;
  display: grid; place-items: center; color: var(--text-secondary);
  transition: background .15s, color .15s;
}
.icon-btn:hover { background: var(--bg-hover); color: var(--text); }
.icon-btn svg { width: 17px; height: 17px; }

.search-wrap { padding: 0 12px 8px; position: relative; }
.search-input {
  width: 100%; height: 34px; border-radius: 8px;
  background: var(--bg-hover); border: 1px solid transparent;
  padding: 0 12px 0 34px; font-size: 13.5px; color: var(--text);
  outline: none; transition: border .15s, background .15s;
}
.search-input::placeholder { color: var(--text-tertiary); }
.search-input:focus { background: var(--bg-elevated); border-color: var(--accent); }
.search-wrap > svg { position: absolute; left: 24px; top: 50%; transform: translateY(-50%); width: 15px; height: 15px; color: var(--text-tertiary); pointer-events: none; }

.side-scroll { flex: 1; overflow-y: auto; padding-bottom: 8px; }

.new-note-btn {
  margin: 4px 12px 6px; height: 36px; border-radius: 8px;
  background: var(--accent); color: var(--accent-text);
  display: flex; align-items: center; gap: 8px; padding: 0 12px;
  font-size: 13.5px; font-weight: 600;
  transition: background .15s, transform .08s;
}
.new-note-btn:hover { background: var(--accent-hover); }
.new-note-btn:active { transform: scale(.98); }
.new-note-btn svg { width: 16px; height: 16px; }

.nav-section { padding: 6px 8px 2px; }
.section-head {
  display: flex; align-items: center; gap: 4px; width: 100%;
  padding: 6px 8px; border-radius: 6px; color: var(--text-tertiary);
  font-size: 11.5px; font-weight: 700; letter-spacing: .4px; text-transform: uppercase;
}
.section-head .label { flex: 1; text-align: left; }
.caret { width: 14px; height: 14px; transition: transform .18s; flex: 0 0 auto; opacity: .7; }
.section-head.collapsed .caret { transform: rotate(-90deg); }
.add-mini {
  width: 22px; height: 22px; border-radius: 5px; flex: 0 0 auto;
  display: grid; place-items: center; opacity: 0; transition: opacity .15s, background .15s;
}
.nav-section:hover .add-mini { opacity: 1; }
.add-mini:hover { background: var(--bg-hover); color: var(--text); }
.add-mini svg { width: 14px; height: 14px; }

.section-items { overflow: hidden; transition: max-height .25s ease; }
.section-items.hidden { display: none; }

.nav-item {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 6px 8px; border-radius: 7px; font-size: 13.5px; color: var(--text);
  position: relative; transition: background .12s; user-select: none;
}
.nav-item:hover { background: var(--bg-hover); }
.nav-item.active { background: var(--bg-active); font-weight: 600; }
.nav-item .ni-icon { width: 16px; height: 16px; flex: 0 0 auto; color: var(--text-secondary); }
.nav-item.active .ni-icon { color: var(--accent); }
.nav-item .ni-label { flex: 1; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-item .ni-count { font-size: 11.5px; color: var(--text-tertiary); font-variant-numeric: tabular-nums; }
.nav-item .ni-del {
  position: absolute; right: 6px; width: 20px; height: 20px; border-radius: 5px;
  display: none; place-items: center; color: var(--text-tertiary);
}
.nav-item:hover .ni-del { display: grid; }
.nav-item:hover .ni-count { display: none; }
.nav-item .ni-del:hover { background: var(--danger-soft); color: var(--danger); }
.nav-item .ni-del svg { width: 14px; height: 14px; }
.tag-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; margin-left: 3px; }
.folder-input {
  width: 100%; height: 30px; border-radius: 6px; padding: 0 10px;
  background: var(--bg-elevated); border: 1px solid var(--accent); font-size: 13px; color: var(--text); outline: none;
}

/* ============ NOTE LIST ============ */
.note-list {
  width: var(--list-w); flex: 0 0 var(--list-w);
  background: var(--bg-list); border-right: 1px solid var(--border);
  display: flex; flex-direction: column; transition: margin-left .28s cubic-bezier(.4,0,.2,1);
}
.list-head {
  display: flex; align-items: center; gap: 6px; padding: 16px 16px 10px;
}
.list-title { font-size: 17px; font-weight: 700; letter-spacing: -.3px; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.list-meta { font-size: 12px; color: var(--text-tertiary); font-weight: 500; margin-top: 1px; }

.sort-row { display: flex; align-items: center; gap: 4px; padding: 0 12px 8px; }
.sort-btn {
  height: 26px; padding: 0 9px; border-radius: 6px; font-size: 12px; color: var(--text-secondary);
  display: flex; align-items: center; gap: 5px; transition: background .12s, color .12s;
}
.sort-btn:hover { background: var(--bg-hover); color: var(--text); }
.sort-btn svg { width: 13px; height: 13px; }

.notes-scroll { flex: 1; overflow-y: auto; padding: 0 8px 16px; }
.note-card {
  padding: 11px 12px; border-radius: 9px; cursor: pointer; position: relative;
  transition: background .12s; border: 1px solid transparent;
}
.note-card + .note-card { margin-top: 2px; }
.note-card:hover { background: var(--bg-hover); }
.note-card.active { background: var(--bg-elevated); border-color: var(--border-strong); box-shadow: var(--shadow); }
.nc-top { display: flex; align-items: baseline; gap: 7px; margin-bottom: 4px; }
.nc-title { font-size: 14px; font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); }
.nc-pin { width: 13px; height: 13px; color: var(--star); flex: 0 0 auto; }
.nc-snippet { font-size: 12.5px; color: var(--text-secondary); line-height: 1.5; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.nc-snippet em { color: var(--accent); font-style: normal; font-weight: 600; background: var(--accent-soft); padding: 0 2px; border-radius: 3px; }
.nc-foot { display: flex; align-items: center; gap: 6px; margin-top: 7px; }
.nc-date { font-size: 11px; color: var(--text-tertiary); }
.nc-tags { display: flex; gap: 4px; overflow: hidden; }
.nc-tag { font-size: 10px; padding: 1px 6px; border-radius: 4px; background: var(--bg-active); color: var(--text-secondary); white-space: nowrap; }
.empty-list { padding: 50px 24px; text-align: center; color: var(--text-tertiary); }
.empty-list svg { width: 40px; height: 40px; margin-bottom: 14px; opacity: .5; }
.empty-list h4 { font-size: 14px; color: var(--text-secondary); font-weight: 600; margin-bottom: 5px; }
.empty-list p { font-size: 12.5px; line-height: 1.5; }

/* ============ EDITOR ============ */
.editor {
  flex: 1; min-width: 0; background: var(--bg);
  display: flex; flex-direction: column; position: relative;
}
.editor-empty {
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
  color: var(--text-tertiary); gap: 18px; padding: 40px;
}
.editor-empty .big-logo {
  width: 64px; height: 64px; border-radius: 18px;
  background: linear-gradient(135deg, var(--accent-soft), transparent);
  border: 1px solid var(--border); display: grid; place-items: center;
}
.editor-empty .big-logo svg { width: 30px; height: 30px; color: var(--accent); }
.editor-empty h2 { font-size: 19px; color: var(--text-secondary); font-weight: 700; }
.editor-empty p { font-size: 13.5px; max-width: 300px; text-align: center; line-height: 1.6; }
.editor-empty kbd { font-family: inherit; background: var(--bg-hover); border: 1px solid var(--border-strong); border-bottom-width: 2px; border-radius: 5px; padding: 1px 7px; font-size: 11.5px; color: var(--text-secondary); }

.editor-topbar {
  display: flex; align-items: center; gap: 4px; padding: 10px 14px;
  border-bottom: 1px solid var(--border); flex: 0 0 auto;
}
.breadcrumb { flex: 1; display: flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--text-tertiary); min-width: 0; }
.breadcrumb .crumb { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.breadcrumb svg { width: 12px; height: 12px; opacity: .6; flex: 0 0 auto; }
.topbar-btn { height: 30px; min-width: 30px; padding: 0 8px; border-radius: 7px; display: flex; align-items: center; gap: 6px; color: var(--text-secondary); font-size: 12.5px; transition: background .12s, color .12s; }
.topbar-btn:hover { background: var(--bg-hover); color: var(--text); }
.topbar-btn.on { color: var(--star); }
.topbar-btn svg { width: 16px; height: 16px; }

.format-bar {
  display: flex; align-items: center; gap: 1px; padding: 7px 16px; flex-wrap: wrap;
  border-bottom: 1px solid var(--border); flex: 0 0 auto; position: sticky; top: 0; background: var(--bg); z-index: 5;
}
.fmt-btn {
  height: 30px; min-width: 30px; padding: 0 7px; border-radius: 6px;
  display: grid; place-items: center; color: var(--text-secondary);
  transition: background .12s, color .12s; font-size: 12.5px;
}
.fmt-btn:hover { background: var(--bg-hover); color: var(--text); }
.fmt-btn.active { background: var(--accent-soft); color: var(--accent); }
.fmt-btn svg { width: 16px; height: 16px; }
.fmt-divider { width: 1px; height: 18px; background: var(--border); margin: 0 5px; }

.editor-scroll { flex: 1; overflow-y: auto; }
.editor-paper {
  max-width: 720px; margin: 0 auto; padding: 44px 56px 160px;
}
.note-title {
  font-size: 32px; font-weight: 800; letter-spacing: -.6px; line-height: 1.2;
  color: var(--text); outline: none; word-break: break-word; margin-bottom: 4px;
}
.note-title[data-empty="true"]:before {
  content: attr(data-placeholder); color: var(--text-tertiary); pointer-events: none;
}
.note-body {
  outline: none; min-height: 60vh; font-size: 16px; line-height: 1.75; color: var(--text);
}
.note-body[data-empty="true"]:before {
  content: attr(data-placeholder); color: var(--text-tertiary); pointer-events: none;
}
.note-body.serif { font-family: "Iowan Old Style", "Apple Garamond", Georgia, "Times New Roman", serif; font-size: 17px; }

/* editor content styles */
.note-body > *:first-child { margin-top: 0; }
.note-body h1 { font-size: 26px; font-weight: 800; letter-spacing: -.4px; margin: 28px 0 8px; line-height: 1.25; }
.note-body h2 { font-size: 21px; font-weight: 700; letter-spacing: -.3px; margin: 24px 0 6px; line-height: 1.3; }
.note-body h3 { font-size: 17px; font-weight: 700; margin: 20px 0 4px; }
.note-body p { margin: 0 0 4px; }
.note-body ul, .note-body ol { margin: 6px 0 10px; padding-left: 26px; }
.note-body li { margin: 2px 0; }
.note-body li::marker { color: var(--accent); }
.note-body blockquote {
  border-left: 3px solid var(--accent); padding: 4px 0 4px 16px; margin: 10px 0;
  color: var(--text-secondary); font-style: italic;
}
.note-body code {
  font-family: "SF Mono", "JetBrains Mono", "Fira Code", Menlo, Consolas, monospace;
  font-size: .86em; background: var(--code-bg); padding: 2px 6px; border-radius: 5px;
  border: 1px solid var(--border);
}
.note-body pre {
  background: var(--code-bg); border: 1px solid var(--border); border-radius: 10px;
  padding: 14px 16px; margin: 12px 0; overflow-x: auto; line-height: 1.6;
}
.note-body pre code { background: none; border: none; padding: 0; font-size: 13px; }
.note-body a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px; }
.note-body hr { border: none; border-top: 1px solid var(--border); margin: 20px 0; }
.note-body input[type="checkbox"] { margin-right: 7px; transform: translateY(1px); accent-color: var(--accent); }
.note-body b, .note-body strong { font-weight: 700; }

.status-bar {
  display: flex; align-items: center; gap: 14px; padding: 8px 24px;
  border-top: 1px solid var(--border); font-size: 11.5px; color: var(--text-tertiary);
  flex: 0 0 auto; background: var(--bg);
}
.status-bar .dot { width: 3px; height: 3px; border-radius: 50%; background: var(--text-tertiary); opacity: .5; }
.status-bar .saved { color: var(--accent); }

/* dropdown menu */
.menu {
  position: fixed; background: var(--bg-elevated); border: 1px solid var(--border-strong);
  border-radius: 10px; box-shadow: var(--shadow-lg); padding: 5px; z-index: 100; min-width: 180px;
  animation: pop .12s ease;
}
@keyframes pop { from { opacity: 0; transform: scale(.96) translateY(-4px); } to { opacity: 1; transform: scale(1); } }
.menu-item { display: flex; align-items: center; gap: 9px; padding: 8px 10px; border-radius: 6px; font-size: 13px; width: 100%; text-align: left; color: var(--text); transition: background .1s; }
.menu-item:hover { background: var(--bg-hover); }
.menu-item.danger { color: var(--danger); }
.menu-item.danger:hover { background: var(--danger-soft); }
.menu-item svg { width: 15px; height: 15px; flex: 0 0 auto; }
.menu-item .shortcut { margin-left: auto; font-size: 11px; color: var(--text-tertiary); }
.menu-sep { height: 1px; background: var(--border); margin: 4px 2px; }

/* toast */
.toast {
  position: fixed; bottom: 28px; left: 50%; transform: translateX(-50%) translateY(20px);
  background: var(--text); color: var(--bg); padding: 10px 18px; border-radius: 9px;
  font-size: 13px; font-weight: 500; box-shadow: var(--shadow-lg); z-index: 200;
  opacity: 0; pointer-events: none; transition: opacity .2s, transform .2s; display: flex; align-items: center; gap: 10px;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.toast button { color: var(--accent); font-weight: 700; font-size: 13px; }

/* mobile toggle */
.menu-toggle { display: none; }
.sidebar-scrim { display: none; }

@media (max-width: 900px) {
  .sidebar { position: fixed; left: 0; top: 0; bottom: 0; margin-left: calc(-1 * var(--side-w)); box-shadow: var(--shadow-lg); }
  .sidebar.open { margin-left: 0; }
  .sidebar-scrim.show { display: block; position: fixed; inset: 0; background: rgba(0,0,0,.35); z-index: 35; }
  .menu-toggle { display: grid; }
}
@media (max-width: 640px) {
  .note-list { position: fixed; left: 0; top: 0; bottom: 0; z-index: 30; width: 100%; flex: none; margin-left: 0; }
  .note-list.hide-mobile { margin-left: -100%; }
  .editor-paper { padding: 28px 22px 140px; }
  :root { --side-w: 230px; }
}
</style>
</head>
<body>
<div class="app">

  <!-- ===== SIDEBAR ===== -->
  <aside class="sidebar" id="sidebar">
    <div class="sidebar-head">
      <div class="brand">
        <div class="brand-logo">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h12l4 4v12H4z"/><path d="M8 9h6M8 13h8M8 17h5"/></svg>
        </div>
        <span class="brand-name">Vellum</span>
      </div>
      <button class="icon-btn" id="themeToggle" title="Toggle theme">
        <svg id="themeIcon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></svg>
      </button>
    </div>

    <div class="search-wrap">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
      <input class="search-input" id="searchInput" placeholder="Search notes…" />
    </div>

    <button class="new-note-btn" id="newNoteBtn">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg>
      New Note
    </button>

    <div class="side-scroll">
      <div class="nav-section" id="smartSection">
        <div id="smartItems"></div>
      </div>

      <div class="nav-section" data-collapsible="folders">
        <button class="section-head" data-toggle="folders">
          <svg class="caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
          <span class="label">Folders</span>
          <span class="add-mini" data-add="folder" title="New folder">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg>
          </span>
        </button>
        <div class="section-items" id="folderItems"></div>
      </div>

      <div class="nav-section" data-collapsible="tags">
        <button class="section-head" data-toggle="tags">
          <svg class="caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
          <span class="label">Tags</span>
          <span class="add-mini" data-add="tag" title="New tag">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg>
          </span>
        </button>
        <div class="section-items" id="tagItems"></div>
      </div>
    </div>
  </aside>
  <div class="sidebar-scrim" id="scrim"></div>

  <!-- ===== NOTE LIST ===== -->
  <section class="note-list" id="noteList">
    <div class="list-head">
      <button class="icon-btn menu-toggle" id="menuToggle">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M3 6h18M3 12h18M3 18h18"/></svg>
      </button>
      <div style="flex:1;min-width:0">
        <div class="list-title" id="listTitle">All Notes</div>
        <div class="list-meta" id="listMeta"></div>
      </div>
      <button class="icon-btn" id="sortBtn" title="Sort">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h13M3 12h9M3 18h5M17 16l3 3 3-3M20 5v14"/></svg>
      </button>
    </div>
    <div class="notes-scroll" id="notesScroll"></div>
  </section>

  <!-- ===== EDITOR ===== -->
  <main class="editor" id="editorPane">
    <div id="editorContent"></div>
    <div class="status-bar" id="statusBar" style="display:none"></div>
  </main>
</div>

<div class="toast" id="toast"></div>

<script>
"use strict";

/* ============ STORAGE & STATE ============ */
const KEY = 'vellum_notes_v2';

const TAG_COLORS = ['#e57373','#f0b400','#7cb342','#26a69a','#42a5f5','#ab47bc','#ec407a','#8d6e63','#78909c','#ff7043'];

function uid(){ return Date.now().toString(36)+Math.random().toString(36).slice(2,7); }

function defaultData(){
  const now = Date.now();
  const fPersonal = uid(), fWork = uid(), fIdeas = uid();
  const t1 = uid(), t2 = uid(), t3 = uid();
  return {
    theme: 'light',
    collapsed: { folders:false, tags:false },
    sort: 'updated',
    folders: [
      { id:fPersonal, name:'Personal', color:'#26a69a' },
      { id:fWork, name:'Work', color:'#42a5f5' },
      { id:fIdeas, name:'Ideas', color:'#ab47bc' },
    ],
    tags: [
      { id:t1, name:'important', color:'#e57373' },
      { id:t2, name:'reading', color:'#7cb342' },
      { id:t3, name:'quick', color:'#f0b400' },
    ],
    view: { type:'all' },
    activeNoteId: null,
    notes: [
      {
        id: uid(), folderId: fPersonal, title:'Welcome to Vellum ✨', pinned:true, trashed:false, serif:false,
        tags:[t1], createdAt: now-86400000*3, updatedAt: now-3600000,
        body: `<p>This is your space to think, write, and organize. Vellum is a calm, fast notebook inspired by the best of Obsidian, Notion, and Bear.</p>
<h2>Getting around</h2>
<ul>
<li><b>Three panels</b> — navigate folders on the left, browse notes in the middle, write on the right.</li>
<li>Click <b>New Note</b> or press <code>Ctrl/⌘ + N</code> to start writing.</li>
<li>Search everything instantly from the top of the sidebar.</li>
</ul>
<h2>Formatting</h2>
<p>Use the toolbar above, or try markdown shortcuts:</p>
<ul>
<li>Type <code># </code> <code>## </code> <code>### </code> for headings</li>
<li><code>- </code> or <code>* </code> for a bullet list</li>
<li><code>&gt; </code> for a quote</li>
<li><code>\`\`\`</code> for a code block</li>
<li><code>1. </code> for a numbered list</li>
</ul>
<blockquote>The palest ink is better than the best memory.</blockquote>
<p>Everything saves automatically to your browser. Enjoy writing! 🖋️</p>`
      },
      {
        id: uid(), folderId: fWork, title:'Q4 Product Roadmap', pinned:false, trashed:false, serif:false,
        tags:[t1,t2], createdAt: now-86400000*5, updatedAt: now-86400000*2,
        body:`<h1>Q4 Roadmap</h1><p>Key initiatives for the quarter:</p><ol><li>Ship the new editor <b>(in progress)</b></li><li>Mobile app beta</li><li>Offline sync overhaul</li></ol><h3>Risks</h3><ul><li>Timeline pressure on sync</li><li>Hiring bandwidth</li></ul><blockquote>Prioritize the editor launch above all else.</blockquote>`
      },
      {
        id: uid(), folderId: fIdeas, title:'App idea: a quiet garden', pinned:false, trashed:false, serif:true,
        tags:[t3], createdAt: now-86400000*8, updatedAt: now-86400000*4,
        body:`<p>A plant-care app that's deliberately slow. No streaks, no notifications shouting at you. Just a gentle, living garden that grows as you remember to water it.</p><h2>Why</h2><p>Most habit apps feel like performance reviews. Plants deserve patience — and so do people.</p>`
      },
      {
        id: uid(), folderId: fPersonal, title:'Books to read', pinned:false, trashed:false, serif:false,
        tags:[t2], createdAt: now-86400000*10, updatedAt: now-86400000*6,
        body:`<h2>Reading list</h2><ul><li>The Beginning of Infinity — David Deutsch</li><li>Four Thousand Weeks — Oliver Burkeman</li><li>Piranesi — Susanna Clarke</li></ul>`
      },
    ]
  };
}

let state;
function load(){
  try {
    const raw = localStorage.getItem(KEY);
    state = raw ? JSON.parse(raw) : defaultData();
  } catch(e){ state = defaultData(); }
  if(!state.notes) state = defaultData();
}
let saveTimer;
function save(){
  clearTimeout(saveTimer);
  saveTimer = setTimeout(()=>{ localStorage.setItem(KEY, JSON.stringify(state)); }, 350);
}

/* ============ HELPERS ============ */
const $ = s => document.querySelector(s);
const icons = {
  search:'<circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/>',
  notes:'<path d="M4 4h12l4 4v12H4z"/><path d="M8 9h6M8 13h8M8 17h5"/>',
  star:'<path d="M12 2.5 14.9 8.6l6.6.9-4.8 4.6 1.2 6.6L12 17.8 6.1 20.7l1.2-6.6L2.5 9.5l6.6-.9z"/>',
  trash:'<path d="M3 6h18M8 6V4h8v2M6 6l1 14h10l1-14"/>',
  folder:'<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>',
  tag:'<path d="M20.6 13.4 13 21l-9-9V4h8z"/><circle cx="7.5" cy="7.5" r="1.3"/>',
  pin:'<path d="M9 4h6l-1 6 4 3v2h-5v5l-1 1-1-1v-5H6v-2l4-3z"/>',
  sun:'<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2M5 5l1.5 1.5M17.5 17.5 19 19M19 5l-1.5 1.5M6.5 17.5 5 19"/>',
  moon:'<path d="M21 12.8A9 9 0 1 1 11.2 3 7 7 0 0 0 21 12.8z"/>',
  plus:'<path d="M12 5v14M5 12h14"/>',
  sort:'<path d="M3 6h13M3 12h9M3 18h5M17 16l3 3 3-3M20 5v14"/>',
  bold:'<path d="M7 5h6a3.5 3.5 0 0 1 0 7H7zM7 12h7a3.5 3.5 0 0 1 0 7H7z"/>',
  italic:'<path d="M19 5h-6M11 19H5M15 5 9 19"/>',
  underline:'<path d="M7 4v7a5 5 0 0 0 10 0V4M5 21h14"/>',
  strike:'<path d="M4 12h16M9 5.5A5 5 0 0 1 16 8M9 18.5A5 5 0 0 0 16 16"/>',
  h1:'<path d="M4 5v14M14 5v14M4 12h10M18 12l3-1v8"/>',
  h2:'<path d="M4 5v14M14 5v14M4 12h10M17 9a2.5 2.5 0 1 1 4.5 1.5L17 19h5"/>',
  quote:'<path d="M7 7H4v4h4l-1 4M17 7h-3v4h4l-1 4"/>',
  code:'<path d="m9 8-4 4 4 4M15 8l4 4-4 4"/>',
  list:'<path d="M8 6h12M8 12h12M8 18h12M3.5 6h.01M3.5 12h.01M3.5 18h.01"/>',
  num:'<path d="M9 6h12M9 12h12M9 18h12M4 5l-1 1 1 1M3 11h2v3"/>',
  link:'<path d="M10 13a4 4 0 0 0 5.7 0l3-3a4 4 0 1 0-5.7-5.7L11 6M14 11a4 4 0 0 0-5.7 0l-3 3a4 4 0 1 0 5.7 5.7L13 18"/>',
  check:'<path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>',
  del:'<path d="M3 6h18M8 6V4h8v2M6 6l1 14h10l1-14"/>',
  copy:'<rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h8"/>',
  restore:'<path d="M3 12a9 9 0 1 0 3-6.7L3 8M3 4v4h4"/>',
  pen:'<path d="M12 20h9M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z"/>',
  empty:'<path d="M14 3v5h5M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>',
  chevron:'<path d="m9 6 6 6-6 6"/>',
};
function svg(name, sw=2){ return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round">${icons[name]||''}</svg>`; }

function getNote(id){ return state.notes.find(n=>n.id===id); }
function folderName(id){ const f=state.folders.find(x=>x.id===id); return f?f.name:'Unsorted'; }
function tagById(id){ return state.tags.find(t=>t.id===id); }
function tagName(id){ const t=tagById(id); return t?t.name:''; }
function tagColor(id){ const t=tagById(id); return t?t.color:'#999'; }
function folderColor(id){ const f=state.folders.find(x=>x.id===id); return f?f.color:'#78909c'; }

function relativeDate(ts){
  const d = new Date(ts), now = new Date();
  const diff = now - d;
  const day = 86400000;
  if (diff < 60000) return 'just now';
  if (diff < 3600000) return Math.floor(diff/60000)+'m ago';
  if (d.toDateString() === now.toDateString()) return 'Today';
  if (d.toDateString() === new Date(now-day).toDateString()) return 'Yesterday';
  if (diff < day*7) return Math.floor(diff/day)+'d ago';
  return d.toLocaleDateString(undefined,{month:'short',day:'numeric'});
}

function plainText(html){
  const tmp = document.createElement('div');
  tmp.innerHTML = html || '';
  return (tmp.textContent || '').replace(/\s+/g,' ').trim();
}
function snippet(html, n=110){
  const t = plainText(html);
  return t.length>n ? t.slice(0,n)+'…' : t;
}

/* ============ VIEW FILTERING ============ */
function visibleNotes(){
  let list = state.notes.filter(n=>!n.trashed);
  const v = state.view;
  if (v.type==='folder') list = list.filter(n=>n.folderId===v.id);
  else if (v.type==='tag') list = list.filter(n=>n.tags.includes(v.id));
  else if (v.type==='favorites') list = list.filter(n=>n.pinned);
  const q = (state.search||'').toLowerCase().trim();
  if (q){
    list = list.filter(n=>{
      const hay = (n.title+' '+plainText(n.body)+' '+n.tags.map(tagName).join(' ')).toLowerCase();
      return hay.includes(q);
    });
  }
  // sort
  list.sort((a,b)=>{
    if (a.pinned !== b.pinned) return a.pinned? -1:1;
    if (state.sort==='created') return b.createdAt-a.createdAt;
    if (state.sort==='title') return (a.title||'Untitled').localeCompare(b.title||'Untitled');
    return b.updatedAt-a.updatedAt;
  });
  return list;
}
function trashNotes(){ return state.notes.filter(n=>n.trashed).sort((a,b)=>b.updatedAt-a.updatedAt); }

/* ============ RENDER SIDEBAR ============ */
function renderSidebar(){
  const total = state.notes.filter(n=>!n.trashed).length;
  const fav = state.notes.filter(n=>!n.trashed && n.pinned).length;
  const trash = state.notes.filter(n=>n.trashed).length;

  const smart = $('#smartItems');
  smart.innerHTML = `
    ${navRow('all','notes','All Notes',total)}
    ${navRow('favorites','star','Favorites',fav)}
    ${navRow('trash','trash','Trash',trash)}
  `;

  // folders
  const fEl = $('#folderItems');
  fEl.innerHTML = state.folders.map(f=>{
    const cnt = state.notes.filter(n=>!n.trashed && n.folderId===f.id).length;
    return navRow('folder', folderIcon(f.color), f.name, cnt, f.id, 'folder');
  }).join('');

  // tags
  const tEl = $('#tagItems');
  if (state.tags.length === 0){
    tEl.innerHTML = '<div style="padding:6px 12px;font-size:12px;color:var(--text-tertiary)">No tags yet</div>';
  } else {
    tEl.innerHTML = state.tags.map(t=>{
      const cnt = state.notes.filter(n=>!n.trashed && n.tags.includes(t.id)).length;
      return navRow('tag', `<span class="tag-dot" style="background:${t.color}"></span>`, t.name, cnt, t.id, 'tag');
    }).join('');
  }

  // collapsed state
  applyCollapsed();
  bindNavClicks();
}
function folderIcon(color){
  return `<span class="ni-icon"><svg viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg></span>`;
}
function navRow(type, iconHtml, label, count, id, kind){
  const v = state.view;
  const active = (v.type===type && (!id || v.id===id)) ? 'active':'';
  let del = '';
  if (kind){ del = `<span class="ni-del" data-del-kind="${kind}" data-del-id="${id}" title="Delete">${svg('del',2)}</span>`; }
  return `<button class="nav-item ${active}" data-nav-type="${type}" data-nav-id="${id||''}">
    ${typeof iconHtml==='string' && iconHtml.startsWith('<') ? iconHtml : `<span class="ni-icon">${svg(iconHtml)}</span>`}
    <span class="ni-label">${escapeHtml(label)}</span>
    <span class="ni-count">${count}</span>
    ${del}
  </button>`;
}
function escapeHtml(s){ return (s||'').replace(/[&<>"']/g, c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); }

function applyCollapsed(){
  ['folders','tags'].forEach(sec=>{
    const head = document.querySelector(`[data-toggle="${sec}"]`);
    const items = $('#'+sec+'Items');
    if (state.collapsed[sec]){ head.classList.add('collapsed'); items.classList.add('hidden'); }
    else { head.classList.remove('collapsed'); items.classList.remove('hidden'); }
  });
}

function bindNavClicks(){
  document.querySelectorAll('.nav-item').forEach(el=>{
    el.addEventListener('click', e=>{
      if (e.target.closest('.ni-del')) return;
      const type = el.dataset.navType;
      const id = el.dataset.navId || null;
      setView(type, id);
    });
  });
  document.querySelectorAll('.ni-del').forEach(el=>{
    el.addEventListener('click', e=>{
      e.stopPropagation();
      const kind = el.dataset.delKind, id = el.dataset.delId;
      deleteCollection(kind, id);
    });
  });
}

function setView(type, id){
  if (type==='trash'){ state.view={type:'trash'}; state.activeNoteId=null; renderAll(); closeMobileSidebar(); return; }
  state.view = { type, id: id||null };
  state.activeNoteId = null;
  state.search = '';
  $('#searchInput').value = '';
  renderAll();
  closeMobileSidebar();
}

/* ============ RENDER NOTE LIST ============ */
function renderNoteList(){
  const isTrash = state.view.type==='trash';
  const list = isTrash ? trashNotes() : visibleNotes();
  const titleEl = $('#listTitle');
  const metaEl = $('#listMeta');
  // title
  const titles = { all:'All Notes', favorites:'Favorites', trash:'Trash' };
  if (state.view.type==='folder') titles.folder = folderName(state.view.id);
  if (state.view.type==='tag') titles.tag = '#'+tagName(state.view.id);
  titleEl.textContent = titles[state.view.type] || 'Notes';
  metaEl.innerHTML = list.length + (list.length===1?' note':' notes');
  if (isTrash && list.length){
    const eb = document.createElement('button');
    eb.textContent = 'Empty Trash';
    eb.style.cssText = 'margin-left:8px;background:none;border:none;color:var(--danger);font-size:12px;cursor:pointer;font-weight:600';
    eb.addEventListener('click', emptyTrash);
    metaEl.appendChild(eb);
  }

  const wrap = $('#notesScroll');
  if (list.length===0){
    const msg = isTrash ? 'Trash is empty' : (state.search ? 'No matches' : 'No notes here yet');
    const sub = isTrash ? 'Deleted notes will appear here.' : (state.search ? 'Try a different search.' : 'Create your first note to get started.');
    wrap.innerHTML = `<div class="empty-list">${svg('empty',1.5)}<h4>${msg}</h4><p>${sub}</p></div>`;
    return;
  }
  const q = (state.search||'').toLowerCase().trim();
  wrap.innerHTML = list.map(n=>{
    const active = n.id===state.activeNoteId ? 'active':'';
    const title = n.title || 'Untitled';
    const snip = snippet(n.body);
    const tagsHtml = n.tags.slice(0,2).map(t=>`<span class="nc-tag" style="border-left:2px solid ${tagColor(t)};padding-left:5px">${escapeHtml(tagName(t))}</span>`).join('');
    return `<div class="note-card ${active}" data-note="${n.id}">
      <div class="nc-top">
        <span class="nc-title">${highlight(title,q)}</span>
        ${n.pinned? svg('pin',2).replace('<svg','<svg class="nc-pin"') : ''}
      </div>
      <div class="nc-snippet">${highlight(snip,q)}</div>
      <div class="nc-foot">
        <span class="nc-date">${relativeDate(n.updatedAt)}</span>
        <span class="nc-tags">${tagsHtml}</span>
      </div>
    </div>`;
  }).join('');

  wrap.querySelectorAll('.note-card').forEach(c=>{
    c.addEventListener('click', ()=> openNote(c.dataset.note));
  });
}
function highlight(text, q){
  let html = escapeHtml(text);
  if (!q) return html;
  const re = new RegExp('('+q.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')+')','gi');
  return html.replace(re,'<em>$1</em>');
}

/* ============ RENDER EDITOR ============ */
function renderEditor(){
  const pane = $('#editorContent');
  const statusBar = $('#statusBar');
  const note = state.activeNoteId ? getNote(state.activeNoteId) : null;

  if (!note){
    statusBar.style.display='none';
    pane.innerHTML = `<div class="editor-empty">
      <div class="big-logo">${svg('notes',1.8)}</div>
      <h2>Nothing selected</h2>
      <p>Pick a note from the list, or press <kbd>Ctrl</kbd>+<kbd>N</kbd> to write something new.</p>
    </div>`;
    return;
  }
  const inTrash = note.trashed;
  statusBar.style.display='flex';
  pane.innerHTML = `
    <div class="editor-topbar">
      <div class="breadcrumb">
        ${svg('folder',2)}
        <span class="crumb">${escapeHtml(folderName(note.folderId))}</span>
        ${note.tags.length? svg('chevron',2.5)+'<span class="crumb">'+note.tags.map(tagName).map(t=>'#'+escapeHtml(t)).join(' ')+'</span>':''}
      </div>
      <button class="topbar-btn ${note.serif?'on':''}" id="fontToggle" title="Toggle serif font">${svg('pen',1.8)}</button>
      <button class="topbar-btn ${note.pinned?'on':''}" id="pinBtn" title="Pin to favorites">${svg('star',2)}</button>
      <button class="topbar-btn" id="moreBtn" title="More">${svg('sort',2)}</button>
    </div>
    ${inTrash ? '' : `
    <div class="format-bar">
      <button class="fmt-btn" data-cmd="bold" title="Bold (Ctrl+B)">${svg('bold',2.1)}</button>
      <button class="fmt-btn" data-cmd="italic" title="Italic (Ctrl+I)">${svg('italic',2)}</button>
      <button class="fmt-btn" data-cmd="underline" title="Underline (Ctrl+U)">${svg('underline',2)}</button>
      <button class="fmt-btn" data-cmd="strikeThrough" title="Strikethrough">${svg('strike',2)}</button>
      <span class="fmt-divider"></span>
      <button class="fmt-btn" data-block="H1" title="Heading 1">${svg('h1',2)}</button>
      <button class="fmt-btn" data-block="H2" title="Heading 2">${svg('h2',2)}</button>
      <span class="fmt-divider"></span>
      <button class="fmt-btn" data-block="UL" title="Bullet list">${svg('list',2)}</button>
      <button class="fmt-btn" data-block="OL" title="Numbered list">${svg('num',2)}</button>
      <button class="fmt-btn" data-block="BLOCKQUOTE" title="Quote">${svg('quote',2)}</button>
      <button class="fmt-btn" data-block="PRE" title="Code block">${svg('code',2)}</button>
      <span class="fmt-divider"></span>
      <button class="fmt-btn" data-inline="code" title="Inline code">${svg('code',2)}</button>
      <button class="fmt-btn" data-cmd="createLink" title="Link">${svg('link',2)}</button>
    </div>`}
    <div class="editor-scroll">
      <div class="editor-paper">
        <h1 class="note-title ${note.serif?'serif':''}" contenteditable="${!inTrash}" data-empty="true" data-placeholder="Untitled" id="noteTitle">${escapeHtml(note.title)}</h1>
        <div class="note-body ${note.serif?'serif':''}" contenteditable="${!inTrash}" data-empty="true" data-placeholder="Start writing…" id="noteBody">${note.body||''}</div>
      </div>
    </div>
  `;
  bindEditor();
  updateStatus();
}

let saveIndicatorTimer;
function updateStatus(){
  const note = state.activeNoteId ? getNote(state.activeNoteId) : null;
  const bar = $('#statusBar');
  if (!note){ bar.style.display='none'; return; }
  const body = $('#noteBody');
  const words = body ? (plainText(body.innerHTML).match(/\S+/g)||[]).length : 0;
  const chars = body ? plainText(body.innerHTML).length : 0;
  bar.innerHTML = `
    <span class="saved" id="saveDot">● Saved</span>
    <span class="dot"></span>
    <span>${words} ${words===1?'word':'words'}</span>
    <span class="dot"></span>
    <span>${chars} characters</span>
    <span class="dot"></span>
    <span>Edited ${relativeDate(note.updatedAt)}</span>
  `;
}

function bindEditor(){
  const note = getNote(state.activeNoteId);
  if (!note) return;
  const titleEl = $('#noteTitle');
  const bodyEl = $('#noteBody');
  if (titleEl){
    checkEmpty(titleEl);
    titleEl.addEventListener('input', ()=>{
      note.title = titleEl.textContent.trim();
      note.updatedAt = Date.now();
      checkEmpty(titleEl);
      updateNoteListItem(note.id);
      updateStatus();
      save();
    });
    titleEl.addEventListener('keydown', e=>{
      if (e.key==='Enter'){ e.preventDefault(); bodyEl.focus(); }
    });
  }
  if (bodyEl){
    checkEmpty(bodyEl);
    bodyEl.addEventListener('input', ()=>{
      note.body = bodyEl.innerHTML;
      note.updatedAt = Date.now();
      checkEmpty(bodyEl);
      updateNoteListItem(note.id);
      updateStatus();
      save();
    });
    bodyEl.addEventListener('keydown', handleEditorKeydown);
    bodyEl.addEventListener('keyup', updateFmtState);
    bodyEl.addEventListener('mouseup', updateFmtState);
  }
  // toolbar
  document.querySelectorAll('.fmt-btn').forEach(btn=>{
    btn.addEventListener('mousedown', e=> e.preventDefault());
    btn.addEventListener('click', ()=>{
      bodyEl.focus();
      if (btn.dataset.cmd){
        if (btn.dataset.cmd==='createLink'){
          const url = prompt('Enter URL:','https://');
          if (url){ document.execCommand('createLink', false, url); }
        } else {
          document.execCommand(btn.dataset.cmd, false, null);
        }
      } else if (btn.dataset.block){
        const tag = btn.dataset.block;
        document.execCommand('formatBlock', false, tag);
      } else if (btn.dataset.inline==='code'){
        wrapInline('code');
      }
      note.body = bodyEl.innerHTML;
      note.updatedAt = Date.now();
      updateStatus(); updateNoteListItem(note.id); save();
    });
  });
  // topbar buttons
  const pin = $('#pinBtn');
  if (pin) pin.addEventListener('click', ()=>{
    note.pinned = !note.pinned;
    renderEditor(); renderNoteList(); renderSidebar(); save();
    toast(note.pinned?'Pinned to favorites':'Removed from favorites');
  });
  const fontT = $('#fontToggle');
  if (fontT) fontT.addEventListener('click', ()=>{
    note.serif = !note.serif; renderEditor(); save();
    toast(note.serif?'Serif font on':'Serif font off');
  });
  const more = $('#moreBtn');
  if (more) more.addEventListener('click', e=> openMoreMenu(e, note));
}

function checkEmpty(el){
  if (!el) return;
  const empty = el.textContent.trim()==='' && !el.querySelector('img,hr,ul,ol,blockquote,pre,li');
  el.setAttribute('data-empty', empty?'true':'false');
}

function updateFmtState(){
  document.querySelectorAll('.fmt-btn').forEach(btn=>{
    let on=false;
    if (btn.dataset.cmd && !btn.dataset.cmd.startsWith('create')){
      try{ on = document.queryCommandState(btn.dataset.cmd); }catch(e){}
    }
    btn.classList.toggle('active', on);
  });
}

function wrapInline(tag){
  const sel = window.getSelection();
  if (!sel.rangeCount || sel.isCollapsed) return;
  const range = sel.getRangeAt(0);
  const node = document.createElement(tag);
  try {
    node.appendChild(range.extractContents());
    range.insertNode(node);
    sel.removeAllRanges();
    const r = document.createRange();
    r.selectNodeContents(node);
    r.collapse(false);
    sel.addRange(r);
  } catch(e){}
}

/* markdown shortcuts */
function getBlock(node, root){
  let el = node.nodeType===3 ? node.parentNode : node;
  if (el === root) return null;
  while (el && el.parentNode !== root){ el = el.parentNode; }
  return el;
}
function handleEditorKeydown(e){
  const body = $('#noteBody');
  if (e.key===' ' && !e.shiftKey){
    markdownBlock(e, body);
  }
  if (e.key==='Tab'){
    e.preventDefault();
    document.execCommand(e.shiftKey?'outdent':'indent');
  }
}
function markdownBlock(e, root){
  const sel = window.getSelection();
  if (!sel.rangeCount) return;
  const range = sel.getRangeAt(0);
  const node = range.startContainer;
  if (node.nodeType!==3) return;
  const block = getBlock(node, root);
  if (!block || block===root) return;
  if (node.parentNode !== block) return; // simple flat block
  const text = node.textContent;
  if (range.startOffset !== text.length) return;

  const map = [
    { re:/^#\s$/, tag:'H1' },
    { re:/^##\s$/, tag:'H2' },
    { re:/^###\s$/, tag:'H3' },
    { re:/^>\s$/, tag:'BLOCKQUOTE' },
    { re:/^```$/, tag:'PRE' },
  ];
  for (const m of map){
    if (m.re.test(text)){ e.preventDefault(); block.textContent=''; document.execCommand('formatBlock', false, m.tag); return; }
  }
  if (/^[-*]\s$/.test(text)){ e.preventDefault(); block.textContent=''; document.execCommand('insertUnorderedList'); return; }
  if (/^1\.\s$/.test(text)){ e.preventDefault(); block.textContent=''; document.execCommand('insertOrderedList'); return; }
  if (/^\[\]\s$/.test(text)){
    e.preventDefault(); block.textContent='';
    document.execCommand('formatBlock', false, 'P');
    block.innerHTML = '<input type="checkbox"> ';
    placeCaretEnd(block);
    return;
  }
}
function placeCaretEnd(el){
  const sel = window.getSelection();
  const r = document.createRange();
  r.selectNodeContents(el); r.collapse(false);
  sel.removeAllRanges(); sel.addRange(r);
}

function updateNoteListItem(id){
  const note = getNote(id);
  const card = document.querySelector(`.note-card[data-note="${id}"]`);
  if (!card || !note) return;
  const titleEl = card.querySelector('.nc-title');
  const snipEl = card.querySelector('.nc-snippet');
  const dateEl = card.querySelector('.nc-date');
  if (titleEl) titleEl.textContent = note.title || 'Untitled';
  if (snipEl) snipEl.textContent = snippet(note.body);
  if (dateEl) dateEl.textContent = relativeDate(note.updatedAt);
}

/* ============ ACTIONS ============ */
function newNote(){
  // if in trash view, switch to current folder context or all
  let folderId = null;
  if (state.view.type==='folder') folderId = state.view.id;
  else folderId = state.folders[0]?.id || null;
  const note = {
    id: uid(), folderId, title:'', body:'', pinned:false, trashed:false, serif:false,
    tags:[], createdAt:Date.now(), updatedAt:Date.now()
  };
  state.notes.unshift(note);
  state.activeNoteId = note.id;
  if (state.view.type==='trash' || state.view.type==='tag' || state.view.type==='favorites'){
    state.view = { type:'all' };
  }
  save();
  renderSidebar(); renderNoteList(); renderEditor();
  setTimeout(()=>{ const t=$('#noteTitle'); if(t) t.focus(); }, 30);
}

function openNote(id){
  state.activeNoteId = id;
  renderNoteList(); renderEditor();
  if (window.innerWidth <= 640){ $('#noteList').classList.add('hide-mobile'); }
}

function deleteNote(id){
  const note = getNote(id);
  if (!note) return;
  if (note.trashed){
    state.notes = state.notes.filter(n=>n.id!==id);
    toast('Note permanently deleted');
  } else {
    note.trashed = true;
    toast('Moved to Trash', 'Undo', ()=>{ note.trashed=false; renderAll(); });
  }
  if (state.activeNoteId===id) state.activeNoteId=null;
  save(); renderAll();
}

function restoreNote(id){
  const note = getNote(id); if(!note) return;
  note.trashed=false; note.updatedAt=Date.now();
  save(); renderAll(); toast('Note restored');
}

function deleteCollection(kind, id){
  if (kind==='folder'){
    if (!confirm('Delete this folder? Notes inside will move to "All Notes" (kept, not deleted).')) return;
    state.notes.forEach(n=>{ if(n.folderId===id) n.folderId=null; });
    state.folders = state.folders.filter(f=>f.id!==id);
    if (state.view.type==='folder' && state.view.id===id) state.view={type:'all'};
  } else if (kind==='tag'){
    state.notes.forEach(n=>{ n.tags = n.tags.filter(t=>t!==id); });
    state.tags = state.tags.filter(t=>t.id!==id);
    if (state.view.type==='tag' && state.view.id===id) state.view={type:'all'};
  }
  save(); renderAll();
}

function addCollection(kind){
  const name = prompt(kind==='folder'?'New folder name:':'New tag name:');
  if (!name || !name.trim()) return;
  const clean = name.trim();
  if (kind==='folder'){
    const id = uid();
    state.folders.push({ id, name:clean, color: TAG_COLORS[state.folders.length % TAG_COLORS.length] });
    state.view = { type:'folder', id };
  } else {
    if (state.tags.some(t=>t.name.toLowerCase()===clean.toLowerCase())){ toast('Tag already exists'); return; }
    const id = uid();
    state.tags.push({ id, name:clean, color: TAG_COLORS[state.tags.length % TAG_COLORS.length] });
    state.view = { type:'tag', id };
  }
  save(); renderAll();
}

function emptyTrash(){
  if (!confirm('Permanently delete all notes in Trash?')) return;
  state.notes = state.notes.filter(n=>!n.trashed);
  save(); renderAll(); toast('Trash emptied');
}

/* ============ MORE MENU ============ */
let openMenu=null;
function closeMenu(){ if(openMenu){ openMenu.remove(); openMenu=null; document.removeEventListener('mousedown', onDocClick); } }
function onDocClick(e){ if (openMenu && !openMenu.contains(e.target)) closeMenu(); }

function openMoreMenu(e, note){
  closeMenu();
  const menu = document.createElement('div');
  menu.className='menu';
  const inTrash = note.trashed;
  let html='';
  if (inTrash){
    html += `<button class="menu-item" data-act="restore">${svg('restore',2)} Restore</button>`;
    html += `<div class="menu-sep"></div>`;
    html += `<button class="menu-item danger" data-act="delete">${svg('del',2)} Delete forever</button>`;
  } else {
    html += `<button class="menu-item" data-act="pin">${svg('star',2)} ${note.pinned?'Unpin':'Pin'} note</button>`;
    html += `<button class="menu-item" data-act="copy">${svg('copy',2)} Copy as text</button>`;
    html += `<div class="menu-sep"></div>`;
    html += `<div style="padding:6px 10px 2px;font-size:11px;text-transform:uppercase;letter-spacing:.4px;color:var(--text-tertiary);font-weight:700">Move to</div>`;
    html += state.folders.map(f=>`<button class="menu-item" data-act="move" data-fid="${f.id}"><span style="width:12px;height:9px;border-radius:2px;background:${f.color};display:inline-block"></span> ${escapeHtml(f.name)}</button>`).join('');
    html += `<div class="menu-sep"></div>`;
    html += `<div style="padding:6px 10px 2px;font-size:11px;text-transform:uppercase;letter-spacing:.4px;color:var(--text-tertiary);font-weight:700">Tags</div>`;
    html += state.tags.map(t=>{
      const on = note.tags.includes(t.id);
      return `<button class="menu-item" data-act="tag" data-tid="${t.id}"><span class="tag-dot" style="background:${t.color}"></span> ${escapeHtml(t.name)} <span style="margin-left:auto;color:var(--accent)">${on?'✓':''}</span></button>`;
    }).join('');
    if (state.tags.length) html += `<div class="menu-sep"></div>`;
    html += `<button class="menu-item danger" data-act="trash">${svg('del',2)} Move to Trash</button>`;
  }
  menu.innerHTML = html;
  document.body.appendChild(menu);
  const rect = e.currentTarget.getBoundingClientRect();
  let left = rect.right - menu.offsetWidth;
  let top = rect.bottom + 6;
  if (top + menu.offsetHeight > window.innerHeight-10) top = rect.top - menu.offsetHeight - 6;
  menu.style.left = Math.max(10,left)+'px';
  menu.style.top = top+'px';
  openMenu = menu;

  menu.querySelectorAll('.menu-item').forEach(mi=>{
    mi.addEventListener('click', ()=>{
      const act = mi.dataset.act;
      if (act==='pin'){ note.pinned=!note.pinned; }
      else if (act==='copy'){ copyNoteText(note); }
      else if (act==='move'){ note.folderId=mi.dataset.fid; }
      else if (act==='tag'){
        const tid=mi.dataset.tid;
        if (note.tags.includes(tid)) note.tags=note.tags.filter(x=>x!==tid);
        else note.tags.push(tid);
      }
      else if (act==='trash'){ deleteNote(note.id); closeMenu(); return; }
      else if (act==='restore'){ restoreNote(note.id); closeMenu(); return; }
      else if (act==='delete'){ deleteNote(note.id); closeMenu(); return; }
      note.updatedAt=Date.now(); save(); renderAll(); closeMenu();
    });
  });
  setTimeout(()=> document.addEventListener('mousedown', onDocClick), 0);
}

function copyNoteText(note){
  const text = (note.title? note.title+'\n\n':'') + plainText(note.body);
  navigator.clipboard.writeText(text).then(()=>toast('Copied to clipboard'));
}

/* ============ SORT MENU ============ */
function openSortMenu(e){
  closeMenu();
  const menu = document.createElement('div');
  menu.className='menu';
  const opts = [
    {k:'updated', label:'Date updated'},
    {k:'created', label:'Date created'},
    {k:'title', label:'Title (A–Z)'},
  ];
  menu.innerHTML = opts.map(o=>`<button class="menu-item" data-sort="${o.k}">${escapeHtml(o.label)} <span style="margin-left:auto;color:var(--accent)">${state.sort===o.k?'✓':''}</span></button>`).join('');
  document.body.appendChild(menu);
  const rect = e.currentTarget.getBoundingClientRect();
  menu.style.left = (rect.right - menu.offsetWidth)+'px';
  menu.style.top = (rect.bottom+6)+'px';
  openMenu = menu;
  menu.querySelectorAll('.menu-item').forEach(mi=> mi.addEventListener('click', ()=>{
    state.sort = mi.dataset.sort; save(); renderNoteList(); closeMenu();
  }));
  setTimeout(()=> document.addEventListener('mousedown', onDocClick), 0);
}

/* ============ TOAST ============ */
let toastTimer;
function toast(msg, actionLabel, actionFn){
  const t = $('#toast');
  t.innerHTML = '';
  const span = document.createElement('span'); span.textContent = msg;
  t.appendChild(span);
  if (actionLabel){
    const b = document.createElement('button'); b.textContent = actionLabel;
    b.addEventListener('click', ()=>{ actionFn&&actionFn(); hideToast(); });
    t.appendChild(b);
  }
  t.classList.add('show');
  clearTimeout(toastTimer);
  toastTimer = setTimeout(hideToast, 3800);
  function hideToast(){ t.classList.remove('show'); }
}

/* ============ THEME ============ */
function setTheme(theme){
  state.theme = theme;
  document.documentElement.setAttribute('data-theme', theme);
  $('#themeIcon').innerHTML = theme==='dark'? icons.sun : icons.moon;
  save();
}

/* ============ MOBILE ============ */
function openMobileSidebar(){ $('#sidebar').classList.add('open'); $('#scrim').classList.add('show'); }
function closeMobileSidebar(){ $('#sidebar').classList.remove('open'); $('#scrim').classList.remove('show'); }

/* ============ MASTER RENDER ============ */
function renderAll(){ renderSidebar(); renderNoteList(); renderEditor(); }

/* ============ INIT ============ */
function init(){
  load();
  setTheme(state.theme || 'light');

  // theme toggle
  $('#themeToggle').addEventListener('click', ()=> setTheme(state.theme==='dark'?'light':'dark'));

  // new note
  $('#newNoteBtn').addEventListener('click', newNote);

  // search
  $('#searchInput').addEventListener('input', e=>{
    state.search = e.target.value;
    if (state.view.type==='trash') state.view={type:'all'};
    renderNoteList();
  });

  // collapsible sections
  document.querySelectorAll('[data-toggle]').forEach(h=>{
    h.addEventListener('click', ()=>{
      const sec = h.dataset.toggle;
      state.collapsed[sec] = !state.collapsed[sec];
      applyCollapsed(); save();
    });
  });

  // add folder/tag
  document.querySelectorAll('[data-add]').forEach(b=>{
    b.addEventListener('click', e=>{ e.stopPropagation(); addCollection(b.dataset.add); });
  });

  // sort menu
  $('#sortBtn').addEventListener('click', e=>{ e.stopPropagation(); openSortMenu(e); });

  // mobile
  $('#menuToggle').addEventListener('click', openMobileSidebar);
  $('#scrim').addEventListener('click', closeMobileSidebar);

  // keyboard shortcuts
  document.addEventListener('keydown', e=>{
    const mod = e.ctrlKey || e.metaKey;
    if (mod && e.key.toLowerCase()==='n'){ e.preventDefault(); newNote(); }
    else if (mod && e.key.toLowerCase()==='f'){ e.preventDefault(); $('#searchInput').focus(); }
    else if (mod && e.key.toLowerCase()==='k'){ e.preventDefault(); $('#searchInput').focus(); }
    else if (mod && e.key==='\\'){ e.preventDefault(); toggleSidebar(); }
    else if (e.key==='Escape'){ closeMenu(); closeMobileSidebar(); }
    if (mod && e.shiftKey && e.key.toLowerCase()==='d'){
      const n = getNote(state.activeNoteId);
      if (n){ e.preventDefault(); deleteNote(n.id); }
    }
  });

  renderAll();
}

function toggleSidebar(){
  const sb = $('#sidebar');
  if (window.innerWidth <= 900){ sb.classList.toggle('open'); $('#scrim').classList.toggle('show'); }
  else { sb.style.marginLeft = sb.style.marginLeft==='0px' ? '' : '0px'; }
}

document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>
<!-- agent-meta {"model":"glm-5.2","provider":"zai","persona":"middle","sessionId":"d6f6489e-78ba-4131-9381-074b515e2bed","tokensIn":494920,"tokensOut":28427,"tokensTotal":523347,"turns":18,"toolCalls":17,"failedToolCalls":0,"timestamp":"2026-07-12T02:06:27.384Z"} -->