← Notes results

Notes

Qwen 3.6 35b a3b · composer

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 39,590 bytes · SHA-256 23a1c911e95f
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Noteflow — Beautiful Notes</title>
<style>
/* ── css: layout ── */
:root {
  --bg-primary: #ffffff;
  --bg-secondary: #f8f9fa;
  --bg-tertiary: #f0f1f3;
  --bg-sidebar: #1e1e2e;
  --bg-sidebar-hover: #2a2a3e;
  --bg-sidebar-active: #3b82f6;
  --bg-toolbar: #ffffff;
  --bg-editor: #ffffff;
  --bg-empty: #f8f9fa;
  --bg-context: #ffffff;
  --bg-context-hover: #f0f1f3;
  --bg-notebook: #2a2a3e;
  --bg-notebook-hover: #353550;
  --text-primary: #1a1a2e;
  --text-secondary: #6b7280;
  --text-tertiary: #9ca3af;
  --text-sidebar: #c4c4d4;
  --text-sidebar-active: #ffffff;
  --text-toolbar: #374151;
  --text-empty: #9ca3af;
  --border-color: #e5e7eb;
  --border-sidebar: #2a2a3e;
  --border-toolbar: #e5e7eb;
  --accent: #3b82f6;
  --accent-hover: #2563eb;
  --accent-light: #dbeafe;
  --danger: #ef4444;
  --danger-hover: #dc2626;
  --shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
  --shadow-md: 0 4px 12px rgba(0,0,0,0.1);
  --shadow-lg: 0 8px 30px rgba(0,0,0,0.12);
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 14px;
  --transition: 0.2s ease;
}

[data-theme="dark"] {
  --bg-primary: #0f0f1a;
  --bg-secondary: #1a1a2e;
  --bg-tertiary: #252540;
  --bg-sidebar: #0a0a14;
  --bg-sidebar-hover: #1a1a2e;
  --bg-sidebar-active: #3b82f6;
  --bg-toolbar: #1a1a2e;
  --bg-editor: #0f0f1a;
  --bg-empty: #1a1a2e;
  --bg-context: #1e1e2e;
  --bg-context-hover: #2a2a3e;
  --bg-notebook: #1a1a2e;
  --bg-notebook-hover: #252540;
  --text-primary: #e5e7eb;
  --text-secondary: #9ca3af;
  --text-tertiary: #6b7280;
  --text-sidebar: #9ca3af;
  --text-sidebar-active: #ffffff;
  --text-toolbar: #d1d5db;
  --text-empty: #6b7280;
  --border-color: #2a2a3e;
  --border-sidebar: #1a1a2e;
  --border-toolbar: #2a2a3e;
  --accent: #3b82f6;
  --accent-hover: #60a5fa;
  --accent-light: #1e3a5f;
  --danger: #ef4444;
  --danger-hover: #f87171;
  --shadow-sm: 0 1px 2px rgba(0,0,0,0.2);
  --shadow-md: 0 4px 12px rgba(0,0,0,0.3);
  --shadow-lg: 0 8px 30px rgba(0,0,0,0.4);
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body {
  height: 100%;
  overflow: hidden;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

#app {
  display: flex;
  height: 100vh;
  width: 100vw;
  background: var(--bg-primary);
  color: var(--text-primary);
}

/* ── Sidebar ── */
#sidebar {
  width: 260px;
  min-width: 260px;
  background: var(--bg-sidebar);
  display: flex;
  flex-direction: column;
  border-right: 1px solid var(--border-sidebar);
  overflow: hidden;
  user-select: none;
}

#sidebar-header {
  padding: 16px 16px 12px;
}

#logo {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--text-sidebar-active);
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -0.3px;
}

#logo svg {
  opacity: 0.9;
}

#sidebar-search {
  padding: 0 12px 12px;
}

#sidebar-search svg {
  position: absolute;
  left: 24px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-tertiary);
  pointer-events: none;
}

#search-input {
  width: 100%;
  padding: 8px 12px 8px 34px;
  background: var(--bg-notebook);
  border: none;
  border-radius: var(--radius-sm);
  color: var(--text-sidebar);
  font-size: 13px;
  outline: none;
  transition: background var(--transition);
}

#search-input::placeholder {
  color: var(--text-tertiary);
}

#search-input:focus {
  background: var(--bg-notebook-hover);
}

#notebooks-section, #notes-section {
  flex-shrink: 0;
}

#notebooks-section {
  padding: 0 12px 8px;
}

#notes-section {
  padding: 0 12px 8px;
  flex: 1;
  overflow-y: auto;
  min-height: 0;
}

.section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 8px;
  margin-bottom: 2px;
}

.section-header span {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--text-tertiary);
}

.icon-btn {
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--text-tertiary);
  cursor: pointer;
  transition: all var(--transition);
}

.icon-btn:hover {
  background: var(--bg-notebook-hover);
  color: var(--text-sidebar-active);
}

#notebook-list, #note-list {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.notebook-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: all var(--transition);
  color: var(--text-sidebar);
  font-size: 13px;
}

.notebook-item:hover {
  background: var(--bg-notebook-hover);
}

.notebook-item.active {
  background: var(--bg-sidebar-active);
  color: var(--text-sidebar-active);
}

.notebook-item.active svg {
  opacity: 1;
}

.notebook-count {
  margin-left: auto;
  font-size: 11px;
  opacity: 0.5;
}

.note-item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: all var(--transition);
  border-left: 3px solid transparent;
}

.note-item:hover {
  background: var(--bg-notebook-hover);
}

.note-item.active {
  background: var(--bg-sidebar-active);
}

.note-item.active .note-item-title,
.note-item.active .note-item-preview,
.note-item.active .note-item-date {
  color: var(--text-sidebar-active);
}

.note-item.active .note-item-date {
  color: rgba(255,255,255,0.6);
}

.note-item-icon {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  margin-top: 2px;
  background: var(--bg-notebook);
}

.note-item.active .note-item-icon {
  background: rgba(255,255,255,0.15);
}

.note-item-content {
  flex: 1;
  min-width: 0;
}

.note-item-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-bottom: 2px;
}

.note-item-preview {
  font-size: 12px;
  color: var(--text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-bottom: 3px;
}

.note-item-date {
  font-size: 11px;
  color: var(--text-tertiary);
}

#sidebar-footer {
  padding: 12px;
  border-top: 1px solid var(--border-sidebar);
  display: flex;
  justify-content: center;
}

/* ── Main area ── */
#main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--bg-primary);
}

/* ── Toolbar ── */
#toolbar {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 8px 16px;
  background: var(--bg-toolbar);
  border-bottom: 1px solid var(--border-toolbar);
  flex-wrap: wrap;
  box-shadow: var(--shadow-sm);
  z-index: 10;
}

#toolbar.hidden {
  display: none;
}

.toolbar-group {
  display: flex;
  align-items: center;
  gap: 2px;
}

.toolbar-divider {
  width: 1px;
  height: 24px;
  background: var(--border-toolbar);
  margin: 0 6px;
}

.toolbar-btn {
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--text-toolbar);
  cursor: pointer;
  transition: all var(--transition);
  position: relative;
}

.toolbar-btn:hover {
  background: var(--bg-tertiary);
}

.toolbar-btn:active {
  transform: scale(0.95);
}

.toolbar-btn.active {
  background: var(--accent-light);
  color: var(--accent);
}

.toolbar-select {
  padding: 6px 10px;
  background: var(--bg-tertiary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  color: var(--text-toolbar);
  font-size: 13px;
  cursor: pointer;
  outline: none;
  transition: all var(--transition);
}

.toolbar-select:hover {
  border-color: var(--accent);
}

/* ── Editor container ── */
#editor-container {
  flex: 1;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

#empty-state {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 20px;
  color: var(--text-empty);
  padding: 40px;
}

#empty-state svg {
  opacity: 0.15;
}

#empty-state p {
  font-size: 20px;
  font-weight: 600;
  color: var(--text-secondary);
}

#empty-state span {
  font-size: 14px;
  color: var(--text-tertiary);
}

/* ── Editor wrapper ── */
#editor-wrapper {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  padding: 0;
}

#editor-wrapper.hidden {
  display: none;
}

#note-title {
  padding: 40px 80px 0;
  font-size: 32px;
  font-weight: 700;
  outline: none;
  border: none;
  background: transparent;
  color: var(--text-primary);
  letter-spacing: -0.5px;
  line-height: 1.2;
  width: 100%;
  max-width: 800px;
  margin: 0 auto;
}

#note-title::placeholder {
  color: var(--text-tertiary);
}

#note-title:focus {
  color: var(--text-primary);
}

#note-meta {
  padding: 8px 80px 0;
  display: flex;
  gap: 16px;
  font-size: 12px;
  color: var(--text-tertiary);
  max-width: 800px;
  margin: 0 auto;
}

#editor {
  flex: 1;
  padding: 20px 80px 100px;
  outline: none;
  border: none;
  background: transparent;
  color: var(--text-primary);
  font-size: 15px;
  line-height: 1.75;
  max-width: 800px;
  margin: 0 auto;
  width: 100%;
  overflow-wrap: break-word;
}

#editor::placeholder {
  color: var(--text-tertiary);
}

#editor:focus {
  outline: none;
}

#editor h1 { font-size: 2em; font-weight: 700; margin: 0.5em 0 0.3em; letter-spacing: -0.5px; }
#editor h2 { font-size: 1.5em; font-weight: 600; margin: 0.5em 0 0.3em; letter-spacing: -0.3px; }
#editor h3 { font-size: 1.25em; font-weight: 600; margin: 0.4em 0 0.2em; }
#editor p { margin: 0.3em 0; }
#editor ul, #editor ol { padding-left: 1.5em; margin: 0.3em 0; }
#editor blockquote {
  border-left: 3px solid var(--accent);
  padding-left: 16px;
  margin: 0.5em 0;
  color: var(--text-secondary);
  font-style: italic;
}
#editor code {
  background: var(--bg-tertiary);
  padding: 2px 6px;
  border-radius: 4px;
  font-size: 0.9em;
  font-family: 'SF Mono', 'Fira Code', monospace;
}
#editor pre {
  background: var(--bg-tertiary);
  padding: 16px;
  border-radius: var(--radius-sm);
  overflow-x: auto;
  margin: 0.5em 0;
}
#editor pre code {
  background: none;
  padding: 0;
}
#editor a { color: var(--accent); }
#editor hr {
  border: none;
  border-top: 1px solid var(--border-color);
  margin: 1.5em 0;
}

/* ── Context menu ── */
#context-menu {
  position: fixed;
  background: var(--bg-context);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: 4px;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 160px;
}

#context-menu.hidden {
  display: none;
}

#context-menu button {
  padding: 8px 12px;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  font-size: 13px;
  cursor: pointer;
  text-align: left;
  transition: background var(--transition);
  display: flex;
  align-items: center;
  gap: 8px;
}

#context-menu button:hover {
  background: var(--bg-context-hover);
}

#context-menu button.danger {
  color: var(--danger);
}

#context-menu button.danger:hover {
  background: rgba(239, 68, 68, 0.1);
}

/* ── Scrollbar ── */
::-webkit-scrollbar {
  width: 6px;
}
::-webkit-scrollbar-track {
  background: transparent;
}
::-webkit-scrollbar-thumb {
  background: var(--border-color);
  border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
  background: var(--text-tertiary);
}

/* ── Animations ── */
@keyframes fadeIn {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes slideIn {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

#note-list .note-item {
  animation: slideIn 0.2s ease forwards;
}
</style>
</head>
<body>
<div></div>
<div id="app">
  <div id="sidebar">
    <div id="sidebar-header">
      <div id="logo">
        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>
        <span>Noteflow</span>
      </div>
    </div>
    <div id="sidebar-search">
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
      <input type="text" id="search-input" placeholder="Search notes...">
    </div>
    <div id="notebooks-section">
      <div class="section-header">
        <span>Notebooks</span>
        <button id="add-notebook-btn" class="icon-btn" title="New notebook">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
        </button>
      </div>
      <div id="notebook-list"></div>
    </div>
    <div id="notes-section">
      <div class="section-header">
        <span>Notes</span>
        <button id="add-note-btn" class="icon-btn" title="New note">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
        </button>
      </div>
      <div id="note-list"></div>
    </div>
    <div id="sidebar-footer">
      <button id="theme-toggle" class="icon-btn" title="Toggle theme">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>
      </button>
    </div>
  </div>
  <div id="main">
    <div id="toolbar" class="hidden">
      <div class="toolbar-group">
        <button class="toolbar-btn" data-action="bold" title="Bold (Ctrl+B)"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"></path><path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"></path></svg></button>
        <button class="toolbar-btn" data-action="italic" title="Italic (Ctrl+I)"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="19" y1="4" x2="10" y2="4"></line><line x1="14" y1="20" x2="5" y2="20"></line><line x1="15" y1="4" x2="9" y2="20"></line></svg></button>
        <button class="toolbar-btn" data-action="underline" title="Underline (Ctrl+U)"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 3v7a6 6 0 0 0 6 6 6 6 0 0 0 6-6V3"></path><line x1="4" y1="21" x2="20" y2="21"></line></svg></button>
        <button class="toolbar-btn" data-action="strikeThrough" title="Strikethrough"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 4H9a3 3 0 0 0-3 3v0a3 3 0 0 0 3 3h6a3 3 0 0 1 3 3v0a3 3 0 0 1-3 3H8"></path><line x1="4" y1="12" x2="20" y2="12"></line></svg></button>
      </div>
      <div class="toolbar-divider"></div>
      <div class="toolbar-group">
        <button class="toolbar-btn" data-action="insertUnorderedList" title="Bullet list"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><circle cx="4" cy="6" r="1" fill="currentColor"></circle><circle cx="4" cy="12" r="1" fill="currentColor"></circle><circle cx="4" cy="18" r="1" fill="currentColor"></circle></svg></button>
        <button class="toolbar-btn" data-action="insertOrderedList" title="Numbered list"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="10" y1="6" x2="21" y2="6"></line><line x1="10" y1="12" x2="21" y2="12"></line><line x1="10" y1="18" x2="21" y2="18"></line><text x="3" y="8" font-size="7" fill="currentColor" stroke="none">1</text><text x="3" y="14" font-size="7" fill="currentColor" stroke="none">2</text><text x="3" y="20" font-size="7" fill="currentColor" stroke="none">3</text></svg></button>
      </div>
      <div class="toolbar-divider"></div>
      <div class="toolbar-group">
        <select class="toolbar-select" id="heading-select" title="Heading level">
          <option value="p">Paragraph</option>
          <option value="h1">Heading 1</option>
          <option value="h2">Heading 2</option>
          <option value="h3">Heading 3</option>
        </select>
      </div>
      <div class="toolbar-divider"></div>
      <div class="toolbar-group">
        <button class="toolbar-btn" data-action="foreColor" title="Text color" id="color-btn"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3L3 21h18L12 3z" fill="#3B82F6" stroke="#2563EB"></path><path d="M9 9l3 3 3-3"></path></svg></button>
        <input type="color" id="color-picker" value="#3B82F6" style="display:none">
        <button class="toolbar-btn" data-action="hiliteColor" title="Highlight" id="hilite-btn"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9" fill="#FBBF24" stroke="#F59E0B"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" fill="#FBBF24" stroke="#F59E0B"></path></svg></button>
        <input type="color" id="hilite-picker" value="#FBBF24" style="display:none">
      </div>
      <div class="toolbar-divider"></div>
      <div class="toolbar-group">
        <button class="toolbar-btn" data-action="removeFormat" title="Clear formatting"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 7h16M7 7v10M17 7v10"></path><line x1="18" y1="4" x2="6" y2="20" stroke-width="3"></line></svg></button>
      </div>
    </div>
    <div id="editor-container">
      <div id="empty-state">
        <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" opacity="0.3"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>
        <p>No note selected</p>
        <span>Create a new note or select one from the sidebar</span>
      </div>
      <div id="editor-wrapper" class="hidden">
        <div id="note-title" contenteditable="true" spellcheck="true"></div>
        <div id="note-meta">
          <span id="note-date"></span>
          <span id="note-word-count"></span>
        </div>
        <div id="editor" contenteditable="true" spellcheck="true"></div>
      </div>
    </div>
  </div>
  <div id="context-menu" class="hidden">
    <button data-action="rename">Rename</button>
    <button data-action="duplicate">Duplicate</button>
    <button data-action="delete" class="danger">Delete</button>
  </div>
</div>
<script>
'use strict';

// ── function: generateId ──
function generateId() {
  return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
}

// ── function: formatDate ──
function formatDate(dateStr) {
  const d = new Date(dateStr);
  const now = new Date();
  const diff = now - d;
  if (diff < 60000) return 'Just now';
  if (diff < 3600000) return Math.floor(diff / 60000) + 'm ago';
  if (diff < 86400000) return Math.floor(diff / 3600000) + 'h ago';
  if (diff < 604800000) return Math.floor(diff / 86400000) + 'd ago';
  return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: d.getFullYear() !== now.getFullYear() ? 'numeric' : undefined });
}

// ── function: stripHtml ──
function stripHtml(html) {
  const tmp = document.createElement('div');
  tmp.innerHTML = html;
  return tmp.textContent || tmp.innerText || '';
}

// ── function: getNotebookIcon ──
function getNotebookIcon(name) {
  const icons = ['📓', '📔', '📒', '📕', '📗', '📘', '📙', '📚', '📁', '📂'];
  let hash = 0;
  for (let i = 0; i < name.length; i++) hash = name.charCodeAt(i) + ((hash << 5) - hash);
  return icons[Math.abs(hash) % icons.length];
}

// ── function: getWordCount ──
function getWordCount(html) {
  const text = stripHtml(html).trim();
  if (!text) return 0;
  return text.split(/\s+/).length;
}

// ── const: STORAGE_KEY ──
const STORAGE_KEY = 'noteflow_data';

// ── function: loadStore ──
function loadStore() {
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    if (raw) return JSON.parse(raw);
  } catch(e) {}
  
  // Seed initial data
  const notebooks = [
    { id: 'nb_personal', name: 'Personal' },
    { id: 'nb_work', name: 'Work' },
    { id: 'nb_ideas', name: 'Ideas' }
  ];
  
  const notes = [
    {
      id: 'welcome_note',
      title: 'Welcome to Noteflow ✨',
      content: '<h1>Welcome to Noteflow</h1><p>Your beautiful, minimal notes app. Here are some things you can do:</p><ul><li><b>Create new notes</b> with the + button</li><li><b>Format text</b> using the toolbar</li><li><b>Organize</b> notes into notebooks</li><li><b>Search</b> across all your notes</li><li><b>Toggle theme</b> with the sun icon</li></ul><p><b>Keyboard shortcuts:</b></p><ul><li><b>Ctrl+N</b> — New note</li><li><b>Ctrl+F</b> — Search</li><li><b>Ctrl+B</b> — Bold</li><li><b>Ctrl+I</b> — Italic</li><li><b>Ctrl+U</b> — Underline</li></ul><p>Right-click any note for more options. All your data is saved locally in your browser.</p>',
      notebookId: 'nb_personal',
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString()
    },
    {
      id: 'meeting_notes',
      title: 'Meeting Notes — Q4 Planning',
      content: '<h2>Agenda</h2><ol><li>Review Q3 results</li><li>Set Q4 objectives</li><li>Resource allocation</li></ol><h3>Key Decisions</h3><ul><li>Focus on user retention</li><li>Launch new feature set by November</li><li>Hire 2 additional engineers</li></ul><p><b>Action items:</b></p><ul><li>Prepare financial report — Sarah</li><li>Draft product roadmap — Mike</li><li>Update hiring plan — HR</li></ul>',
      notebookId: 'nb_work',
      createdAt: new Date(Date.now() - 3600000).toISOString(),
      updatedAt: new Date(Date.now() - 3600000).toISOString()
    },
    {
      id: 'recipe_note',
      title: 'Recipe: Homemade Pasta',
      content: '<h2>Ingredients</h2><ul><li>2 cups all-purpose flour</li><li>3 large eggs</li><li>1 tbsp olive oil</li><li>Semolina for dusting</li></ul><h2>Instructions</h2><p>1. Make a well with the flour on a clean surface.</p><p>2. Crack eggs into the well, add olive oil.</p><p>3. Gradually incorporate flour into eggs.</p><p>4. Knead for 10 minutes until smooth.</p><p>5. Rest for 30 minutes, then roll thin.</p><blockquote>Pro tip: Let the pasta dry for 15 minutes before cooking for better texture.</blockquote>',
      notebookId: 'nb_personal',
      createdAt: new Date(Date.now() - 86400000).toISOString(),
      updatedAt: new Date(Date.now() - 86400000).toISOString()
    },
    {
      id: 'app_idea',
      title: 'App Idea: Habit Tracker',
      content: '<h2>Concept</h2><p>A minimal habit tracker that focuses on <b>consistency over intensity</b>.</p><h2>Features</h2><ul><li>📊 Visual streak calendar</li><li>🔔 Gentle reminders</li><li>📈 Weekly/monthly analytics</li><li>🎯 Focus mode for deep work</li><li>🤝 Group challenges</li></ul><h2>Design Principles</h2><p>Keep it simple. No social features unless they add real value. Dark mode first. Offline capable.</p>',
      notebookId: 'nb_ideas',
      createdAt: new Date(Date.now() - 172800000).toISOString(),
      updatedAt: new Date(Date.now() - 172800000).toISOString()
    }
  ];
  
  return { notes, notebooks, activeNoteId: null, activeNotebookId: null, theme: 'light' };
}

// ── function: saveStore ──
function saveStore(store) {
  try {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(store));
  } catch(e) {}
}

// ── function: createNote ──
function createNote(title = 'Untitled Note', notebookId = null) {
  const store = loadStore();
  const note = {
    id: generateId(),
    title: title,
    content: '',
    notebookId: notebookId,
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString()
  };
  store.notes.unshift(note);
  saveStore(store);
  return note;
}

// ── function: updateNote ──
function updateNote(id, updates) {
  const store = loadStore();
  const idx = store.notes.findIndex(n => n.id === id);
  if (idx === -1) return null;
  store.notes[idx] = { ...store.notes[idx], ...updates, updatedAt: new Date().toISOString() };
  saveStore(store);
  return store.notes[idx];
}

// ── function: deleteNote ──
function deleteNote(id) {
  const store = loadStore();
  store.notes = store.notes.filter(n => n.id !== id);
  saveStore(store);
}

// ── function: createNotebook ──
function createNotebook(name = 'New Notebook') {
  const store = loadStore();
  const notebook = { id: generateId(), name };
  store.notebooks.push(notebook);
  saveStore(store);
  return notebook;
}

// ── function: deleteNotebook ──
function deleteNotebook(id) {
  const store = loadStore();
  store.notebooks = store.notebooks.filter(n => n.id !== id);
  store.notes.forEach(n => { if (n.notebookId === id) n.notebookId = null; });
  saveStore(store);
}

// ── function: duplicateNote ──
function duplicateNote(id) {
  const store = loadStore();
  const note = store.notes.find(n => n.id === id);
  if (!note) return null;
  const dup = { ...note, id: generateId(), title: note.title + ' (copy)', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() };
  store.notes.unshift(dup);
  saveStore(store);
  return dup;
}

// ── function: getNotesByNotebook ──
function getNotesByNotebook(store, notebookId) {
  if (!notebookId) return store.notes;
  return store.notes.filter(n => n.notebookId === notebookId);
}

// ── function: searchNotes ──
function searchNotes(store, query) {
  if (!query.trim()) return store.notes;
  const q = query.toLowerCase();
  return store.notes.filter(n => n.title.toLowerCase().includes(q) || stripHtml(n.content).toLowerCase().includes(q));
}

// ── let: currentNoteId ──
let currentNoteId = null;

// ── let: currentNotebookId ──
let currentNotebookId = null;

// ── let: searchQuery ──
let searchQuery = '';

// ── let: debounceTimer ──
let debounceTimer = null;

// ── function: renderNotebooks ──
function renderNotebooks(store) {
  const list = document.getElementById('notebook-list');
  const allLabel = currentNotebookId === null ? 'All Notes' : 'All';
  const allActive = currentNotebookId === null ? 'active' : '';
  list.innerHTML = `
    <div class="notebook-item ${allActive}" data-notebook="all">
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
      <span>${allLabel}</span>
      <span class="notebook-count">${store.notes.length}</span>
    </div>
    ${store.notebooks.map(nb => `
      <div class="notebook-item ${currentNotebookId === nb.id ? 'active' : ''}" data-notebook="${nb.id}">
        <span>${getNotebookIcon(nb.name)}</span>
        <span>${escapeHtml(nb.name)}</span>
        <span class="notebook-count">${store.notes.filter(n => n.notebookId === nb.id).length}</span>
      </div>
    `).join('')}
  `;
}

// ── function: renderNoteList ──
function renderNoteList(store) {
  const list = document.getElementById('note-list');
  let notes = currentNotebookId !== null ? getNotesByNotebook(store, currentNotebookId) : store.notes;
  if (searchQuery) notes = searchNotes(store, searchQuery);
  
  if (notes.length === 0) {
    list.innerHTML = `<div style="padding: 20px; text-align: center; color: var(--text-tertiary); font-size: 13px;">No notes yet</div>`;
    return;
  }
  
  list.innerHTML = notes.map(note => {
    const preview = stripHtml(note.content).slice(0, 60) || 'Empty note';
    const date = formatDate(note.updatedAt);
    return `
      <div class="note-item ${currentNoteId === note.id ? 'active' : ''}" data-note-id="${note.id}">
        <div class="note-item-icon">📝</div>
        <div class="note-item-content">
          <div class="note-item-title">${escapeHtml(note.title) || 'Untitled'}</div>
          <div class="note-item-preview">${escapeHtml(preview)}</div>
          <div class="note-item-date">${date}</div>
        </div>
      </div>
    `;
  }).join('');
}

// ── function: selectNote ──
function selectNote(id) {
  currentNoteId = id;
  const store = loadStore();
  const note = store.notes.find(n => n.id === id);
  if (!note) return;
  
  document.getElementById('empty-state').classList.add('hidden');
  document.getElementById('editor-wrapper').classList.remove('hidden');
  document.getElementById('toolbar').classList.remove('hidden');
  
  document.getElementById('note-title').textContent = note.title;
  document.getElementById('editor').innerHTML = note.content;
  updateMeta(note);
  renderNoteList(store);
}

// ── function: updateMeta ──
function updateMeta(note) {
  const date = new Date(note.updatedAt).toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit' });
  const words = getWordCount(note.content);
  document.getElementById('note-date').textContent = date;
  document.getElementById('note-word-count').textContent = words + ' word' + (words !== 1 ? 's' : '');
}

// ── function: escapeHtml ──
function escapeHtml(str) {
  const div = document.createElement('div');
  div.textContent = str;
  return div.innerHTML;
}

// ── function: deselectNote ──
function deselectNote() {
  currentNoteId = null;
  document.getElementById('empty-state').classList.remove('hidden');
  document.getElementById('editor-wrapper').classList.add('hidden');
  document.getElementById('toolbar').classList.add('hidden');
  const store = loadStore();
  renderNoteList(store);
}

// ── function: setTheme ──
function setTheme(theme) {
  document.documentElement.setAttribute('data-theme', theme);
  const store = loadStore();
  store.theme = theme;
  saveStore(store);
}

// ── function: execCmd ──
function execCmd(command, value = null) {
  document.execCommand(command, false, value);
  document.getElementById('editor').focus();
}

// ── function: applyHeading ──
function applyHeading(tag) {
  if (tag === 'p') {
    document.execCommand('formatBlock', false, 'p');
  } else {
    document.execCommand('formatBlock', false, tag);
  }
  document.getElementById('editor').focus();
}

// ── function: updateToolbarState ──
function updateToolbarState() {
  const actions = ['bold', 'italic', 'underline', 'strikeThrough', 'insertUnorderedList', 'insertOrderedList'];
  actions.forEach(action => {
    const btn = document.querySelector(`.toolbar-btn[data-action="${action}"]`);
    if (btn) {
      if (document.queryCommandState(action)) {
        btn.classList.add('active');
      } else {
        btn.classList.remove('active');
      }
    }
  });
  
  const block = document.queryCommandValue('formatBlock');
  const select = document.getElementById('heading-select');
  if (block) {
    const tag = block.toLowerCase().replace(/[<>]/g, '');
    if (['p', 'h1', 'h2', 'h3'].includes(tag)) {
      select.value = tag;
    }
  }
}

// ── function: autoSave ──
function autoSave() {
  if (!currentNoteId) return;
  const title = document.getElementById('note-title').textContent.trim();
  const content = document.getElementById('editor').innerHTML;
  updateNote(currentNoteId, { title: title || 'Untitled Note', content });
  const store = loadStore();
  updateMeta(store.notes.find(n => n.id === currentNoteId));
  renderNoteList(store);
}

// ── function: debouncedSave ──
function debouncedSave() {
  clearTimeout(debounceTimer);
  debounceTimer = setTimeout(autoSave, 300);
}

// ── main ──
// ── Initialize ──
let store = loadStore();
setTheme(store.theme || 'light');
renderNotebooks(store);
renderNoteList(store);

// ── Search ──
document.getElementById('search-input').addEventListener('input', (e) => {
  searchQuery = e.target.value;
  const s = loadStore();
  renderNoteList(s);
});

// ── Add note ──
document.getElementById('add-note-btn').addEventListener('click', () => {
  const s = loadStore();
  const note = createNote('Untitled Note', currentNotebookId);
  selectNote(note.id);
  document.getElementById('note-title').focus();
});

// ── Add notebook ──
document.getElementById('add-notebook-btn').addEventListener('click', () => {
  createNotebook('New Notebook');
  const s = loadStore();
  renderNotebooks(s);
  renderNoteList(s);
});

// ── Notebook selection ──
document.getElementById('notebook-list').addEventListener('click', (e) => {
  const item = e.target.closest('.notebook-item');
  if (!item) return;
  const notebookId = item.dataset.notebook;
  currentNotebookId = notebookId === 'all' ? null : notebookId;
  const s = loadStore();
  renderNotebooks(s);
  renderNoteList(s);
});

// ── Note selection ──
document.getElementById('note-list').addEventListener('click', (e) => {
  const item = e.target.closest('.note-item');
  if (!item) return;
  selectNote(item.dataset.noteId);
});

// ── Note title edit ──
document.getElementById('note-title').addEventListener('input', debouncedSave);

// ── Editor content edit ──
document.getElementById('editor').addEventListener('input', () => {
  debouncedSave();
  updateToolbarState();
});

document.getElementById('editor').addEventListener('keydown', (e) => {
  if (e.key === 'Tab') {
    e.preventDefault();
    document.execCommand('insertText', false, '  ');
  }
});

// ── Toolbar buttons ──
document.getElementById('toolbar').addEventListener('click', (e) => {
  const btn = e.target.closest('.toolbar-btn');
  if (!btn) return;
  const action = btn.dataset.action;
  
  if (action === 'foreColor' || action === 'hiliteColor') {
    const pickerId = action === 'foreColor' ? 'color-picker' : 'hilite-picker';
    document.getElementById(pickerId).click();
    return;
  }
  
  execCmd(action);
});

document.getElementById('color-picker').addEventListener('input', (e) => {
  execCmd('foreColor', e.target.value);
});

document.getElementById('hilite-picker').addEventListener('change', (e) => {
  execCmd('hiliteColor', e.target.value);
});

document.getElementById('heading-select').addEventListener('change', (e) => {
  applyHeading(e.target.value);
  updateToolbarState();
});

// ── Keyboard shortcuts ──
document.addEventListener('keydown', (e) => {
  if ((e.ctrlKey || e.metaKey) && e.key === 'n') {
    e.preventDefault();
    const s = loadStore();
    const note = createNote('Untitled Note', currentNotebookId);
    selectNote(note.id);
    document.getElementById('note-title').focus();
  }
  if ((e.ctrlKey || e.metaKey) && e.key === 'f') {
    e.preventDefault();
    document.getElementById('search-input').focus();
  }
  if (e.key === 'Escape') {
    hideContextMenu();
    document.getElementById('search-input').blur();
  }
});

// ── Context menu ──
let contextTarget = null;

document.getElementById('note-list').addEventListener('contextmenu', (e) => {
  const item = e.target.closest('.note-item');
  if (!item) return;
  e.preventDefault();
  contextTarget = item.dataset.noteId;
  showContextMenu(e.clientX, e.clientY);
});

document.getElementById('notebook-list').addEventListener('contextmenu', (e) => {
  const item = e.target.closest('.notebook-item');
  if (!item || item.dataset.notebook === 'all') return;
  e.preventDefault();
  contextTarget = 'nb_' + item.dataset.notebook;
  showContextMenu(e.clientX, e.clientY);
});

function showContextMenu(x, y) {
  const menu = document.getElementById('context-menu');
  menu.classList.remove('hidden');
  menu.style.left = Math.min(x, window.innerWidth - 180) + 'px';
  menu.style.top = Math.min(y, window.innerHeight - 120) + 'px';
}

function hideContextMenu() {
  document.getElementById('context-menu').classList.add('hidden');
}

document.addEventListener('click', (e) => {
  if (!e.target.closest('#context-menu')) hideContextMenu();
});

document.getElementById('context-menu').addEventListener('click', (e) => {
  const btn = e.target.closest('button');
  if (!btn) return;
  const action = btn.dataset.action;
  
  if (contextTarget && !contextTarget.startsWith('nb_')) {
    const s = loadStore();
    if (action === 'delete') {
      deleteNote(contextTarget);
      if (currentNoteId === contextTarget) deselectNote();
      renderNoteList(s);
    }
    if (action === 'duplicate') {
      const dup = duplicateNote(contextTarget);
      if (dup) selectNote(dup.id);
      renderNoteList(s);
    }
    if (action === 'rename') {
      const note = s.notes.find(n => n.id === contextTarget);
      if (note) {
        const newName = prompt('Rename note:', note.title);
        if (newName !== null) {
          updateNote(contextTarget, { title: newName });
          renderNoteList(s);
        }
      }
    }
  }
  
  if (contextTarget && contextTarget.startsWith('nb_')) {
    const nbId = contextTarget.replace('nb_', '');
    const s = loadStore();
    if (action === 'delete') {
      deleteNotebook(nbId);
      currentNotebookId = null;
      renderNotebooks(s);
      renderNoteList(s);
    }
    if (action === 'rename') {
      const nb = s.notebooks.find(n => n.id === nbId);
      if (nb) {
        const newName = prompt('Rename notebook:', nb.name);
        if (newName !== null) {
          const idx = s.notebooks.findIndex(n => n.id === nbId);
          if (idx !== -1) {
            s.notebooks[idx].name = newName;
            saveStore(s);
          }
          renderNotebooks(s);
          renderNoteList(s);
        }
      }
    }
  }
  
  hideContextMenu();
});

// ── Theme toggle ──
document.getElementById('theme-toggle').addEventListener('click', () => {
  const current = document.documentElement.getAttribute('data-theme');
  setTheme(current === 'dark' ? 'light' : 'dark');
  const s = loadStore();
  renderNotebooks(s);
});

// ── Init with first note or empty state ──
const s = loadStore();
if (s.notes.length > 0) {
  selectNote(s.notes[0].id);
} else {
  deselectNote();
}
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen/qwen3.6-35b-a3b","provider":"lmstudio","persona":"composer","sessionId":"7b52134e-144e-42bf-bbcb-6bb58cbd63dc","tokensIn":771443,"tokensOut":29862,"tokensTotal":801305,"turns":31,"toolCalls":30,"failedToolCalls":2,"timestamp":"2026-07-20T22:14:36.761Z"} -->