Notes
GLM 5.2 · basic

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 48,076 bytes · SHA-256 b8393cfe6825
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quill — Notes</title>
<style>
:root{
--bg:#fbfaf7;
--bg-soft:#f4f1ea;
--panel:#ffffff;
--ink:#2b2a27;
--ink-soft:#5c5a55;
--ink-faint:#9a968d;
--line:#e8e3d8;
--line-soft:#f0ebe0;
--accent:#c8553d;
--accent-soft:#f0d9d2;
--gold:#d4a574;
--green:#7c8a5e;
--shadow:0 1px 2px rgba(43,42,39,.04), 0 8px 24px rgba(43,42,39,.06);
--shadow-lg:0 4px 12px rgba(43,42,39,.08), 0 24px 48px rgba(43,42,39,.10);
--radius:10px;
--radius-sm:6px;
--sidebar-w:260px;
--list-w:320px;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%;overflow:hidden}
body{
font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',systemui,sans-serif;
background:var(--bg);
color:var(--ink);
font-size:14px;
line-height:1.5;
-webkit-font-smoothing:antialiased;
text-rendering:optimizeLegibility;
}
button{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}
input,textarea{font-family:inherit;color:inherit;background:none;border:none;outline:none}
/* Scrollbars */
::-webkit-scrollbar{width:8px;height:8px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:rgba(43,42,39,.12);border-radius:8px;border:2px solid transparent;background-clip:content-box}
::-webkit-scrollbar-thumb:hover{background:rgba(43,42,39,.22);background-clip:content-box}
.app{
display:grid;
grid-template-columns:var(--sidebar-w) var(--list-w) 1fr;
height:100vh;
}
/* ============ SIDEBAR ============ */
.sidebar{
background:var(--bg-soft);
border-right:1px solid var(--line);
display:flex;
flex-direction:column;
padding:14px 10px;
overflow:hidden;
}
.brand{
display:flex;align-items:center;gap:10px;
padding:8px 10px 16px;
}
.brand-mark{
width:30px;height:30px;border-radius:8px;
background:linear-gradient(135deg,var(--accent) 0%,#a93f2a 100%);
display:grid;place-items:center;
box-shadow:0 2px 8px rgba(200,85,61,.35);
color:#fff;font-weight:800;font-size:15px;
letter-spacing:-.5px;
}
.brand-name{font-weight:700;font-size:15px;letter-spacing:-.3px}
.brand-name span{color:var(--ink-faint);font-weight:400}
.search-wrap{
position:relative;margin:0 6px 14px;
}
.search{
width:100%;
background:var(--panel);
border:1px solid var(--line);
border-radius:8px;
padding:8px 10px 8px 32px;
font-size:13px;
transition:all .15s;
}
.search:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.search-icon{
position:absolute;left:10px;top:50%;transform:translateY(-50%);
color:var(--ink-faint);pointer-events:none;
}
.kbd{
position:absolute;right:8px;top:50%;transform:translateY(-50%);
font-size:10px;color:var(--ink-faint);
background:var(--bg-soft);
padding:2px 6px;border-radius:4px;border:1px solid var(--line);
}
.new-btn{
display:flex;align-items:center;gap:8px;
width:calc(100% - 12px);margin:0 6px 16px;
padding:9px 12px;
background:var(--accent);color:#fff;
border-radius:8px;font-weight:600;font-size:13px;
box-shadow:0 2px 6px rgba(200,85,61,.3);
transition:all .15s;
}
.new-btn:hover{background:#b5482f;transform:translateY(-1px);box-shadow:0 4px 10px rgba(200,85,61,.4)}
.new-btn:active{transform:translateY(0)}
.new-btn svg{flex-shrink:0}
.nav-section{margin-bottom:18px}
.nav-label{
font-size:11px;font-weight:600;letter-spacing:.08em;
text-transform:uppercase;color:var(--ink-faint);
padding:0 10px 6px;
}
.nav-item{
display:flex;align-items:center;gap:10px;
padding:6px 10px;border-radius:6px;
color:var(--ink-soft);font-size:13px;
cursor:pointer;transition:all .12s;
user-select:none;
}
.nav-item:hover{background:rgba(43,42,39,.05);color:var(--ink)}
.nav-item.active{background:rgba(200,85,61,.10);color:var(--accent)}
.nav-item.active .nav-count{background:var(--accent);color:#fff}
.nav-item svg{flex-shrink:0;opacity:.7}
.nav-item.active svg{opacity:1}
.nav-text{flex:1;font-weight:500}
.nav-count{
font-size:11px;color:var(--ink-faint);
background:rgba(43,42,39,.06);
padding:1px 7px;border-radius:10px;font-weight:600;
}
.folder-list{padding:0 4px}
.folder-item{
display:flex;align-items:center;gap:8px;
padding:5px 8px;border-radius:6px;
color:var(--ink-soft);font-size:13px;
cursor:pointer;transition:all .12s;
}
.folder-item:hover{background:rgba(43,42,39,.05);color:var(--ink)}
.folder-item.active{color:var(--accent)}
.folder-item.active .folder-dot{background:var(--accent)}
.folder-dot{
width:8px;height:8px;border-radius:50%;
background:var(--gold);flex-shrink:0;
}
.folder-name{flex:1}
.folder-count{font-size:11px;color:var(--ink-faint)}
.sidebar-footer{
margin-top:auto;padding:10px;
border-top:1px solid var(--line);
display:flex;align-items:center;gap:10px;
}
.avatar{
width:30px;height:30px;border-radius:50%;
background:linear-gradient(135deg,var(--green),#5a6845);
color:#fff;display:grid;place-items:center;
font-weight:700;font-size:12px;flex-shrink:0;
}
.user-info{flex:1;min-width:0}
.user-name{font-size:12px;font-weight:600}
.user-plan{font-size:11px;color:var(--ink-faint)}
.icon-btn{
width:28px;height:28px;border-radius:6px;
display:grid;place-items:center;
color:var(--ink-faint);transition:all .12s;
}
.icon-btn:hover{background:rgba(43,42,39,.06);color:var(--ink)}
/* ============ NOTES LIST ============ */
.list-pane{
background:var(--bg);
border-right:1px solid var(--line);
display:flex;flex-direction:column;
overflow:hidden;
}
.list-header{
padding:18px 20px 12px;
border-bottom:1px solid var(--line);
}
.list-title{
font-size:18px;font-weight:700;letter-spacing:-.3px;
display:flex;align-items:center;gap:8px;
}
.list-title .badge{
font-size:11px;font-weight:600;color:var(--ink-faint);
background:var(--bg-soft);padding:2px 8px;border-radius:10px;
}
.list-meta{
font-size:12px;color:var(--ink-faint);
margin-top:4px;
}
.list-sort{
display:flex;gap:4px;margin-top:12px;
}
.sort-chip{
font-size:11px;padding:4px 10px;border-radius:6px;
color:var(--ink-faint);font-weight:500;
transition:all .12s;
}
.sort-chip:hover{background:var(--bg-soft);color:var(--ink)}
.sort-chip.active{background:var(--ink);color:#fff}
.notes-scroll{flex:1;overflow-y:auto;padding:8px 10px 20px}
.note-card{
padding:12px;border-radius:8px;
cursor:pointer;transition:all .12s;
margin-bottom:2px;border:1px solid transparent;
position:relative;
}
.note-card:hover{background:var(--panel);border-color:var(--line-soft)}
.note-card.active{
background:var(--panel);border-color:var(--line);
box-shadow:0 1px 3px rgba(43,42,39,.04);
}
.note-card.active::before{
content:'';position:absolute;left:0;top:14px;bottom:14px;width:3px;
background:var(--accent);border-radius:0 3px 3px 0;margin-left:-10px;
}
.note-card-title{
font-size:14px;font-weight:600;color:var(--ink);
margin-bottom:3px;letter-spacing:-.2px;
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
display:flex;align-items:center;gap:6px;
}
.pinned-icon{color:var(--gold);flex-shrink:0}
.note-card-preview{
font-size:12.5px;color:var(--ink-faint);
line-height:1.45;
display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
overflow:hidden;margin-bottom:8px;
}
.note-card-footer{
display:flex;align-items:center;gap:8px;
font-size:11px;color:var(--ink-faint);
}
.note-folder-tag{
display:inline-flex;align-items:center;gap:4px;
padding:1px 7px;border-radius:4px;
background:var(--bg-soft);color:var(--ink-soft);
font-weight:500;
}
.note-folder-tag .dot{width:6px;height:6px;border-radius:50%}
.empty-list{
padding:60px 30px;text-align:center;color:var(--ink-faint);
}
.empty-list svg{margin-bottom:12px;opacity:.5}
/* ============ EDITOR ============ */
.editor-pane{
background:var(--panel);
display:flex;flex-direction:column;
overflow:hidden;position:relative;
}
.editor-toolbar{
display:flex;align-items:center;gap:4px;
padding:10px 24px;
border-bottom:1px solid var(--line);
background:var(--panel);
}
.toolbar-breadcrumb{
font-size:12px;color:var(--ink-faint);
display:flex;align-items:center;gap:6px;flex:1;
}
.toolbar-breadcrumb b{color:var(--ink-soft);font-weight:500}
.toolbar-divider{width:1px;height:18px;background:var(--line);margin:0 6px}
.tool-btn{
width:30px;height:30px;border-radius:6px;
display:grid;place-items:center;
color:var(--ink-soft);transition:all .12s;
}
.tool-btn:hover{background:var(--bg-soft);color:var(--ink)}
.tool-btn.active{background:var(--accent-soft);color:var(--accent)}
.save-status{
font-size:11px;color:var(--ink-faint);
display:flex;align-items:center;gap:6px;
padding:0 8px;
}
.save-dot{
width:6px;height:6px;border-radius:50%;background:var(--green);
}
.save-dot.saving{background:var(--gold);animation:pulse 1s infinite}
@keyframes pulse{50%{opacity:.4}}
.editor-scroll{
flex:1;overflow-y:auto;
display:flex;justify-content:center;
}
.editor-content{
width:100%;max-width:720px;
padding:48px 64px 200px;
}
.note-title-input{
font-size:34px;font-weight:800;letter-spacing:-.8px;
color:var(--ink);width:100%;
padding:0;margin-bottom:6px;line-height:1.2;
}
.note-title-input::placeholder{color:var(--line)}
.note-meta-row{
display:flex;align-items:center;gap:14px;
margin-bottom:32px;padding-bottom:20px;
border-bottom:1px solid var(--line-soft);
font-size:12px;color:var(--ink-faint);
}
.meta-item{display:flex;align-items:center;gap:5px}
.meta-item svg{opacity:.7}
.editor-body{
font-size:15.5px;line-height:1.75;color:var(--ink);
outline:none;min-height:400px;
}
.editor-body h1{font-size:24px;font-weight:700;margin:24px 0 12px;letter-spacing:-.4px}
.editor-body h2{font-size:19px;font-weight:700;margin:22px 0 10px;letter-spacing:-.3px}
.editor-body h3{font-size:16px;font-weight:700;margin:18px 0 8px}
.editor-body p{margin:0 0 14px}
.editor-body ul,.editor-body ol{margin:0 0 14px;padding-left:24px}
.editor-body li{margin-bottom:4px}
.editor-body li::marker{color:var(--accent)}
.editor-body blockquote{
border-left:3px solid var(--accent);
padding:4px 16px;margin:14px 0;
color:var(--ink-soft);font-style:italic;
background:var(--bg-soft);border-radius:0 6px 6px 0;
}
.editor-body code{
font-family:'SF Mono',Menlo,Monaco,Consolas,monospace;
font-size:13px;background:var(--bg-soft);
padding:2px 6px;border-radius:4px;color:var(--accent);
}
.editor-body pre{
background:#2b2a27;color:#e8e3d8;
padding:16px;border-radius:8px;
font-size:13px;overflow-x:auto;margin:14px 0;
font-family:'SF Mono',Menlo,Monaco,Consolas,monospace;
}
.editor-body pre code{background:none;color:inherit;padding:0}
.editor-body a{color:var(--accent);text-decoration:underline;text-underline-offset:2px}
.editor-body hr{border:none;border-top:1px solid var(--line);margin:24px 0}
.editor-body mark{background:#fff3a8;padding:1px 3px;border-radius:2px}
.editor-body .tag{color:var(--accent);font-weight:500}
.editor-body:empty::before{
content:attr(data-placeholder);
color:var(--ink-faint);pointer-events:none;
}
.word-count{
position:absolute;bottom:0;left:0;right:0;
padding:8px 24px;
display:flex;justify-content:center;
pointer-events:none;
}
.word-count-inner{
background:var(--bg-soft);
border:1px solid var(--line);
padding:4px 12px;border-radius:20px;
font-size:11px;color:var(--ink-faint);
display:flex;gap:14px;align-items:center;
}
.word-count-inner b{color:var(--ink-soft);font-weight:600}
/* Empty editor state */
.empty-editor{
flex:1;display:flex;flex-direction:column;
align-items:center;justify-content:center;
color:var(--ink-faint);text-align:center;padding:40px;
}
.empty-editor-mark{
width:80px;height:80px;border-radius:20px;
background:linear-gradient(135deg,var(--accent),#a93f2a);
display:grid;place-items:center;color:#fff;
margin-bottom:24px;font-size:36px;font-weight:800;
box-shadow:0 12px 32px rgba(200,85,61,.3);
transform:rotate(-6deg);
}
.empty-editor h2{font-size:22px;color:var(--ink);margin-bottom:8px;font-weight:700;letter-spacing:-.3px}
.empty-editor p{font-size:14px;max-width:340px;margin-bottom:24px;line-height:1.6}
.empty-editor-hint{
display:flex;gap:12px;margin-top:8px;
}
.hint-card{
background:var(--bg-soft);border:1px solid var(--line);
padding:10px 14px;border-radius:8px;
font-size:12px;color:var(--ink-soft);
display:flex;align-items:center;gap:8px;
}
.hint-card kbd{
background:var(--panel);border:1px solid var(--line);
padding:2px 6px;border-radius:4px;font-size:11px;
font-family:inherit;
}
/* Toast */
.toast{
position:fixed;bottom:24px;left:50%;
transform:translateX(-50%) translateY(100px);
background:var(--ink);color:#fff;
padding:10px 18px;border-radius:8px;
font-size:13px;font-weight:500;
box-shadow:var(--shadow-lg);
transition:transform .3s cubic-bezier(.4,0,.2,1);
z-index:1000;
display:flex;align-items:center;gap:8px;
}
.toast.show{transform:translateX(-50%) translateY(0)}
/* Modal */
.modal-backdrop{
position:fixed;inset:0;
background:rgba(43,42,39,.4);
backdrop-filter:blur(4px);
display:none;align-items:center;justify-content:center;
z-index:100;
}
.modal-backdrop.show{display:flex;animation:fadeIn .2s}
@keyframes fadeIn{from{opacity:0}}
.modal{
background:var(--panel);border-radius:12px;
padding:24px;width:90%;max-width:420px;
box-shadow:var(--shadow-lg);
animation:scaleIn .2s;
}
@keyframes scaleIn{from{transform:scale(.95);opacity:0}}
.modal h3{font-size:18px;font-weight:700;margin-bottom:6px;letter-spacing:-.3px}
.modal p{color:var(--ink-faint);font-size:13px;margin-bottom:18px}
.modal-input{
width:100%;padding:10px 12px;
border:1px solid var(--line);border-radius:8px;
font-size:14px;margin-bottom:18px;
background:var(--bg);
}
.modal-input:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.modal-actions{display:flex;gap:8px;justify-content:flex-end}
.btn{
padding:8px 16px;border-radius:7px;font-size:13px;font-weight:600;
transition:all .12s;
}
.btn-ghost{color:var(--ink-soft)}
.btn-ghost:hover{background:var(--bg-soft)}
.btn-primary{background:var(--accent);color:#fff}
.btn-primary:hover{background:#b5482f}
.btn-danger{background:#c8553d;color:#fff}
.btn-danger:hover{background:#a93f2a}
/* Responsive */
@media (max-width:1024px){
:root{--sidebar-w:220px;--list-w:280px}
.editor-content{padding:36px 40px 160px}
}
@media (max-width:768px){
.app{grid-template-columns:1fr}
.sidebar,.list-pane{display:none}
.sidebar.mobile-show,.list-pane.mobile-show{display:flex;position:absolute;inset:0;z-index:50}
}
</style>
</head>
<body>
<div class="app">
<!-- ============ SIDEBAR ============ -->
<aside class="sidebar">
<div class="brand">
<div class="brand-mark">Q</div>
<div class="brand-name">Quill<span> · Notes</span></div>
</div>
<div class="search-wrap">
<svg class="search-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<input class="search" id="searchInput" placeholder="Search notes..." />
<span class="kbd">⌘K</span>
</div>
<button class="new-btn" id="newNoteBtn">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
New Note
</button>
<div class="nav-section">
<div class="nav-label">Library</div>
<div class="nav-item active" data-filter="all">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/></svg>
<span class="nav-text">All Notes</span>
<span class="nav-count" id="countAll">0</span>
</div>
<div class="nav-item" data-filter="starred">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
<span class="nav-text">Starred</span>
<span class="nav-count" id="countStarred">0</span>
</div>
<div class="nav-item" data-filter="today">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
<span class="nav-text">Today</span>
<span class="nav-count" id="countToday">0</span>
</div>
<div class="nav-item" data-filter="recent">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
<span class="nav-text">Recent</span>
</div>
</div>
<div class="nav-section" style="flex:1;overflow-y:auto">
<div class="nav-label">Folders</div>
<div class="folder-list" id="folderList"></div>
</div>
<div class="sidebar-footer">
<div class="avatar">EM</div>
<div class="user-info">
<div class="user-name">Elena Marsh</div>
<div class="user-plan">Quill Pro · 2.4 GB</div>
</div>
<button class="icon-btn" title="Settings">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
</button>
</div>
</aside>
<!-- ============ NOTES LIST ============ -->
<section class="list-pane">
<div class="list-header">
<div class="list-title">
<span id="listTitle">All Notes</span>
<span class="badge" id="listBadge">0</span>
</div>
<div class="list-meta" id="listSubtitle">Everything in one place</div>
<div class="list-sort">
<button class="sort-chip active" data-sort="updated">Modified</button>
<button class="sort-chip" data-sort="created">Created</button>
<button class="sort-chip" data-sort="title">Title</button>
</div>
</div>
<div class="notes-scroll" id="notesScroll"></div>
</section>
<!-- ============ EDITOR ============ -->
<main class="editor-pane" id="editorPane">
<!-- Filled dynamically -->
</main>
</div>
<!-- New folder modal -->
<div class="modal-backdrop" id="folderModal">
<div class="modal">
<h3>New Folder</h3>
<p>Organize your notes into folders for quick access.</p>
<input class="modal-input" id="folderNameInput" placeholder="Folder name (e.g. Travel)" />
<div class="modal-actions">
<button class="btn btn-ghost" onclick="closeFolderModal()">Cancel</button>
<button class="btn btn-primary" onclick="createFolder()">Create</button>
</div>
</div>
</div>
<!-- Delete confirm -->
<div class="modal-backdrop" id="deleteModal">
<div class="modal">
<h3>Delete note?</h3>
<p id="deleteText">This note will be permanently removed. This action cannot be undone.</p>
<div class="modal-actions">
<button class="btn btn-ghost" onclick="closeDeleteModal()">Cancel</button>
<button class="btn btn-danger" id="confirmDeleteBtn">Delete</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* =========== ICONS =========== */
const icons = {
star: '<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>',
pin: '<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor"><path d="M16 3l5 5-3 1-3 3-1 5-3-3-5 5v-2l5-5-3-3 5-1 3-3z"/></svg>',
bold:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/><path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/></svg>',
italic:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/></svg>',
underline:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3v7a6 6 0 0 0 6 6 6 6 0 0 0 6-6V3"/><line x1="4" y1="21" x2="20" y2="21"/></svg>',
strike:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M16 4H9a3 3 0 0 0-2.83 4"/><path d="M14 12a4 4 0 0 1 0 8H6"/><line x1="4" y1="12" x2="20" y2="12"/></svg>',
code:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>',
h1:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12h8"/><path d="M4 18V6"/><path d="M12 18V6"/><path d="M17 12l3-2v8"/></svg>',
h2:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12h8"/><path d="M4 18V6"/><path d="M12 18V6"/><path d="M21 18h-4c0-4 4-3 4-6 0-1.5-2-2.5-4-1"/></svg>',
list:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>',
quote:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><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.031V20c0 1 0 1 1 1z"/><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 4v3c0 1 0 1 1 1z"/></svg>',
trash:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>',
share:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><polyline points="16 6 12 2 8 6"/><line x1="12" y1="2" x2="12" y2="15"/></svg>',
more:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg>',
cal:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>',
folder:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>',
edit:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>',
add:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>',
feather:'<svg width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20.24 12.24a6 6 0 0 0-8.49-8.49L5 10.5V19h8.5z"/><line x1="16" y1="8" x2="2" y2="22"/><line x1="17.5" y1="15" x2="9" y2="15"/></svg>',
check:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>',
};
/* =========== STATE =========== */
const STORAGE_KEY = 'quill_notes_v1';
let state = {
notes: [],
folders: [],
activeNoteId: null,
filter: 'all',
sort: 'updated',
search: '',
};
/* =========== SEED DATA =========== */
function seedData(){
const now = Date.now();
const day = 86400000;
return {
folders: [
{id:'f1',name:'Personal',color:'#d4a574'},
{id:'f2',name:'Work',color:'#7c8a5e'},
{id:'f3',name:'Ideas',color:'#c8553d'},
{id:'f4',name:'Reading',color:'#5e8a8a'},
],
notes: [
{
id:'n1', folderId:'f2',
title:'Q4 Product Roadmap',
content:`<h1>Q4 Product Roadmap</h1><p>Our focus this quarter centers on three pillars: <strong>performance</strong>, <em>collaboration</em>, and <mark>delight</mark>.</p><h2>Key Initiatives</h2><ul><li>Real-time collaborative editing</li><li>Offline-first sync engine</li><li>Redesigned command palette (<code>⌘K</code>)</li></ul><blockquote>The best way to predict the future is to invent it. — Alan Kay</blockquote><h3>Risks</h3><p>We need to balance shipping speed with stability. The migration to the new sync layer is the biggest unknown.</p>`,
starred:true, pinned:true,
created: now - day*5, updated: now - 3600000*2,
},
{
id:'n2', folderId:'f3',
title:'The Garden Metaphor',
content:`<h1>The Garden Metaphor</h1><p>Notes are like a garden — they need tending. A note left untended becomes overgrown and useless.</p><h2>Two modes of thinking</h2><ul><li><strong>Cultivation</strong> — adding, refining, connecting</li><li><strong>Harvest</strong> — using what you've grown</li></ul><p>Schedule weekly review sessions.</p>`,
starred:false, pinned:false,
created: now - day*8, updated: now - day*2,
},
{
id:'n3', folderId:'f1',
title:'Morning Pages — Nov 12',
content:`<h1>Morning Pages</h1><p>Woke up before the sun today. The house was perfectly still. Made coffee, sat by the window, watched the fog lift off the hills.</p><p>Three things I'm grateful for:</p><ol><li>Quiet mornings</li><li>Good coffee</li><li>Time to write</li></ol>`,
starred:true, pinned:false,
created: now - day*1, updated: now - 3600000*5,
},
{
id:'n4', folderId:'f4',
title:'Notes on "Range" by David Epstein',
content:`<h1>Range — David Epstein</h1><h2>Key argument</h2><p>Generalists triumph in a specialized world. Late specializers often outperform early ones in complex domains.</p><blockquote>Compare yourself to who you were yesterday, not to who someone else is today.</blockquote><h2>Implications for learning</h2><ul><li>Match quality improves with exploration</li><li>Horizontal thinking transfers across domains</li><li>"Frustration early = deeper learning later"</li></ul>`,
starred:false, pinned:false,
created: now - day*14, updated: now - day*3,
},
{
id:'n5', folderId:'f2',
title:'Meeting Notes — Design Review',
content:`<h1>Design Review — Nov 10</h1><p><strong>Attendees:</strong> Elena, Marcus, Priya, Tom</p><h2>Decisions</h2><ul><li>Ship the new sidebar in v2.4</li><li>Defer drag-and-drop to v2.5</li><li>Run A/B test on color scheme</li></ul><h2>Action items</h2><ul><li>Marcus: prototype the folder collapse animation</li><li>Priya: write spec for keyboard shortcuts</li><li>Tom: usability test with 5 users by Friday</li></ul>`,
starred:false, pinned:true,
created: now - day*4, updated: now - day*1,
},
{
id:'n6', folderId:'f3',
title:'App Ideas Brainstorm',
content:`<h1>App Ideas</h1><p>Quick brain dump before I forget:</p><ul><li>A reading tracker that connects highlights to your notes</li><li>A weather app that suggests what to wear</li><li>A shared grocery list that knows what's already in your fridge</li><li>A pomodoro timer that plans your day in advance</li></ul><p>Winner: the reading tracker. There's a real gap between <em>highlighting</em> a book and <em>using</em> what you learned.</p>`,
starred:false, pinned:false,
created: now - day*20, updated: now - day*7,
},
{
id:'n7', folderId:'f1',
title:'Recipe — Miso Glazed Salmon',
content:`<h1>Miso Glazed Salmon</h1><h2>Ingredients</h2><ul><li>2 salmon fillets (6 oz each)</li><li>3 tbsp white miso</li><li>2 tbsp mirin</li><li>1 tbsp sake</li><li>1 tbsp brown sugar</li></ul><h2>Method</h2><ol><li>Whisk glaze ingredients together</li><li>Marinate salmon for 30 minutes</li><li>Broil on high for 6-8 minutes until caramelized</li></ol><p>Serve with rice and steamed greens.</p>`,
starred:true, pinned:false,
created: now - day*30, updated: now - day*10,
},
{
id:'n8', folderId:'f4',
title:'Quotes I Keep Coming Back To',
content:`<h1>Quotes</h1><blockquote>We are what we repeatedly do. Excellence, then, is not an act, but a habit. — Aristotle</blockquote><blockquote>The impediment to action advances action. What stands in the way becomes the way. — Marcus Aurelius</blockquote><blockquote>Tell me, what is it you plan to do with your one wild and precious life? — Mary Oliver</blockquote><blockquote>Perfection is achieved when there is nothing left to take away. — Antoine de Saint-Exupéry</blockquote>`,
starred:false, pinned:false,
created: now - day*45, updated: now - day*15,
},
]
};
}
/* =========== PERSISTENCE =========== */
function load(){
try{
const raw = localStorage.getItem(STORAGE_KEY);
if(raw){
const data = JSON.parse(raw);
state.notes = data.notes || [];
state.folders = data.folders || [];
return;
}
}catch(e){}
const seed = seedData();
state.notes = seed.notes;
state.folders = seed.folders;
}
function save(){
localStorage.setItem(STORAGE_KEY, JSON.stringify({
notes: state.notes,
folders: state.folders,
}));
}
/* =========== HELPERS =========== */
function uid(){return 'n'+Math.random().toString(36).slice(2,9)}
function escapeHtml(s){return (s||'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]))}
function plainText(html){const d=document.createElement('div');d.innerHTML=html||'';return d.textContent||''}
function relativeTime(ts){
const diff = Date.now()-ts;
const min = 60000, hr = 3600000, day = 86400000;
if(diff<min) return 'just now';
if(diff<hr) return Math.floor(diff/min)+'m ago';
if(diff<day) return Math.floor(diff/hr)+'h ago';
if(diff<day*7) return Math.floor(diff/day)+'d ago';
const d = new Date(ts);
return d.toLocaleDateString('en-US',{month:'short',day:'numeric'});
}
function fullDate(ts){
return new Date(ts).toLocaleString('en-US',{
month:'long',day:'numeric',year:'numeric',
hour:'numeric',minute:'2-digit'
});
}
function getFolder(id){return state.folders.find(f=>f.id===id)}
function wordCount(html){return plainText(html).trim().split(/\s+/).filter(Boolean).length}
function readingTime(wc){return Math.max(1,Math.ceil(wc/220))}
/* =========== FILTER / SORT =========== */
function getFilteredNotes(){
let notes = [...state.notes];
const filter = state.filter;
if(filter==='starred') notes = notes.filter(n=>n.starred);
else if(filter==='today'){
const startDay = new Date();startDay.setHours(0,0,0,0);
notes = notes.filter(n=>n.updated >= startDay.getTime());
}
else if(filter==='recent') notes = notes.slice(0,20);
else if(filter.startsWith('folder:')){
const fid = filter.split(':')[1];
notes = notes.filter(n=>n.folderId===fid);
}
if(state.search){
const q = state.search.toLowerCase();
notes = notes.filter(n =>
(n.title||'').toLowerCase().includes(q) ||
plainText(n.content).toLowerCase().includes(q)
);
}
const sort = state.sort;
notes.sort((a,b)=>{
if(a.pinned && !b.pinned) return -1;
if(!a.pinned && b.pinned) return 1;
if(sort==='updated') return b.updated-a.updated;
if(sort==='created') return b.created-a.created;
if(sort==='title') return (a.title||'').localeCompare(b.title||'');
return 0;
});
return notes;
}
/* =========== RENDER: SIDEBAR =========== */
function renderFolders(){
const list = document.getElementById('folderList');
const counts = {};
state.notes.forEach(n=>{counts[n.folderId]=(counts[n.folderId]||0)+1});
list.innerHTML = state.folders.map(f=>`
<div class="folder-item ${state.filter==='folder:'+f.id?'active':''}" data-folder="${f.id}">
<span class="folder-dot" style="background:${f.color}"></span>
<span class="folder-name">${escapeHtml(f.name)}</span>
<span class="folder-count">${counts[f.id]||0}</span>
</div>
`).join('') + `
<div class="folder-item" id="addFolderBtn" style="color:var(--ink-faint);margin-top:4px">
<span style="width:8px;display:grid;place-items:center">${icons.add}</span>
<span class="folder-name">New Folder</span>
</div>
`;
list.querySelectorAll('.folder-item[data-folder]').forEach(el=>{
el.onclick = ()=> setFilter('folder:'+el.dataset.folder);
});
document.getElementById('addFolderBtn').onclick = openFolderModal;
}
function renderCounts(){
document.getElementById('countAll').textContent = state.notes.length;
document.getElementById('countStarred').textContent = state.notes.filter(n=>n.starred).length;
const startDay = new Date();startDay.setHours(0,0,0,0);
document.getElementById('countToday').textContent = state.notes.filter(n=>n.updated>=startDay.getTime()).length;
}
/* =========== RENDER: LIST =========== */
function renderList(){
const notes = getFilteredNotes();
const scroll = document.getElementById('notesScroll');
document.getElementById('listBadge').textContent = notes.length;
const titleMap = {
all:['All Notes','Everything in one place'],
starred:['Starred','Your favorite notes'],
today:['Today','Edited today'],
recent:['Recent','Last opened'],
};
let title='Notes', subtitle='';
if(state.filter.startsWith('folder:')){
const f = getFolder(state.filter.split(':')[1]);
title = f?f.name:'Folder';
subtitle = 'Folder contents';
} else {
[title,subtitle] = titleMap[state.filter] || ['Notes',''];
}
if(state.search){
title = 'Search Results';
subtitle = `Matching "${state.search}"`;
}
document.getElementById('listTitle').textContent = title;
document.getElementById('listSubtitle').textContent = subtitle;
if(notes.length===0){
scroll.innerHTML = `
<div class="empty-list">
${icons.feather}
<div style="font-weight:600;color:var(--ink-soft);margin-bottom:4px">No notes here</div>
<div style="font-size:12px">Create your first note to get started</div>
</div>`;
return;
}
scroll.innerHTML = notes.map(n=>{
const folder = getFolder(n.folderId);
const folderColor = folder?folder.color:'#999';
const folderName = folder?folder.name:'';
const preview = plainText(n.content).slice(0,140) || 'Empty note';
return `
<div class="note-card ${n.id===state.activeNoteId?'active':''}" data-id="${n.id}">
<div class="note-card-title">
${n.pinned?`<span class="pinned-icon">${icons.pin}</span>`:''}
<span style="flex:1;overflow:hidden;text-overflow:ellipsis">${escapeHtml(n.title||'Untitled')}</span>
${n.starred?`<span style="color:var(--gold)">${icons.star}</span>`:''}
</div>
<div class="note-card-preview">${escapeHtml(preview)}</div>
<div class="note-card-footer">
<span class="note-folder-tag">
<span class="dot" style="background:${folderColor}"></span>
${escapeHtml(folderName)}
</span>
<span>${relativeTime(n.updated)}</span>
</div>
</div>
`;
}).join('');
scroll.querySelectorAll('.note-card').forEach(el=>{
el.onclick = ()=> selectNote(el.dataset.id);
});
}
/* =========== RENDER: EDITOR =========== */
function renderEditor(){
const pane = document.getElementById('editorPane');
const note = state.notes.find(n=>n.id===state.activeNoteId);
if(!note){
pane.innerHTML = `
<div class="empty-editor">
<div class="empty-editor-mark">Q</div>
<h2>Welcome back, Elena</h2>
<p>Select a note from the list, or create a new one. Your thoughts are safer here than in your head.</p>
<button class="btn btn-primary" onclick="createNote()" style="padding:10px 20px;font-size:13px">
<span style="display:inline-flex;align-items:center;gap:6px">${icons.add} New Note</span>
</button>
<div class="empty-editor-hint">
<div class="hint-card"><kbd>⌘</kbd><kbd>K</kbd> Quick search</div>
<div class="hint-card"><kbd>⌘</kbd><kbd>N</kbd> New note</div>
</div>
</div>`;
return;
}
const folder = getFolder(note.folderId);
const wc = wordCount(note.content);
pane.innerHTML = `
<div class="editor-toolbar">
<div class="toolbar-breadcrumb">
${icons.folder}
<b>${escapeHtml(folder?folder.name:'Unsorted')}</b>
<span style="opacity:.5">/</span>
<span>${escapeHtml((note.title||'Untitled').slice(0,40))}</span>
</div>
<div class="toolbar-divider"></div>
<button class="tool-btn" id="starBtn" title="Star" style="color:${note.starred?'var(--gold)':'var(--ink-soft)'}">
${icons.star}
</button>
<button class="tool-btn" id="pinBtn" title="Pin to top" style="color:${note.pinned?'var(--accent)':'var(--ink-soft)'}">
${icons.pin}
</button>
<button class="tool-btn" id="shareBtn" title="Share">${icons.share}</button>
<button class="tool-btn" id="deleteBtn" title="Delete">${icons.trash}</button>
<div class="toolbar-divider"></div>
<div class="save-status">
<span class="save-dot" id="saveDot"></span>
<span id="saveText">Saved</span>
</div>
</div>
<div class="editor-scroll">
<div class="editor-content">
<input class="note-title-input" id="titleInput" placeholder="Untitled" value="${escapeHtml(note.title||'')}" />
<div class="note-meta-row">
<div class="meta-item">${icons.cal} ${fullDate(note.updated)}</div>
<div class="meta-item">${icons.edit} ${wc} words · ${readingTime(wc)} min read</div>
<div class="meta-item">${icons.folder} ${escapeHtml(folder?folder.name:'Unsorted')}</div>
</div>
<div class="editor-body" id="bodyInput" contenteditable="true" data-placeholder="Start writing...">${note.content||''}</div>
</div>
</div>
<div class="word-count">
<div class="word-count-inner">
<span><b>${wc}</b> words</span>
<span><b>${readingTime(wc)}</b> min read</span>
<span><b>${note.content?note.content.split('\n').length:0}</b> lines</span>
</div>
</div>
`;
// Wire toolbar
document.getElementById('starBtn').onclick = ()=>{ note.starred = !note.starred; save(); renderAll(); toast(note.starred?'Starred':'Unstarred'); };
document.getElementById('pinBtn').onclick = ()=>{ note.pinned = !note.pinned; save(); renderAll(); toast(note.pinned?'Pinned to top':'Unpinned'); };
document.getElementById('shareBtn').onclick = ()=> toast('Share link copied');
document.getElementById('deleteBtn').onclick = ()=> openDeleteModal(note.id);
// Title input
const titleEl = document.getElementById('titleInput');
titleEl.addEventListener('input', ()=>{
note.title = titleEl.value;
scheduleSave();
renderList();
renderCounts();
});
// Body input
const bodyEl = document.getElementById('bodyInput');
bodyEl.addEventListener('input', ()=>{
note.content = bodyEl.innerHTML;
scheduleSave();
updateWordCount();
});
bodyEl.addEventListener('paste', (e)=>{
e.preventDefault();
const text = (e.clipboardData||window.clipboardData).getData('text/plain');
document.execCommand('insertText', false, text);
});
}
function updateWordCount(){
const note = state.notes.find(n=>n.id===state.activeNoteId);
if(!note) return;
const wc = wordCount(note.content);
document.querySelector('.word-count-inner').innerHTML = `
<span><b>${wc}</b> words</span>
<span><b>${readingTime(wc)}</b> min read</span>
<span><b>${note.content?note.content.split('\n').length:0}</b> lines</span>
`;
}
/* =========== SAVE INDICATOR =========== */
let saveTimer = null;
function scheduleSave(){
const dot = document.getElementById('saveDot');
const txt = document.getElementById('saveText');
if(dot){dot.classList.add('saving');txt.textContent='Saving...';}
clearTimeout(saveTimer);
saveTimer = setTimeout(()=>{
save();
if(dot){dot.classList.remove('saving');txt.textContent='Saved';}
}, 400);
}
/* =========== ACTIONS =========== */
function selectNote(id){
state.activeNoteId = id;
renderList();
renderEditor();
}
function setFilter(f){
state.filter = f;
document.querySelectorAll('.nav-item').forEach(el=>el.classList.remove('active'));
const el = document.querySelector(`.nav-item[data-filter="${f}"]`);
if(el) el.classList.add('active');
renderList();
renderFolders();
}
function createNote(){
const folderId = state.filter.startsWith('folder:') ? state.filter.split(':')[1] : (state.folders[0]&&state.folders[0].id);
const note = {
id: uid(),
folderId: folderId,
title: '',
content: '',
starred: false,
pinned: false,
created: Date.now(),
updated: Date.now(),
};
state.notes.unshift(note);
state.activeNoteId = note.id;
save();
renderAll();
setTimeout(()=>{
const t = document.getElementById('titleInput');
if(t) t.focus();
},50);
toast('New note created');
}
function deleteNote(id){
state.notes = state.notes.filter(n=>n.id!==id);
if(state.activeNoteId===id) state.activeNoteId = null;
save();
renderAll();
toast('Note deleted');
}
function openDeleteModal(id){
document.getElementById('deleteModal').classList.add('show');
document.getElementById('confirmDeleteBtn').onclick = ()=>{ deleteNote(id); closeDeleteModal(); };
}
function closeDeleteModal(){ document.getElementById('deleteModal').classList.remove('show'); }
function openFolderModal(){
document.getElementById('folderModal').classList.add('show');
const inp = document.getElementById('folderNameInput');
inp.value=''; setTimeout(()=>inp.focus(),50);
}
function closeFolderModal(){ document.getElementById('folderModal').classList.remove('show'); }
function createFolder(){
const name = document.getElementById('folderNameInput').value.trim();
if(!name) return;
const colors = ['#d4a574','#7c8a5e','#c8553d','#5e8a8a','#8a5e7c','#5e7c8a'];
state.folders.push({
id: 'f'+Math.random().toString(36).slice(2,7),
name,
color: colors[state.folders.length % colors.length],
});
save();
renderFolders();
closeFolderModal();
toast('Folder created');
}
/* =========== TOAST =========== */
function toast(msg){
const t = document.getElementById('toast');
t.innerHTML = `<span style="color:var(--green)">${icons.check}</span> ${msg}`;
t.classList.add('show');
clearTimeout(t._timer);
t._timer = setTimeout(()=>t.classList.remove('show'), 2000);
}
/* =========== RENDER ALL =========== */
function renderAll(){
renderCounts();
renderFolders();
renderList();
renderEditor();
}
/* =========== EVENTS =========== */
document.getElementById('newNoteBtn').onclick = createNote;
document.getElementById('searchInput').addEventListener('input', e=>{
state.search = e.target.value;
renderList();
});
document.querySelectorAll('.nav-item[data-filter]').forEach(el=>{
el.onclick = ()=> setFilter(el.dataset.filter);
});
document.querySelectorAll('.sort-chip').forEach(el=>{
el.onclick = ()=>{
document.querySelectorAll('.sort-chip').forEach(c=>c.classList.remove('active'));
el.classList.add('active');
state.sort = el.dataset.sort;
renderList();
};
});
document.getElementById('folderNameInput').addEventListener('keydown', e=>{
if(e.key==='Enter') createFolder();
if(e.key==='Escape') closeFolderModal();
});
document.querySelectorAll('.modal-backdrop').forEach(m=>{
m.addEventListener('click', e=>{ if(e.target===m) m.classList.remove('show'); });
});
// Keyboard shortcuts
document.addEventListener('keydown', e=>{
const mod = e.metaKey||e.ctrlKey;
if(mod && e.key==='k'){
e.preventDefault();
document.getElementById('searchInput').focus();
}
if(mod && e.key==='n'){
e.preventDefault();
createNote();
}
if(e.key==='Escape'){
document.querySelectorAll('.modal-backdrop.show').forEach(m=>m.classList.remove('show'));
}
});
// Init
load();
renderAll();
if(state.notes.length>0){
// Don't auto-select so user sees welcome screen
}
</script>
</body>
</html>
<!-- agent-meta {"model":"glm-5.2","provider":"zai","persona":"basic","sessionId":"3178b680-d4bf-49da-bc91-cc5efa2c850e","tokensIn":216,"tokensOut":15283,"tokensTotal":15499,"turns":1,"toolCalls":0,"failedToolCalls":0,"timestamp":"2026-07-12T02:00:01.025Z"} -->