← Notes results

Notes

ling 3.0 Flash · full

Initial view of Notes

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

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

Generation prompt · exact text unavailable

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

Inspect original source 71,010 bytes · SHA-256 f1a45a6e13cc
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Notes</title>
<style>
/* ============================================================
   CONSTANTS
   ============================================================ */
:root {
  --bg-app: #f7f5f2;
  --bg-sidebar: #ffffff;
  --bg-notelist: #ffffff;
  --bg-editor: #ffffff;
  --bg-toolbar: #fafaf9;
  --bg-hover: #f0efeb;
  --bg-selected: #e8f0fe;
  --bg-input: #f5f4f1;
  --text-primary: #1a1a2e;
  --text-secondary: #5a5a6e;
  --text-muted: #9a9aae;
  --accent: #4a6fa5;
  --accent-light: #e8f0fe;
  --border: #e2e0db;
  --border-light: #eceae6;
  --shadow-sm: 0 1px 2px rgba(0,0,0,0.04);
  --shadow-md: 0 2px 8px rgba(0,0,0,0.06);
  --shadow-lg: 0 4px 16px rgba(0,0,0,0.08);
  --radius-sm: 6px;
  --radius-md: 10px;
  --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --font-serif: Georgia, 'Times New Roman', serif;
  --font-mono: 'SF Mono', 'Fira Code', 'Consolas', monospace;
  --sidebar-width: 260px;
  --notelist-width: 280px;
  --toolbar-height: 44px;
  --header-height: 52px;
  --transition-fast: 150ms ease;
  --transition-normal: 250ms ease;
  --autosave-delay: 600;
  --snippet-length: 120;
  --tag-bg: #eef2f7;
  --tag-text: #4a5568;
  --danger: #c53030;
  --danger-bg: #fff5f5;
  --success: #2f855a;
  --success-bg: #f0fff4;
  --warning: #b7791f;
  --warning-bg: #fffff0;
}

[data-theme="dark"] {
  --bg-app: #1a1a2e;
  --bg-sidebar: #22223a;
  --bg-notelist: #22223a;
  --bg-editor: #252540;
  --bg-toolbar: #2a2a44;
  --bg-hover: #333350;
  --bg-selected: #2a3a5c;
  --bg-input: #2e2e48;
  --text-primary: #e0e0e8;
  --text-secondary: #a0a0b8;
  --text-muted: #6a6a80;
  --accent: #6b8cce;
  --accent-light: #2a3a5c;
  --border: #3a3a5c;
  --border-light: #33334a;
  --shadow-sm: 0 1px 2px rgba(0,0,0,0.2);
  --shadow-md: 0 2px 8px rgba(0,0,0,0.3);
  --shadow-lg: 0 4px 16px rgba(0,0,0,0.4);
  --tag-bg: #2a2a44;
  --tag-text: #b0b0c8;
  --danger-bg: #3a1a1a;
  --success-bg: #1a2a1a;
  --warning-bg: #3a2a1a;
}

/* ============================================================
   RESET & BASE
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; overflow: hidden; }
body {
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.5;
  color: var(--text-primary);
  background: var(--bg-app);
  transition: background var(--transition-normal), color var(--transition-normal);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; }
input, textarea { font-family: inherit; color: inherit; }
a { color: var(--accent); text-decoration: none; }
::selection { background: var(--accent); color: #fff; }

/* ============================================================
   APP LAYOUT
   ============================================================ */
#app {
  display: flex;
  height: 100vh;
  width: 100vw;
  overflow: hidden;
}

/* ============================================================
   SIDEBAR
   ============================================================ */
#sidebar {
  width: var(--sidebar-width);
  min-width: 0;
  background: var(--bg-sidebar);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  transition: width var(--transition-normal), min-width var(--transition-normal), opacity var(--transition-normal);
  overflow: hidden;
  flex-shrink: 0;
  z-index: 10;
}
#sidebar.collapsed {
  width: 0;
  border-right: none;
}
.sidebar-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 16px;
  height: var(--header-height);
  border-bottom: 1px solid var(--border-light);
  flex-shrink: 0;
}
.sidebar-header h1 {
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -0.3px;
  flex: 1;
  color: var(--text-primary);
}
.sidebar-header .icon-btn {
  width: 32px; height: 32px;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm);
  font-size: 16px;
  color: var(--text-secondary);
  transition: background var(--transition-fast), color var(--transition-fast);
}
.sidebar-header .icon-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
.sidebar-header .icon-btn.active { color: var(--accent); }

.sidebar-search {
  padding: 8px 12px;
  flex-shrink: 0;
}
.sidebar-search input {
  width: 100%;
  padding: 7px 10px 7px 30px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-input);
  font-size: 13px;
  outline: none;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.sidebar-search input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-light);
}
.sidebar-search {
  position: relative;
}
.sidebar-search::before {
  content: '⌕';
  position: absolute;
  left: 22px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-muted);
  font-size: 14px;
  pointer-events: none;
}

.sidebar-nav {
  padding: 4px 8px;
  flex-shrink: 0;
}
.nav-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 12px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  font-weight: 500;
  color: var(--text-secondary);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
  user-select: none;
}
.nav-item:hover { background: var(--bg-hover); color: var(--text-primary); }
.nav-item.active { background: var(--accent-light); color: var(--accent); font-weight: 600; }
.nav-item .nav-icon { font-size: 16px; width: 20px; text-align: center; flex-shrink: 0; }
.nav-item .nav-count {
  margin-left: auto;
  font-size: 11px;
  color: var(--text-muted);
  background: var(--bg-input);
  padding: 1px 6px;
  border-radius: 10px;
}

.sidebar-notebooks {
  flex: 1;
  overflow-y: auto;
  padding: 4px 8px;
}
.notebook-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--transition-fast);
  user-select: none;
  font-size: 13px;
  color: var(--text-secondary);
}
.notebook-item:hover { background: var(--bg-hover); color: var(--text-primary); }
.notebook-item.active { background: var(--accent-light); color: var(--accent); font-weight: 600; }
.notebook-item .nb-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
.notebook-item .nb-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.notebook-item .nb-actions {
  display: flex; gap: 2px; opacity: 0;
  transition: opacity var(--transition-fast);
}
.notebook-item:hover .nb-actions { opacity: 1; }
.notebook-item .nb-actions button {
  width: 22px; height: 22px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 4px;
  font-size: 12px;
  color: var(--text-muted);
}
.notebook-item .nb-actions button:hover { background: var(--bg-hover); color: var(--text-primary); }

.sidebar-footer {
  padding: 8px 12px;
  border-top: 1px solid var(--border-light);
  flex-shrink: 0;
}
.btn-new-notebook {
  width: 100%;
  padding: 7px 12px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  font-weight: 500;
  color: var(--accent);
  background: transparent;
  transition: background var(--transition-fast);
  text-align: left;
}
.btn-new-notebook:hover { background: var(--accent-light); }

/* ============================================================
   RESIZE HANDLES
   ============================================================ */
.resize-handle {
  width: 4px;
  cursor: col-resize;
  background: transparent;
  transition: background var(--transition-fast);
  flex-shrink: 0;
  z-index: 5;
}
.resize-handle:hover, .resize-handle.active { background: var(--accent); }

/* ============================================================
   NOTE LIST
   ============================================================ */
#note-list {
  width: var(--notelist-width);
  min-width: 0;
  background: var(--bg-notelist);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  transition: width var(--transition-normal), min-width var(--transition-normal), opacity var(--transition-normal);
  overflow: hidden;
  flex-shrink: 0;
}
.note-list-header {
  padding: 12px 16px;
  border-bottom: 1px solid var(--border-light);
  flex-shrink: 0;
}
.note-list-header h2 {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-secondary);
  margin-bottom: 8px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.note-list-controls {
  display: flex;
  gap: 6px;
  align-items: center;
}
.note-list-controls .sort-btn, .note-list-controls .new-note-btn {
  padding: 5px 10px;
  border-radius: var(--radius-sm);
  font-size: 12px;
  font-weight: 500;
  color: var(--text-secondary);
  background: var(--bg-input);
  transition: background var(--transition-fast), color var(--transition-fast);
}
.note-list-controls .sort-btn:hover, .note-list-controls .new-note-btn:hover {
  background: var(--bg-hover);
  color: var(--text-primary);
}
.note-list-controls .new-note-btn {
  margin-left: auto;
  background: var(--accent);
  color: #fff;
}
.note-list-controls .new-note-btn:hover { opacity: 0.9; }

.note-list-items {
  flex: 1;
  overflow-y: auto;
  padding: 4px 8px;
}
.note-item {
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast);
  border: 1px solid transparent;
  margin-bottom: 2px;
}
.note-item:hover { background: var(--bg-hover); border-color: var(--border-light); }
.note-item.selected { background: var(--accent-light); border-color: var(--accent); }
.note-item .note-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-primary);
  margin-bottom: 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.note-item .note-snippet {
  font-size: 12px;
  color: var(--text-secondary);
  line-height: 1.4;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin-bottom: 4px;
}
.note-item .note-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  color: var(--text-muted);
}
.note-item .note-meta .note-time { flex: 1; }
.note-item .note-meta .note-tag {
  padding: 1px 5px;
  border-radius: 3px;
  font-size: 10px;
  background: var(--tag-bg);
  color: var(--tag-text);
  font-weight: 500;
}
.note-item .note-pin {
  font-size: 12px;
  color: var(--warning);
}
.note-item.pinned .note-title::before {
  content: '📌 ';
  font-size: 11px;
}

.note-item .note-actions {
  display: flex; gap: 2px; opacity: 0;
  transition: opacity var(--transition-fast);
  margin-top: 4px;
}
.note-item:hover .note-actions { opacity: 1; }
.note-item .note-actions button {
  padding: 2px 6px;
  font-size: 11px;
  border-radius: 3px;
  color: var(--text-muted);
}
.note-item .note-actions button:hover { background: var(--bg-hover); color: var(--text-primary); }

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 48px 24px;
  text-align: center;
  color: var(--text-muted);
}
.empty-state .empty-icon { font-size: 48px; margin-bottom: 16px; opacity: 0.4; }
.empty-state .empty-title { font-size: 16px; font-weight: 600; color: var(--text-secondary); margin-bottom: 6px; }
.empty-state .empty-desc { font-size: 13px; line-height: 1.5; }

/* ============================================================
   EDITOR PANE
   ============================================================ */
#editor-pane {
  flex: 1;
  display: flex;
  flex-direction: column;
  background: var(--bg-editor);
  min-width: 0;
  overflow: hidden;
}

/* Toolbar */
#toolbar {
  display: flex;
  align-items: center;
  gap: 1px;
  padding: 4px 8px;
  height: var(--toolbar-height);
  background: var(--bg-toolbar);
  border-bottom: 1px solid var(--border-light);
  flex-shrink: 0;
  overflow-x: auto;
  scrollbar-width: none;
}
#toolbar::-webkit-scrollbar { display: none; }
.toolbar-group {
  display: flex;
  align-items: center;
  gap: 1px;
  padding: 0 4px;
}
.toolbar-group:not(:last-child) {
  border-right: 1px solid var(--border-light);
  margin-right: 4px;
  padding-right: 8px;
}
.toolbar-btn {
  width: 30px; height: 28px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 4px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
  transition: background var(--transition-fast), color var(--transition-fast);
  position: relative;
}
.toolbar-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
.toolbar-btn.active { background: var(--accent-light); color: var(--accent); }
.toolbar-btn .shortcut {
  position: absolute;
  bottom: 1px; right: 2px;
  font-size: 8px;
  color: var(--text-muted);
  font-weight: 400;
}
.toolbar-btn.large { width: auto; padding: 0 8px; font-size: 12px; gap: 4px; }

/* Tag input in toolbar */
.tag-input-wrap {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 0 8px;
  flex: 1;
  min-width: 0;
}
.tag-input-wrap input {
  border: none;
  background: transparent;
  font-size: 12px;
  color: var(--text-primary);
  outline: none;
  width: 100%;
  min-width: 60px;
}
.tag-input-wrap input::placeholder { color: var(--text-muted); }
.tag-chip {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 2px 6px;
  border-radius: 4px;
  font-size: 11px;
  font-weight: 500;
  background: var(--tag-bg);
  color: var(--tag-text);
  cursor: pointer;
  transition: background var(--transition-fast);
}
.tag-chip:hover { background: var(--bg-hover); }
.tag-chip .tag-remove {
  font-size: 13px;
  line-height: 1;
  opacity: 0.6;
}
.tag-chip .tag-remove:hover { opacity: 1; }

/* Editor content */
#editor {
  flex: 1;
  padding: 32px 48px;
  overflow-y: auto;
  outline: none;
  font-family: var(--font-serif);
  font-size: 16px;
  line-height: 1.75;
  color: var(--text-primary);
  tab-size: 2;
}
#editor:empty::before {
  content: 'Select or create a note';
  color: var(--text-muted);
  pointer-events: none;
  font-style: italic;
  font-family: var(--font-sans);
  font-size: 15px;
}
#editor h1 { font-size: 2em; margin: 0.67em 0 0.3em; font-weight: 700; letter-spacing: -0.5px; }
#editor h2 { font-size: 1.5em; margin: 0.83em 0 0.3em; font-weight: 600; }
#editor h3 { font-size: 1.17em; margin: 1em 0 0.3em; font-weight: 600; }
#editor p { margin: 0.5em 0; }
#editor ul, #editor ol { padding-left: 1.5em; margin: 0.5em 0; }
#editor li { margin: 0.25em 0; }
#editor blockquote {
  border-left: 3px solid var(--accent);
  padding: 4px 16px;
  margin: 0.5em 0;
  color: var(--text-secondary);
  background: var(--bg-input);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
#editor hr { border: none; border-top: 1px solid var(--border); margin: 1.5em 0; }
#editor code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--bg-input);
  padding: 1px 5px;
  border-radius: 3px;
}
#editor input[type="checkbox"] {
  margin-right: 6px;
  cursor: pointer;
  transform: scale(1.15);
  accent-color: var(--accent);
}
#editor mark {
  background: #fef08a;
  color: inherit;
  border-radius: 2px;
  padding: 0 1px;
}
[data-theme="dark"] #editor mark {
  background: #3a3a1a;
  color: inherit;
}

/* Save indicator */
#save-indicator {
  padding: 4px 16px;
  font-size: 11px;
  color: var(--text-muted);
  text-align: right;
  border-top: 1px solid var(--border-light);
  flex-shrink: 0;
  transition: opacity var(--transition-fast);
  height: 24px;
  line-height: 24px;
}
#save-indicator.saved { color: var(--success); }
#save-indicator.unsaved { color: var(--warning); }

/* ============================================================
   CONTEXT MENU
   ============================================================ */
#context-menu {
  position: fixed;
  background: var(--bg-sidebar);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: 4px;
  z-index: 100;
  min-width: 160px;
  display: none;
}
#context-menu .menu-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 12px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  cursor: pointer;
  transition: background var(--transition-fast);
  color: var(--text-primary);
}
#context-menu .menu-item:hover { background: var(--bg-hover); }
#context-menu .menu-item.danger { color: var(--danger); }
#context-menu .menu-item .menu-icon { width: 18px; text-align: center; }
#context-menu .menu-separator { height: 1px; background: var(--border-light); margin: 4px 0; }

/* ============================================================
   MODAL
   ============================================================ */
#modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.3);
  z-index: 200;
  display: none;
  align-items: center;
  justify-content: center;
}
#modal-overlay.visible { display: flex; }
#modal {
  background: var(--bg-sidebar);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: 24px;
  min-width: 320px;
  max-width: 400px;
}
#modal h3 { font-size: 16px; margin-bottom: 12px; font-weight: 600; }
#modal input {
  width: 100%;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 14px;
  background: var(--bg-input);
  outline: none;
  margin-bottom: 16px;
}
#modal input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-light); }
#modal .modal-actions { display: flex; gap: 8px; justify-content: flex-end; }
#modal .modal-actions button {
  padding: 6px 16px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  font-weight: 500;
}
#modal .btn-cancel { color: var(--text-secondary); }
#modal .btn-cancel:hover { background: var(--bg-hover); }
#modal .btn-confirm { background: var(--accent); color: #fff; }
#modal .btn-confirm:hover { opacity: 0.9; }

/* ============================================================
   ANIMATIONS
   ============================================================ */
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes slideIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }

.fade-in { animation: fadeIn 0.2s ease; }
.slide-in { animation: slideIn 0.2s ease; }

/* ============================================================
   SCROLLBAR
   ============================================================ */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }

/* ============================================================
   FOCUS STATES
   ============================================================ */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
button:focus-visible { outline-offset: 1px; }

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media (max-width: 900px) {
  :root { --sidebar-width: 220px; --notelist-width: 220px; }
  #editor { padding: 20px 24px; }
}
@media (max-width: 640px) {
  :root { --sidebar-width: 0px; --notelist-width: 0px; }
  #sidebar, #note-list { display: none; }
  .resize-handle { display: none; }
}
</style>
</head>
<body>
<div id="app">
  <!-- Sidebar -->
  <aside id="sidebar">
    <div class="sidebar-header">
      <h1>Notes</h1>
      <button class="icon-btn" id="btn-theme" title="Toggle theme" aria-label="Toggle theme">🌙</button>
      <button class="icon-btn" id="btn-collapse" title="Collapse sidebar" aria-label="Collapse sidebar">◀</button>
    </div>
    <div class="sidebar-search">
      <input type="text" id="global-search" placeholder="Search notes…" aria-label="Search notes">
    </div>
    <nav class="sidebar-nav">
      <div class="nav-item active" data-view="all"><span class="nav-icon">📄</span>All Notes<span class="nav-count" id="count-all">0</span></div>
      <div class="nav-item" data-view="trash"><span class="nav-icon">🗑</span>Trash<span class="nav-count" id="count-trash">0</span></div>
    </nav>
    <div class="sidebar-notebooks" id="notebook-list"></div>
    <div class="sidebar-footer">
      <button class="btn-new-notebook" id="btn-new-notebook">+ New Notebook</button>
    </div>
  </aside>

  <!-- Resize handle: sidebar ↔ note list -->
  <div class="resize-handle" id="resize-sidebar"></div>

  <!-- Note List -->
  <div id="note-list">
    <div class="note-list-header">
      <h2 id="note-list-title">All Notes</h2>
      <div class="note-list-controls">
        <button class="sort-btn" id="btn-sort" title="Toggle sort order">Modified ↓</button>
        <button class="new-note-btn" id="btn-new-note" title="New note (Ctrl+N)">+ New</button>
      </div>
    </div>
    <div class="note-list-items" id="note-list-items"></div>
  </div>

  <!-- Resize handle: note list ↔ editor -->
  <div class="resize-handle" id="resize-notelist"></div>

  <!-- Editor Pane -->
  <div id="editor-pane">
    <div id="toolbar">
      <div class="toolbar-group">
        <button class="toolbar-btn" data-cmd="bold" title="Bold (Ctrl+B)" aria-label="Bold"><b>B</b><span class="shortcut">⌘B</span></button>
        <button class="toolbar-btn" data-cmd="italic" title="Italic (Ctrl+I)" aria-label="Italic"><i>I</i><span class="shortcut">⌘I</span></button>
        <button class="toolbar-btn" data-cmd="underline" title="Underline (Ctrl+U)" aria-label="Underline"><u>U</u><span class="shortcut">⌘U</span></button>
      </div>
      <div class="toolbar-group">
        <button class="toolbar-btn" data-cmd="strikeThrough" title="Strikethrough" aria-label="Strikethrough"><s>S</s></button>
        <button class="toolbar-btn" data-cmd="formatBlock" data-value="code" title="Inline Code" aria-label="Inline Code" style="font-family:var(--font-mono);font-size:12px;">code</button>
      </div>
      <div class="toolbar-group">
        <button class="toolbar-btn" data-cmd="formatBlock" data-value="h1" title="Heading 1" aria-label="Heading 1">H1</button>
        <button class="toolbar-btn" data-cmd="formatBlock" data-value="h2" title="Heading 2" aria-label="Heading 2">H2</button>
        <button class="toolbar-btn" data-cmd="formatBlock" data-value="h3" title="Heading 3" aria-label="Heading 3">H3</button>
      </div>
      <div class="toolbar-group">
        <button class="toolbar-btn" data-cmd="insertUnorderedList" title="Bullet List" aria-label="Bullet List">•≡</button>
        <button class="toolbar-btn" data-cmd="insertOrderedList" title="Numbered List" aria-label="Numbered List">1.</button>
        <button class="toolbar-btn" data-cmd="insertCheckList" title="Checklist" aria-label="Checklist">☑</button>
      </div>
      <div class="toolbar-group">
        <button class="toolbar-btn" data-cmd="formatBlock" data-value="blockquote" title="Blockquote" aria-label="Blockquote">❝</button>
        <button class="toolbar-btn" data-cmd="insertHorizontalRule" title="Horizontal Rule" aria-label="Horizontal Rule">―</button>
      </div>
      <div class="tag-input-wrap">
        <input type="text" id="tag-input" placeholder="Add tags (comma)…" aria-label="Add tags">
      </div>
    </div>
    <div id="editor" contenteditable="true" spellcheck="false" aria-label="Note editor"></div>
    <div id="save-indicator">Saved</div>
  </div>
</div>

<!-- Context Menu -->
<div id="context-menu">
  <div class="menu-item" data-action="pin"><span class="menu-icon">📌</span>Pin/Unpin</div>
  <div class="menu-item" data-action="duplicate"><span class="menu-icon">📋</span>Duplicate</div>
  <div class="menu-item" data-action="move"><span class="menu-icon">📁</span>Move to…</div>
  <div class="menu-separator"></div>
  <div class="menu-item danger" data-action="delete"><span class="menu-icon">🗑</span>Delete</div>
</div>

<!-- Modal -->
<div id="modal-overlay">
  <div id="modal">
    <h3 id="modal-title">Rename Notebook</h3>
    <input type="text" id="modal-input" placeholder="Name…" aria-label="Input">
    <div class="modal-actions">
      <button class="btn-cancel" id="modal-cancel">Cancel</button>
      <button class="btn-confirm" id="modal-confirm">OK</button>
    </div>
  </div>
</div>

<script>
/* ============================================================
   CONSTANTS
   ============================================================ */
const CONFIG = {
  autosaveDelay: 600,
  snippetLength: 120,
  maxUndoStack: 50,
  allowedTags: ['p','br','strong','em','u','s','code','h1','h2','h3','ul','ol','li','blockquote','hr','input'],
  allowedAttributes: {
    'input': ['type','checked'],
    '*': ['style']
  },
  allowedStyles: [],
  storageKey: 'notes-app-state',
  themeKey: 'notes-app-theme',
};

/* ============================================================
   DATA MODEL — Single Source of Truth
   ============================================================ */
const Store = {
  notebooks: [],       // [{id, name, color, createdAt}]
  notes: [],           // [{id, notebookId, title, content, tags, pinned, createdAt, updatedAt}]
  trash: [],           // [{id, notebookId, title, content, tags, pinned, createdAt, updatedAt}]
  selectedNotebookId: 'all', // 'all', 'trash', or notebook id
  selectedNoteId: null,
  sortOrder: 'modified', // 'modified' | 'created' | 'alpha'
  sidebarCollapsed: false,
  sidebarWidth: 260,
  notelistWidth: 280,
  theme: 'light',
  searchQuery: '',
  activeTag: null,

  // --- CRUD ---
  nextId: 1,
  _generateId() { return 'id_' + (this.nextId++); },

  createNotebook(name) {
    const nb = { id: this._generateId(), name, color: this._randomColor(), createdAt: Date.now() };
    this.notebooks.push(nb);
    this._save();
    return nb;
  },

  renameNotebook(id, name) {
    const nb = this.notebooks.find(n => n.id === id);
    if (nb) { nb.name = name; this._save(); }
  },

  deleteNotebook(id) {
    // Move all notes to trash first
    const notes = this.notebooks.find(n => n.id === id);
    if (!notes) return;
    this.notes.filter(n => n.notebookId === id).forEach(n => this._moveToTrash(n.id));
    this.notebooks = this.notebooks.filter(n => n.id !== id);
    if (this.selectedNotebookId === id) this.selectedNotebookId = 'all';
    this._save();
  },

  createNote(notebookId) {
    const note = {
      id: this._generateId(),
      notebookId: notebookId || this.selectedNotebookId === 'all' ? (this.notebooks[0]?.id || 'default') : this.selectedNotebookId,
      title: 'Untitled Note',
      content: '',
      tags: [],
      pinned: false,
      createdAt: Date.now(),
      updatedAt: Date.now(),
    };
    this.notes.push(note);
    this.selectNote(note.id);
    this._save();
    return note;
  },

  updateNoteContent(id, content) {
    const note = this.notes.find(n => n.id === id);
    if (note) {
      note.content = content;
      note.updatedAt = Date.now();
      note.title = this._extractTitle(content) || 'Untitled Note';
      this._save();
    }
  },

  updateNoteTitle(id, title) {
    const note = this.notes.find(n => n.id === id);
    if (note) { note.title = title; note.updatedAt = Date.now(); this._save(); }
  },

  updateNoteTags(id, tags) {
    const note = this.notes.find(n => n.id === id);
    if (note) { note.tags = tags; this._save(); }
  },

  deleteNote(id) {
    this._moveToTrash(id);
    if (this.selectedNoteId === id) this.selectedNoteId = null;
    this._save();
  },

  _moveToTrash(id) {
    const idx = this.notes.findIndex(n => n.id === id);
    if (idx === -1) return;
    const note = this.notes.splice(idx, 1)[0];
    this.trash.push(note);
  },

  restoreNote(id) {
    const idx = this.trash.findIndex(n => n.id === id);
    if (idx === -1) return;
    const note = this.trash.splice(idx, 1)[0];
    this.notes.push(note);
    this._save();
  },

  permanentlyDelete(id) {
    this.trash = this.trash.filter(n => n.id !== id);
    this._save();
  },

  duplicateNote(id) {
    const note = this.notes.find(n => n.id === id);
    if (!note) return;
    const dup = { ...note, id: this._generateId(), title: note.title + ' (copy)', content: note.content, createdAt: Date.now(), updatedAt: Date.now(), pinned: false };
    this.notes.push(dup);
    this._save();
    return dup;
  },

  togglePin(id) {
    const note = this.notes.find(n => n.id === id);
    if (note) { note.pinned = !note.pinned; this._save(); }
  },

  moveNote(id, notebookId) {
    const note = this.notes.find(n => n.id === id);
    if (note) { note.notebookId = notebookId; this._save(); }
  },

  setSortOrder(order) {
    this.sortOrder = order;
    this._save();
  },

  setSelectedNotebook(id) {
    this.selectedNotebookId = id;
    this.selectedNoteId = null;
    this._save();
  },

  selectNote(id) {
    this.selectedNoteId = id;
    this._save();
  },

  toggleSidebar() {
    this.sidebarCollapsed = !this.sidebarCollapsed;
    this._save();
  },

  setTheme(theme) {
    this.theme = theme;
    document.documentElement.setAttribute('data-theme', theme);
    this._save();
  },

  setSearchQuery(q) {
    this.searchQuery = q;
  },

  setActiveTag(tag) {
    this.activeTag = tag;
  },

  // --- Helpers ---
  _extractTitle(html) {
    const text = htmlToPlainText(html).trim();
    if (!text) return '';
    // First line, truncated
    const firstLine = text.split('\n')[0].substring(0, 60);
    return firstLine;
  },

  _randomColor() {
    const colors = ['#4a6fa5','#5b8c5a','#c53030','#b7791f','#6b46c1','#2b6cb0','#2f855a','#c05621','#63b3ed','#fc8181'];
    return colors[Math.floor(Math.random() * colors.length)];
  },

  // --- Persistence ---
  _save() {
    try {
      const state = {
        notebooks: this.notebooks,
        notes: this.notes,
        trash: this.trash,
        selectedNotebookId: this.selectedNotebookId,
        selectedNoteId: this.selectedNoteId,
        sortOrder: this.sortOrder,
        sidebarCollapsed: this.sidebarCollapsed,
        sidebarWidth: this.sidebarWidth,
        notelistWidth: this.notelistWidth,
        theme: this.theme,
        nextId: this.nextId,
      };
      localStorage.setItem(CONFIG.storageKey, JSON.stringify(state));
    } catch(e) { /* quota exceeded, etc */ }
  },

  load() {
    try {
      const raw = localStorage.getItem(CONFIG.storageKey);
      if (!raw) return false;
      const state = JSON.parse(raw);
      this.notebooks = state.notebooks || [];
      this.notes = state.notes || [];
      this.trash = state.trash || [];
      this.selectedNotebookId = state.selectedNotebookId || 'all';
      this.selectedNoteId = state.selectedNoteId || null;
      this.sortOrder = state.sortOrder || 'modified';
      this.sidebarCollapsed = state.sidebarCollapsed || false;
      this.sidebarWidth = state.sidebarWidth || 260;
      this.notelistWidth = state.notelistWidth || 280;
      this.theme = state.theme || 'light';
      this.nextId = state.nextId || 1;
      return true;
    } catch(e) { return false; }
  },

  getNotebook(id) { return this.notebooks.find(n => n.id === id); },
  getNote(id) { return this.notes.find(n => n.id === id) || this.trash.find(n => n.id === id); },

  getFilteredNotes() {
    let list;
    if (this.selectedNotebookId === 'trash') {
      list = [...this.trash];
    } else if (this.selectedNotebookId === 'all') {
      list = [...this.notes];
    } else {
      list = this.notes.filter(n => n.notebookId === this.selectedNotebookId);
    }

    // Tag filter
    if (this.activeTag) {
      list = list.filter(n => n.tags.includes(this.activeTag));
    }

    // Search filter
    if (this.searchQuery) {
      const q = this.searchQuery.toLowerCase();
      list = list.filter(n => {
        const title = n.title.toLowerCase();
        const body = htmlToPlainText(n.content).toLowerCase();
        return title.includes(q) || body.includes(q);
      });
    }

    // Sort
    const order = this.sortOrder;
    list.sort((a, b) => {
      if (order === 'pinned') {
        if (a.pinned && !b.pinned) return -1;
        if (!a.pinned && b.pinned) return 1;
      }
      if (order === 'alpha') return a.title.localeCompare(b.title);
      if (order === 'created') return b.createdAt - a.createdAt;
      // modified (default)
      return b.updatedAt - a.updatedAt;
    });

    // Pinned always on top (unless sort is alpha/created)
    if (order !== 'alpha' && order !== 'created') {
      const pinned = list.filter(n => n.pinned);
      const unpinned = list.filter(n => !n.pinned);
      list = [...pinned, ...unpinned];
    }

    return list;
  },
};

/* ============================================================
   SANITIZER & TEXT EXTRACTION
   ============================================================ */
function sanitizeHTML(html) {
  if (!html) return '';
  const doc = new DOMParser().parseFromString(html, 'text/html');
  const allowed = new Set(CONFIG.allowedTags);
  const allowedAttrs = CONFIG.allowedAttributes;

  function cleanNode(node) {
    if (node.nodeType === Node.TEXT_NODE) return;
    if (node.nodeType === Node.ELEMENT_NODE) {
      const tag = node.tagName.toLowerCase();
      if (!allowed.has(tag)) {
        // Replace disallowed element with its children
        while (node.firstChild) {
          node.parentNode.insertBefore(node.firstChild, node);
        }
        node.parentNode.removeChild(node);
        return;
      }
      // Clean attributes
      const attrs = Array.from(node.attributes);
      const allowedForTag = allowedAttrs[tag] || [];
      const globalAllowed = allowedAttrs['*'] || [];
      attrs.forEach(attr => {
        const name = attr.name.toLowerCase();
        // Remove event handlers
        if (name.startsWith('on')) {
          node.removeAttribute(name);
          return;
        }
        // Remove style attribute (except for allowed styles)
        if (name === 'style' && CONFIG.allowedStyles.length === 0) {
          node.removeAttribute(name);
          return;
        }
        if (!allowedForTag.includes(name) && !globalAllowed.includes(name)) {
          node.removeAttribute(name);
        }
      });
      // For checkboxes, ensure type is checkbox
      if (tag === 'input') {
        if (node.getAttribute('type') !== 'checkbox') {
          node.setAttribute('type', 'checkbox');
        }
      }
      // Clean children
      Array.from(node.childNodes).forEach(cleanNode);
    }
  }

  Array.from(doc.body.childNodes).forEach(cleanNode);
  return doc.body.innerHTML;
}

function htmlToPlainText(html) {
  if (!html) return '';
  const doc = new DOMParser().parseFromString(html, 'text/html');
  // Replace checkbox inputs with their checked state
  doc.querySelectorAll('input[type="checkbox"]').forEach(cb => {
    const text = document.createTextNode(cb.checked ? '☑ ' : '☐ ');
    cb.parentNode.replaceChild(text, cb);
  });
  return doc.body.textContent || '';
}

/* ============================================================
   UI RENDERING
   ============================================================ */
const $sidebar = document.getElementById('sidebar');
const $notebookList = document.getElementById('notebook-list');
const $noteListItems = document.getElementById('note-list-items');
const $noteListTitle = document.getElementById('note-list-title');
const $editor = document.getElementById('editor');
const $editorPane = document.getElementById('editor-pane');
const $toolbar = document.getElementById('toolbar');
const $saveIndicator = document.getElementById('save-indicator');
const $globalSearch = document.getElementById('global-search');
const $tagInput = document.getElementById('tag-input');
const $btnTheme = document.getElementById('btn-theme');
const $btnCollapse = document.getElementById('btn-collapse');
const $btnNewNotebook = document.getElementById('btn-new-notebook');
const $btnNewNote = document.getElementById('btn-new-note');
const $btnSort = document.getElementById('btn-sort');
const $contextMenu = document.getElementById('context-menu');
const $modalOverlay = document.getElementById('modal-overlay');
const $modalInput = document.getElementById('modal-input');
const $modalTitle = document.getElementById('modal-title');
const $modalConfirm = document.getElementById('modal-confirm');
const $modalCancel = document.getElementById('modal-cancel');

let autoSaveTimer = null;
let saveIndicatorTimeout = null;

function renderSidebar() {
  // Update counts
  document.getElementById('count-all').textContent = Store.notes.length;
  document.getElementById('count-trash').textContent = Store.trash.length;

  // Render notebooks
  $notebookList.innerHTML = '';
  Store.notebooks.forEach(nb => {
    const item = document.createElement('div');
    item.className = 'notebook-item' + (Store.selectedNotebookId === nb.id ? ' active' : '');
    item.dataset.notebookId = nb.id;
    item.draggable = true;
    item.innerHTML = `
      <span class="nb-dot" style="background:${nb.color}"></span>
      <span class="nb-name">${escHtml(nb.name)}</span>
      <span class="nb-actions">
        <button data-action="rename" title="Rename" aria-label="Rename notebook">✎</button>
        <button data-action="delete" title="Delete" aria-label="Delete notebook">✕</button>
      </span>
    `;
    $notebookList.appendChild(item);
  });
}

function renderNoteList() {
  const notes = Store.getFilteredNotes();
  const view = Store.selectedNotebookId;

  // Update title
  if (view === 'all') $noteListTitle.textContent = 'All Notes';
  else if (view === 'trash') $noteListTitle.textContent = 'Trash';
  else {
    const nb = Store.getNotebook(view);
    $noteListTitle.textContent = nb ? nb.name : 'Notes';
  }

  // Update sort button text
  const sortLabels = { modified: 'Modified ↓', created: 'Created ↓', alpha: 'A → Z', pinned: 'Pinned' };
  $btnSort.textContent = sortLabels[Store.sortOrder] || 'Modified ↓';

  // Render notes
  $noteListItems.innerHTML = '';

  if (notes.length === 0) {
    const empty = document.createElement('div');
    empty.className = 'empty-state';
    if (view === 'trash') {
      empty.innerHTML = '<div class="empty-icon">🗑</div><div class="empty-title">Trash is empty</div><div class="empty-desc">Deleted notes will appear here.</div>';
    } else if (Store.searchQuery) {
      empty.innerHTML = '<div class="empty-icon">🔍</div><div class="empty-title">No results</div><div class="empty-desc">No notes match your search.</div>';
    } else if (Store.activeTag) {
      empty.innerHTML = '<div class="empty-icon">🏷</div><div class="empty-title">No notes with this tag</div>';
    } else if (Store.selectedNotebookId !== 'trash' && Store.notebooks.length === 0) {
      empty.innerHTML = '<div class="empty-icon">📝</div><div class="empty-title">No notebooks yet</div><div class="empty-desc">Create a notebook to get started.</div>';
    } else {
      empty.innerHTML = '<div class="empty-icon">📄</div><div class="empty-title">No notes yet</div><div class="empty-desc">Create a new note to get started.</div>';
    }
    $noteListItems.appendChild(empty);
    return;
  }

  notes.forEach(note => {
    const item = document.createElement('div');
    item.className = 'note-item' + (Store.selectedNoteId === note.id ? ' selected' : '') + (note.pinned ? ' pinned' : '');
    item.dataset.noteId = note.id;
    item.draggable = true;

    const snippet = htmlToPlainText(note.content).substring(0, CONFIG.snippetLength);
    const timeAgo = formatTimeAgo(note.updatedAt);
    const tagsHtml = note.tags.map(t => `<span class="note-tag">${escHtml(t)}</span>`).join('');

    item.innerHTML = `
      <div class="note-title">${escHtml(note.title)}</div>
      <div class="note-snippet">${escHtml(snippet || 'No content')}</div>
      <div class="note-meta">
        <span class="note-time">${timeAgo}</span>
        ${tagsHtml}
      </div>
      <div class="note-actions">
        <button data-action="pin" title="${note.pinned ? 'Unpin' : 'Pin'}">📌</button>
        <button data-action="duplicate" title="Duplicate">📋</button>
        <button data-action="move" title="Move">📁</button>
        <button data-action="delete" title="Delete">🗑</button>
      </div>
    `;

    $noteListItems.appendChild(item);
  });
}

function renderEditor() {
  const note = Store.getNote(Store.selectedNoteId);
  if (!note) {
    $editor.innerHTML = '';
    return;
  }
  // Only update if content differs (avoid cursor jump)
  if ($editor.innerHTML !== note.content) {
    $editor.innerHTML = note.content;
  }
  // Update tag input
  $tagInput.value = note.tags.join(', ');
  updateToolbarState();
}

function updateToolbarState() {
  // Update active states on toolbar buttons based on current selection
  const commands = ['bold','italic','underline','strikeThrough','insertUnorderedList','insertOrderedList'];
  commands.forEach(cmd => {
    const btn = $toolbar.querySelector(`[data-cmd="${cmd}"]`);
    if (btn) {
      try {
        btn.classList.toggle('active', document.queryCommandState(cmd));
      } catch(e) {}
    }
  });
  // formatBlock is special
  const blockBtns = $toolbar.querySelectorAll('[data-cmd="formatBlock"]');
  blockBtns.forEach(btn => {
    const val = btn.dataset.value;
    try {
      const current = document.queryCommandValue('formatBlock');
      btn.classList.toggle('active', current && current.toLowerCase() === val);
    } catch(e) {}
  });
}

function escHtml(str) {
  const div = document.createElement('div');
  div.textContent = str;
  return div.innerHTML;
}

function formatTimeAgo(ts) {
  const diff = Date.now() - ts;
  const mins = Math.floor(diff / 60000);
  if (mins < 1) return 'Just now';
  if (mins < 60) return `${mins}m ago`;
  const hours = Math.floor(mins / 60);
  if (hours < 24) return `${hours}h ago`;
  const days = Math.floor(hours / 24);
  if (days < 30) return `${days}d ago`;
  return new Date(ts).toLocaleDateString();
}

function showSaveIndicator(msg, type) {
  $saveIndicator.textContent = msg;
  $saveIndicator.className = type;
  clearTimeout(saveIndicatorTimeout);
  saveIndicatorTimeout = setTimeout(() => {
    $saveIndicator.textContent = 'Saved';
    $saveIndicator.className = 'saved';
  }, 2000);
}

function triggerAutoSave() {
  clearTimeout(autoSaveTimer);
  autoSaveTimer = setTimeout(() => {
    const note = Store.getNote(Store.selectedNoteId);
    if (note && Store.selectedNoteId) {
      const content = $editor.innerHTML;
      Store.updateNoteContent(Store.selectedNoteId, content);
      showSaveIndicator('Saved', 'saved');
    }
  }, CONFIG.autosaveDelay);
  showSaveIndicator('Saving…', 'unsaved');
}

/* ============================================================
   EDITOR CONTROLLER
   ============================================================ */
let undoStack = [];
let redoStack = [];
let isUndoRedo = false;
let isHandlingMarkdown = false;

function pushUndoSnapshot() {
  if (isUndoRedo || isHandlingMarkdown) return;
  const content = $editor.innerHTML;
  undoStack.push(content);
  if (undoStack.length > CONFIG.maxUndoStack) undoStack.shift();
  redoStack = [];
}

function execCommand(cmd, value) {
  pushUndoSnapshot();
  document.execCommand(cmd, false, value || null);
  $editor.focus();
  triggerAutoSave();
  updateToolbarState();
}

function editorKeydown(e) {
  const ctrl = e.ctrlKey || e.metaKey;

  // Undo/Redo
  if (ctrl && e.key === 'z' && !e.shiftKey) {
    e.preventDefault();
    doUndo();
    return;
  }
  if (ctrl && e.key === 'z' && e.shiftKey) {
    e.preventDefault();
    doRedo();
    return;
  }
  if (ctrl && e.key === 'y') {
    e.preventDefault();
    doRedo();
    return;
  }

  // Bold/Italic/Underline shortcuts
  if (ctrl && e.key === 'b') { e.preventDefault(); execCommand('bold'); return; }
  if (ctrl && e.key === 'i') { e.preventDefault(); execCommand('italic'); return; }
  if (ctrl && e.key === 'u') { e.preventDefault(); execCommand('underline'); return; }

  // Markdown shortcuts: detect on space key at line start
  if (e.key === ' ' && !ctrl && !e.altKey && !e.metaKey) {
    handleMarkdownShortcut(e);
    return;
  }

  // Enter key: handle list continuation and empty item exit
  if (e.key === 'Enter' && !ctrl && !e.altKey) {
    handleEnterKey(e);
    return;
  }

  // Escape: exit empty list item
  if (e.key === 'Escape') {
    handleEscapeKey(e);
    return;
  }
}

function handleMarkdownShortcut(e) {
  const sel = window.getSelection();
  if (!sel.rangeCount) return;
  const range = sel.getRangeAt(0);

  // Find the start of the current line
  const preCaretRange = range.cloneRange();
  preCaretRange.selectNodeContents($editor);
  preCaretRange.setEnd(range.startContainer, range.startOffset);
  const textBefore = preCaretRange.toString();
  const lineStart = textBefore.lastIndexOf('\n') + 1;
  const currentLine = textBefore.substring(lineStart);

  // Check patterns (must be at line start, marker only - space hasn't been inserted yet)
  const patterns = [
    { regex: /^#$/, tag: 'h1' },
    { regex: /^##$/, tag: 'h2' },
    { regex: /^###$/, tag: 'h3' },
    { regex: /^-$/, tag: 'ul' },
    { regex: /^>$/, tag: 'blockquote' },
    { regex: /^\[\]$/, tag: 'checklist' },
  ];

  for (const p of patterns) {
    if (p.regex.test(currentLine)) {
      e.preventDefault();
      isHandlingMarkdown = true;
      pushUndoSnapshot();
      isHandlingMarkdown = false;

      // Delete the marker text (e.g., "#") from the line
      const markerLen = currentLine.length;
      const deleteRange = range.cloneRange();
      deleteRange.setStart(range.startContainer, Math.max(0, range.startOffset - markerLen));
      deleteRange.deleteContents();

      // Apply the appropriate formatting
      switch (p.tag) {
        case 'h1':
        case 'h2':
        case 'h3':
          document.execCommand('formatBlock', false, p.tag);
          break;
        case 'ul':
          document.execCommand('insertUnorderedList');
          break;
        case 'blockquote':
          document.execCommand('formatBlock', false, 'blockquote');
          break;
        case 'checklist':
          document.execCommand('insertUnorderedList');
          // Convert the new list item to have a checkbox
          setTimeout(() => {
            const lis = $editor.querySelectorAll('li');
            const lastLi = lis[lis.length - 1];
            if (lastLi && !lastLi.querySelector('input[type="checkbox"]')) {
              const cb = document.createElement('input');
              cb.type = 'checkbox';
              lastLi.insertBefore(cb, lastLi.firstChild);
            }
          }, 0);
          break;
      }

      $editor.focus();
      updateToolbarState();
      triggerAutoSave();
      return;
    }
  }
}

function handleEnterKey(e) {
  const sel = window.getSelection();
  if (!sel.rangeCount) return;
  const range = sel.getRangeAt(0);

  const node = range.startContainer;
  const li = getParentElement(node, 'LI');

  if (li) {
    // Check if the list item is effectively empty (only a checkbox or nothing)
    const text = li.textContent.trim();
    const hasCheckbox = li.querySelector('input[type="checkbox"]');

    if (text === '' || (hasCheckbox && text.length <= 1)) {
      // Empty list item — exit the list by converting to a paragraph
      e.preventDefault();
      pushUndoSnapshot();
      const block = document.createElement('p');
      li.parentNode.replaceChild(block, li);
      const newRange = document.createRange();
      newRange.selectNodeContents(block);
      newRange.collapse(false);
      sel.removeAllRanges();
      sel.addRange(newRange);
      triggerAutoSave();
      return;
    }
  }
  // For non-empty list items, let the browser handle Enter naturally
  // (it will create a new <li> in the list)
  pushUndoSnapshot();
}

function handleEscapeKey(e) {
  const sel = window.getSelection();
  if (!sel.rangeCount) return;
  const node = sel.anchorNode;
  const li = getParentElement(node, 'LI');

  if (li && li.textContent.trim() === '') {
    e.preventDefault();
    pushUndoSnapshot();
    const block = document.createElement('p');
    li.parentNode.replaceChild(block, li);
    const newRange = document.createRange();
    newRange.selectNodeContents(block);
    newRange.collapse(false);
    sel.removeAllRanges();
    sel.addRange(newRange);
    triggerAutoSave();
  }
}

function getParentElement(node, tagName) {
  let el = node;
  while (el && el !== $editor) {
    if (el.nodeType === Node.ELEMENT_NODE && el.tagName.toLowerCase() === tagName.toLowerCase()) {
      return el;
    }
    el = el.parentNode;
  }
  return null;
}

function doUndo() {
  if (undoStack.length === 0) return;
  redoStack.push($editor.innerHTML);
  isUndoRedo = true;
  $editor.innerHTML = undoStack.pop();
  isUndoRedo = false;
  triggerAutoSave();
  updateToolbarState();
}

function doRedo() {
  if (redoStack.length === 0) return;
  undoStack.push($editor.innerHTML);
  isUndoRedo = true;
  $editor.innerHTML = redoStack.pop();
  isUndoRedo = false;
  triggerAutoSave();
  updateToolbarState();
}

/* ============================================================
   CHECKLIST CHECKBOX PERSISTENCE & SEARCH HIGHLIGHTING
   ============================================================ */
$editor.addEventListener('change', e => {
  if (e.target.matches('input[type="checkbox"]')) {
    pushUndoSnapshot();
    triggerAutoSave();
  }
});

// Also handle click on checkboxes to ensure model is updated
$editor.addEventListener('click', e => {
  const cb = e.target.closest('input[type="checkbox"]');
  if (cb) {
    e.stopPropagation();
    pushUndoSnapshot();
    triggerAutoSave();
  }
});

/* ============================================================
   PASTE SANITIZATION
   ============================================================ */
function handlePaste(e) {
  e.preventDefault();
  pushUndoSnapshot();

  let html = '';
  let text = '';

  const clipboardData = e.clipboardData || window.clipboardData;
  if (clipboardData) {
    html = clipboardData.getData('text/html') || '';
    text = clipboardData.getData('text/plain') || '';
  }

  if (html) {
    // Sanitize the HTML
    const clean = sanitizeHTML(html);
    document.execCommand('insertHTML', false, clean);
  } else if (text) {
    // Insert plain text, escaping HTML entities
    const escaped = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br>');
    document.execCommand('insertHTML', false, escaped);
  }

  triggerAutoSave();
  updateToolbarState();
}

/* ============================================================
   SEARCH
   ============================================================ */
function performSearch(query) {
  Store.setSearchQuery(query);
  renderNoteList();
  // Highlight matches in editor if a note is selected
  highlightSearchInEditor(query);
}

function highlightSearchInEditor(query) {
  // Remove existing highlights
  $editor.querySelectorAll('mark').forEach(m => {
    const parent = m.parentNode;
    parent.replaceChild(document.createTextNode(m.textContent), m);
    parent.normalize();
  });

  if (!query || !Store.selectedNoteId) return;

  const textNodes = getTextNodes($editor);
  const lowerQuery = query.toLowerCase();

  textNodes.forEach(node => {
    const text = node.textContent;
    const lower = text.toLowerCase();
    let idx = lower.indexOf(lowerQuery);
    while (idx !== -1) {
      const mark = document.createElement('mark');
      mark.textContent = text.substring(idx, idx + query.length);
      const before = text.substring(0, idx);
      const after = text.substring(idx + query.length);
      const parent = node.parentNode;
      if (before) parent.insertBefore(document.createTextNode(before), node);
      parent.insertBefore(mark, node);
      if (after) parent.insertBefore(document.createTextNode(after), node);
      parent.removeChild(node);
      // Move to next text node
      node = after ? parent.childNodes[parent.childNodes.length - 1] : null;
      if (node && node.nodeType === Node.TEXT_NODE) {
        const newLower = node.textContent.toLowerCase();
        idx = newLower.indexOf(lowerQuery);
      } else {
        idx = -1;
      }
    }
  });
}

function getTextNodes(node) {
  const result = [];
  if (node.nodeType === Node.TEXT_NODE && node.textContent.trim()) {
    result.push(node);
  } else if (node.nodeType === Node.ELEMENT_NODE) {
    // Skip mark elements to avoid double-highlighting
    if (node.tagName.toLowerCase() === 'mark') return result;
    Array.from(node.childNodes).forEach(child => {
      result.push(...getTextNodes(child));
    });
  }
  return result;
}

/* ============================================================
   DRAG & DROP — Move notes between notebooks
   ============================================================ */
let draggedNoteId = null;

function setupDragAndDrop() {
  // Note list items are draggable
  $noteListItems.addEventListener('dragstart', e => {
    const noteItem = e.target.closest('.note-item');
    if (!noteItem) return;
    draggedNoteId = noteItem.dataset.noteId;
    e.dataTransfer.effectAllowed = 'move';
    e.dataTransfer.setData('text/plain', draggedNoteId);
    noteItem.style.opacity = '0.5';
  });

  $noteListItems.addEventListener('dragend', e => {
    const noteItem = e.target.closest('.note-item');
    if (noteItem) noteItem.style.opacity = '';
    draggedNoteId = null;
  });

  // Sidebar notebooks are drop targets
  $notebookList.addEventListener('dragover', e => {
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
    const notebookItem = e.target.closest('.notebook-item');
    if (notebookItem) {
      notebookItem.style.background = 'var(--accent-light)';
    }
  });

  $notebookList.addEventListener('dragleave', e => {
    const notebookItem = e.target.closest('.notebook-item');
    if (notebookItem) notebookItem.style.background = '';
  });

  $notebookList.addEventListener('drop', e => {
    e.preventDefault();
    const notebookItem = e.target.closest('.notebook-item');
    if (notebookItem) notebookItem.style.background = '';
    const notebookId = notebookItem?.dataset.notebookId;
    if (draggedNoteId && notebookId && notebookId !== 'all' && notebookId !== 'trash') {
      Store.moveNote(draggedNoteId, notebookId);
      renderNoteList();
      renderSidebar();
    }
    draggedNoteId = null;
  });
}

/* ============================================================
   CONTEXT MENU
   ============================================================ */
function showContextMenu(x, y) {
  const note = Store.getNote(Store.selectedNoteId);
  if (!note) return;

  const menu = $contextMenu;
  // Update pin label
  const pinItem = menu.querySelector('[data-action="pin"]');
  pinItem.innerHTML = `<span class="menu-icon">${note.pinned ? '📌' : '📍'}</span>${note.pinned ? 'Unpin' : 'Pin'}`;

  menu.style.left = x + 'px';
  menu.style.top = y + 'px';
  menu.style.display = 'block';
}

function hideContextMenu() {
  $contextMenu.style.display = 'none';
}

/* ============================================================
   MODAL
   ============================================================ */
let modalCallback = null;

function showModal(title, placeholder, value, callback) {
  $modalTitle.textContent = title;
  $modalInput.value = value || '';
  $modalInput.placeholder = placeholder || '';
  $modalOverlay.classList.add('visible');
  $modalInput.focus();
  modalCallback = callback;
}

function hideModal() {
  $modalOverlay.classList.remove('visible');
  modalCallback = null;
}

/* ============================================================
   EVENT HANDLERS
   ============================================================ */

// --- Sidebar notebook list ---
$notebookList.addEventListener('click', e => {
  const renameBtn = e.target.closest('[data-action="rename"]');
  const deleteBtn = e.target.closest('[data-action="delete"]');
  const notebookItem = e.target.closest('.notebook-item');

  if (renameBtn && notebookItem) {
    const nb = Store.getNotebook(notebookItem.dataset.notebookId);
    if (nb) {
      showModal('Rename Notebook', 'Notebook name…', nb.name, (name) => {
        if (name.trim()) Store.renameNotebook(nb.id, name.trim());
        renderSidebar();
        renderNoteList();
      });
    }
    return;
  }

  if (deleteBtn && notebookItem) {
    const nb = Store.getNotebook(notebookItem.dataset.notebookId);
    if (nb && confirm(`Delete notebook "${nb.name}"? Notes will be moved to trash.`)) {
      Store.deleteNotebook(nb.id);
      renderSidebar();
      renderNoteList();
    }
    return;
  }

  if (notebookItem) {
    Store.setSelectedNotebook(notebookItem.dataset.notebookId);
    renderSidebar();
    renderNoteList();
    renderEditor();
  }
});

// --- Note list ---
$noteListItems.addEventListener('click', e => {
  // Check for action buttons
  const actionBtn = e.target.closest('[data-action]');
  if (actionBtn) {
    e.stopPropagation();
    const noteItem = e.target.closest('.note-item');
    const noteId = noteItem?.dataset.noteId;
    if (!noteId) return;

    switch(actionBtn.dataset.action) {
      case 'pin':
        Store.togglePin(noteId);
        renderNoteList();
        break;
      case 'duplicate':
        const dup = Store.duplicateNote(noteId);
        if (dup) { Store.selectNote(dup.id); renderNoteList(); renderEditor(); }
        break;
      case 'move':
        showMoveMenu(noteId, e.target.closest('.note-item'));
        break;
      case 'delete':
        Store.deleteNote(noteId);
        renderNoteList();
        renderEditor();
        break;
    }
    return;
  }

  // Select note
  const noteItem = e.target.closest('.note-item');
  if (noteItem) {
    Store.selectNote(noteItem.dataset.noteId);
    renderNoteList();
    renderEditor();
    $editor.focus();
  }
});

// Note list drag and drop (for reordering within list)
$noteListItems.addEventListener('dragstart', e => {
  const noteItem = e.target.closest('.note-item');
  if (!noteItem) return;
  draggedNoteId = noteItem.dataset.noteId;
  e.dataTransfer.effectAllowed = 'move';
  noteItem.style.opacity = '0.5';
});

$noteListItems.addEventListener('dragend', e => {
  const noteItem = e.target.closest('.note-item');
  if (noteItem) noteItem.style.opacity = '';
  draggedNoteId = null;
});

function showMoveMenu(noteId, anchorEl) {
  // Show a simple move-to notebook menu
  const menu = $contextMenu;
  menu.innerHTML = '';

  // Add notebook options
  Store.notebooks.forEach(nb => {
    const item = document.createElement('div');
    item.className = 'menu-item';
    item.innerHTML = `<span class="menu-icon" style="color:${nb.color}">●</span>${escHtml(nb.name)}`;
    item.addEventListener('click', () => {
      Store.moveNote(noteId, nb.id);
      renderNoteList();
      renderSidebar();
      hideContextMenu();
    });
    menu.appendChild(item);
  });

  // Separator
  const sep = document.createElement('div');
  sep.className = 'menu-separator';
  menu.appendChild(sep);

  // Trash option
  const trashItem = document.createElement('div');
  trashItem.className = 'menu-item danger';
  trashItem.innerHTML = '<span class="menu-icon">🗑</span>Trash';
  trashItem.addEventListener('click', () => {
    Store.deleteNote(noteId);
    renderNoteList();
    renderEditor();
    hideContextMenu();
  });
  menu.appendChild(trashItem);

  // Position and show
  const rect = anchorEl.getBoundingClientRect();
  menu.style.left = rect.left + 'px';
  menu.style.top = rect.bottom + 'px';
  menu.style.display = 'block';
}

// --- Toolbar ---
$toolbar.addEventListener('click', e => {
  const btn = e.target.closest('.toolbar-btn');
  if (!btn) return;
  const cmd = btn.dataset.cmd;
  const value = btn.dataset.value;

  if (cmd === 'insertCheckList') {
    // Custom checklist handling
    pushUndoSnapshot();
    document.execCommand('insertUnorderedList');
    // Convert the new list item to a checkbox
    setTimeout(() => {
      const lis = $editor.querySelectorAll('li');
      const lastLi = lis[lis.length - 1];
      if (lastLi && !lastLi.querySelector('input[type="checkbox"]')) {
        const cb = document.createElement('input');
        cb.type = 'checkbox';
        lastLi.insertBefore(cb, lastLi.firstChild);
      }
    }, 0);
    triggerAutoSave();
    updateToolbarState();
    return;
  }

  if (cmd === 'insertHorizontalRule') {
    pushUndoSnapshot();
    document.execCommand('insertHorizontalRule');
    triggerAutoSave();
    return;
  }

  if (cmd === 'formatBlock' && value) {
    execCommand(cmd, value);
    return;
  }

  if (cmd) {
    execCommand(cmd);
  }
});

// --- Editor events ---
$editor.addEventListener('input', () => {
  triggerAutoSave();
  updateToolbarState();
});

$editor.addEventListener('keydown', editorKeydown);
$editor.addEventListener('paste', handlePaste);
$editor.addEventListener('mouseup', updateToolbarState);
$editor.addEventListener('keyup', updateToolbarState);
$editor.addEventListener('focus', updateToolbarState);

// --- Global search ---
$globalSearch.addEventListener('input', e => {
  performSearch(e.target.value.trim());
});

// --- Tag input ---
$tagInput.addEventListener('keydown', e => {
  if (e.key === 'Enter' || e.key === ',') {
    e.preventDefault();
    const raw = $tagInput.value.trim().replace(/,/g, '');
    if (!raw || !Store.selectedNoteId) return;
    const note = Store.getNote(Store.selectedNoteId);
    if (!note) return;
    const tags = [...new Set([...note.tags, raw.toLowerCase()])];
    Store.updateNoteTags(Store.selectedNoteId, tags);
    $tagInput.value = '';
    renderNoteList();
    triggerAutoSave();
  }
  if (e.key === 'Backspace' && !$tagInput.value && Store.selectedNoteId) {
    const note = Store.getNote(Store.selectedNoteId);
    if (note && note.tags.length > 0) {
      const tags = note.tags.slice(0, -1);
      Store.updateNoteTags(Store.selectedNoteId, tags);
      renderNoteList();
      triggerAutoSave();
    }
  }
});

// --- Sidebar collapse ---
$btnCollapse.addEventListener('click', () => {
  Store.toggleSidebar();
  $sidebar.classList.toggle('collapsed', Store.sidebarCollapsed);
  // Trigger resize
  setTimeout(() => window.dispatchEvent(new Event('resize')), 300);
});

// --- Theme toggle ---
$btnTheme.addEventListener('click', () => {
  const newTheme = Store.theme === 'light' ? 'dark' : 'light';
  Store.setTheme(newTheme);
  $btnTheme.textContent = newTheme === 'light' ? '🌙' : '☀';
});

// --- New notebook ---
$btnNewNotebook.addEventListener('click', () => {
  showModal('New Notebook', 'Notebook name…', '', (name) => {
    if (name.trim()) {
      Store.createNotebook(name.trim());
      renderSidebar();
      renderNoteList();
    }
  });
});

// --- New note ---
$btnNewNote.addEventListener('click', () => {
  const note = Store.createNote();
  renderNoteList();
  renderEditor();
  $editor.focus();
  // Place cursor at end
  placeCursorAtEnd($editor);
});

// --- Sort toggle ---
$btnSort.addEventListener('click', () => {
  const orders = ['modified', 'created', 'alpha', 'pinned'];
  const idx = orders.indexOf(Store.sortOrder);
  const next = orders[(idx + 1) % orders.length];
  Store.setSortOrder(next);
  renderNoteList();
});

// --- Context menu ---
$noteListItems.addEventListener('contextmenu', e => {
  e.preventDefault();
  const noteItem = e.target.closest('.note-item');
  if (!noteItem) return;
  Store.selectNote(noteItem.dataset.noteId);
  renderNoteList();
  renderEditor();
  showContextMenu(e.clientX, e.clientY);
});

$contextMenu.addEventListener('click', e => {
  const item = e.target.closest('.menu-item');
  if (!item) return;
  const action = item.dataset.action;
  const noteId = Store.selectedNoteId;

  switch(action) {
    case 'pin':
      if (noteId) Store.togglePin(noteId);
      break;
    case 'duplicate':
      if (noteId) {
        const dup = Store.duplicateNote(noteId);
        if (dup) { Store.selectNote(dup.id); }
      }
      break;
    case 'move':
      if (noteId) {
        const noteItem = document.querySelector('.note-item.selected');
        showMoveMenu(noteId, noteItem);
      }
      break;
    case 'delete':
      if (noteId) {
        Store.deleteNote(noteId);
        renderNoteList();
        renderEditor();
      }
      break;
  }
  hideContextMenu();
  renderNoteList();
  renderSidebar();
});

document.addEventListener('click', () => hideContextMenu());

// --- Modal ---
$modalConfirm.addEventListener('click', () => {
  if (modalCallback) modalCallback($modalInput.value);
  hideModal();
});

$modalCancel.addEventListener('click', hideModal);

$modalOverlay.addEventListener('click', e => {
  if (e.target === $modalOverlay) hideModal();
});

$modalInput.addEventListener('keydown', e => {
  if (e.key === 'Enter') {
    e.preventDefault();
    if (modalCallback) modalCallback($modalInput.value);
    hideModal();
  }
  if (e.key === 'Escape') hideModal();
});

// --- Resize handles ---
function setupResize(handle, pane, isSidebar) {
  let startX, startWidth;

  handle.addEventListener('mousedown', e => {
    e.preventDefault();
    startX = e.clientX;
    startWidth = isSidebar ? pane.offsetWidth : pane.offsetWidth;
    handle.classList.add('active');

    const onMouseMove = e => {
      const diff = e.clientX - startX;
      const newWidth = isSidebar ? startWidth + diff : startWidth - diff;
      const minW = 180;
      const maxW = 500;
      const clamped = Math.max(minW, Math.min(maxW, newWidth));
      pane.style.width = clamped + 'px';
      if (isSidebar) Store.sidebarWidth = clamped;
      else Store.notelistWidth = clamped;
    };

    const onMouseUp = () => {
      handle.classList.remove('active');
      document.removeEventListener('mousemove', onMouseMove);
      document.removeEventListener('mouseup', onMouseUp);
      Store._save();
    };

    document.addEventListener('mousemove', onMouseMove);
    document.addEventListener('mouseup', onMouseUp);
  });
}

setupResize(document.getElementById('resize-sidebar'), $sidebar, true);
setupResize(document.getElementById('resize-notelist'), document.getElementById('note-list'), false);

// --- Notebook item drag for reordering (not implemented in detail, skip) ---

/* ============================================================
   NAV ITEM CLICK (All Notes / Trash)
   ============================================================ */
document.querySelector('.sidebar-nav').addEventListener('click', e => {
  const navItem = e.target.closest('.nav-item');
  if (!navItem) return;
  const view = navItem.dataset.view;
  Store.setSelectedNotebook(view);
  document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
  navItem.classList.add('active');
  renderNoteList();
  renderEditor();
});

/* ============================================================
   INITIALIZATION
   ============================================================ */
function init() {
  // Load theme
  const savedTheme = Store.theme || 'light';
  Store.setTheme(savedTheme);
  $btnTheme.textContent = savedTheme === 'light' ? '🌙' : '☀';

  // Load persisted state
  const loaded = Store.load();

  // Seed default data if first load
  if (!loaded || (Store.notebooks.length === 0 && Store.notes.length === 0)) {
    const inbox = Store.createNotebook('Inbox');
    const personal = Store.createNotebook('Personal');
    const work = Store.createNotebook('Work');

    Store.createNote(inbox.id);
    const welcome = Store.notes[Store.notes.length - 1];
    welcome.title = 'Welcome to Notes';
    welcome.content = '<h1>Welcome to Notes</h1><p>This is your <strong>new notes app</strong>. Start writing to see the magic.</p><ul><li><input type="checkbox"> Try checking this box</li><li>Edit this note with the toolbar above</li><li>Press <code>Ctrl+B</code> for bold</li></ul><blockquote>Every great note starts with a single word.</blockquote>';
    welcome.pinned = true;
    welcome.updatedAt = Date.now();

    Store.createNote(work.id);
    const meeting = Store.notes[Store.notes.length - 1];
    meeting.title = 'Meeting Notes';
    meeting.content = '<h2>Sprint Planning</h2><p>Discussed the new features for next sprint:</p><ol><li>Three-pane layout</li><li>Rich text editor</li><li>Search &amp; tags</li></ol><p><em>Meeting ended at 3pm</em></p>';
    meeting.updatedAt = Date.now();

    Store.createNote(personal.id);
    const ideas = Store.notes[Store.notes.length - 1];
    ideas.title = 'Project Ideas';
    ideas.content = '<h3>Ideas to explore</h3><ul><li><input type="checkbox"> Build a canvas drawing app</li><li><input type="checkbox"> Create a markdown parser</li><li>Study WebGL shaders</li></ul><p>Tags: <span style="color:var(--accent)">#creative</span></p>';
    ideas.tags = ['creative'];
    ideas.updatedAt = Date.now();

    Store._save();
  }

  // Restore layout
  $sidebar.classList.toggle('collapsed', Store.sidebarCollapsed);
  $sidebar.style.width = Store.sidebarWidth + 'px';
  document.getElementById('note-list').style.width = Store.notelistWidth + 'px';

  // Render all
  renderSidebar();
  renderNoteList();
  renderEditor();
  setupDragAndDrop();

  // Set active nav item
  document.querySelectorAll('.nav-item').forEach(n => {
    n.classList.toggle('active', n.dataset.view === Store.selectedNotebookId);
  });

  // Restore sort button text
  const sortLabels = { modified: 'Modified ↓', created: 'Created ↓', alpha: 'A → Z', pinned: 'Pinned' };
  $btnSort.textContent = sortLabels[Store.sortOrder] || 'Modified ↓';

  // Restore tag input
  if (Store.selectedNoteId) {
    const note = Store.getNote(Store.selectedNoteId);
    if (note) $tagInput.value = note.tags.join(', ');
  }

  // Show save indicator
  showSaveIndicator('Ready', 'saved');
}

function placeCursorAtEnd(el) {
  const range = document.createRange();
  const sel = window.getSelection();
  range.selectNodeContents(el);
  range.collapse(false);
  sel.removeAllRanges();
  sel.addRange(range);
}

// Initialize
init();

// Auto-save on beforeunload
window.addEventListener('beforeunload', () => {
  const note = Store.getNote(Store.selectedNoteId);
  if (note && Store.selectedNoteId) {
    Store.updateNoteContent(Store.selectedNoteId, $editor.innerHTML);
  }
});
</script>
</body>
</html>
<!-- agent-meta {"model":"inclusionai/ling-3.0-flash","provider":"openrouter","persona":"full","sessionId":"3c265fbf-d482-40da-be55-a120dbe587fc","tokensIn":2094337,"tokensOut":63262,"tokensTotal":2157599,"cost":0.0165356898,"turns":34,"toolCalls":33,"failedToolCalls":0,"timestamp":"2026-08-24T23:07:11.780Z"} -->