← Notes results

Notes

ornith 1.0 9b · full

Initial view of Notes

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

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

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

Generation prompt · exact text unavailable

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

Inspect original source 35,874 bytes · SHA-256 b23224105bd8
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lumina Notes</title>
<style>
:root {
  --bg-primary: #f7f7f9;
  --bg-secondary: #ffffff;
  --bg-tertiary: #e8eaf0;
  --bg-sidebar: #fafafb;
  --text-primary: #1a1a2e;
  --text-secondary: #6b7280;
  --text-muted: #9ca3af;
  --accent: #6c5ce7;
  --accent-light: #a29bfe;
  --accent-dark: #4834d4;
  --border: #e2e5ea;
  --border-light: #f0f1f3;
  --shadow-sm: 0 1px 3px rgba(0,0,0,.06);
  --shadow-md: 0 4px 12px rgba(0,0,0,.08);
  --shadow-lg: 0 8px 25px rgba(0,0,0,.1);
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 14px;
  --sidebar-width: 260px;
  --header-height: 52px;
  --transition: .2s cubic-bezier(.4,0,.2,1);
}

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

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
  background: var(--bg-primary);
  color: var(--text-primary);
  height: 100vh;
  overflow: hidden;
}

.app { display: flex; height: 100vh; }

/* ======================== SIDEBAR ======================== */

.sidebar {
  width: var(--sidebar-width);
  background: var(--bg-sidebar);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  transition: width var(--transition);
}

.sidebar-header {
  padding: 16px;
  border-bottom: 1px solid var(--border-light);
}

.sidebar-logo {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}

.logo-icon {
  width: 32px; height: 32px;
  background: linear-gradient(135deg, var(--accent), var(--accent-light));
  border-radius: var(--radius-sm);
  display: flex; align-items: center; justify-content: center;
}

.logo-icon svg { width: 20px; height: 20px; fill: white; }

.sidebar-title { font-size: 15px; font-weight: 600; color: var(--text-primary); letter-spacing: -.3px; }

.search-box {
  margin-top: 14px;
  position: relative;
}

.search-box input {
  width: 100%;
  padding: 8px 12px 8px 36px;
  border: 1.5px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-secondary);
  color: var(--text-primary);
  font-size: 13px;
  outline: none;
  transition: var(--transition);
}

.search-box input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(108,92,231,.1); }

.search-box::before {
  content: '🔍'; position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
  font-size: 14px; opacity: .6;
}

.sidebar-nav { flex: 1; overflow-y: auto; padding: 8px; }

.nav-section { margin-bottom: 4px; }

.nav-label {
  display: flex; align-items: center; gap: 6px;
  padding: 7px 10px;
  font-size: 11px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .5px; color: var(--text-muted); cursor: pointer;
}

.nav-label:hover { background: var(--bg-tertiary); border-radius: var(--radius-sm); }

.nav-item {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px;
  font-size: 13.5px; cursor: pointer;
  border-radius: var(--radius-sm);
  color: var(--text-secondary); transition: var(--transition);
  margin-bottom: .25px;
}

.nav-item:hover { background: var(--bg-tertiary); color: var(--text-primary); }

.nav-item.active { background: rgba(108,92,231,.08); color: var(--accent); font-weight: 500; }

.nav-icon { width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; opacity: .7; }
.nav-item.active .nav-icon { opacity: 1; }

.nav-spacer { padding: 8px 12px; font-size: 13.5px; color: var(--text-secondary); cursor: pointer; border-radius: var(--radius-sm); transition: var(--transition); display: flex; align-items: center; gap: 8px; }
.nav-spacer:hover { background: var(--bg-tertiary); color: var(--text-primary); }

/* ======================== MAIN AREA ======================== */

.main { flex: 1; display: flex; flex-direction: column; overflow: hidden; min-width: 0; }

.toolbar {
  height: var(--header-height);
  background: var(--bg-secondary);
  border-bottom: 1px solid var(--border-light);
  display: flex; align-items: center; padding: 0 20px;
  gap: 8px; flex-shrink: 0;
}

.toolbar-btn {
  width: 34px; height: 34px; border-radius: var(--radius-sm);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; transition: var(--transition);
  color: var(--text-muted); background: none; border: none; font-size: 15px; flex-shrink: 0;
}

.toolbar-btn:hover { background: var(--bg-tertiary); color: var(--text-primary); }
.toolbar-btn.active { background: rgba(108,92,231,.12); color: var(--accent); }

.note-title-input {
  flex: 1; border: none; outline: none; font-size: 15px; font-weight: 600;
  padding: 0 4px; background: transparent; color: var(--text-primary); min-width: 200px;
}

.note-title-input::placeholder { color: var(--text-muted); }

.content-area { flex: 1; overflow-y: auto; display: flex; flex-direction: column; padding: 36px 48px 48px; }

.empty-state {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  flex: 1; text-align: center; color: var(--text-muted);
}

.empty-state-icon { font-size: 56px; margin-bottom: 20px; opacity: .4; }
.empty-state h3 { font-size: 18px; color: var(--text-secondary); margin-bottom: 8px; }
.empty-state p { font-size: 14px; max-width: 360px; line-height: 1.5; }

.note-editor-wrapper { display: flex; flex-direction: column; gap: 20px; max-width: 780px; width: 100%; margin: 0 auto; }

.editor-meta {
  display: grid; grid-template-columns: subgrid; gap: 6px; padding-bottom: 14px;
}

.meta-item { font-size: 13px; color: var(--text-muted); }

.content-input {
  flex: 1; border: none; outline: none; background: transparent; font-size: 15.5px; line-height: 1.8;
  color: var(--text-primary); min-height: 400px; resize: none; padding-top: 6px;
}

.content-input::placeholder { color: var(--text-muted); }

/* ======================== RIGHT PANEL ======================== */

.right-panel { width: 240px; background: var(--bg-secondary); border-left: 1px solid var(--border-light); overflow-y: auto; padding: 16px; display: none; flex-direction: column; gap: 12px; }
.right-panel.open { display: flex; }

.tag-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px; border-radius: 99px; font-size: 13px; background: var(--bg-tertiary); color: var(--text-secondary); cursor: pointer; transition: var(--transition); user-select: none;
}

.tag-chip:hover { background: rgba(108,92,231,.12); }
.tag-chip .tag-remove { width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; font-size: 14px; opacity: .5; transition: var(--transition); cursor: pointer; }
.tag-chip .tag-remove:hover { opacity: 1; color: var(--accent-dark); }

/* ======================== MODAL ======================== */

.modal-overlay {
  position: fixed; inset: 0; background: rgba(26,26,46,.5); backdrop-filter: blur(4px);
  display: flex; align-items: center; justify-content: center; z-index: 100; opacity: 0; transition: var(--transition); pointer-events: none;
}

.modal-overlay.active { opacity: 1; pointer-events: all; }

.modal { background: var(--bg-secondary); border-radius: var(--radius-lg); padding: 28px; width: 440px; max-width: 90vw; box-shadow: var(--shadow-lg); transform: scale(.95) translateY(-10px); transition: var(--transition); }
.modal-overlay.active .modal { transform: scale(1) translateY(0); }

.modal h3 { font-size: 16px; margin-bottom: 16px; color: var(--text-primary); }

.form-group { margin-bottom: 14px; }

.form-group label { display: block; font-size: 12px; font-weight: 500; color: var(--text-muted); margin-bottom: 6px; text-transform: uppercase; letter-spacing: .3px; }

.form-input, .form-textarea {
  width: 100%; border: 1.5px solid var(--border); border-radius: var(--radius-sm); padding: 8px 12px; font-size: 14px; color: var(--text-primary); outline: none; transition: var(--transition); background: var(--bg-secondary);
}

.form-input:focus, .form-textarea:focus { border-color: var(--accent); }

.form-textarea { resize: vertical; min-height: 80px; line-height: 1.5; }

.modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px; }

.btn { padding: 7px 18px; border-radius: var(--radius-sm); font-size: 13.5px; font-weight: 500; cursor: pointer; transition: var(--transition); border: none; display: inline-flex; align-items: center; gap: 6px; }

.btn-primary { background: var(--accent); color: white; }
.btn-primary:hover { background: var(--accent-dark); box-shadow: 0 2px 8px rgba(108,92,231,.35); }
.btn-secondary { background: var(--bg-tertiary); color: var(--text-secondary); }
.btn-secondary:hover { background: #dce0e6; }

/* ======================== CONTEXT MENU ======================== */

.context-menu {
  position: fixed; z-index: 200; background: var(--bg-secondary); border: 1px solid var(--border-light); border-radius: var(--radius-md); box-shadow: var(--shadow-lg); padding: 4px; min-width: 180px; transform: scale(.95); opacity: 0; transition: var(--transition); pointer-events: none;
}

.context-menu.open { transform: scale(1) translateY(-2px); opacity: 1; }

.ctx-item { display: flex; align-items: center; gap: 10px; padding: 7px 12px; font-size: 13.5px; cursor: pointer; border-radius: var(--radius-sm); color: var(--text-primary); transition: var(--transition); }
.ctx-item:hover { background: var(--bg-tertiary); }

/* ======================== PROGRESSIVE ANIMATION ======================== */

@keyframes fadeUp { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
.fade-up { animation: fadeUp .3s ease both; }

/* Scrollbar */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #d1d5db; }

/* Focus ring */
:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 4px; }

/* Responsive */
@media (max-width: 768px) { .sidebar { position: fixed; left: -300px; z-index: 50; height: 100%; box-shadow: var(--shadow-lg); } }
</style>
</head>
<body>

<div class="app">
  <!-- SIDEBAR -->
  <aside class="sidebar" id="sidebar">
    <div class="sidebar-header">
      <div class="sidebar-logo">
        <div class="logo-icon"><svg viewBox="0 0 24 24"><path d="M3 18c0-6.627 5.373-12 12-12s12 5.373 12 12v.01h-6V9.5L14 12.5V18H3z"/></svg></div>
        <span class="sidebar-title">Lumina</span>
      </div>
      <div class="search-box">
        <input type="text" id="searchInput" placeholder="Search notes..." autocomplete="off">
      </div>
    </div>

    <nav class="sidebar-nav">
      <div class="nav-section">
        <div class="nav-label" onclick="app.showSection('all-notes')">📋 All Notes</div>
        <div id="folder-list"></div>
      </div>
      <div style="padding: 10px 4px;">
        <div class="nav-spacer" title="Collections">
          <span>📁</span> Collections
        </div>
        <div id="collection-list"></div>
      </div>
      <div class="nav-section">
        <div class="nav-label" style="cursor: default;">🏷️ Tags</div>
        <div id="tag-list"></div>
      </div>
      <div style="padding: 10px 4px;">
        <div class="nav-spacer">
          <span>⭐</span> Bookmarks
        </div>
        <div id="bookmark-list"></div>
      </div>
    </nav>

    <div class="sidebar-header" style="border-top: 1px solid var(--border-light); padding: 12px;">
      <div class="nav-spacer">
        <span>📋</span> Keyboard Shortcuts
      </div>
      <ul style="font-size:12px; color:var(--text-muted); line-height:1.7;">
        <li><code>N</code> — New Note</li>
        <li><code>B</code> — Bookmarks</li>
        <li><code>PgUp/PgDn</code> — Navigate Notes</li>
      </ul>
    </div>
  </aside>

  <!-- MAIN -->
  <main class="main">
    <div class="toolbar">
      <button class="toolbar-btn" onclick="app.openNewNote()" title="New Note (Ctrl+N)">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/></svg>
      </button>
      <button class="toolbar-btn active" onclick="app.setEditorMode('markdown')" title="Markdown Mode">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M4 7V4h3v3h3V4h3v3h3.5M4 18v-3h3v3h3v-3h3v3h3.5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
      </button>
      <button class="toolbar-btn" onclick="app.setEditorMode('outline')" title="Outline Mode">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M8.5 4h3v16h-3M15.5 4h3v16h-3M4.5 10l7.5 5L22 9" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
      </button>
    </div>

    <div class="content-area" id="contentArea">
      <!-- Empty state -->
      <div class="empty-state" id="emptyState">
        <div class="empty-state-icon">✨</div>
        <h3>Welcome to Lumina Notes</h3>
        <p>Create your first note or explore existing ones. Your notes are saved locally in your browser.</p>
      </div>

      <div id="note-list"></div>

      <!-- Editor -->
      <div class="note-editor-wrapper hidden" id="editorWrapper">
        <div class="editor-meta">
          <div class="meta-item"><span style="color:var(--accent)">📂</span> <strong id="metaFolder"></strong></div>
          <div class="meta-item"><span style="color:#e17055">🏷️</span> <span id="metaTags"></span></div>
          <div class="meta-item"><span style="color:#fdcb6e">🕐</span> <strong id="metaDate"></strong></div>
        </div>
        <input type="text" class="note-title-input" id="titleInput" placeholder="Untitled Note" oninput="app.updateNoteTitle()">
        <textarea class="content-input" id="contentInput" placeholder="Start writing...&#10;&#10;Markdown supported:" oninput="app.onContentChange()"></textarea>
      </div>
    </div>

    <!-- Right Panel -->
    <aside class="right-panel" id="rightPanel">
      <h3 style="font-size:14px; color:var(--text-muted); letter-spacing:.5px; text-transform:uppercase;">Tags</h3>
      <div id="noteTags"></div>
    </aside>
  </main>
</div>

<!-- Modal -->
<div class="modal-overlay" id="modalOverlay">
  <div class="modal fade-up">
    <h3 id="modalTitle">New Note</h3>
    <div class="form-group">
      <label>Title</label>
      <input type="text" class="form-input" id="modalTitleInput" placeholder="Note title...">
    </div>
    <div class="form-group">
      <label>Folder</label>
      <select class="form-select" id="modalFolderSelect"></select>
    </div>
    <div class="form-group">
      <label>Tags (comma separated)</label>
      <input type="text" class="form-input" id="modalTagsInput" placeholder="work, ideas, project">
    </div>
    <div class="form-actions modal-actions">
      <button class="btn btn-secondary" onclick="app.closeModal()">Cancel</button>
      <button class="btn btn-primary" id="modalSaveBtn">Create Note</button>
    </div>
  </div>
</div>

<!-- Context Menu -->
<div class="context-menu hidden" id="ctxMenu"></div>

<script>
const STORAGE_KEY = 'lumina-notes';

// ======================== DATA MODEL ========================
function generateId() { return Date.now().toString(36) + Math.random().toString(36).slice(2, 9); }
function formatDate(iso) { if(!iso) return ''; const d = new Date(iso); return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); }
function formatTime(iso) { if(!iso) return ''; const d = new Date(iso); return d.toLocaleTimeString('en-US', { hour: '2-digit', minute:'2-digit' }); }

const defaultFolders = [
  { id: 'inbox', name: '📥 Inbox', icon: '📥', color: '#6c5ce7' },
  { id: 'journal', name: '📔 Journal', icon: '📔', color: '#e17055' },
  { id: 'projects', name: '💼 Projects', icon: '💼', color: '#fdcb6e' },
  { id: 'ideas', name: '💡 Ideas', icon: '💡', color: '#00b894' },
  { id: 'archive', name: '📦 Archive', icon: '📦', color: '#636e72' }
];

const defaultCollections = [
  { id: 'favorites', name: '⭐ Favorites', icon: '⭐', color: '#fdcb6e' },
  { id: 'bookmarks', name: '🔖 Bookmarks', icon: '🔖', color: '#0984e3' }
];

const defaultTags = [
  { id: 'work', name: 'Work', icon: '💼', color: '#6c5ce7' },
  { id: 'personal', name: 'Personal', icon: '🏠', color: '#fdcb6e' },
  { id: 'ideas', name: 'Ideas', icon: '💡', color: '#00b894' },
  { id: 'reading', name: 'Reading', icon: '📖', color: '#e17055' }
];

class AppStore {
  constructor() { this.notes = []; this.currentFolderId = null; this.bookmarks = new Set(); this.selectedTags = new Map(); this.searchQuery = ''; }

  load() {
    try { const raw = localStorage.getItem(STORAGE_KEY); if(!raw) return; this.notes = JSON.parse(raw); } catch(e) {}
    // Ensure folders exist
    if(this.notes.length === 0) this.save();
  }

  save() { localStorage.setItem(STORAGE_KEY, JSON.stringify({ notes: this.notes, bookmarks: [...this.bookmarks], selectedTags: Object.fromEntries([...this.selectedTags]) })); }

  addNote(data = {}) { const id = generateId(); const now = new Date().toISOString();
    data.id = id; data.createdAt = now || Date.now().toString(); data.updatedAt = now; data.folderId = (data.folderId && defaultFolders.find(f => f.id === data.folderId)) ? data.folderId : 'inbox';
    if(!data.title) data.title = ''; if(!data.content) data.content = '';
    this.notes.unshift(data);
  }

  updateNote(id, updates) { const idx = this.notes.findIndex(n => n.id === id); if(idx < 0) return; Object.assign(this.notes[idx], updates); this.notes[idx].updatedAt = new Date().toISOString(); }

  deleteNote(id) { const idx = this.notes.findIndex(n => n.id === id); if(idx >= 0) this.notes.splice(idx, 1); }

  toggleBookmark(id) { if(this.bookmarks.has(id)) { this.bookmarks.delete(id); } else { this.bookmarks.add(id); } }

  addTag(name) { const id = name.toLowerCase().replace(/[^a-z0-9\s-]/g, '').trim() || 'untitled';
    let tag = this.selectedTags.get(id); if(!tag) { tag = { id, name: name.trim(), icon: '🏷️', color: '#' + Math.floor(Math.random()*16777215).toString(16), count: 0 }; this.selectedTags.set(id, tag); }
    tag.count++; this.save(); return tag;
  }

  removeTag(id) { const tag = this.selectedTags.get(id); if(tag && tag.count > 0) { tag.count--; if(tag.count <= 0) this.selectedTags.delete(id); } }
}

// ======================== MARKDOWN RENDERER ========================
function parseMarkdown(text) { return text.replace(/\n\n+/g, '</p><p style="margin-top:8px">').replace(/^(\s*)(#{1,6})\s+(.+)$/gm, (_, indent, hash, title) => `<h${hash.length} style="margin-top:16px;margin-bottom:8px;color:${getAccentColorForHeading(parseInt(hash))}">${title}</h${hash.length}>`).replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>').replace(/\*([^*]+)\*/g, '<em>$1</em>'); }
function renderMarkdown(text) { return text.replace(mdRE, (_, bold, b1, b2, b3, indent, heading, marker) => {
  if(heading && marker) return `<h${indent.length + 1} style="margin-top:16px;margin-bottom:8px;color:${getAccentColorForHeading(indent+1)}">${bold || b1 || b2 || b3}</h${indent.length + 1}>`;
  if(bold !== undefined && bold) return `<strong>${bold}</strong>`;
  return text;
}).replace(/\n{2,}/g, '</p><p style="margin-top:8px">').replace(/: \[(\d+)\/(\d+)\]/g, '<progress class="md-progress" value="$1" max="$2"></progress>'); }

function getAccentColorForHeading(level) { const colors = ['#6c5ce7','#e17055','#fdcb6e','#00b894','#74b9ff','#a29bfe']; return colors[(level-1) % colors.length]; }
const progressStyle = (v, mx) => `style="width:${(v/mx)*100}%;background:linear-gradient(to right,var(--accent),var(--accent-light));border-radius:3px;"`;

// ======================== UI APP ========================
class AppUI {
  constructor(store) { this.store = store; }

  init() {
    appStore.load();
    // Restore session
    const lastNoteId = sessionStorage.getItem('lastNoteId');
    if(lastNoteId && appStore.notes.find(n => n.id === lastNoteId)) { this.openNote(lastNoteId); } else { this.showEmptyState(); }

    this.bindEvents();
    this.renderSidebarFolders();
    this.renderSidebarCollections();
    this.renderSidebarTags();
    this.renderBookmarkList();
    this.setupSearch();
  }

  bindEvents() {
    document.getElementById('searchInput').addEventListener('input', () => { this.store.searchQuery = appSearchEl.value; this.filterNotes(); });

    // Keyboard shortcuts
    document.addEventListener('keydown', e => {
      if(['INPUT','TEXTAREA'].includes(document.activeElement.tagName)) return;
      if(e.key === 'n' || e.key === 'N') { e.preventDefault(); this.openNewNote(); }
      if(e.key === 'b' || e.key === 'B') { e.preventDefault(); this.showBookmarks(); }
      if(['ArrowUp','ArrowDown'].includes(e.key)) { e.preventDefault(); const idx = appStore.notes.findIndex(n => n.id === currentNoteId); if(idx < 0) return; const dir = e.key === 'ArrowUp' ? -1 : 1; this.openNote(this.store.notes[idx+dir]?.id || null); }
    });

    // Right-click context menu on sidebar items
    document.addEventListener('contextmenu', e => { if(e.target.closest('.nav-item')) { e.preventDefault(); this.showContextMenu(e, 'sidebar'); } });
  }

  showEmptyState() { document.getElementById('emptyState').classList.remove('hidden'); document.getElementById('editorWrapper').classList.add('hidden'); }
  hideEmptyState() { document.getElementById('emptyState').classList.add('hidden'); }

  filterNotes() { let notes = this.store.notes; const q = (this.store.searchQuery || '').toLowerCase(); if(q) notes = notes.filter(n => n.title.toLowerCase().includes(q) || n.content.toLowerCase().includes(q));
    return notes; }

  getFilteredNotes() {
    let notes = this.filterNotes();
    switch(this.currentSection) { case 'folder': const id = this.activeFolderId; if(id && defaultFolders.find(f => f.id === id)) notes = notes.filter(n => n.folderId === id); break;
      case 'tag': const tagId = appSearchEl.value || ''; if(tagId) notes = notes.filter(n => (n.tags||[]).map(t=>t.id).includes(tagId)); break;
      case 'bookmark': notes = notes.filter(n => this.store.bookmarks.has(n.id)); break; }
    return notes;
  }

  renderSidebarFolders() { const el = document.getElementById('folder-list'); el.innerHTML = defaultFolders.map(f => `<div class="nav-item ${f.id === this.store.currentFolderId ? 'active' : ''}" data-folder-id="${f.id}"><span style="opacity:.7">${f.icon}</span> ${f.name}</div>`).join('');
    el.querySelectorAll('.nav-item').forEach(el => { el.addEventListener('click', () => { this.currentSection = 'folder'; this.activeFolderId = el.dataset.folderId; this.renderSidebarFolders(); this.filterNotes(); this.renderNoteList(); }); }); }

  renderSidebarCollections() { const el = document.getElementById('collection-list'); if(this.store.bookmarks.size === 0) return; el.innerHTML = defaultCollections.map(c => `<div class="nav-item" style="cursor:pointer" onclick="app.showBookmarks()"><span style="opacity:.7">${c.icon}</span> ${c.name}</div>`).join(''); }

  renderSidebarTags() { const el = document.getElementById('tag-list'); const tags = Object.values(this.store.selectedTags);
    if(tags.length === 0) { el.innerHTML = ''; return; }
    el.innerHTML = tags.map(t => `<div class="nav-item" data-tag-id="${t.id}"><span style="opacity:.7;font-size:14px">${t.icon}</span> <span>${t.name}</span></div>`).join('');
    el.querySelectorAll('.nav-item').forEach(el => { el.addEventListener('click', () => { this.currentSection = 'tag'; appSearchEl.value = el.dataset.tagId; this.filterNotes(); this.renderNoteList(); }); }); }

  renderBookmarkList() { const el = document.getElementById('bookmark-list');
    if(this.store.bookmarks.size === 0) return;
    const bkNotes = this.store.notes.filter(n => this.store.bookmarks.has(n.id));
    if(bkNotes.length === 0) { el.innerHTML = '<div class="nav-item" style="color:var(--text-muted)">No bookmarks yet</div>'; return; }
    el.innerHTML = bkNotes.map(n => `<div class="nav-item active" data-note-id="${n.id}"><span>🔖</span> <span>${n.title || 'Untitled'}</span></div>`).join('');
    el.querySelectorAll('.nav-item').forEach(el => { el.addEventListener('click', () => this.openNote(el.dataset.noteId)); }); }

  renderNoteList() { const notes = this.getFilteredNotes(); const list = document.getElementById('note-list');
    if(list) list.innerHTML = notes.map((n, i) => `<div class="nav-item ${currentNoteId === n.id ? 'active' : ''}" data-note-index="${i}"><span>${this.store.bookmarks.has(n.id) ? '⭐' : '⬜'}<span style="opacity:.6;margin-left:4px;font-size:12px">●</span></span> <div><div style="font-weight:500">${n.title || 'Untitled'}</div><div style="font-size:12px;color:var(--text-muted)">${formatDate(n.updatedAt)}${n.folderId ? ` · ${defaultFolders.find(f=>f.id===n.folderId)?.name}` : ''}</div></div></div>`).join('');
    list.querySelectorAll('.nav-item').forEach(el => { el.addEventListener('click', () => this.openNote(appStore.notes[Number(el.dataset.noteIndex)]?.id || null)); });
  }

  showContextMenu(e, source) { const menu = document.getElementById('ctxMenu'); menu.innerHTML = '';
    if(source === 'sidebar') {
      menu.innerHTML = `<div class="ctx-item" data-action="new-note"><span>📝</span> New Note</div><div class="ctx-item" data-action="create-folder"><span>📁</span> Create Folder</div>`;
    } else if(source === 'editor') {
      menu.innerHTML = `<div class="ctx-item" data-action="bookmark"><span>${this.store.bookmarks.has(currentNoteId) ? '★' : '☆'}<span style="opacity:.7">Bookmark</span></div><div class="ctx-item" data-action="copy-title"><span>📋</span> Copy Title</div><div class="ctx-item" data-action="export"><span>📤</span> Export Note</div>`;
    }
    menu.classList.add('open');
    const pos = source === 'sidebar' ? { left: e.pageX - 40, top: e.pageY } : { left: e.pageX + 120, top: e.pageY };
    Object.assign(menu.style, { left: `${pos.left}px`, top: `${pos.top}px` });

    menu.querySelectorAll('.ctx-item').forEach(el => { el.addEventListener('click', () => { this.hideCtxMenu(); const action = el.dataset.action; if(action === 'new-note') { e.preventDefault(); this.openNewNote(); } else if(action === 'create-folder') { const name = prompt('Folder name:'); if(name) { const id = 'folder-' + generateId().slice(2); defaultFolders.push({id, name, icon:'📁', color:'#6c5ce7'}); this.renderSidebarFolders(); } } else if(action === 'bookmark') { this.store.toggleBookmark(currentNoteId); this.renderBookmarkList(); } else if(action === 'copy-title') { navigator.clipboard.writeText(this.activeNote.title).then(() => alert('Title copied!')); } else if(action === 'export') { const blob = new Blob([this.activeNote.content], {type:'text/markdown'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = this.activeNote.id + '.md'; a.click(); } });
    });
  }

  hideCtxMenu() { const menu = document.getElementById('ctxMenu'); menu.classList.remove('open'); setTimeout(() => { if(!menu.matches(':hover')) menu.innerHTML = ''; }, 200); }

  openNewNote() { appStore.addNote(); this.openModal({ title: 'New Note', folderId: this.store.currentFolderId || 'inbox' }); }

  openModal(opts) { const m = document.getElementById('modalOverlay'); document.getElementById('modalTitle').textContent = opts.title;
    document.getElementById('modalFolderSelect').innerHTML = defaultFolders.map(f => `<option value="${f.id}" ${opts.folderId === f.id ? 'selected' : ''}>${f.icon} ${f.name}</option>`).join('');
    document.getElementById('modalTitleInput').value = opts.title; document.getElementById('modalTagsInput').value = '';
    m.classList.add('active'); setTimeout(() => { document.getElementById('modalTitleInput').focus(); }, 300);

    const saveBtn = document.getElementById('modalSaveBtn'); saveBtn.addEventListener('click', () => {
      const title = document.getElementById('modalTitleInput').value, folderId = document.getElementById('modalFolderSelect').value;
      let tags = [];
      if(document.getElementById('modalTagsInput').value) { tags = (document.getElementById('modalTagsInput').value || '').split(',').map(t => t.trim()).filter(Boolean); tags.forEach(t => appStore.addTag(t)); }
      const data = { title, folderId, content: '', tags };
      appStore.addNote(data); this.closeModal(); this.openNote(appStore.notes[0].id); this.scrollToEditor();
    });

    // Close on overlay click
    m.addEventListener('click', e => { if(e.target === m) this.closeModal(); });
    setTimeout(() => document.getElementById('modalTitleInput').focus(), 400);
  }

  closeModal() { document.getElementById('modalOverlay').classList.remove('active'); const btn = document.getElementById('modalSaveBtn'); if(btn) btn.removeEventListener('click', null); }

  openNote(id) { if(!id) return; currentNoteId = id; this.store.currentFolderId = appStore.notes.find(n => n.id === id)?.folderId || null;
    const note = appStore.notes.find(n => n.id === id); if(!note) return this.showEmptyState();

    // Hide empty state, show editor
    document.getElementById('emptyState').classList.add('hidden');
    document.getElementById('editorWrapper').classList.remove('hidden');
    this.renderNoteList();

    // Update metadata
    const folder = defaultFolders.find(f => f.id === note.folderId);
    document.getElementById('metaFolder').textContent = folder ? `${folder.icon} ${folder.name}` : 'Unsorted';
    const tagsDisplay = (note.tags || []).map(t => t.name).join(', ') || '';
    document.getElementById('metaTags').innerHTML = tagsDisplay ? tagsDisplay.split(',').map(n => `<span style="display:inline-block;margin:2px 6px 0 0;padding:1px 8px;background:var(--bg-tertiary);border-radius:99px;font-size:12px">${n}</span>`).join('') : '';
    document.getElementById('metaDate').textContent = `${formatDate(note.updatedAt)} ${formatTime(note.createdAt)}`;

    // Set input values
    setTimeout(() => { document.getElementById('titleInput').value = note.title; document.getElementById('contentInput').value = note.content; }, 50);
  }

  setEditorMode(mode) { document.querySelectorAll('.toolbar-btn').forEach(b => b.classList.toggle('active', mode === 'markdown' ? b.onclick.toString().includes('markdown') : false)); }

  scrollToEditor() { setTimeout(() => document.getElementById('editorWrapper').scrollIntoView({ behavior:'smooth' }), 100); }

  onContentChange() { const note = appStore.notes.find(n => n.id === currentNoteId); if(!note) return;
    appStore.updateNote(currentNoteId, { content: document.getElementById('contentInput').value }); this.renderNoteList();
    // Update right panel tags
    this.updateRightPanel(); }

  updateNoteTitle() { const note = appStore.notes.find(n => n.id === currentNoteId); if(!note) return;
    appStore.updateNote(currentNoteId, { title: document.getElementById('titleInput').value }); this.renderNoteList(); }

  deleteCurrentNote() { if(!currentNoteId || !confirm('Delete this note?')) return; appStore.deleteNote(currentNoteId);
    const idx = appStore.notes.findIndex(n => n.id === currentNoteId); if(idx >= 0) { sessionStorage.setItem('lastNoteId', appStore.notes[idx+1]?.id || null); } else { sessionStorage.removeItem('lastNoteId'); }
    this.store.currentFolderId = appStore.notes.find(n => n.id === currentNoteId)?.folderId || defaultFolders[0].id;
    if(appStore.notes.length <= 1) this.showEmptyState(); else this.openNote(appStore.notes[Math.max(0, idx-1)].id); }

  updateRightPanel() { const note = appStore.notes.find(n => n.id === currentNoteId); if(!note || !document.getElementById('rightPanel').classList.contains('open')) return;
    const tagsEl = document.getElementById('noteTags'); if(note.tags && note.tags.length > 0) { tagsEl.innerHTML = note.tags.map(t => `<div class="tag-chip" data-tag-id="${t.id}"><span style="font-size:13px">${t.icon}</span> ${t.name}<span class="tag-remove">✕</span></div>`).join(''); } else { tagsEl.innerHTML = '<div style="color:var(--text-muted);font-size:13px;padding-top:8px;">Add tags to organize your notes</div>'; }
    tagsEl.querySelectorAll('.tag-chip').forEach(el => { el.addEventListener('click', () => { const tagId = el.dataset.tagId; if(note.tags) note.tags = note.tags.filter(t => t.id !== tagId); appStore.updateNote(currentNoteId, { tags: note.tags }); if(tagId in this.store.selectedTags) delete this.store.selectedTags[tagId]; }); });
    tagsEl.querySelectorAll('.tag-remove').forEach(el => { el.addEventListener('click', e => { e.stopPropagation(); const chip = el.closest('.tag-chip'); if(chip) tagsEl.querySelector('.tag-chip').click(); }); }); }

  setupSearch() { const input = document.getElementById('searchInput'); let timer;
    input.addEventListener('input', () => clearTimeout(timer), true);
    input.addEventListener('input', () => { clearTimeout(timer); timer = setTimeout(() => { this.filterNotes(); this.renderNoteList(); }, 150); }, false); }

  showBookmarks() { currentSection = 'bookmark'; const list = document.getElementById('note-list'); if(list) list.innerHTML = this.getFilteredNotes().map((n, i) => `<div class="nav-item ${currentNoteId === n.id ? 'active' : ''}" data-note-index="${i}"><span>🔖</span> <span>${n.title || 'Untitled'}</span></div>`).join('');
    list.querySelectorAll('.nav-item').forEach(el => { el.addEventListener('click', () => this.openNote(appStore.notes[Number(el.dataset.noteIndex)]?.id)); }); }

  showSection(name) { currentSection = name; const list = document.getElementById('note-list'); if(list) list.innerHTML = this.getFilteredNotes().map((n, i) => `<div class="nav-item ${currentNoteId === n.id ? 'active' : ''}" data-note-index="${i}"><span>${this.store.bookmarks.has(n.id) ? '⭐' : '⬜'}<span style="opacity:.6;margin-left:4px;font-size:12px">●</span></span> <div><div style="font-weight:500">${n.title || 'Untitled'}</div><div style="font-size:12px;color:var(--text-muted)">${formatDate(n.updatedAt)}${n.folderId ? ` · ${defaultFolders.find(f=>f.id===n.folderId)?.name}` : ''}</div></div></div>`).join('');
    list.querySelectorAll('.nav-item').forEach(el => { el.addEventListener('click', () => this.openNote(appStore.notes[Number(el.dataset.noteIndex)]?.id)); }); }

  // Context menu on right click of note in editor
  showEditorContextMenu() { const menu = document.getElementById('ctxMenu'); const pos = { left: window.innerWidth - 160, top: (document.querySelector('.content-area')?.offsetTop || 0) + 80 };
    Object.assign(menu.style, { ...pos, 'top': `${pos.top}px`, left: `${pos.left}px` }); menu.classList.add('open'); }

  updateNoteTags() { const note = appStore.notes.find(n => n.id === currentNoteId); if(!note) return;
    const input = document.getElementById('modalTagsInput');
    // Rebuild tag chips from note's tags
    const tagsEl = document.getElementById('noteTags'); tagsEl.innerHTML = '';
  }
}

// ======================== GLOBALS ========================
const appStore = new AppStore();
let currentNoteId = null; let currentSection = 'all-notes'; let activeFolderId = null;
let appSearchEl = document.createElement('input'); // alias for searchInput reference
appStore.searchQuery = '';

function getAccentColorForHeading(level) { return ['#6c5ce7','#e17055','#fdcb6e','#00b894','#74b9ff','#a29bfe'][(level-1)%6]; }

// ======================== INIT ========================
const app = new AppUI(appStore);
app.init();
</script>
</body>
</html>
<!-- agent-meta {"model":"ornith-1.0-9b","provider":"lmstudio","persona":"full","sessionId":"830f03cc-e37c-4395-a7fa-c3407359a844","tokensIn":493836,"tokensOut":19915,"tokensTotal":513751,"turns":19,"toolCalls":18,"failedToolCalls":0,"timestamp":"2026-07-13T17:32:05.086Z"} -->