Notes
thinkingcap qwen3.6 27b · composer

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 41,057 bytes · SHA-256 67abd22722dd
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Noteflow — Notes App</title>
<style>
/* ── css: base ── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg-primary: #ffffff;
--bg-secondary: #f8f9fa;
--bg-tertiary: #f1f3f5;
--bg-hover: #e9ecef;
--bg-active: #dee2e6;
--text-primary: #1a1a2e;
--text-secondary: #495057;
--text-tertiary: #868e96;
--border-color: #e9ecef;
--accent: #6c5ce7;
--accent-hover: #5a4bd1;
--accent-light: rgba(108, 92, 231, 0.1);
--danger: #e74c3c;
--danger-hover: #c0392b;
--success: #2ecc71;
--shadow-sm: 0 1px 3px rgba(0,0,0,0.08);
--shadow-md: 0 4px 12px rgba(0,0,0,0.1);
--shadow-lg: 0 8px 30px rgba(0,0,0,0.12);
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 14px;
--sidebar-width: 260px;
--note-list-width: 300px;
--transition: 0.2s ease;
}
[data-theme="dark"] {
--bg-primary: #1a1b2e;
--bg-secondary: #1e1f35;
--bg-tertiary: #252640;
--bg-hover: #2d2e48;
--bg-active: #363756;
--text-primary: #e8e8f0;
--text-secondary: #b0b0c0;
--text-tertiary: #7a7a90;
--border-color: #2d2e48;
--accent: #a29bfe;
--accent-hover: #6c5ce7;
--accent-light: rgba(162, 155, 254, 0.15);
--shadow-sm: 0 1px 3px rgba(0,0,0,0.3);
--shadow-md: 0 4px 12px rgba(0,0,0,0.4);
--shadow-lg: 0 8px 30px rgba(0,0,0,0.5);
}
html { font-size: 15px; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Inter', sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
overflow: hidden;
height: 100vh;
}
#app-inner {
display: flex;
height: 100vh;
width: 100vw;
}
.hidden { display: none !important; }
/* ── css: sidebar ── */
#sidebar {
width: var(--sidebar-width);
min-width: var(--sidebar-width);
background: var(--bg-secondary);
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
padding: 20px 14px;
gap: 8px;
overflow-y: auto;
}
.sidebar-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 6px;
}
.logo {
display: flex;
align-items: center;
gap: 10px;
font-size: 1.2rem;
font-weight: 700;
color: var(--accent);
letter-spacing: -0.5px;
}
.icon-btn {
background: none;
border: none;
cursor: pointer;
padding: 6px;
border-radius: var(--radius-sm);
color: var(--text-tertiary);
transition: all var(--transition);
display: flex;
align-items: center;
justify-content: center;
}
.icon-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.btn-primary {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 10px 16px;
background: var(--accent);
color: #fff;
border: none;
border-radius: var(--radius-md);
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
transition: all var(--transition);
box-shadow: var(--shadow-sm);
}
.btn-primary:hover {
background: var(--accent-hover);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.sidebar-search {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: var(--bg-tertiary);
border-radius: var(--radius-sm);
color: var(--text-tertiary);
}
.sidebar-search svg { flex-shrink: 0; }
#search-input {
border: none;
background: transparent;
outline: none;
font-size: 0.85rem;
color: var(--text-primary);
width: 100%;
}
#search-input::placeholder { color: var(--text-tertiary); }
.sidebar-nav { display: flex; flex-direction: column; gap: 2px; margin-top: 4px; }
.nav-item {
display: flex;
align-items: center;
gap: 10px;
padding: 9px 12px;
background: none;
border: none;
border-radius: var(--radius-sm);
font-size: 0.88rem;
color: var(--text-secondary);
cursor: pointer;
transition: all var(--transition);
text-align: left;
width: 100%;
}
.nav-item:hover { background: var(--bg-hover); color: var(--text-primary); }
.nav-item.active {
background: var(--accent-light);
color: var(--accent);
font-weight: 600;
}
.sidebar-tags {
margin-top: auto;
padding-top: 12px;
border-top: 1px solid var(--border-color);
}
.sidebar-tags h3 {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-tertiary);
margin-bottom: 8px;
font-weight: 600;
}
#tags-list { display: flex; flex-wrap: wrap; gap: 4px; }
.tag-pill {
display: inline-flex;
align-items: center;
padding: 3px 10px;
background: var(--bg-tertiary);
border-radius: 20px;
font-size: 0.78rem;
color: var(--text-secondary);
cursor: pointer;
transition: all var(--transition);
}
.tag-pill:hover {
background: var(--accent-light);
color: var(--accent);
}
/* ── css: note-list ── */
#note-list-panel {
width: var(--note-list-width);
min-width: var(--note-list-width);
background: var(--bg-primary);
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
}
.note-list-header {
padding: 20px 18px 14px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--border-color);
}
.note-list-header h2 {
font-size: 1.1rem;
font-weight: 700;
color: var(--text-primary);
}
.badge {
font-size: 0.75rem;
padding: 3px 10px;
background: var(--bg-tertiary);
border-radius: 20px;
color: var(--text-tertiary);
font-weight: 600;
}
#note-list {
flex: 1;
overflow-y: auto;
padding: 8px;
}
.note-item {
padding: 14px 14px;
border-radius: var(--radius-md);
cursor: pointer;
transition: all var(--transition);
margin-bottom: 2px;
border: 1px solid transparent;
}
.note-item:hover { background: var(--bg-secondary); }
.note-item.active {
background: var(--accent-light);
border-color: rgba(108, 92, 231, 0.2);
}
.note-item-title {
font-size: 0.92rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.note-item-preview {
font-size: 0.8rem;
color: var(--text-tertiary);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
line-height: 1.5;
}
.note-item-meta {
display: flex;
align-items: center;
gap: 8px;
margin-top: 6px;
font-size: 0.72rem;
color: var(--text-tertiary);
}
.note-item-fav {
color: #f39c12;
font-size: 0.75rem;
}
.note-item-tag {
padding: 1px 6px;
background: var(--bg-tertiary);
border-radius: 8px;
font-size: 0.68rem;
}
/* ── css: editor ── */
#editor-panel {
flex: 1;
display: flex;
flex-direction: column;
background: var(--bg-primary);
min-width: 0;
}
.empty-state {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--text-tertiary);
gap: 16px;
}
.empty-state svg { opacity: 0.3; }
.empty-state p { font-size: 1rem; }
#editor-container {
flex: 1;
display: flex;
flex-direction: column;
max-width: 820px;
width: 100%;
margin: 0 auto;
}
.toolbar {
display: flex;
align-items: center;
gap: 2px;
padding: 10px 24px;
border-bottom: 1px solid var(--border-color);
flex-wrap: wrap;
}
.tool-btn {
background: none;
border: none;
cursor: pointer;
padding: 6px 8px;
border-radius: var(--radius-sm);
color: var(--text-secondary);
font-size: 0.85rem;
transition: all var(--transition);
display: flex;
align-items: center;
justify-content: center;
min-width: 32px;
}
.tool-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
.tool-btn.active { background: var(--accent-light); color: var(--accent); }
.divider {
width: 1px;
height: 20px;
background: var(--border-color);
margin: 0 4px;
}
.note-title-input {
border: none;
outline: none;
font-size: 1.8rem;
font-weight: 700;
padding: 24px 36px 8px;
background: transparent;
color: var(--text-primary);
width: 100%;
}
.note-title-input::placeholder { color: var(--text-tertiary); }
.tags-bar {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 36px 8px;
flex-wrap: wrap;
}
.tag-icon {
color: var(--text-tertiary);
font-size: 0.85rem;
}
.tag-input {
border: none;
background: transparent;
outline: none;
font-size: 0.82rem;
color: var(--text-secondary);
min-width: 100px;
}
.tag-input::placeholder { color: var(--text-tertiary); }
#note-tags { display: flex; gap: 4px; flex-wrap: wrap; }
.note-tag {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 10px;
background: var(--accent-light);
color: var(--accent);
border-radius: 20px;
font-size: 0.78rem;
font-weight: 500;
}
.note-tag .remove-tag {
cursor: pointer;
opacity: 0.6;
font-size: 0.9rem;
line-height: 1;
}
.note-tag .remove-tag:hover { opacity: 1; }
.editor-area {
flex: 1;
padding: 8px 36px 24px;
overflow-y: auto;
font-size: 0.95rem;
line-height: 1.75;
color: var(--text-primary);
outline: none;
}
.editor-area:empty::before {
content: attr(data-placeholder);
color: var(--text-tertiary);
pointer-events: none;
}
.editor-area h1 { font-size: 1.8rem; margin: 0.6em 0 0.3em; }
.editor-area h2 { font-size: 1.4rem; margin: 0.5em 0 0.25em; }
.editor-area h3 { font-size: 1.15rem; margin: 0.4em 0 0.2em; }
.editor-area blockquote {
border-left: 3px solid var(--accent);
padding-left: 16px;
color: var(--text-secondary);
margin: 0.5em 0;
}
.editor-area pre {
background: var(--bg-tertiary);
padding: 12px 16px;
border-radius: var(--radius-sm);
font-family: 'SF Mono', 'Fira Code', monospace;
font-size: 0.85rem;
overflow-x: auto;
}
.status-bar {
display: flex;
align-items: center;
gap: 16px;
padding: 8px 36px;
border-top: 1px solid var(--border-color);
font-size: 0.75rem;
color: var(--text-tertiary);
}
#note-actions {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 36px;
border-top: 1px solid var(--border-color);
}
.action-btn {
background: none;
border: none;
cursor: pointer;
padding: 6px;
border-radius: var(--radius-sm);
color: var(--text-tertiary);
transition: all var(--transition);
display: flex;
align-items: center;
}
.action-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
.action-btn.danger:hover { background: rgba(231,76,60,0.1); color: var(--danger); }
.action-btn.favorited svg { fill: #f39c12; stroke: #f39c12; }
.action-select {
padding: 5px 10px;
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
background: var(--bg-secondary);
color: var(--text-primary);
font-size: 0.82rem;
outline: none;
cursor: pointer;
}
/* Modal */
.modal { position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center; }
.modal-backdrop {
position: absolute;
inset: 0;
background: rgba(0,0,0,0.5);
backdrop-filter: blur(4px);
}
.modal-content {
position: relative;
background: var(--bg-primary);
border-radius: var(--radius-lg);
padding: 28px;
max-width: 380px;
width: 90%;
box-shadow: var(--shadow-lg);
}
.modal-content h3 { margin-bottom: 10px; font-size: 1.1rem; }
.modal-content p { color: var(--text-secondary); font-size: 0.9rem; margin-bottom: 20px; line-height: 1.5; }
.modal-actions { display: flex; gap: 10px; justify-content: flex-end; }
.btn-secondary, .btn-danger {
padding: 8px 20px;
border-radius: var(--radius-sm);
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
transition: all var(--transition);
border: none;
}
.btn-secondary { background: var(--bg-tertiary); color: var(--text-primary); }
.btn-secondary:hover { background: var(--bg-hover); }
.btn-danger { background: var(--danger); color: #fff; }
.btn-danger:hover { background: var(--danger-hover); }
/* Scrollbar */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--bg-active); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-tertiary); }
/* Animations */
@keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
.note-item { animation: fadeIn 0.2s ease; }
</style>
</head>
<body>
<div id="app-inner">
<!-- Sidebar -->
<aside id="sidebar">
<div class="sidebar-header">
<div class="logo">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" rx="3" stroke="currentColor" stroke-width="2"></rect><path d="M8 8h8M8 12h5" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path></svg>
<span>Noteflow</span>
</div>
<button id="theme-toggle" class="icon-btn" title="Toggle theme">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="5" stroke="currentColor" stroke-width="2"></circle><path d="M12 1v2m0 18v2M4.22 4.22l1.42 1.42m12.72 12.72l1.42 1.42M1 12h2m18 0h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path></svg>
</button>
</div>
<button id="new-note-btn" class="btn-primary">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M12 5v14m-7-7h14" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"></path></svg>
New Note
</button>
<div class="sidebar-search">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"><circle cx="11" cy="11" r="7" stroke="currentColor" stroke-width="2"></circle><path d="M16 16l5 5" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path></svg>
<input type="text" id="search-input" placeholder="Search notes…">
</div>
<nav class="sidebar-nav">
<button class="nav-item active" data-folder="all">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" stroke="currentColor" stroke-width="2"></path></svg>
All Notes
</button>
<button class="nav-item" data-folder="favorites">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87L18.18 22 12 18.27 5.82 22 7 14.14 2 9.27l6.91-1.01z" stroke="currentColor" stroke-width="2"></path></svg>
Favorites
</button>
<button class="nav-item" data-folder="personal">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2" stroke="currentColor" stroke-width="2"></path><circle cx="12" cy="7" r="4" stroke="currentColor" stroke-width="2"></circle></svg>
Personal
</button>
<button class="nav-item" data-folder="work">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><rect x="2" y="7" width="20" height="14" rx="2" stroke="currentColor" stroke-width="2"></rect><path d="M16 7V5a2 2 0 00-2-2h-4a2 2 0 00-2 2v2" stroke="currentColor" stroke-width="2"></path></svg>
Work
</button>
<button class="nav-item" data-folder="ideas">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.992-.442-1.927-1.212-2.586z" stroke="currentColor" stroke-width="2"></path></svg>
Ideas
</button>
</nav>
<div class="sidebar-tags">
<h3>Tags</h3>
<div id="tags-list"></div>
</div>
</aside>
<!-- Note List -->
<section id="note-list-panel">
<div class="note-list-header">
<h2 id="folder-title">All Notes</h2>
<span id="note-count" class="badge">0 notes</span>
</div>
<div id="note-list"></div>
</section>
<!-- Editor -->
<main id="editor-panel">
<div id="empty-state" class="empty-state">
<svg width="80" height="80" viewBox="0 0 24 24" fill="none"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" stroke="currentColor" stroke-width="1.5"></path><path d="M14 2v6h6M16 13H8m8 4H8m2-8H8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path></svg>
<p>Select a note or create a new one</p>
</div>
<div id="editor-container" class="hidden">
<!-- Toolbar -->
<div class="toolbar">
<button class="tool-btn" data-action="bold" title="Bold"><strong>B</strong></button>
<button class="tool-btn" data-action="italic" title="Italic"><em>I</em></button>
<button class="tool-btn" data-action="underline" title="Underline"><u>U</u></button>
<button class="tool-btn" data-action="strikethrough" title="Strikethrough"><s>S</s></button>
<span class="divider"></span>
<button class="tool-btn" data-action="h1" title="Heading 1">H1</button>
<button class="tool-btn" data-action="h2" title="Heading 2">H2</button>
<button class="tool-btn" data-action="h3" title="Heading 3">H3</button>
<span class="divider"></span>
<button class="tool-btn" data-action="ul" title="Bullet List">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"><line x1="9" y1="6" x2="20" y2="6" stroke="currentColor" stroke-width="2"></line><line x1="9" y1="12" x2="20" y2="12" stroke="currentColor" stroke-width="2"></line><line x1="9" y1="18" x2="20" y2="18" stroke="currentColor" stroke-width="2"></line><circle cx="5" cy="6" r="1.5" fill="currentColor"></circle><circle cx="5" cy="12" r="1.5" fill="currentColor"></circle><circle cx="5" cy="18" r="1.5" fill="currentColor"></circle></svg>
</button>
<button class="tool-btn" data-action="ol" title="Numbered List">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"><line x1="10" y1="6" x2="21" y2="6" stroke="currentColor" stroke-width="2"></line><line x1="10" y1="12" x2="21" y2="12" stroke="currentColor" stroke-width="2"></line><line x1="10" y1="18" x2="21" y2="18" stroke="currentColor" stroke-width="2"></line><text x="3" y="8" font-size="7" fill="currentColor">1</text><text x="3" y="14" font-size="7" fill="currentColor">2</text><text x="3" y="20" font-size="7" fill="currentColor">3</text></svg>
</button>
<button class="tool-btn" data-action="checklist" title="Checklist">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"><rect x="3" y="5" width="6" height="6" rx="1" stroke="currentColor" stroke-width="2"></rect><path d="M5 8l1.5 1.5L9 7" stroke="currentColor" stroke-width="2"></path><line x1="12" y1="8" x2="21" y2="8" stroke="currentColor" stroke-width="2"></line><rect x="3" y="13" width="6" height="6" rx="1" stroke="currentColor" stroke-width="2"></rect><line x1="12" y1="16" x2="21" y2="16" stroke="currentColor" stroke-width="2"></line></svg>
</button>
<span class="divider"></span>
<button class="tool-btn" data-action="quote" title="Quote">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V21z" stroke="currentColor" stroke-width="2"></path><path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3z" stroke="currentColor" stroke-width="2"></path></svg>
</button>
<button class="tool-btn" data-action="code" title="Code Block">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"><polyline points="16,18 22,12 16,6" stroke="currentColor" stroke-width="2"></polyline><polyline points="8,6 2,12 8,18" stroke="currentColor" stroke-width="2"></polyline></svg>
</button>
<span class="divider"></span>
<button class="tool-btn" data-action="link" title="Insert Link">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" stroke="currentColor" stroke-width="2"></path><path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" stroke="currentColor" stroke-width="2"></path></svg>
</button>
</div>
<!-- Title input -->
<input type="text" id="note-title-input" class="note-title-input" placeholder="Untitled note…">
<!-- Tags bar -->
<div class="tags-bar">
<span class="tag-icon">#</span>
<input type="text" id="tag-input" class="tag-input" placeholder="Add tags…">
<div id="note-tags"></div>
</div>
<!-- Content editor -->
<div id="editor-content" contenteditable="true" class="editor-area" data-placeholder="Start writing…"></div>
<!-- Status bar -->
<div class="status-bar">
<span id="word-count">0 words</span>
<span id="char-count">0 characters</span>
<span id="last-saved">Not saved yet</span>
</div>
</div>
<!-- Note actions -->
<div id="note-actions" class="hidden">
<button id="favorite-btn" class="action-btn" title="Toggle favorite">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87L18.18 22 12 18.27 5.82 22 7 14.14 2 9.27l6.91-1.01z" stroke="currentColor" stroke-width="2"></path></svg>
</button>
<select id="folder-select" class="action-select">
<option value="personal">Personal</option>
<option value="work">Work</option>
<option value="ideas">Ideas</option>
</select>
<button id="delete-btn" class="action-btn danger" title="Delete note">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><polyline points="3,6 5,6 21,6" stroke="currentColor" stroke-width="2"></polyline><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2" stroke="currentColor" stroke-width="2"></path></svg>
</button>
</div>
</main>
<!-- Delete confirmation modal -->
<div id="delete-modal" class="modal hidden">
<div class="modal-backdrop"></div>
<div class="modal-content">
<h3>Delete Note</h3>
<p>Are you sure you want to delete this note? This action cannot be undone.</p>
<div class="modal-actions">
<button id="cancel-delete" class="btn-secondary">Cancel</button>
<button id="confirm-delete" class="btn-danger">Delete</button>
</div>
</div>
</div>
</div>
<script>
'use strict';
// ── const: STORAGE_KEY ──
const STORAGE_KEY = 'noteflow_notes';
// ── const: THEME_KEY ──
const THEME_KEY = 'noteflow_theme';
// ── function: loadNotes ──
function loadNotes() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw) return JSON.parse(raw);
} catch (e) {}
return getDefaultNotes();
}
// ── function: saveNotes ──
function saveNotes(notes) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(notes));
}
// ── function: loadTheme ──
function loadTheme() {
return localStorage.getItem(THEME_KEY) || 'light';
}
// ── function: saveTheme ──
function saveTheme(theme) {
localStorage.setItem(THEME_KEY, theme);
}
// ── function: generateId ──
function generateId() {
return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
}
// ── function: formatDate ──
function formatDate(ts) {
const d = new Date(ts);
const now = new Date();
if (d.toDateString() === now.toDateString()) {
return 'Today at ' + d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}
const yesterday = new Date(now);
yesterday.setDate(yesterday.getDate() - 1);
if (d.toDateString() === yesterday.toDateString()) {
return 'Yesterday';
}
return d.toLocaleDateString([], { month: 'short', day: 'numeric' });
}
// ── function: stripHtml ──
function stripHtml(html) {
const tmp = document.createElement('div');
tmp.innerHTML = html;
return tmp.textContent || '';
}
// ── function: countWords ──
function countWords(text) {
const clean = text.trim();
if (!clean) return 0;
return clean.split(/\s+/).length;
}
// ── function: getDefaultNotes ──
function getDefaultNotes() {
return [
{
id: generateId(), title: 'Welcome to Noteflow ✨', folder: 'personal', favorite: true, tags: ['welcome'],
content: '<h1>Welcome to Noteflow</h1><p>Your beautiful new notes application. Here are some things you can do:</p><ul><li>Create and organize notes into folders</li><li>Use the rich text toolbar for formatting</li><li>Add tags to categorize your notes</li><li>Favorite important notes</li><li>Search across all your notes</li><li>Toggle dark mode with the sun icon</li></ul><blockquote>All your notes are saved locally in your browser.</blockquote>',
created: Date.now() - 86400000, modified: Date.now() - 3600000
},
{
id: generateId(), title: 'Project Roadmap Q1', folder: 'work', favorite: false, tags: ['planning', 'work'],
content: '<h2>Q1 Goals</h2><p>Prioritize the following initiatives:</p><ol><li>User onboarding redesign</li><li>Performance optimization sprint</li><li>New dashboard widgets</li></ol><p><strong>Deadline:</strong> March 31st</p>',
created: Date.now() - 172800000, modified: Date.now() - 7200000
},
{
id: generateId(), title: 'App Ideas Brainstorm', folder: 'ideas', favorite: true, tags: ['ideas'],
content: '<p>Some app ideas to explore:</p><ul><li>Habit tracker with streaks and social accountability</li><li>Recipe organizer that generates weekly meal plans</li><li>Meditation timer with ambient soundscapes</li><li>Book notes with highlight export</li></ul>',
created: Date.now() - 259200000, modified: Date.now() - 86400000
},
{
id: generateId(), title: 'Meeting Notes — Design Review', folder: 'work', favorite: false, tags: ['meeting'],
content: '<p><strong>Date:</strong> Monday</p><h3>Attendees</h3><ul><li>Sarah (Design Lead)</li><li>Marcus (Engineering)</li><li>Jen (Product)</li></ul><h3>Action Items</h3><ul><li>Update component library by Friday</li><li>Schedule user testing sessions</li><li>Review accessibility audit results</li></ul>',
created: Date.now() - 345600000, modified: Date.now() - 172800000
},
{
id: generateId(), title: 'Reading List', folder: 'personal', favorite: false, tags: ['books'],
content: '<h3>To Read</h3><ul><li>Atomic Habits — James Clear</li><li>Deep Work — Cal Newport</li><li>The Design of Everyday Things — Don Norman</li></ul><h3>In Progress</h3><p>Thinking, Fast and Slow — Daniel Kahneman (ch. 12)</p>',
created: Date.now() - 432000000, modified: Date.now() - 259200000
}
];
}
// ── let: state ──
let state = {
notes: [],
activeNoteId: null,
currentFolder: 'all',
searchQuery: '',
theme: 'light'
};
// ── function: getActiveNote ──
function getActiveNote() {
return state.notes.find(n => n.id === state.activeNoteId) || null;
}
// ── function: getFilteredNotes ──
function getFilteredNotes() {
let filtered = [...state.notes];
if (state.currentFolder === 'favorites') {
filtered = filtered.filter(n => n.favorite);
} else if (state.currentFolder !== 'all') {
filtered = filtered.filter(n => n.folder === state.currentFolder);
}
if (state.searchQuery) {
const q = state.searchQuery.toLowerCase();
filtered = filtered.filter(n =>
n.title.toLowerCase().includes(q) ||
stripHtml(n.content).toLowerCase().includes(q) ||
n.tags.some(t => t.toLowerCase().includes(q))
);
}
return filtered.sort((a, b) => b.modified - a.modified);
}
// ── function: getAllTags ──
function getAllTags() {
const tagMap = {};
state.notes.forEach(n => {
n.tags.forEach(t => { tagMap[t] = (tagMap[t] || 0) + 1; });
});
return Object.entries(tagMap).sort((a, b) => b[1] - a[1]);
}
// ── function: getFolderName ──
function getFolderName(folder) {
const names = { all: 'All Notes', favorites: 'Favorites', personal: 'Personal', work: 'Work', ideas: 'Ideas' };
return names[folder] || folder;
}
// ── function: renderNoteList ──
function renderNoteList() {
const list = document.getElementById('note-list');
const filtered = getFilteredNotes();
document.getElementById('folder-title').textContent = getFolderName(state.currentFolder);
document.getElementById('note-count').textContent = `${filtered.length} note${filtered.length !== 1 ? 's' : ''}`;
if (!filtered.length) {
list.innerHTML = `<div style="text-align:center;padding:40px 20px;color:var(--text-tertiary);font-size:0.85rem;">No notes found</div>`;
return;
}
list.innerHTML = filtered.map(note => {
const preview = stripHtml(note.content).slice(0, 100);
const isActive = note.id === state.activeNoteId;
return `
<div class="note-item${isActive ? ' active' : ''}" data-id="${note.id}">
<div class="note-item-title">${note.title || 'Untitled'}</div>
<div class="note-item-preview">${preview || 'Empty note'}</div>
<div class="note-item-meta">
${note.favorite ? '<span class="note-item-fav">★</span>' : ''}
<span>${formatDate(note.modified)}</span>
${note.tags.slice(0, 2).map(t => `<span class="note-item-tag">${t}</span>`).join('')}
</div>
</div>
`;
}).join('');
list.querySelectorAll('.note-item').forEach(el => {
el.addEventListener('click', () => selectNote(el.dataset.id));
});
}
// ── function: renderSidebarTags ──
function renderSidebarTags() {
const container = document.getElementById('tags-list');
const tags = getAllTags();
container.innerHTML = tags.map(([tag, count]) =>
`<span class="tag-pill" data-tag="${tag}">${tag} (${count})</span>`
).join('');
container.querySelectorAll('.tag-pill').forEach(el => {
el.addEventListener('click', () => {
state.searchQuery = el.dataset.tag;
document.getElementById('search-input').value = '#' + el.dataset.tag;
renderNoteList();
});
});
}
// ── function: renderEditor ──
function renderEditor() {
const note = getActiveNote();
const emptyState = document.getElementById('empty-state');
const editorContainer = document.getElementById('editor-container');
const noteActions = document.getElementById('note-actions');
if (!note) {
emptyState.classList.remove('hidden');
editorContainer.classList.add('hidden');
noteActions.classList.add('hidden');
return;
}
emptyState.classList.add('hidden');
editorContainer.classList.remove('hidden');
noteActions.classList.remove('hidden');
document.getElementById('note-title-input').value = note.title;
document.getElementById('editor-content').innerHTML = note.content;
document.getElementById('folder-select').value = note.folder;
// Favorite button state
const favBtn = document.getElementById('favorite-btn');
if (note.favorite) {
favBtn.classList.add('favorited');
} else {
favBtn.classList.remove('favorited');
}
renderNoteTags(note);
updateWordCount();
}
// ── function: renderNoteTags ──
function renderNoteTags(note) {
const container = document.getElementById('note-tags');
container.innerHTML = note.tags.map(tag =>
`<span class="note-tag">${tag}<span class="remove-tag" data-tag="${tag}">×</span></span>`
).join('');
container.querySelectorAll('.remove-tag').forEach(el => {
el.addEventListener('click', (e) => {
e.stopPropagation();
const n = getActiveNote();
if (!n) return;
n.tags = n.tags.filter(t => t !== el.dataset.tag);
n.modified = Date.now();
saveNotes(state.notes);
renderEditor();
renderNoteList();
renderSidebarTags();
});
});
}
// ── function: updateWordCount ──
function updateWordCount() {
const editor = document.getElementById('editor-content');
const text = stripHtml(editor.innerHTML);
const words = countWords(text);
const chars = text.length;
document.getElementById('word-count').textContent = `${words} word${words !== 1 ? 's' : ''}`;
document.getElementById('char-count').textContent = `${chars} character${chars !== 1 ? 's' : ''}`;
}
// ── function: updateLastSaved ──
function updateLastSaved() {
const el = document.getElementById('last-saved');
if (el) {
el.textContent = `Saved ${new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`;
}
}
// ── function: selectNote ──
function selectNote(id) {
state.activeNoteId = id;
renderEditor();
renderNoteList();
}
// ── function: createNewNote ──
function createNewNote() {
const note = {
id: generateId(),
title: '',
folder: state.currentFolder === 'all' || state.currentFolder === 'favorites' ? 'personal' : state.currentFolder,
favorite: false,
tags: [],
content: '',
created: Date.now(),
modified: Date.now()
};
state.notes.unshift(note);
saveNotes(state.notes);
selectNote(note.id);
renderSidebarTags();
setTimeout(() => document.getElementById('note-title-input').focus(), 50);
}
// ── function: deleteActiveNote ──
function deleteActiveNote() {
if (!state.activeNoteId) return;
state.notes = state.notes.filter(n => n.id !== state.activeNoteId);
saveNotes(state.notes);
state.activeNoteId = null;
renderEditor();
renderNoteList();
renderSidebarTags();
}
// ── function: toggleFavorite ──
function toggleFavorite() {
const note = getActiveNote();
if (!note) return;
note.favorite = !note.favorite;
note.modified = Date.now();
saveNotes(state.notes);
renderEditor();
renderNoteList();
}
// ── function: switchFolder ──
function switchFolder(folder) {
state.currentFolder = folder;
document.querySelectorAll('.nav-item').forEach(el => {
el.classList.toggle('active', el.dataset.folder === folder);
});
renderNoteList();
}
// ── function: filterByTag ──
function filterByTag(tag) {
state.searchQuery = tag;
document.getElementById('search-input').value = '#' + tag;
renderNoteList();
}
// ── function: saveCurrentNote ──
function saveCurrentNote() {
const note = getActiveNote();
if (!note) return;
note.title = document.getElementById('note-title-input').value;
note.content = document.getElementById('editor-content').innerHTML;
note.modified = Date.now();
saveNotes(state.notes);
updateLastSaved();
renderNoteList();
}
// ── function: executeToolbarAction ──
function executeToolbarAction(action) {
const editor = document.getElementById('editor-content');
editor.focus();
switch (action) {
case 'bold':
document.execCommand('bold', false, null);
break;
case 'italic':
document.execCommand('italic', false, null);
break;
case 'underline':
document.execCommand('underline', false, null);
break;
case 'strikethrough':
document.execCommand('strikeThrough', false, null);
break;
case 'h1':
document.execCommand('formatBlock', false, '<h1>');
break;
case 'h2':
document.execCommand('formatBlock', false, '<h2>');
break;
case 'h3':
document.execCommand('formatBlock', false, '<h3>');
break;
case 'ul':
document.execCommand('insertUnorderedList', false, null);
break;
case 'ol':
document.execCommand('insertOrderedList', false, null);
break;
case 'checklist':
insertChecklist();
break;
case 'quote':
document.execCommand('formatBlock', false, '<blockquote>');
break;
case 'code':
insertCodeBlock();
break;
case 'link':
const url = prompt('Enter URL:');
if (url) {
document.execCommand('createLink', false, url);
}
break;
}
saveCurrentNote();
}
// ── function: insertChecklist ──
function insertChecklist() {
const html = '<div style="display:flex;align-items:center;gap:8px;margin:4px 0;"><input type="checkbox" style="width:16px;height:16px;accent-color:var(--accent);"><span>Task item</span></div>';
document.execCommand('insertHTML', false, html);
}
// ── function: insertCodeBlock ──
function insertCodeBlock() {
const code = prompt('Enter code:');
if (code) {
document.execCommand('insertHTML', false, `<pre><code>${code.replace(/</g, '<').replace(/>/g, '>')}</code></pre><p><br></p>`);
}
}
// ── function: addTagToNote ──
function addTagToNote(tag) {
const note = getActiveNote();
if (!note || !tag.trim()) return;
tag = tag.trim().toLowerCase().replace(/[^a-z0-9-_]/g, '');
if (tag && !note.tags.includes(tag)) {
note.tags.push(tag);
note.modified = Date.now();
saveNotes(state.notes);
renderEditor();
renderSidebarTags();
}
}
// ── function: toggleTheme ──
function toggleTheme() {
state.theme = state.theme === 'light' ? 'dark' : 'light';
document.documentElement.setAttribute('data-theme', state.theme);
saveTheme(state.theme);
}
// ── main ──
// Initialize theme
state.theme = loadTheme();
document.documentElement.setAttribute('data-theme', state.theme);
// Load notes
state.notes = loadNotes();
// Initial render
renderNoteList();
renderSidebarTags();
renderEditor();
// Event: New note button
document.getElementById('new-note-btn').addEventListener('click', createNewNote);
// Event: Theme toggle
document.getElementById('theme-toggle').addEventListener('click', toggleTheme);
// Event: Folder navigation
document.querySelectorAll('.nav-item').forEach(el => {
el.addEventListener('click', () => switchFolder(el.dataset.folder));
});
// Event: Search input
let searchTimeout;
document.getElementById('search-input').addEventListener('input', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
state.searchQuery = e.target.value.replace(/^#/, '').trim();
renderNoteList();
}, 200);
});
// Event: Note title change
document.getElementById('note-title-input').addEventListener('input', () => {
saveCurrentNote();
});
// Event: Editor content change (debounced)
let editorTimeout;
document.getElementById('editor-content').addEventListener('input', () => {
clearTimeout(editorTimeout);
editorTimeout = setTimeout(() => {
saveCurrentNote();
updateWordCount();
}, 500);
});
// Event: Word count on keyup for responsiveness
document.getElementById('editor-content').addEventListener('keyup', updateWordCount);
// Event: Toolbar buttons
document.querySelectorAll('.tool-btn').forEach(btn => {
btn.addEventListener('click', () => executeToolbarAction(btn.dataset.action));
});
// Event: Tag input
document.getElementById('tag-input').addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
addTagToNote(e.target.value);
e.target.value = '';
}
});
// Event: Folder select change
document.getElementById('folder-select').addEventListener('change', (e) => {
const note = getActiveNote();
if (!note) return;
note.folder = e.target.value;
note.modified = Date.now();
saveNotes(state.notes);
renderNoteList();
});
// Event: Favorite button
document.getElementById('favorite-btn').addEventListener('click', toggleFavorite);
// Event: Delete button (show modal)
document.getElementById('delete-btn').addEventListener('click', () => {
document.getElementById('delete-modal').classList.remove('hidden');
});
// Event: Cancel delete
document.getElementById('cancel-delete').addEventListener('click', () => {
document.getElementById('delete-modal').classList.add('hidden');
});
// Event: Confirm delete
document.getElementById('confirm-delete').addEventListener('click', () => {
deleteActiveNote();
document.getElementById('delete-modal').classList.add('hidden');
});
// Event: Modal backdrop click to close
document.querySelector('.modal-backdrop').addEventListener('click', () => {
document.getElementById('delete-modal').classList.add('hidden');
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
// Ctrl/Cmd + N = new note
if ((e.ctrlKey || e.metaKey) && e.key === 'n') {
e.preventDefault();
createNewNote();
}
// Escape to deselect
if (e.key === 'Escape' && state.activeNoteId) {
const activeEl = document.activeElement;
if (activeEl && (activeEl.tagName === 'INPUT' || activeEl.contentEditable === 'true')) return;
state.activeNoteId = null;
renderEditor();
renderNoteList();
}
});
// Auto-save on page unload
window.addEventListener('beforeunload', () => {
saveCurrentNote();
});
console.log('%c📝 Noteflow loaded!', 'color: #6c5ce7; font-size: 14px; font-weight: bold;');
</script>
</body>
</html>
<!-- agent-meta {"model":"thinkingcap-qwen3.6-27b","provider":"lmstudio","persona":"composer","sessionId":"dad3018d-603b-4227-8cf2-d704802f380b","tokensIn":567107,"tokensOut":20875,"tokensTotal":587982,"turns":27,"toolCalls":26,"failedToolCalls":2,"timestamp":"2026-07-18T18:05:38.578Z"} -->