Notes
ornith 1.0 9b · composer

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 76,948 bytes · SHA-256 5bb1ab5fea2f
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Notes</title>
<style>
/* ── css: note-cards ── */
/* Note Card */
.note-card { width: 340px; background: var(--bg-secondary); border-radius: var(--radius-md); padding: 20px; box-shadow: var(--shadow-sm); cursor: pointer; transition: all .35s cubic-bezier(.4,0,.2,1); border: 1px solid transparent; position: relative; overflow: hidden; }
.note-card::before { content: ''; position: absolute; top: 0; left: 0; width: 4px; height: 100%; background: var(--accent); opacity: 0; transition: opacity .3s ease; }
.note-card:hover::before { opacity: 1; }
.note-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); border-color: rgba(93,93,93,.12); }
.note-card.selected { border-color: rgba(93,93,93,.25); box-shadow: 0 0 0 2px rgba(93,93,93,.1), var(--shadow-md); }
.card-title { font-size: 16px; font-weight: 600; color: var(--text-primary); margin-bottom: 8px; line-height: 1.4; transition: color .2s ease; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.note-card:hover .card-title { color: var(--accent); }
.card-preview { font-size: 13px; color: var(--text-secondary); line-height: 1.6; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin-bottom: 8px; }
.card-meta { display: flex; align-items: center; justify-content: space-between; margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border); font-size: 12px; color: var(--text-tertiary); }
.card-tags span { display: inline-block; background: rgba(93,93,93,.08); color: var(--accent); padding: 2px 8px; border-radius: 4px; font-size: 11px; margin-left: 6px; }
/* Star button */
.card-star { position: absolute; top: 14px; left: 14px; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; border: none; background: transparent; color: var(--text-tertiary); font-size: 18px; cursor: pointer; transition: all .2s ease; opacity: 0; z-index: 2; }
.note-card:hover .card-star { opacity: 1; }
.card-star:hover { background: var(--accent-light); color: var(--accent); transform: scale(1.15); }
/* Grid vs List */
.list-toolbar ~ .note-grid.grid-mode { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; padding: 32px; align-content: start; }
.list-toolbar ~ .note-grid.list-mode { grid-template-columns: 1fr; gap: 16px; padding: 24px; align-items: stretch; }
.note-card.list-item { cursor: default; transition: background var(--transition); }
.note-card.list-item:hover { background: var(--accent-light); border-color: transparent; box-shadow: none; transform: none; }
/* Delete Button */
.note-card .delete-btn { position: absolute; top: 14px; right: 14px; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; border: none; background: transparent; color: var(--text-tertiary); font-size: 16px; cursor: pointer; border-radius: 50%; transition: all .2s ease; opacity: 0; z-index: 2; }
.note-card:hover .delete-btn { opacity: 1; }
.note-card .delete-btn:hover { background: #fee2e2; color: #ef4444; transform: scale(1.1); }
/* Welcome / Empty State */
.empty-state, .welcome-state, .single-note-view { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 60px; text-align: center; animation: page-in .45s cubic-bezier(.4,0,.2,1); }
@keyframes page-in { from { opacity: 0; transform: scale(.95); } to { opacity: 1; transform: scale(1); } }
.empty-state svg, .welcome-state h1 { width: 48px; height: 48px; color: var(--text-tertiary); margin-bottom: 24px; opacity: .6; transition: all .3s ease; }
.welcome-state h1:hover, .empty-state svg:hover { opacity: 1; transform: scale(1.05); }
.btn-primary { padding: 12px 28px; border-radius: var(--radius-sm); background: var(--accent); color: white; border: none; font-size: 14px; font-weight: 500; cursor: pointer; transition: all .3s ease; }
.btn-primary:hover { background: #4a6ce6; transform: translateY(-2px); box-shadow: 0 8px 16px rgba(92,124,250,.2); }
.btn-ghost { padding: 12px 28px; border-radius: var(--radius-sm); background: none; color: var(--text-secondary); border: 1px solid var(--border); font-size: 14px; cursor: pointer; transition: all .3s ease; margin-left: 12px; }
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); background: rgba(92,124,250,.05); transform: translateY(-2px); }
.btn-lg { padding: 16px 32px !important; font-size: 15px !important; }
/* Toast */
.toast-container { position: fixed; bottom: 24px; right: 24px; display: flex; flex-direction: column; gap: 8px; z-index: 9999; }
.toast { padding: 12px 20px; background: var(--bg-secondary); border-radius: var(--radius-sm); box-shadow: var(--shadow-lg); color: var(--text-primary); font-size: 14px; animation: toast-in .3s ease; display: flex; align-items: center; gap: 8px; }
@keyframes toast-in { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
/* Card Entry Animation */
.note-card.new-entry { animation: card-in .4s ease both; }
@keyframes card-in {
from { opacity: 0; transform: translateX(-20px) scale(.95); }
to { opacity: 1; transform: translateX(0) scale(1); }
}
/* List mode spacing */
.list-mode .note-card { padding: 16px; }
/* ── css: editor ── */
/* Editor Container */
.editor-container { flex: 1; display: flex; flex-direction: column; background: var(--bg-secondary); }
.editor-header { display: flex; align-items: center; gap: 16px; padding: 20px 32px; border-bottom: 1px solid var(--border); min-height: 72px; }
.title-input { flex: 1; border: none; outline: none; background: none; font-size: 24px; font-weight: 700; color: var(--text-primary); padding: 8px 0; }
.editor-meta { font-size: 13px; color: var(--text-tertiary); white-space: nowrap; }
/* Editor Toolbar */
.editor-toolbar { display: flex; align-items: center; gap: 4px; padding: 6px 32px; border-bottom: 1px solid var(--border); background: rgba(255,255,255,.8); backdrop-filter: blur(10px); overflow-x: auto; }
.editor-format { display: flex; align-items: center; justify-content: center; width: 36px; height: 34px; border: none; background: none; color: var(--text-tertiary); font-size: 14px; cursor: pointer; border-radius: 6px; transition: var(--transition); white-space: nowrap; }
.editor-format:hover { color: var(--accent); background: var(--accent-light); }
.format-divider { width: 1px; height: 20px; background: var(--border); margin: 0 8px; }
/* Editor Body */
.editor-body { flex: 1; padding: 32px 40px; overflow-y: auto; line-height: 1.75; font-size: 16px; outline: none; color: var(--text-primary); min-height: 400px; max-width: 900px; margin: 0 auto; width: 100%; }
.editor-body h1 { font-size: 32px; font-weight: 700; line-height: 1.3; margin-top: 24px; margin-bottom: 12px; letter-spacing: -.3px; color: var(--text-primary); }
.editor-body h2 { font-size: 26px; font-weight: 600; line-height: 1.35; margin-top: 20px; margin-bottom: 8px; }
.editor-body blockquote { border-left: 3px solid var(--accent); padding-left: 16px; color: var(--text-secondary); font-style: italic; margin: 12px 0; background: var(--accent-light); padding: 12px 16px; border-radius: 0 8px 8px 0; }
.editor-body ul, .editor-body ol { padding-left: 24px; }
.editor-body li { margin: 4px 0; }
/* Editor Footer */
.editor-footer { display: flex; align-items: center; justify-content: space-between; padding: 16px 32px; border-top: 1px solid var(--border); background: rgba(255,255,255,.7); }
.word-count { font-size: 13px; color: var(--text-tertiary); }
/* Back Button */
.back-btn { display: flex; align-items: center; gap: 6px; padding: 8px 16px; border-radius: var(--radius-sm); background: none; border: none; color: var(--text-secondary); font-size: 14px; cursor: pointer; transition: var(--transition); }
.back-btn:hover { color: var(--accent); background: var(--accent-light); }
/* Note Card in Single View */
.note-card.full-view { width: 100%; max-width: 900px; box-shadow: none; border: none; padding: 0; cursor: default; animation: card-in .3s ease; background: transparent; }
@keyframes card-in { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
/* Scrollbar */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #d1d5db; }
/* Hidden utility */
.hidden { display: none !important; }
/* ── css: theme ── */
:root {
--bg-primary: #fafaf9;
--bg-secondary: #ffffff;
--bg-tertiary: #f5f5f4;
--bg-sidebar: #f8f7fa;
--text-primary: #1c1917;
--text-secondary: #6b7280;
--text-tertiary: #9ca3af;
--accent: #6d5dfc;
--accent-light: #eef0ff;
--accent-dark: #4f3fd6;
--border: #e7e5e4;
--shadow-sm: 0 1px 2px rgba(0,0,0,.06);
--shadow-md: 0 4px 12px rgba(0,0,0,.08);
--shadow-lg: 0 8px 24px rgba(0,0,0,.1);
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--transition: .25s cubic-bezier(.4,0,.2,1);
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: var(--font-sans);
background: linear-gradient(135deg, #fafaf9 0%, #f3f4f6 100%);
color: var(--text-primary);
overflow: hidden;
height: 100vh;
}
.app-shell { display: flex; height: 100vh; width: 100vw; }
/* Subtle background pattern */
body::before {
content: '';
position: fixed;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle at 30% 40%, rgba(93,93,93,.03) 0%, transparent 50%),
radial-gradient(circle at 70% 60%, rgba(109,93,252,.05) 0%, transparent 50%);
pointer-events: none;
z-index: 0;
}
/* Sidebar */
.sidebar {
width: 260px;
background: var(--bg-sidebar);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
padding: 0 16px;
overflow-y: auto;
transition: width .3s ease, box-shadow .3s ease;
z-index: 10;
}
.brand {
display: flex; align-items: center; gap: 12px; padding: 24px 16px 20px; border-bottom: 1px solid var(--border);
}
.logo-icon { width: 32px; height: 32px; color: var(--accent); flex-shrink: 0; }
.brand span { font-size: 18px; font-weight: 700; letter-spacing: -.5px; }
.sidebar-section { padding: 20px 16px 8px; }
.sidebar-btn {
display: flex; align-items: center; gap: 12px; width: 100%; padding: 10px 14px; border: none; background: none; color: var(--text-secondary); font-size: 14px; font-weight: 500; cursor: pointer; border-radius: var(--radius-sm); transition: all .2s ease; text-align: left;
}
.sidebar-btn:hover { background: rgba(93,93,93,.06); color: var(--text-primary); }
.sidebar-btn.active { background: var(--accent-light); color: var(--accent); font-weight: 600; }
.btn-icon { font-size: 18px; line-height: 1; }
/* Search */
.search-box { position: relative; margin: 0 4px 20px; }
.search-box svg { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); width: 16px; height: 16px; color: var(--text-tertiary); }
.search-box input {
width: 100%; padding: 10px 12px 10px 36px; border: 1.5px solid var(--border); background: var(--bg-secondary); border-radius: var(--radius-sm); font-size: 14px; color: var(--text-primary); outline: none; transition: all .2s ease;
}
.search-box input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(93,93,93,.08); }
/* New Note Button */
.new-note-btn { display: flex; align-items: center; gap: 10px; width: 100%; padding: 12px 16px; border: none; background: var(--bg-secondary); border-radius: var(--radius-sm); font-size: 14px; font-weight: 500; color: var(--accent); cursor: pointer; transition: all .2s ease; box-shadow: var(--shadow-sm); }
.new-note-btn:hover { background: var(--accent-light); transform: translateX(4px); }
/* Folders */
.folders-list-container { flex: 1; overflow-y: auto; }
.folder-item { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-radius: var(--radius-sm); cursor: pointer; transition: all .15s ease; font-size: 14px; }
.folder-item:hover { background: rgba(93,93,93,.06); }
.folder-item.active { background: var(--accent-light); color: var(--accent); font-weight: 500; }
.folder-icon { width: 20px; height: 20px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 14px; }
.folder-count { margin-left: auto; color: var(--text-tertiary); font-size: 12px; }
.tag-item { cursor: pointer; transition: all .15s ease !important; padding: 6px 10px !important; border-radius: 12px !important; margin-bottom: 4px !important; background: #f5f5f4 !important; color: var(--text-secondary) !important; }
.tag-item:hover { opacity: .7; background: #e5e7eb !important; }
/* Sidebar Footer */
.sidebar-footer { padding: 16px; border-top: 1px solid var(--border); }
.storage-info { font-size: 12px; color: var(--text-tertiary); }
.main-area { flex: 1; display: flex; overflow: hidden; z-index: 5; position: relative; }
/* Note Grid */
.note-list-container { flex: 1; display: flex; flex-direction: column; background: transparent !important; }
.list-toolbar { display: flex; align-items: center; gap: 4px; padding: 12px 32px; border-bottom: 1px solid var(--border); }
.toolbar-btn { display: flex; align-items: center; justify-content: center; width: 36px; height: 34px; border: none; background: none; color: var(--text-tertiary); font-size: 16px; cursor: pointer; border-radius: var(--radius-sm); transition: all .2s ease; }
.toolbar-btn:hover { color: var(--accent); background: var(--accent-light); }
.toolbar-btn.active { color: var(--accent); background: var(--accent-light); font-weight: 600; }
.note-grid { flex: 1; overflow-y: auto !important; padding: 32px; align-content: start; }
.list-mode .note-grid { grid-template-columns: 1fr; gap: 16px; padding: 24px; align-items: stretch; }
/* Note Card */
.note-card {
width: 340px; background: var(--bg-secondary); border-radius: var(--radius-md); padding: 20px; box-shadow: var(--shadow-sm); cursor: pointer; transition: all .35s cubic-bezier(.4,0,.2,1); border: 1px solid transparent; position: relative; overflow: hidden;
}
.note-card::before { content: ''; position: absolute; top: 0; left: 0; width: 4px; height: 100%; background: var(--accent); opacity: 0; transition: opacity .3s ease; }
.note-card:hover::before { opacity: 1; }
.note-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); border-color: rgba(93,93,93,.12); }
.note-card.selected { border-color: rgba(93,93,93,.25); box-shadow: 0 0 0 2px rgba(93,93,93,.1), var(--shadow-md); }
.card-title { font-size: 16px; font-weight: 600; color: var(--text-primary); margin-bottom: 8px; line-height: 1.4; transition: color .2s ease; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.note-card:hover .card-title { color: var(--accent); }
.card-preview { font-size: 13px; color: var(--text-secondary); line-height: 1.6; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin-bottom: 8px; }
.card-meta { display: flex; align-items: center; justify-content: space-between; margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border); font-size: 12px; color: var(--text-tertiary); }
.card-tags span { display: inline-block; background: rgba(93,93,93,.08); color: var(--accent); padding: 2px 8px; border-radius: 4px; font-size: 11px; margin-left: 6px; }
.note-card.new-entry { animation: card-in .5s cubic-bezier(.4,0,.2,1) both; }
@keyframes card-in { from { opacity: 0; transform: translateX(-20px) scale(.95); } to { opacity: 1; transform: translateX(0) scale(1); } }
.delete-btn { position: absolute; top: 14px; right: 14px; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; border: none; background: transparent; color: var(--text-tertiary); font-size: 16px; cursor: pointer; border-radius: 50%; transition: all .2s ease; opacity: 0; }
.note-card:hover .delete-btn { opacity: 1; }
.delete-btn:hover { background: #fee2e2; color: #ef4444; transform: scale(1.1); }
/* Editor */
.editor-container { flex: 1; display: flex; flex-direction: column; background: var(--bg-secondary); overflow: hidden; z-index: 5; position: relative; }
.editor-header { display: flex; align-items: center; gap: 20px; padding: 24px 36px; border-bottom: 1px solid var(--border); box-shadow: var(--shadow-sm); }
.back-btn { display: flex; align-items: center; gap: 8px; padding: 8px 16px; border-radius: var(--radius-sm); background: none; border: none; color: var(--text-secondary); font-size: 14px; cursor: pointer; transition: all .2s ease; }
.back-btn:hover { color: var(--accent); background: var(--accent-light); }
.title-input { flex: 1; border: none; outline: none; background: none; font-size: 28px; font-weight: 700; color: var(--text-primary); padding: 4px 0; }
.editor-meta { font-size: 13px; color: var(--text-tertiary); white-space: nowrap; }
/* Editor Toolbar */
.editor-toolbar { display: flex; align-items: center; gap: 4px; padding: 6px 28px; border-bottom: 1px solid var(--border); background: rgba(255,255,255,.75); backdrop-filter: blur(12px); overflow-x: auto; }
.editor-format { display: flex; align-items: center; justify-content: center; width: 38px; height: 36px; border: none; background: none; color: var(--text-tertiary); font-size: 15px; cursor: pointer; border-radius: var(--radius-sm); transition: all .2s ease; white-space: nowrap; }
.editor-format:hover { color: var(--accent); background: var(--accent-light); }
.format-divider { width: 1px; height: 20px; background: var(--border); margin: 0 8px; }
/* Editor Body */
.editor-body { flex: 1; padding: 40px 56px; overflow-y: auto !important; line-height: 1.8; font-size: 16px; outline: none; color: var(--text-primary); min-height: 300px; max-width: 920px; margin: 0 auto; width: 100%; }
.editor-body-empty { cursor: text; transition: all .3s ease; }
.editor-body:hover + .editor-block, .editor-block[contenteditable]:first-of-type:not(:focus) ~ * { display: none; }
/* Editor Footer */
.editor-footer { display: flex; align-items: center; justify-content: space-between; padding: 16px 28px; border-top: 1px solid var(--border); background: rgba(250,250,249,.75); }
.word-count { font-size: 13px; color: var(--text-tertiary); }
.btn-save { padding: 8px 20px; border-radius: var(--radius-sm); background: var(--accent); color: white; border: none; font-size: 14px; font-weight: 500; cursor: pointer; transition: all .2s ease; }
.btn-save:hover { background: #4f3fd6; transform: translateY(-1px); box-shadow: 0 4px 8px rgba(93,93,93,.2); }
/* Empty / Welcome States */
.empty-state, .welcome-state, .single-note-view { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 60px; text-align: center; animation: page-in .45s cubic-bezier(.4,0,.2,1); z-index: 10 !important; position: relative; background: var(--bg-primary); }
@keyframes page-in { from { opacity: 0; transform: scale(.98); } to { opacity: 1; transform: scale(1); } }
.empty-state svg, .welcome-state h1 { width: 56px; height: 56px; color: var(--text-tertiary); margin-bottom: 24px; opacity: .7; transition: all .3s ease; }
.welcome-state h1:hover, .empty-state svg:hover { opacity: 1; transform: scale(1.05); }
.btn-primary { padding: 14px 32px; border-radius: var(--radius-sm); background: var(--accent); color: white; border: none; font-size: 15px; font-weight: 500; cursor: pointer; transition: all .3s ease; }
.btn-primary:hover { background: #4f3fd6; transform: translateY(-2px); box-shadow: 0 8px 16px rgba(93,93,93,.2); }
.btn-ghost { padding: 14px 32px; border-radius: var(--radius-sm); background: none; color: var(--text-secondary); border: 1.5px solid var(--border); font-size: 15px; cursor: pointer; transition: all .3s ease; margin-left: 16px; }
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); background: rgba(93,93,93,.04); transform: translateY(-2px); }
/* Toast */
.toast-container { position: fixed; bottom: 28px; right: 28px; display: flex; flex-direction: column; gap: 8px; z-index: 9999; }
.toast { padding: 14px 20px; background: var(--bg-secondary); border-radius: var(--radius-sm); box-shadow: var(--shadow-lg); color: var(--text-primary); font-size: 14px; animation: toast-in .35s ease; display: flex; align-items: center; gap: 8px; }
@keyframes toast-in { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
/* Scrollbar */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #d4d0cb; }
/* Hidden utility */
.hidden { display: none !important; }
/* Mobile sidebar toggle */
.sidebar-toggle { display: none; position: fixed; top: 12px; left: 12px; z-index: 100; width: 36px; height: 36px; border-radius: var(--radius-sm); background: white; border: none; box-shadow: var(--shadow-md); cursor: pointer; color: var(--text-primary); font-size: 20px; }
@media (max-width: 768px) { .sidebar-toggle { display: flex; align-items: center; justify-content: center; } .sidebar { position: fixed; left: -260px; z-index: 50; box-shadow: var(--shadow-lg); height: 100vh; } }
/* ── css: blocks ── */
/* Block types in editor */
.editor-block { position: relative; margin-bottom: 2px; }
.editor-block--heading1 { padding-top: 20px; margin-bottom: 6px; }
.editor-block--heading2 { padding-top: 14px; margin-bottom: 4px; }
/* Empty state indicator */
.editor-body-empty { cursor: text; transition: all .3s ease; }
.editor-body:hover + .editor-block,
.editor-block[contenteditable]:first-of-type:not(:focus) ~ * { display: none; }
/* Block icons/indicators */
.block-bullet { font-size: 24px; color: #9ca3af !important; flex-shrink: 0; line-height: 1.8; user-select: none; }
.check-box { transition: all .2s ease; background: white; border: 2px solid #d1d5db; display: flex; align-items: center; justify-content: center; font-size: 14px; color: white; width: 20px; height: 20px; border-radius: 6px; }
.check-box.checked { background: #6d5dfc !important; border-color: #6d5dfc !important; }
/* Code block */
.editor-block--code pre { margin: 0; padding-top: 12px; background: #1e293b; color: #e2e8f0; border-radius: 8px; font-family: 'Fira Code','Cascadia Code',monospace !important; font-size: 14px; line-height: 1.7; overflow-x: auto; }
.editor-block--code pre [contenteditable] { white-space: pre-wrap; word-break: break-word; outline: none; border-radius: 8px; padding-top: 0; }
/* Divider */
.editor-block--divider hr { border: none; border-top: 2px solid #e7e5e4; margin: 16px 0; width: 100%; }
/* Callout blocks */
.editor-block--callout { padding-bottom: 4px; }
/* Block selection indicator (click to add after) */
.editor-block:hover::after {
content: '⬇';
position: absolute;
bottom: -20px;
left: 50%;
transform: translateX(-50%);
font-size: 14px;
color: var(--accent);
opacity: .6;
}
/* Block hover highlight */
.editor-block:hover { background: #fafafa !important; }
/* Scrollbar styling for code blocks */
::-webkit-scrollbar-thumb.code-block-scroll { background: #334155 !important; border-radius: 0px !important; width: 8px !important; }
</style>
</head>
<body>
<div class="app-shell">
<div id="sidebar" class="sidebar">
<div class="brand">
<svg class="logo-icon" viewBox="0 0 24 24"><path d="M3 19h18v-2H3v2zm0-5h18v-2H3v2zm0-5h18V7H3v2z"></path></svg>
<span>NoteForge</span>
</div>
<div class="sidebar-section">
<button class="sidebar-btn active" data-tab="all-notes">
<span class="btn-icon">◷</span><span>All Notes</span>
</button>
<button class="sidebar-btn" data-tab="favorites">
<span class="btn-icon">♡</span><span>Favorites</span>
</button>
</div>
<div class="search-box">
<svg class="search-icon" viewBox="0 0 24 24"><path d="M12 5c-3.9 0-7 3.1-7 7s3.1 7 7 7 7-3.1 7-7-3.1-7-7-7zm0 11c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"></path></svg>
<input type="text" id="search-input" placeholder="Search notes...">
</div>
<div class="sidebar-section">
<button class="new-note-btn" id="btn-new-note">
<span class="btn-icon">+</span><span>New Note</span>
</button>
</div>
<div class="sidebar-section folders-list-container">
<div id="folders-list"></div>
</div>
<div class="sidebar-footer">
<div class="storage-info">124 notes · 8 folders</div>
</div>
</div>
<div id="main-area" class="main-area">
<div class="note-list-container" id="note-list-view">
<div class="list-toolbar">
<button class="toolbar-btn active" data-list-mode="grid">◻</button>
<button class="toolbar-btn" data-list-mode="list">▾</button>
<button class="toolbar-btn" id="btn-sort-asc">↑</button>
<button class="toolbar-btn" id="btn-sort-desc">↓</button>
</div>
<div class="note-grid" id="note-grid"></div>
</div>
<div class="editor-container hidden" id="editor-view">
<div class="editor-header">
<button class="back-btn" id="btn-back-to-list">← Notes</button>
<input type="text" id="note-title" placeholder="Untitled Note" class="title-input">
<span class="editor-meta"></span>
</div>
<div class="editor-toolbar">
<button class="toolbar-btn editor-format" data-format="bold"><b>B</b></button>
<button class="toolbar-btn editor-format" data-format="italic"><i>I</i></button>
<button class="toolbar-btn editor-format" data-format="underline"><u>U</u></button>
<button class="toolbar-btn editor-format" data-format="strikethrough"><s>S</s></button>
<div class="format-divider"></div>
<button class="toolbar-btn editor-format" data-format="heading">H1</button>
<button class="toolbar-btn editor-format" data-format="h2">H2</button>
<button class="toolbar-btn editor-format" data-format="quote">"</button>
<div class="format-divider"></div>
<button class="toolbar-btn editor-format" data-format="bullet-list">• List</button>
<button class="toolbar-btn editor-format" data-format="numbered-list"># List</button>
<div class="format-divider"></div>
<button class="toolbar-btn editor-format" data-format="link">🔗</button>
</div>
<div id="editor-body" contenteditable="true" class="editor-body" spellcheck="false"></div>
<div class="editor-footer">
<span class="word-count">0 words · 0 characters</span>
<button class="btn-save" id="btn-save-note">Save Note</button>
</div>
</div>
<div class="empty-state hidden" id="empty-view">
<svg viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-6 14H7v-4l3.5 2L18 9v8z"></path></svg>
<p>No notes yet</p>
<button class="btn-primary" id="btn-empty-new">Create Your First Note</button>
</div>
<div class="welcome-state hidden" id="welcome-view">
<h1>Welcome to NoteForge</h1>
<p>A beautiful place to capture your thoughts and organize your ideas.</p>
<div class="welcome-actions">
<button class="btn-primary btn-lg" id="btn-welcome-new">+ New Note</button>
<button class="btn-ghost btn-lg" id="btn-welcome-browse">Browse Notes</button>
</div>
</div>
<div class="single-note-view hidden" id="single-note-view">
<div class="note-card" id="note-card"></div>
</div>
</div>
<div class="toast-container" id="toast-container"></div>
</div>
<script>
'use strict';
// ── let: activeNote ──
let activeNote;
// ── let: currentTab ──
let currentTab = 'all-notes';
// ── let: listMode ──
let listMode = 'grid';
// ── let: sortDir ──
let sortDir = 'desc';
// ── function: genId ──
function genId() { return ++NOTE_DB.nextId; }
// ── function: formatDate ──
function formatDate(date) { return date.toLocaleDateString('en-US', { month:'short', day:'numeric', year: 'numeric' }); }
// ── function: formatTime ──
function formatTime(date) { return date.toLocaleTimeString('en-US', { hour:'2-digit', minute:'2-digit' }); }
// ── function: showToast ──
function showToast(msg, timeout = 3000) {
const c = document.getElementById('toast-container');
const t = document.createElement('div');
t.className = 'toast';
t.textContent = msg;
c.appendChild(t);
setTimeout(() => { if (t.parentNode) t.parentNode.removeChild(t); }, timeout + 500);
}
// ── function: updateWordCount ──
function updateWordCount(text) {
const words = text.trim() ? text.split(/\s+/).length : 0;
document.querySelector('.word-count').textContent = `${words} word${words !== 1 ? 's' : ''} · ${text.replace(/<[^>]+>/g, '').length || 0} characters`;
}
// ── function: execFormat ──
function execFormat(format, value) {
if (format === 'link') { const url = prompt('Enter URL:', ''); document.execCommand('createLink', false, url); }
else { document.execCommand(format, false, value || null); }
}
// ── function: insertMarkdown ──
function insertMarkdown(text) { document.execCommand('insertText', false, text.replace(/[*#`_~]/g, '')); }
// ── const: NoteStore ──
// === NOTE STORE & BLOCKS ===
const NoteStore = {
create() { const now = new Date(); return NOTE_DB.notes.unshift({ id: genId(), title: '', body: [], folder: 'Inbox', tags: [], createdAt: now, updatedAt: now }); },
get(id) { return NOTE_DB.notes.find(n => n.id === id); },
update(id, data = {}) { const note = this.get(id); if (!note) return null; Object.assign(note, data); note.updatedAt = new Date(); return note; },
delete(id) { NOTE_DB.notes = NOTE_DB.notes.filter(n => n.id !== id); return true; },
search(query) { if (!query) return [...NOTE_DB.notes]; const q = query.toLowerCase(); return NOTE_DB.notes.filter(n => n.title.toLowerCase().includes(q) || (n.body && JSON.stringify(n.body).toLowerCase().includes(q)) || (n.folder && n.folder.toLowerCase().includes(q))); },
sort(arr, dir) { return [...arr].sort((a, b) => { const c = a.title.localeCompare(b.title); return dir === 'asc' ? c : -c; }); },
};
// ── function: renderNoteCard ──
function renderNoteCard(note, fullView = false) {
if (fullView) {
const container = document.getElementById('note-card');
container.className = 'note-card';
var titleEl = document.createElement('div');
titleEl.className = 'card-title';
titleEl.textContent = note.title || 'Untitled Note';
container.appendChild(titleEl);
if (note.tags && note.tags.length > 0) {
var tagsDiv = document.createElement('div');
tagsDiv.style.marginBottom = '12px';
tagsDiv.style.display = 'flex';
tagsDiv.style.flexWrap = 'wrap';
tagsDiv.style.gap = '6px';
note.tags.forEach(function(tag) {
var span = document.createElement('span');
span.textContent = tag;
span.style.cssText = 'background:#eef0ff;color:#6d5dfc;padding:3px 10px;border-radius:20px;font-size:12px;font-weight:500';
tagsDiv.appendChild(span);
});
container.appendChild(tagsDiv);
}
var bodyContainer = document.createElement('div');
bodyContainer.style.cssText = 'margin-top:14px;line-height:1.8;font-size:14px;color:#6b7280';
bodyContainer.textContent = renderBlocksPreview(note.body, 250);
container.appendChild(bodyContainer);
var metaDiv = document.createElement('div');
metaDiv.className = 'card-meta';
var dateSpan = document.createElement('span');
dateSpan.style.cssText = 'color:#9ca3af;font-size:12px';
dateSpan.textContent = formatDate(note.updatedAt);
if (note.tags && note.tags.length > 0) {
var tagLabel = document.createElement('span');
tagLabel.style.cssText = 'color:#9ca3af;font-size:12px;margin-right:6px';
tagLabel.textContent = '#';
metaDiv.appendChild(tagLabel);
if (note.tags.length <= 2) {
var tagSpan = document.createElement('span');
tagSpan.style.cssText = 'color:#9ca3af;font-size:12px';
tagSpan.textContent = note.tags.join(', ');
metaDiv.appendChild(tagSpan);
} else {
var tagCount = document.createElement('span');
tagCount.style.cssText = 'color:#9ca3af;font-size:12px';
tagCount.textContent = note.tags.length + ' tags';
metaDiv.appendChild(tagCount);
}
}
metaDiv.appendChild(dateSpan);
container.appendChild(metaDiv);
var delBtn = document.createElement('button');
delBtn.className = 'delete-btn';
delBtn.innerHTML = '✕';
delBtn.title = 'Delete note';
delBtn.addEventListener('click', function(e) { e.stopPropagation(); deleteCurrentNote(); });
container.appendChild(delBtn);
if (!fullView) {
titleEl.style.cursor = 'pointer';
bodyContainer.style.cursor = 'pointer';
}
} else {
var card = document.createElement('div');
card.className = 'note-card new-entry';
var titleEl = document.createElement('div');
titleEl.className = 'card-title';
titleEl.textContent = note.title || 'Untitled Note';
card.appendChild(titleEl);
if (note.tags && note.tags.length > 0) {
var tagsDiv = document.createElement('div');
tagsDiv.style.marginBottom = '8px';
tagsDiv.style.display = 'flex';
tagsDiv.style.flexWrap = 'wrap';
tagsDiv.style.gap = '4px';
note.tags.slice(0, 3).forEach(function(tag) {
var span = document.createElement('span');
span.textContent = tag;
span.style.cssText = 'background:#eef0ff;color:#6d5dfc;padding:1.5px 7px;border-radius:4px;font-size:11px';
tagsDiv.appendChild(span);
});
card.appendChild(tagsDiv);
}
var bodyContainer = document.createElement('div');
bodyContainer.className = 'card-preview';
bodyContainer.textContent = renderBlocksPreview(note.body, 200);
card.appendChild(bodyContainer);
var metaDiv = document.createElement('div');
metaDiv.className = 'card-meta';
if (note.folder) {
var folderLabel = document.createElement('span');
folderLabel.style.cssText = 'color:#6b7280;font-size:12px;margin-right:4px';
folderLabel.textContent = note.folder;
metaDiv.appendChild(folderLabel);
}
if (note.tags && note.tags.length > 0) {
var tagCount = document.createElement('span');
tagCount.style.cssText = 'color:#9ca3af;font-size:12px';
tagCount.textContent = '#' + note.tags.length;
metaDiv.appendChild(tagCount);
}
var dateSpan = document.createElement('span');
dateSpan.style.cssText = 'color:#9ca3af;font-size:12px';
dateSpan.textContent = formatDate(note.updatedAt);
metaDiv.appendChild(dateSpan);
card.appendChild(metaDiv);
var delBtn = document.createElement('button');
delBtn.className = 'delete-btn';
delBtn.innerHTML = '✕';
delBtn.title = 'Delete note';
delBtn.addEventListener('click', function(e) { e.stopPropagation(); deleteCurrentNote(); });
card.appendChild(delBtn);
var origTitle = titleEl.textContent;
if (!note.title && !note.body.length) {
titleEl.style.cssText += ';color:#9ca3af;font-weight:400;' +
(origTitle !== note.title ? '' : '');
} else {
titleEl.style.cursor = 'pointer';
}
card.style.cursor = 'pointer';
card.addEventListener('click', function() { openEditor(note.id); });
}
if (!fullView) container.replaceWith(card);
}
// ── const: BLOCK_TYPES ──
// === CONSTANTS ===
const BLOCK_TYPES = {
heading1: 'heading1',
heading2: 'heading2',
paragraph: 'paragraph',
todo: 'todo',
checkbox: 'checkbox',
bullet: 'bullet',
numbered: 'numbered',
quote: 'quote',
code: 'code',
divider: 'divider',
callout: 'callout',
};
// ── const: BLOCK_ICONS ──
const BLOCK_ICONS = {
[BLOCK_TYPES.heading1]: 'H1', [BLOCK_TYPES.heading2]: 'H2',
[BLOCK_TYPES.paragraph]: '¶', [BLOCK_TYPES.todo]: '☑',
[BLOCK_TYPES.checkbox]: '⬜', [BLOCK_TYPES.bullet]: '•',
[BLOCK_TYPES.numbered]: '#', [BLOCK_TYPES.quote]: '"',
[BLOCK_TYPES.code]: '</>', [BLOCK_TYPES.divider]: '─',
[BLOCK_TYPES.callout]: '💡',
};
// ── const: BLOCK_LABELS ──
const BLOCK_LABELS = {
[BLOCK_TYPES.heading1]: 'Heading', [BLOCK_TYPES.heading2]: 'Subheading',
[BLOCK_TYPES.paragraph]: 'Text', [BLOCK_TYPES.todo]: 'To-do',
[BLOCK_TYPES.checkbox]: 'Checkbox', [BLOCK_TYPES.bullet]: 'Bullet list',
[BLOCK_TYPES.numbered]: 'Numbered list', [BLOCK_TYPES.quote]: 'Quote',
[BLOCK_TYPES.code]: 'Code block', [BLOCK_TYPES.divider]: 'Divider',
[BLOCK_TYPES.callout]: 'Callout',
};
// ── function: renderBlocks ──
// Block renderer for Notion-style content editing
function renderBlocks(container, blocks) {
container.innerHTML = '';
if (!blocks || blocks.length === 0) {
const empty = document.createElement('div');
empty.className = 'editor-body-empty';
empty.textContent = 'Click or press Enter to start typing...';
empty.style.cssText = 'display:flex;align-items:center;justify-content:center;height:100%;color:#9ca3af;font-size:24px;padding-top:80px;opacity:.5;transition:all .3s;cursor:text;';
container.appendChild(empty);
return;
}
blocks.forEach((block, idx) => {
const el = document.createElement('div');
el.className = 'editor-block editor-block--' + block.type;
el.dataset.blockIdx = block.idx || idx;
switch (block.type) {
case BLOCK_TYPES.heading1:
el.innerHTML = '<div style="font-size:32px;font-weight:700;line-height:1.3;margin-bottom:8px;color:#1c1917">' + escapeHtml(block.text) + '</div>';
break;
case BLOCK_TYPES.heading2:
el.innerHTML = '<div style="font-size:26px;font-weight:600;line-height:1.35;margin-bottom:4px;color:#1c1917">' + escapeHtml(block.text) + '</div>';
break;
case BLOCK_TYPES.paragraph:
el.innerHTML = '<div contenteditable="true" style="line-height:1.8;font-size:16px;padding-top:4px;color:#374151">' + (block.text || '') + '</div>';
break;
case BLOCK_TYPES.todo:
case BLOCK_TYPES.checkbox: {
const checked = block.checked || false;
el.innerHTML = '<label style="display:flex;align-items:center;gap:8px;padding-top:4px;cursor:pointer;font-size:15px;color:#374151" onmousedown="event.preventDefault()" ontouchstart="event.preventDefault()">' +
'<span class="check-box" style="width:20px;height:20px;border-radius:6px;border:2px solid #d1d5db;background:white;display:flex;align-items:center;justify-content:center;font-size:14px;color:#fff;transition:all .2s"></span>' +
'<div contenteditable="true" style="flex:1;outline:none">' + (block.text || '') + '</div></label>';
if (checked) { el.querySelector('.check-box').style.borderColor = '#6d5dfc'; el.querySelector('.check-box').style.backgroundColor = '#6d5dfc'; el.querySelector('.check-box').textContent = '✓'; }
el.addEventListener('click', function() { toggleCheckbox(el, idx); });
break;
}
case BLOCK_TYPES.bullet:
el.innerHTML = '<div style="display:flex;gap:8px;padding-top:4px;font-size:16px;color:#374151" onmousedown="event.preventDefault()">' +
'<span class="block-bullet">•</span><div contenteditable="true" style="flex:1;outline:none;line-height:1.8">' + (block.text || '') + '</div></div>';
break;
case BLOCK_TYPES.numbered: {
el.innerHTML = '<div style="display:flex;gap:6px;padding-top:4px;font-size:16px;color:#374151" onmousedown="event.preventDefault()">' +
'<span class="block-bullet">#</span><div contenteditable="true" style="flex:1;outline:none;line-height:1.8">' + (block.text || '') + '</div></div>';
break;
}
case BLOCK_TYPES.quote: {
el.innerHTML = '<div style="border-left:3px solid #6d5dfc;padding-left:20px;margin-bottom:4px;background:#f0edff;border-radius:0 8px 8px 0">' +
'<span style="color:#9ca3af;font-size:12px;letter-spacing:2px;text-transform:uppercase;">Quote</span>' +
'<div contenteditable="true" style="font-style:italic;color:#6b7280;line-height:1.8;margin-top:8px;padding-bottom:4px">' + (block.text || '') + '</div></div>';
break;
}
case BLOCK_TYPES.code: {
const lang = block.lang || 'text';
el.innerHTML = '<div style="border-radius:8px;background:#1e293b;color:#e2e8f0;padding:16px;font-family:monospace !important;font-size:14px;overflow-x:auto;line-height:1.7">' +
'<span style="color:#9ca3af;font-size:12px;margin-bottom:8px;display:block">' + escapeHtml(lang) + '</span>' +
'<pre contenteditable="true" style="margin:0;white-space:pre-wrap;word-break:break-word">' + (block.text || '') + '</pre></div>';
break;
}
case BLOCK_TYPES.divider:
el.innerHTML = '<hr style="border:none;border-top:2px solid #e7e5e4;margin:16px 0;width:100%">';
break;
case BLOCK_TYPES.callout: {
const icon = block.icon || '💡';
el.innerHTML = '<div style="background:#f8f7ff;border-radius:12px;padding:16px 20px;margin-bottom:4px;display:flex;gap:12px">' +
'<span style="font-size:24px">' + icon + '</span>' +
'<div contenteditable="true" style="flex:1;line-height:1.8;color:#374151;font-size:15px">' + (block.text || '') + '</div></div>';
break;
}
}
el.addEventListener('click', function(e) { e.stopPropagation(); addBlockAfter(container, idx); });
container.appendChild(el);
});
}
// ── function: toggleCheckbox ──
function toggleCheckbox(blockEl, idx) {
const note = activeNote; if (!note || !note.body[idx]) return;
note.body[idx].checked = !note.body[idx].checked;
NOTE_DB.notes[NOTE_DB.notes.indexOf(note)].body = [...note.body];
NoteStore.update(note.id, { body: note.body });
const cb = blockEl.querySelector('.check-box');
if (cb) {
if (note.body[idx].checked) { cb.style.borderColor = '#6d5dfc'; cb.style.backgroundColor = '#6d5dfc'; cb.textContent = '✓'; }
else { cb.style.borderColor = ''; cb.style.backgroundColor = ''; cb.textContent = ''; }
}
}
// ── function: addBlockAfter ──
function addBlockAfter(container, idx) {
const note = activeNote; if (!note || !note.body[idx]) return;
note.body.splice(idx + 1, 0, { type: BLOCK_TYPES.paragraph, text: '', idx: null });
NOTE_DB.notes[NOTE_DB.notes.indexOf(note)].body = [...note.body];
NoteStore.update(note.id, { body: note.body });
renderBlocks(container, note.body);
const newBlock = container.querySelector('.editor-block--' + BLOCK_TYPES.paragraph);
if (newBlock) setTimeout(function() { newBlock.querySelector('[contenteditable]')?.focus(); }, 50);
}
// ── function: renderBlocksPreview ──
// Block preview for note cards
function renderBlocksPreview(blocks, maxChars = 180) {
const texts = blocks.filter(function(b) { return b.type !== BLOCK_TYPES.divider; }).map(function(b) { return b.text || ''; }).join(' ');
let preview = '';
for (var i = 0; i < texts.length && preview.length + texts[i] <= maxChars; i++) {
// Just grab first chunk of text
if (preview.length < maxChars) break;
}
return texts.substring(0, maxChars).replace(/[\n\r]+/g, ' ').trim();
}
// ── const: NOTE_DB ──
const NOTE_DB = { notes: [], folders: ['Inbox', 'Journal', 'Ideas', 'References'], nextId: 0 };
// ── function: escapeHtml ──
function escapeHtml(text) {
return text.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
}
// ── function: renderNoteList ──
// === LIST & SIDEBAR FUNCTIONS ===
function renderNoteList() {
var filtered = currentTab === 'favorites'
? NOTE_DB.notes.filter(function(n) { return n.favorite || (n.tags && n.tags.includes('Favorite')) || n.folder === 'Favorites'; })
: filterNotes();
if (!filtered.length) return; // handled by clearEmptyStates
var container = document.getElementById('note-grid');
filtered.slice(0, 48).forEach(function(note) {
if (container.querySelector('[data-note-id="' + note.id + '"]')) return;
var card = document.createElement('div');
card.className = 'note-card new-entry';
card.dataset.noteId = note.id;
// Title
var titleEl = document.createElement('div');
titleEl.className = 'card-title';
titleEl.textContent = note.title || 'Untitled Note';
card.appendChild(titleEl);
// Tags preview
if (note.tags && note.tags.length > 0) {
var tagsDiv = document.createElement('div');
tagsDiv.style.marginBottom = '8px';
tagsDiv.style.display = 'flex';
tagsDiv.style.flexWrap = 'wrap';
tagsDiv.style.gap = '4px';
note.tags.slice(0, 3).forEach(function(tag) {
var span = document.createElement('span');
span.textContent = tag;
span.style.cssText = 'background:#eef0ff;color:#6d5dfc;padding:1.5px 7px;border-radius:4px;font-size:11px';
tagsDiv.appendChild(span);
});
card.appendChild(tagsDiv);
}
// Block preview text
var bodyContainer = document.createElement('div');
bodyContainer.className = 'card-preview';
bodyContainer.textContent = renderBlocksPreview(note.body, 200);
card.appendChild(bodyContainer);
// Meta row
var metaDiv = document.createElement('div');
metaDiv.className = 'card-meta';
if (note.folder) {
var folderLabel = document.createElement('span');
folderLabel.style.cssText = 'color:#6b7280;font-size:12px;margin-right:4px';
folderLabel.textContent = note.folder;
metaDiv.appendChild(folderLabel);
}
if (note.tags && note.tags.length > 0) {
var tagCount = document.createElement('span');
tagCount.style.cssText = 'color:#9ca3af;font-size:12px';
tagCount.textContent = '#' + note.tags.length;
metaDiv.appendChild(tagCount);
}
var dateSpan = document.createElement('span');
dateSpan.style.cssText = 'color:#9ca3af;font-size:12px';
dateSpan.textContent = formatDate(note.updatedAt);
metaDiv.appendChild(dateSpan);
card.appendChild(metaDiv);
// Delete button
var delBtn = document.createElement('button');
delBtn.className = 'delete-btn';
delBtn.innerHTML = '✕';
delBtn.title = 'Delete note';
delBtn.addEventListener('click', function(e) { e.stopPropagation(); deleteCurrentNote(); });
card.appendChild(delBtn);
// Click to open editor
var origTitle = titleEl.textContent;
if (!note.title && !note.body.length) {
titleEl.style.cssText += ';color:#9ca3af;font-weight:400;' + (origTitle !== note.title ? '' : '');
} else {
titleEl.style.cursor = 'pointer';
}
card.addEventListener('click', function() { openEditor(note.id); });
// Hover handlers for delete button
var dBtn = card.querySelector('.delete-btn');
if (dBtn) {
card.addEventListener('mouseenter', function() { dBtn.style.opacity = '1'; });
card.addEventListener('mouseleave', function() { dBtn.style.opacity = ''; });
}
container.appendChild(card);
});
// Show "show more" link if needed
var maxHeight = filtered.length > 48 ? (container.scrollHeight + 'px') : 'none';
container.style.maxHeight = maxHeight;
}
// ── function: filterNotes ──
function filterNotes() {
switch (currentTab) {
case 'favorites': return NOTE_DB.notes.filter(function(n) { return n.favorite || (n.tags && n.tags.includes('Favorite')) || n.folder === 'Favorites'; });
case 'journal': return NOTE_DB.notes.filter(function(n) { return n.folder === 'Journal' || (n.tags && n.tags.includes('Journal')); });
case 'ideas': return NOTE_DB.notes.filter(function(n) { return n.folder === 'Ideas' || (n.tags && n.tags.includes('Idea')); });
case 'references': return NOTE_DB.notes.filter(function(n) { return n.folder === 'References' || (n.tags && n.tags.includes('Reference')); });
default: return NOTE_DB.notes;
}
}
// ── function: renderNoteListHandlers ──
function renderNoteListHandlers() {
document.querySelectorAll('.delete-btn').forEach(function(btn) {
btn.addEventListener('click', function(e) { e.stopPropagation(); deleteCurrentNote(); });
});
document.querySelectorAll('.card-title').forEach(function(el) {
el.style.cursor = 'pointer';
});
}
// ── function: clearEmptyStates ──
function clearEmptyStates() {
['empty-view', 'welcome-state'].forEach(function(id) { document.getElementById(id)?.classList.add('hidden'); });
if (NOTE_DB.notes.length === 0) {
var empty = document.getElementById('empty-view');
empty.classList.remove('hidden');
var tabs = {'all-notes': 'Get started', 'favorites': 'No favorites yet', 'journal': 'Journal entries will appear here', 'ideas': 'Capture your ideas here', 'references': 'Reference material will show up here'};
empty.querySelector('p').textContent = 'No notes yet';
} else {
document.getElementById('note-grid')?.classList.remove('hidden');
renderNoteList();
}
}
// ── function: openEditor ──
// === EDITOR FUNCTIONS ===
function openEditor(noteId) {
activeNote = NoteStore.get(noteId);
if (!activeNote) return;
var titleEl = document.getElementById('note-title');
titleEl.value = activeNote.title || 'Untitled Note';
var metaEl = document.querySelector('.editor-meta');
var metaText = formatDate(activeNote.updatedAt) + ' · ' + activeNote.folder;
if (activeNote.tags.length > 0) {
metaText += ' · #' + activeNote.tags.join(', ');
} else if (!activeNote.favorite && activeNote.tags.length === 0) {
var words = activeNote.body.join('') ? activeNote.body.join('').split(/\s+/).length : 0;
metaText += ' · ' + words + ' words';
}
metaEl.textContent = metaText;
renderBlocks(document.getElementById('editor-body'), activeNote.body);
['empty-view', 'welcome-state'].forEach(function(id) { document.getElementById(id)?.classList.add('hidden'); });
var editorView = document.getElementById('editor-view');
editorView.classList.remove('hidden');
if (!activeNote.title) {
titleEl.placeholder = 'Give your note a title...';
titleEl.style.color = '#9ca3af';
titleEl.style.fontWeight = '400';
} else {
titleEl.placeholder = 'Untitled Note';
titleEl.style.color = '';
titleEl.style.fontWeight = '';
}
setupBlockHandlers();
}
// ── function: setupBlockHandlers ──
function setupBlockHandlers() {
var editorBody = document.getElementById('editor-body');
var titleInput = document.getElementById('note-title');
if (titleInput) {
titleInput.addEventListener('input', function() { handleTitleChange(); });
titleInput.addEventListener('focus', function() {
if (!activeNote.title) titleInput.placeholder = 'Untitled Note';
titleEl.style.color = '';
titleEl.style.fontWeight = '';
});
}
editorBody.addEventListener('keydown', function(e) {
var activeEl = document.activeElement;
if (e.key === 'Backspace' || e.key === 'Delete') handleBlockDelete(activeEl, true);
else if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey) {
e.preventDefault();
var parentBlock = activeEl && activeEl.closest('.editor-block');
if (!parentBlock || parseInt(parentBlock.dataset.blockIdx) >= 0) {
addBlockAfter(editorBody, parentBlock ? parseInt(parentBlock.dataset.blockIdx) : -1);
}
} else if ((e.ctrlKey || e.metaKey) && e.key === 'Backspace') {
e.preventDefault();
var ceAncestor = activeEl && (activeEl.closest('[contenteditable]')?.closest('.editor-block'));
if (ceAncestor) handleBlockDelete(parseInt(ceAncestor.dataset.blockIdx), true);
}
});
document.querySelectorAll('.editor-block [contenteditable]').forEach(function(el) {
el.addEventListener('input', function() { handleBlockInput(); });
});
}
// ── function: handleBlockInput ──
function handleBlockInput() { updateWordCount(document.getElementById('editor-body').innerHTML); clearTimeout(window._saveTimer); window._saveTimer = setTimeout(autoSave, 1500); }
// ── function: handleTitleChange ──
function handleTitleChange() { if (activeNote) { activeNote.title = document.getElementById('note-title').value; NOTE_DB.notes[NOTE_DB.notes.indexOf(activeNote)].title = activeNote.title; } updateEditorMeta(); clearTimeout(window._saveTimer); window._saveTimer = setTimeout(autoSave, 1500); }
// ── function: handleBlockDelete ──
function handleBlockDelete(el, isBackspace) {
var parentBlock = el && el.closest('.editor-block');
if (!parentBlock || !activeNote) return;
var idx = parseInt(parentBlock.dataset.blockIdx);
if (idx < 0 || !activeNote.body[idx]) return;
if (idx + 1 >= activeNote.body.length) {
activeNote.body.splice(idx, 1);
NOTE_DB.notes[NOTE_DB.notes.indexOf(activeNote)].body = [...activeNote.body];
NoteStore.update(activeNote.id, { body: [...activeNote.body] });
renderBlocks(document.getElementById('editor-body'), activeNote.body);
showToast('Block deleted', 1500);
} else if (isBackspace) {} // leave for user to type or add block
}
// ── function: autoSave ──
function autoSave() { if (!activeNote) return; NoteStore.update(activeNote.id, { body: [...activeNote.body] }); }
// ── function: closeEditor ──
// Close editor and go back to list view
function closeEditor() {
var noteId = activeNote ? activeNote.id : null;
['editor-view', 'note-list-view'].forEach(function(id) { document.getElementById(id)?.classList.add('hidden'); });
if (currentTab === 'all-notes' && !activeNote) clearEmptyStates();
else { renderNoteList(); highlightSelectedNote(noteId); }
activeNote = null;
}
// ── function: updateEditorMeta ──
function updateEditorMeta() {}
// ── function: highlightSelectedNote ──
// placeholder for future use
// Highlight current note in list when returning from editor
function highlightSelectedNote(noteId) {
document.querySelectorAll('.note-card').forEach(function(card) {
card.classList.toggle('selected', parseInt(card.dataset.noteId) === noteId);
});
var scrollContainer = document.querySelector('.note-list-container');
requestAnimationFrame(function() {
var el = document.querySelector('.note-card[data-note-id="' + noteId + '"]');
if (el && scrollContainer) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
});
}
// ── function: saveNote ──
function saveNote() { autoSave(); showToast('Note saved ✓'); }
// ── function: deleteCurrentNote ──
// Delete current note
function deleteCurrentNote() {
if (!activeNote) return;
var confirmed = confirm('Delete "' + (activeNote.title || 'Untitled') + '"?');
if (confirmed && activeNote.id) {
NoteStore.delete(activeNote.id);
showToast('Note deleted');
closeEditor();
renderSidebarFolders();
document.querySelector('.storage-info').textContent = NOTE_DB.notes.length + ' notes · ' + NOTE_DB.folders.length + ' folders';
}
}
// ── function: toggleFavorite ──
function toggleFavorite() {
if (!activeNote) return;
activeNote.favorite = !activeNote.favorite;
NOTE_DB.notes[NOTE_DB.notes.indexOf(activeNote)].favorite = activeNote.favorite;
NoteStore.update(activeNote.id, { body: [...activeNote.body], tags: [...activeNote.tags] });
}
// ── function: renderSidebarFolders ──
// === SIDEBAR FUNCTIONS ===
function renderSidebarFolders() {
var container = document.getElementById('folders-list');
container.innerHTML = '';
// Inbox first
var inboxDiv = createFolderItem('📥', 'Inbox', NOTE_DB.notes.filter(function(n) { return n.folder === 'Inbox'; }).length);
container.appendChild(inboxDiv);
// Folders
var allFolders = [...new Set(NOTE_DB.notes.map(function(n) { return n.folder; }).filter(Boolean))];
var seenFolders = new Set();
for (var i = 0; i < allFolders.length; i++) {
if (!seenFolders.has(allFolders[i])) {
seenFolders.add(allFolders[i]);
var count = NOTE_DB.notes.filter(function(n) { return n.folder === allFolders[i] || (n.tags && n.tags.includes(allFolders[i])); }).length;
container.appendChild(createFolderItem('📁', allFolders[i], count));
}
}
// Favorites
var favCount = NOTE_DB.notes.filter(function(n) { return n.favorite; }).length;
if (favCount > 0) {
var favDiv = createFolderItem('⭐', 'Favorites', favCount);
container.insertBefore(favDiv, container.firstChild.nextSibling || container.firstChild);
}
// Tags section at bottom
var allTags = [...new Set(NOTE_DB.notes.flatMap(function(n) { return n.tags; }))];
if (allTags.length > 0) {
var tagSection = document.createElement('div');
tagSection.className = 'sidebar-section';
tagSection.style.cssText = 'padding:16px 8px;';
var tagTitle = document.createElement('span');
tagTitle.textContent = 'Tags:';
tagTitle.style.cssText = 'display:block;font-size:12px;color:#9ca3af;margin-bottom:8px;text-transform:uppercase;letter-spacing:1px;font-weight:600;';
tagSection.appendChild(tagTitle);
allTags.forEach(function(tag) {
var count = NOTE_DB.notes.filter(function(n) { return n.tags.includes(tag); }).length;
var tagItem = document.createElement('span');
tagItem.className = 'tag-item';
tagItem.style.cssText = 'display:flex;align-items:center;gap:6px;padding:4px 10px;border-radius:20px;font-size:13px;color:#6b7280;background:#f5f5f4;cursor:pointer;margin-bottom:4px;' +
'transition:all .15s;';
tagItem.textContent = '#' + tag + (count > 1 ? ' (' + count + ')' : '');
var hashIcon = document.createElement('span');
hashIcon.textContent = '#';
hashIcon.style.cssText = 'font-weight:bold;color:#9ca3af;font-size:14px;flex-shrink:0;';
tagItem.appendChild(hashIcon);
var activeFilter = null;
tagItem.addEventListener('click', function() {
if (activeFilter === tag) {
activeFilter = null;
NOTE_DB.notes.forEach(function(n) { n.tags = [...n.tags]; });
showToast('All notes restored');
} else {
activeFilter = tag;
filterByTag(activeFilter);
}
});
tagSection.appendChild(tagItem);
});
container.appendChild(tagSection);
}
var totalTags = NOTE_DB.notes.flatMap(function(n) { return n.tags; }).length;
document.querySelector('.storage-info').textContent = NOTE_DB.notes.length + ' notes · ' + (totalTags > 0 ? totalTags + ' tags' : '');
}
// ── function: createFolderItem ──
function createFolderItem(icon, name, count) {
var div = document.createElement('div');
div.className = 'folder-item';
div.dataset.folderName = name;
div.innerHTML = '<span class="folder-icon">' + icon + '</span><span>' + name + '</span>' +
(count > 0 ? '<span class="folder-count" style="background:rgba(93,93,93,.1);padding:2px 8px;border-radius:10px;font-size:11px;color:#6b7280">' + count + '</span>' : '');
div.addEventListener('click', function() {
if (activeFilter === name) { activeFilter = null; NOTE_DB.notes.forEach(function(n) { n.tags = [...n.tags]; }); showToast('Showing all ' + name + ' notes'); }
else { activeFilter = name; filterByFolder(name); }
});
return div;
}
// ── function: filterByFolder ──
function filterByFolder(folder) {
currentTab = 'all-notes';
document.querySelectorAll('.folder-item').forEach(function(item) { item.classList.remove('active'); });
var activeItem = document.querySelector('.folder-item[data-folder-name="' + folder + '"]');
if (activeItem) activeItem.classList.add('active');
showToast(folder + ' notes selected');
}
// ── function: filterByTag ──
function filterByTag(tag) {
currentTab = 'all-notes';
document.querySelectorAll('.tag-item').forEach(function(item) { item.style.opacity = ''; });
var activeItem = document.querySelector('.tag-item:contains(#' + tag + ')');
if (activeItem) activeItem.style.opacity = '.7; background:#f0edff;';
showToast(tag + ' notes selected (' + NOTE_DB.notes.filter(function(n) { return n.tags.includes(tag); }).length + ' results)');
}
// ── const: SAMPLE_NOTES ──
// === INITIALIZATION ===
const SAMPLE_NOTES = [
{ id: 'sample-1', title: 'Morning Routine', folder: 'Journal', tags: ['health', 'morning'], createdAt: new Date('2024-10-05'), updatedAt: new Date(), body: [{ type: BLOCK_TYPES.heading1, text: 'My Morning Routine' }, { type: BLOCK_TYPES.paragraph, text: "I started my day with a 30-minute meditation session followed by a healthy breakfast." }, { type: BLOCK_TYPES.todo, text: 'Exercise', checked: true }, { type: BLOCK_TYPES.todo, text: 'Read for 20 minutes', checked: false }, { type: BLOCK_TYPES.todo, text: 'Plan the day', checked: true }, { type: BLOCK_TYPES.divider }, { type: BLOCK_TYPES.quote, text: "The key is consistency. Even small habits done daily create remarkable results." }] },
{ id: 'sample-2', title: 'App Ideas 2024', folder: 'Ideas', tags: ['apps', 'startup'], createdAt: new Date('2024-10-03'), updatedAt: new Date(), body: [{ type: BLOCK_TYPES.heading1, text: 'Application Ideas to Explore' }, { type: BLOCK_TYPES.paragraph, text: "A few concepts I want to develop this year:" }, { type: BLOCK_TYPES.bullet, text: 'AI-powered recipe planner that suggests meals based on available ingredients and dietary preferences' }, { type: BLOCK_TYPES.bullet, text: 'Habit tracker with social accountability features for remote teams' }, { type: BLOCK_TYPES.bullet, text: 'Local-first note-taking app with block-based editing (like Notion but faster)' }, { type: BLOCK_TYPES.divider }, { type: BLOCK_TYPES.heading2, text: 'Key Principles' }, { type: BLOCK_TYPES.checkbox, text: 'Mobile-first design', checked: true }, { type: BLOCK_TYPES.checkbox, text: 'Offline-capable sync', checked: false }, { type: BLOCK_TYPES.checkbox, text: 'Zero external dependencies', checked: true }] },
{ id: 'sample-3', title: 'Design System Reference', folder: 'References', tags: ['design', 'system'], createdAt: new Date('2024-10-01'), updatedAt: new Date(), body: [{ type: BLOCK_TYPES.heading1, text: 'Design Token Reference' }, { type: BLOCK_TYPES.paragraph, text: "Primary color palette for the application:" }, { type: BLOCK_TYPES.code, text: ':root {\n --primary-500: #6d5dfc;\n --accent: #1e293b;\n}', lang: 'css' }, { type: BLOCK_TYPES.divider }, { type: BLOCK_TYPES.paragraph, text: 'Typography scale:' }, { type: BLOCK_TYPES.code, text: 'display: 40px/52px\nh1: 36px/48px\nh2: 28px/36px\nbody: 16px/24px', lang: 'css' }] },
];
// ── function: loadSampleNotes ──
function loadSampleNotes() { if (NOTE_DB.notes.length > 0) return; SAMPLE_NOTES.forEach(function(sn) { NOTE_DB.nextId = Math.max(NOTE_DB.nextId, sn.id + 1); NOTE_DB.notes.unshift(sn); }); }
// ── function: setupDeleteHandlers ──
// Setup delete handlers for all note cards after initial render
function setupDeleteHandlers() {
document.querySelectorAll('.delete-btn').forEach(function(btn) { btn.addEventListener('click', function(e) { e.stopPropagation(); deleteCurrentNote(); }); });
var cards = document.querySelectorAll('.note-card');
cards.forEach(function(card) {
var delBtn = card.querySelector('.delete-btn');
if (delBtn) {
card.addEventListener('mouseenter', function() { delBtn.style.opacity = '1'; });
card.addEventListener('mouseleave', function() { delBtn.style.opacity = ''; });
}
});
}
// ── function: initWelcome ──
function initWelcome() { loadSampleNotes(); renderSidebarFolders(); if (NOTE_DB.notes.length === 0) document.getElementById('welcome-view')?.classList.remove('hidden'); else clearEmptyStates(); }
// ── function: enhancedRenderNoteCard ──
function enhancedRenderNoteCard(note, fullView) {
var container = document.getElementById('note-card');
if (fullView) {
container.className = 'note-card';
// Title with pointer
var titleEl = document.createElement('div');
titleEl.className = 'card-title';
titleEl.textContent = note.title || 'Untitled Note';
container.appendChild(titleEl);
if (note.tags && note.tags.length > 0) {
var tagsDiv = document.createElement('div');
tagsDiv.style.marginBottom = '12px';
tagsDiv.style.display = 'flex';
tagsDiv.style.flexWrap = 'wrap';
tagsDiv.style.gap = '6px';
note.tags.forEach(function(tag) {
var span = document.createElement('span');
span.textContent = tag;
span.style.cssText = 'background:#eef0ff;color:#6d5dfc;padding:3px 10px;border-radius:20px;font-size:12px;font-weight:500';
tagsDiv.appendChild(span);
});
container.appendChild(tagsDiv);
}
var bodyContainer = document.createElement('div');
bodyContainer.style.cssText = 'margin-top:14px;line-height:1.8;font-size:14px;color:#6b7280';
bodyContainer.textContent = renderBlocksPreview(note.body, 250);
container.appendChild(bodyContainer);
var metaDiv = document.createElement('div');
metaDiv.className = 'card-meta';
var dateSpan = document.createElement('span');
dateSpan.style.cssText = 'color:#9ca3af;font-size:12px';
dateSpan.textContent = formatDate(note.updatedAt);
if (note.tags && note.tags.length > 0) {
var tagLabel = document.createElement('span');
tagLabel.style.cssText = 'color:#9ca3af;font-size:12px;margin-right:6px';
tagLabel.textContent = '#';
metaDiv.appendChild(tagLabel);
}
metaDiv.appendChild(dateSpan);
container.appendChild(metaDiv);
} else {
// List view card with star and delete buttons
var card = document.createElement('div');
card.className = 'note-card new-entry';
var titleEl = document.createElement('div');
titleEl.className = 'card-title';
titleEl.textContent = note.title || 'Untitled Note';
card.appendChild(titleEl);
if (note.tags && note.tags.length > 0) {
var tagsDiv = document.createElement('div');
tagsDiv.style.marginBottom = '8px';
tagsDiv.style.display = 'flex';
tagsDiv.style.flexWrap = 'wrap';
tagsDiv.style.gap = '4px';
note.tags.slice(0, 3).forEach(function(tag) {
var span = document.createElement('span');
span.textContent = tag;
span.style.cssText = 'background:#eef0ff;color:#6d5dfc;padding:1.5px 7px;border-radius:4px;font-size:11px';
tagsDiv.appendChild(span);
});
card.appendChild(tagsDiv);
}
var bodyContainer = document.createElement('div');
bodyContainer.className = 'card-preview';
bodyContainer.textContent = renderBlocksPreview(note.body, 200);
card.appendChild(bodyContainer);
var metaDiv = document.createElement('div');
metaDiv.className = 'card-meta';
if (note.folder) {
var folderLabel = document.createElement('span');
folderLabel.style.cssText = 'color:#6b7280;font-size:12px;margin-right:4px';
folderLabel.textContent = note.folder;
metaDiv.appendChild(folderLabel);
}
if (note.tags && note.tags.length > 0) {
var tagCount = document.createElement('span');
tagCount.style.cssText = 'color:#9ca3af;font-size:12px';
tagCount.textContent = '#' + note.tags.length;
metaDiv.appendChild(tagCount);
}
var dateSpan = document.createElement('span');
dateSpan.style.cssText = 'color:#9ca3af;font-size:12px';
dateSpan.textContent = formatDate(note.updatedAt);
metaDiv.appendChild(dateSpan);
card.appendChild(metaDiv);
}
// Add star button (hover reveal)
var starBtn = document.createElement('button');
starBtn.className = 'card-star';
starBtn.style.cssText = 'position:absolute;top:14px;left:14px;width:28px;height:28px;font-size:16px;color:#9ca3af;opacity:' + (note.favorite ? '1' : '0') + ';z-index:2;background:none;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s ease;';
starBtn.textContent = note.favorite ? '★' : '☆';
// Add delete button (hover reveal)
var delBtn = document.createElement('button');
delBtn.className = 'delete-btn';
delBtn.innerHTML = '✕';
delBtn.title = 'Delete note';
if (!fullView) {
card.appendChild(starBtn);
card.appendChild(delBtn);
// Event handlers for list view cards
var origTitleText = titleEl.textContent;
if (!note.title && !note.body.length) {
titleEl.style.cssText += ';color:#9ca3af;font-weight:400;' + (origTitleText !== note.title ? '' : '');
} else {
titleEl.style.cursor = 'pointer';
}
card.addEventListener('mouseenter', function() {
starBtn.style.opacity = note.favorite ? '1' : '.6';
delBtn.style.opacity = '1';
});
card.addEventListener('mouseleave', function() {
starBtn.style.opacity = '';
delBtn.style.opacity = '';
});
card.style.cursor = 'pointer';
card.addEventListener('click', function() { openEditor(note.id); });
} else {
container.appendChild(starBtn);
container.appendChild(delBtn);
if (!bodyContainer.querySelector('[contenteditable]')) bodyContainer.style.cursor = 'pointer';
}
if (!fullView) container.replaceWith(card);
}
// ── main ──
// Main: Event Wiring & Initialization
document.getElementById('btn-new-note').addEventListener('click', () => {
const note = NoteStore.create();
document.getElementById('note-title').value = 'Untitled Note';
document.getElementById('editor-body').innerHTML = '';
currentTab = 'all-notes';
document.querySelector('.sidebar-btn[data-tab="all-notes"]').classList.add('active');
document.querySelectorAll('.sidebar-btn:not([data-tab="all-notes"])').forEach(b => b.classList.remove('active'));
openEditor(note.id);
});
document.getElementById('btn-empty-new').addEventListener('click', () => {
const note = NoteStore.create();
document.getElementById('note-title').value = 'Untitled Note';
document.getElementById('editor-body').innerHTML = '';
currentTab = 'all-notes';
openEditor(note.id);
});
document.getElementById('btn-welcome-new').addEventListener('click', () => {
const note = NoteStore.create();
document.getElementById('note-title').value = 'Untitled Note';
document.getElementById('editor-body').innerHTML = '';
currentTab = 'all-notes';
openEditor(note.id);
});
document.getElementById('btn-welcome-browse').addEventListener('click', () => {
closeEditor(); renderNoteList(); clearEmptyStates();
});
// Title input change handler (also called from titleInput above)
const noteTitleInput = document.getElementById('note-title');
if (noteTitleInput) {
noteTitleInput.addEventListener('input', handleTitleChange);
}
// Toolbar format buttons - block-aware formatting
document.querySelectorAll('.editor-format[data-format]').forEach(btn => {
btn.addEventListener('click', () => {
const fmt = btn.dataset.format;
// Find active block or create new one if no focus
const editorBody = document.getElementById('editor-body');
let targetBlock = null;
if (document.activeElement && document.activeElement.closest('.editor-block')) {
targetBlock = document.activeElement.closest('.editor-block');
} else {
// Create a new paragraph block at the end
const blocks = editorBody.querySelectorAll('.editor-block');
if (blocks.length === 0) {
addBlockAfter(editorBody, -1);
targetBlock = editorBody.querySelector('.editor-block');
} else {
const lastIdx = parseInt(blocks[blocks.length-1].dataset.blockIdx);
addBlockAfter(editorBody, lastIdx);
targetBlock = editorBody.querySelectorAll('.editor-block').lastElementChild;
}
}
if (!targetBlock) return;
let text = '';
switch (fmt) {
case 'bold': execFormat('bold'); break;
case 'italic': execFormat('italic'); break;
case 'underline': execFormat('underline'); break;
case 'strikethrough': execFormat('strike'); break;
case 'heading': targetBlock.type = BLOCK_TYPES.heading1; targetBlock.text = '';
// Render as heading block with new content
const textEl = targetBlock.querySelector('.editor-block--heading1 > div:first-child') ||
document.createElement('div');
textEl.style.cssText = 'font-size:32px;font-weight:700;line-height:1.3;margin-bottom:8px;color:#1c1917';
targetBlock.innerHTML = '';
const h = document.createElement('div');
h.style.cssText = 'font-size:32px;font-weight:700;line-height:1.3;margin-bottom:8px;color:#1c1917';
textEl.textContent += execFormat('bold', '# ');
targetBlock.appendChild(h);
break;
case 'h2':
if (targetBlock.type === BLOCK_TYPES.paragraph) {
// Convert paragraph to heading2
const text = targetBlock.innerText || '';
targetBlock.type = BLOCK_TYPES.heading2;
targetBlock.innerHTML = `<div style="font-size:26px;font-weight:600;line-height:1.35;margin-bottom:4px;color:#1c1917">${text}</div>`;
} else {
execFormat(f => document.execCommand(f, false, '# h2'));
}
break;
case 'quote':
if (targetBlock.type === BLOCK_TYPES.paragraph) {
targetBlock.type = BLOCK_TYPES.quote;
const text = targetBlock.innerText || '';
targetBlock.innerHTML = `<div style="border-left:3px solid #6d5dfc;padding-left:20px;margin-bottom:4px;background:#f0edff;border-radius:0 8px 8px 0">
<span style="color:#9ca3af;font-size:12px;letter-spacing:2px;text-transform:uppercase;">Quote</span>
<div contenteditable="true" style="font-style:italic;color:#6b7280;line-height:1.8;margin-top:8px;padding-bottom:4px">${text}</div></div>`;
} else {
execFormat(f => document.execCommand(f, false, '> '));
}
break;
case 'bullet-list':
if (targetBlock.type === BLOCK_TYPES.paragraph) targetBlock.type = BLOCK_TYPES.bullet;
execFormat('insertUnorderedList');
break;
case 'numbered-list':
if (targetBlock.type === BLOCK_TYPES.paragraph) targetBlock.type = BLOCK_TYPES.numbered;
execFormat('insertOrderedList');
break;
}
});
});
// Search input
document.getElementById('search-input').addEventListener('input', () => renderNoteList());
// Tab switching in sidebar
document.querySelectorAll('.sidebar-btn[data-tab]').forEach(btn => {
btn.addEventListener('click', () => {
currentTab = btn.dataset.tab;
document.querySelector('.sidebar-btn.active').classList.remove('active');
btn.classList.add('active');
renderNoteList(); clearEmptyStates();
});
});
// List mode toggle
document.querySelectorAll('.list-toolbar[data-list-mode]').forEach(btn => {
btn.addEventListener('click', () => {
listMode = btn.dataset.listMode;
document.querySelector('.toolbar-btn.active').classList.remove('active');
btn.classList.add('active');
renderNoteList();
});
});
// Sort buttons
document.getElementById('btn-sort-asc').addEventListener('click', () => { sortDir = 'asc'; renderNoteList(); });
document.getElementById('btn-sort-desc').addEventListener('click', () => { sortDir = 'desc'; renderNoteList(); });
// Back button in editor
document.getElementById('btn-back-to-list').addEventListener('click', closeEditor);
// Save button
document.getElementById('btn-save-note').addEventListener('click', saveNote);
// Keyboard shortcuts: Ctrl+S to save, Ctrl+N for new note, Ctrl+Shift+D for duplicate
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); saveNote(); }
if ((e.ctrlKey || e.metaKey) && e.key === 'n') { e.preventDefault(); const n = NoteStore.create(); document.getElementById('note-title').value='Untitled Note'; document.getElementById('editor-body').innerHTML=''; currentTab='all-notes'; openEditor(n.id); }
});
// Initial state setup - use sample notes for demo, else empty welcome
loadSampleNotes();
// Populate sidebar folders based on note data
renderSidebarFolders();
// Setup delete handlers after initial render
setTimeout(setupDeleteHandlers, 100);
// Update word count when editor body changes (for backward compat)
document.getElementById('editor-body').addEventListener('input', () => updateWordCount(document.getElementById('editor-body').innerHTML));
// Initial render
clearEmptyStates();
</script>
</body>
</html>