import { useState, useRef, useEffect, useCallback, useMemo } from "react"; const uid = () => Math.random().toString(36).slice(2, 10); const now = () => new Date().toISOString(); const fmtDate = (d) => new Date(d).toLocaleDateString("en-US", { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" }); const COLORS = ["#0d1117","#e94560","#0f3460","#533483","#e07c24","#21bf73","#fd5e53","#3282b8","#ff6b6b","#4ecdc4","#f7dc6f","#bb8fce","#85c1e9","#f8c471","#82e0aa","#f1948a","#aeb6bf","#ffffff","#f39c12","#000000"]; const PEN_SIZES = [1, 2, 3, 5, 8, 12, 20]; const COVERS = [ { bg: "linear-gradient(135deg,#0f0c29,#302b63,#24243e)", spine: "#1a1540", accent: "#6c63ff" }, { bg: "linear-gradient(135deg,#1a1a2e,#16213e,#0f3460)", spine: "#0a1628", accent: "#4fc3f7" }, { bg: "linear-gradient(135deg,#134e5e,#71b280)", spine: "#0e3d4a", accent: "#a5d6a7" }, { bg: "linear-gradient(135deg,#8e2de2,#4a00e0)", spine: "#3a0099", accent: "#ce93d8" }, { bg: "linear-gradient(135deg,#fc4a1a,#f7b733)", spine: "#c43d15", accent: "#ffe082" }, { bg: "linear-gradient(135deg,#c94b4b,#4b134f)", spine: "#3d0f40", accent: "#f48fb1" }, { bg: "linear-gradient(135deg,#11998e,#38ef7d)", spine: "#0d7a71", accent: "#b9f6ca" }, { bg: "linear-gradient(135deg,#373b44,#4286f4)", spine: "#2a2e35", accent: "#90caf9" }, { bg: "linear-gradient(135deg,#141e30,#243b55)", spine: "#0e1520", accent: "#78909c" }, { bg: "linear-gradient(135deg,#f12711,#f5af19)", spine: "#c01f0d", accent: "#fff59d" }, { bg: "linear-gradient(135deg,#2c3e50,#3498db)", spine: "#1e2b38", accent: "#81d4fa" }, { bg: "linear-gradient(135deg,#0f2027,#203a43,#2c5364)", spine: "#0a171c", accent: "#80cbc4" }, { bg: "linear-gradient(135deg,#aa4b6b,#6b6b83,#3b8d99)", spine: "#7a3550", accent: "#f48fb1" }, { bg: "linear-gradient(135deg,#1f1c2c,#928dab)", spine: "#15131f", accent: "#b39ddb" }, { bg: "linear-gradient(135deg,#654ea3,#eaafc8)", spine: "#4e3c7e", accent: "#f8bbd0" }, ]; const DEFAULT_FOLDERS = [ { id: "all", name: "All Notes", icon: "📋" }, { id: "starred", name: "Starred", icon: "⭐" }, { id: "pdfs", name: "PDFs", icon: "📄" }, { id: "trash", name: "Trash", icon: "🗑️" }, ]; const I = { plus:, search:, bold:, italic:, underline:, list:, listOrd:123, check:, trash:, star:, starFill:, pen:, eraser:, highlight:, undo:, redo:, sun:, moon:, menu:, text:, draw:, edit:, palette:, grip:, }; /* ═══════ CONFIRM DIALOG ═══════ */ function ConfirmDialog({ title, message, confirmLabel="Delete", onConfirm, onCancel, dark }) { const T = useMemo(()=>({ bg:dark?"#161b22":"#fff", border:dark?"#30363d":"#d0d7de", text:dark?"#e6edf3":"#1f2328", muted:dark?"#8b949e":"#656d76", danger:dark?"#f85149":"#cf222e" }),[dark]); return (
e.stopPropagation()}>
{title}
{message}
); } /* ═══════ CONTEXT MENU ═══════ */ function ContextMenu({ x, y, items, onClose, dark }) { const T = useMemo(()=>({ bg:dark?"#1c2128":"#fff", border:dark?"#30363d":"#d0d7de", text:dark?"#e6edf3":"#1f2328", hover:dark?"#262c36":"#f3f4f6", danger:dark?"#f85149":"#cf222e" }),[dark]); useEffect(()=>{ const h=()=>onClose(); window.addEventListener("click",h); window.addEventListener("contextmenu",h); return ()=>{window.removeEventListener("click",h);window.removeEventListener("contextmenu",h);}; },[onClose]); return (
e.stopPropagation()}> {items.map((item,i)=>item.divider?(
):( ))}
); } /* ═══════ EDIT NOTEBOOK MODAL ═══════ */ function EditNotebookModal({ notebook, onClose, onSave, dark }) { const [name,setName]=useState(notebook.name); const [sel,setSel]=useState(notebook.cover); const T=useMemo(()=>({bg:dark?"#161b22":"#fff",border:dark?"#30363d":"#d0d7de",text:dark?"#e6edf3":"#1f2328",muted:dark?"#8b949e":"#656d76",active:dark?"#388bfd":"#0969da"}),[dark]); return (
e.stopPropagation()}>

Edit Notebook

setName(e.target.value)} autoFocus style={{width:"100%",padding:"10px 14px",borderRadius:10,border:`1px solid ${T.border}`,background:dark?"#0d1117":"#f6f8fa",color:T.text,fontSize:14,outline:"none",marginBottom:16,boxSizing:"border-box",fontFamily:"'DM Sans',sans-serif"}} onKeyDown={e=>{if(e.key==="Enter"&&name.trim())onSave({name:name.trim(),cover:sel});}}/>
Cover style
{COVERS.map((c,i)=>)}
); } /* ═══════════════════════════════════════════════════════ UNIFIED NOTE EDITOR — single toolbar, canvas area is always the same size so drawings never shift ═══════════════════════════════════════════════════════ */ function NoteEditor({ note, onUpdate, dark }) { const [mode, setMode] = useState("text"); const editorRef = useRef(null); const canvasRef = useRef(null); const ctxRef = useRef(null); const [isDrawing, setIsDrawing] = useState(false); // Draw tool state (lifted out of canvas) const [tool, setTool] = useState("pen"); const [penColor, setPenColor] = useState(dark ? "#e6edf3" : "#1a1a2e"); const [penSize, setPenSize] = useState(3); const [history, setHistory] = useState([]); const [hIdx, setHIdx] = useState(-1); const [showColors, setShowColors] = useState(false); const [showSizes, setShowSizes] = useState(false); const T = useMemo(() => ({ bg: dark?"#0d1117":"#ffffff", toolbar: dark?"#161b22":"#f6f8fa", border: dark?"#30363d":"#d0d7de", text: dark?"#e6edf3":"#1f2328", muted: dark?"#8b949e":"#656d76", active: dark?"#388bfd":"#0969da", faint: dark?"#484f58":"#afb8c1", }), [dark]); // Init text editor useEffect(() => { if (editorRef.current && editorRef.current.innerHTML !== (note.content||"")) editorRef.current.innerHTML = note.content || ""; }, [note.id]); // Init canvas when switching to draw mode useEffect(() => { if (mode !== "draw") return; const c = canvasRef.current; if (!c) return; const p = c.parentElement; c.width = p.clientWidth; c.height = p.clientHeight; const ctx = c.getContext("2d"); ctx.lineCap = "round"; ctx.lineJoin = "round"; ctxRef.current = ctx; if (note.drawingData) { const img = new Image(); img.onload = () => { ctx.drawImage(img, 0, 0); snap(); }; img.src = note.drawingData; } else snap(); }, [mode]); // Canvas helpers const snap = () => { if (!canvasRef.current) return; const d = canvasRef.current.toDataURL(); setHistory(p => { const n = [...p.slice(0, (hIdx >= 0 ? hIdx + 1 : p.length))]; n.push(d); return n; }); setHIdx(p => p + 1); }; const restoreIdx = (idx) => { setHIdx(idx); const img = new Image(); img.onload = () => { ctxRef.current.clearRect(0,0,canvasRef.current.width,canvasRef.current.height); ctxRef.current.drawImage(img,0,0); }; img.src = history[idx]; }; const undo = () => { if (hIdx > 0) restoreIdx(hIdx - 1); }; const redo = () => { if (hIdx < history.length - 1) restoreIdx(hIdx + 1); }; const clearCanvas = () => { ctxRef.current?.clearRect(0,0,canvasRef.current.width,canvasRef.current.height); snap(); onUpdate({drawingData: canvasRef.current.toDataURL()}); }; const getPos = (e) => { const c = canvasRef.current, r = c.getBoundingClientRect(); const sx = c.width/r.width, sy = c.height/r.height; const t = e.touches ? e.touches[0] : e; return { x: (t.clientX-r.left)*sx, y: (t.clientY-r.top)*sy }; }; const startDraw = (e) => { e.preventDefault(); const p = getPos(e); setIsDrawing(true); const ctx = ctxRef.current; ctx.beginPath(); ctx.moveTo(p.x,p.y); if (tool==="eraser"){ctx.globalCompositeOperation="destination-out";ctx.lineWidth=penSize*5;} else if(tool==="highlighter"){ctx.globalCompositeOperation="source-over";ctx.strokeStyle=penColor+"44";ctx.lineWidth=penSize*6;} else{ctx.globalCompositeOperation="source-over";ctx.strokeStyle=penColor;ctx.lineWidth=penSize;} }; const doDraw = (e) => {if(!isDrawing)return;e.preventDefault();const p=getPos(e);ctxRef.current.lineTo(p.x,p.y);ctxRef.current.stroke();}; const stopDraw = (e) => { if(!isDrawing)return;e?.preventDefault();setIsDrawing(false); ctxRef.current.closePath();ctxRef.current.globalCompositeOperation="source-over"; snap(); onUpdate({drawingData:canvasRef.current.toDataURL()}); }; const exec = (cmd,val=null)=>{document.execCommand(cmd,false,val);editorRef.current?.focus();onUpdate({content:editorRef.current.innerHTML});}; const tbtn = (active)=>({ display:"flex",alignItems:"center",justifyContent:"center",width:30,height:30,borderRadius:6, border:"none",cursor:"pointer",background:active?T.active+"22":"transparent",color:active?T.active:T.text,transition:"all .12s",flexShrink:0, }); const modeBtn = (m,icon,label) => ( ); return (
{/* ── SINGLE UNIFIED TOOLBAR ── */}
{modeBtn("text",I.text,"Write")} {modeBtn("draw",I.draw,"Draw")}
{mode==="text" ? (<>
) : (<>
{/* Color picker */}
{showColors&&
{COLORS.map(c=>
}
{/* Size picker */}
{showSizes&&
{PEN_SIZES.map(s=>)}
}
)}
{/* ── CONTENT AREA — always same size regardless of mode ── */}
{/* Text layer — always rendered */}
onUpdate({content:editorRef.current.innerHTML})} style={{ position:"absolute",inset:0,padding:"20px 24px",outline:"none",fontSize:15,lineHeight:1.7, color:T.text,overflowY:"auto",fontFamily:"'Newsreader','Georgia',serif", zIndex:1, pointerEvents:mode==="text"?"auto":"none", }} /> {/* Drawing image overlay — visible in WRITE mode when drawings exist */} {mode==="text" && note.drawingData && ( )} {/* Live canvas — visible in DRAW mode */} {mode==="draw" && ( )}
); } /* ═══════ NOTEBOOK COVER ═══════ */ function NotebookCover({notebook,noteCount,onClick,onContextMenu,isActive,isDragOver,draggable,onDragStart,onDragEnd,onDragOver,onDrop}) { const cover=COVERS[notebook.cover%COVERS.length]; const [hovered,setHovered]=useState(false); return ( ); } /* ═══════ CREATE NOTEBOOK MODAL ═══════ */ function CreateNotebookModal({onClose,onCreate,dark}) { const [name,setName]=useState("");const [sel,setSel]=useState(0); const T=useMemo(()=>({bg:dark?"#161b22":"#fff",border:dark?"#30363d":"#d0d7de",text:dark?"#e6edf3":"#1f2328",muted:dark?"#8b949e":"#656d76",active:dark?"#388bfd":"#0969da"}),[dark]); return (
e.stopPropagation()}>

New Notebook

setName(e.target.value)} placeholder="Notebook name..." autoFocus style={{width:"100%",padding:"10px 14px",borderRadius:10,border:`1px solid ${T.border}`,background:dark?"#0d1117":"#f6f8fa",color:T.text,fontSize:14,outline:"none",marginBottom:16,boxSizing:"border-box",fontFamily:"'DM Sans',sans-serif"}} onKeyDown={e=>{if(e.key==="Enter"&&name.trim())onCreate({name:name.trim(),cover:sel});}}/>
Choose cover style
{COVERS.map((c,i)=>)}
); } /* ═══════════════════════ MAIN APP ═══════════════════════ */ export default function NoteApp() { const [dark,setDark]=useState(true); const [sidebarOpen,setSidebarOpen]=useState(true); const [activeFolder,setActiveFolder]=useState("dashboard"); const [activeNote,setActiveNote]=useState(null); const [searchQuery,setSearchQuery]=useState(""); const [showNewNb,setShowNewNb]=useState(false); const [syncStatus,setSyncStatus]=useState("synced"); const [ctxMenu,setCtxMenu]=useState(null); const [confirmDialog,setConfirmDialog]=useState(null); const [editingNb,setEditingNb]=useState(null); const [dragNbId,setDragNbId]=useState(null); const [dragOverNbId,setDragOverNbId]=useState(null); const fileRef=useRef(null); const [notebooks,setNotebooks]=useState([ {id:"nb1",name:"Personal",cover:0},{id:"nb2",name:"Work Projects",cover:2}, {id:"nb3",name:"Ideas & Sketches",cover:4},{id:"nb4",name:"Study Notes",cover:7}, ]); const [notes,setNotes]=useState([ {id:"n1",title:"Welcome to InkNote",folderId:"nb1",content:"

Welcome to InkNote ✨

Each note supports both writing and drawing — switch modes with the tabs. Drawings stay visible while you write!

  • Rich text with formatting, lists, checkboxes
  • Freehand drawing layered on top
",drawingData:null,starred:true,createdAt:"2025-06-10T10:00:00Z",updatedAt:now()}, {id:"n2",title:"Sprint Planning",folderId:"nb2",content:"

Q3 Roadmap

  1. Sync engine
  2. Mobile gestures
  3. PDF export

Review: Friday 3pm

",drawingData:null,starred:false,createdAt:"2025-06-12T14:00:00Z",updatedAt:now()}, {id:"n3",title:"UI Wireframes",folderId:"nb3",content:"

Dashboard layout concepts...

",drawingData:null,starred:false,createdAt:"2025-06-11T08:30:00Z",updatedAt:now()}, {id:"n4",title:"Data Structures",folderId:"nb4",content:"

Binary Trees

Each node has at most two children. Operations: insert, delete, search, traversal.

",drawingData:null,starred:true,createdAt:"2025-06-09T11:00:00Z",updatedAt:now()}, ]); const [pdfs,setPdfs]=useState([]); const T=useMemo(()=>dark?{ bg:"#0d1117",sidebar:"#010409",sidebarActive:"#161b22",card:"#161b22",toolbar:"#161b22", border:"#30363d",borderLight:"#21262d",text:"#e6edf3",textSec:"#c9d1d9",muted:"#8b949e",faint:"#484f58", accent:"#388bfd",accentSoft:"#388bfd22",accentText:"#58a6ff",danger:"#f85149",success:"#3fb950",warning:"#d29922",inputBg:"#0d1117",dashBg:"#0d1117", }:{ bg:"#f6f8fa",sidebar:"#ffffff",sidebarActive:"#eef2ff",card:"#ffffff",toolbar:"#ffffff", border:"#d0d7de",borderLight:"#e5e7eb",text:"#1f2328",textSec:"#424a53",muted:"#656d76",faint:"#afb8c1", accent:"#0969da",accentSoft:"#0969da18",accentText:"#0550ae",danger:"#cf222e",success:"#1a7f37",warning:"#9a6700",inputBg:"#f6f8fa",dashBg:"#f0f2f5", },[dark]); const filteredNotes=useMemo(()=>{ let list=notes; if(activeFolder==="starred")list=list.filter(n=>n.starred); else if(activeFolder==="pdfs")return pdfs; else if(activeFolder==="trash")return[]; else if(activeFolder!=="all"&&activeFolder!=="dashboard")list=list.filter(n=>n.folderId===activeFolder); if(searchQuery){const q=searchQuery.toLowerCase();list=list.filter(n=>n.title.toLowerCase().includes(q)||(n.content||"").toLowerCase().includes(q));} return list.sort((a,b)=>new Date(b.updatedAt)-new Date(a.updatedAt)); },[notes,pdfs,activeFolder,searchQuery]); const currentNote=activeNote?notes.find(n=>n.id===activeNote)||pdfs.find(p=>p.id===activeNote):null; const createNote=()=>{ const fid=["all","starred","pdfs","trash","dashboard"].includes(activeFolder)?(notebooks[0]?.id||"nb1"):activeFolder; const n={id:uid(),title:"Untitled Note",folderId:fid,content:"",drawingData:null,starred:false,createdAt:now(),updatedAt:now()}; setNotes(p=>[n,...p]);setActiveNote(n.id);if(activeFolder==="dashboard")setActiveFolder(fid);sim(); }; const updateNote=(id,u)=>{setNotes(p=>p.map(n=>n.id===id?{...n,...u,updatedAt:now()}:n));sim();}; const requestDeleteNote=(id)=>{ const note=notes.find(n=>n.id===id); setConfirmDialog({title:"Delete Note",message:`Are you sure you want to delete "${note?.title||"this note"}"? This cannot be undone.`, onConfirm:()=>{setNotes(p=>p.filter(n=>n.id!==id));if(activeNote===id)setActiveNote(null);sim();setConfirmDialog(null);}}); }; const toggleStar=(id)=>setNotes(p=>p.map(n=>n.id===id?{...n,starred:!n.starred}:n)); const importPDF=(e)=>{const f=e.target.files[0];if(!f)return;const p={id:uid(),type:"pdf",title:f.name,starred:false,createdAt:now(),updatedAt:now()};setPdfs(prev=>[p,...prev]);setActiveNote(p.id);setActiveFolder("pdfs");sim();}; const createNb=({name,cover})=>{const nb={id:uid(),name,cover};setNotebooks(p=>[...p,nb]);setShowNewNb(false);setActiveFolder(nb.id);}; const updateNb=(id,updates)=>{setNotebooks(p=>p.map(nb=>nb.id===id?{...nb,...updates}:nb));setEditingNb(null);sim();}; const requestDeleteNb=(id)=>{ const nb=notebooks.find(n=>n.id===id);const nc=notes.filter(n=>n.folderId===id).length; setConfirmDialog({title:"Delete Notebook",message:`Delete "${nb?.name}"${nc>0?` and its ${nc} note${nc>1?"s":""}`:""} permanently?`, onConfirm:()=>{setNotes(p=>p.filter(n=>n.folderId!==id));setNotebooks(p=>p.filter(n=>n.id!==id));if(activeFolder===id)setActiveFolder("dashboard");setConfirmDialog(null);sim();}}); }; const sim=()=>{setSyncStatus("syncing");setTimeout(()=>setSyncStatus("synced"),1200);}; // Drag & Drop const handleNbDragStart=(e,id)=>{setDragNbId(id);e.dataTransfer.effectAllowed="move";}; const handleNbDragOver=(e,id)=>{e.preventDefault();if(dragNbId&&dragNbId!==id)setDragOverNbId(id);}; const handleNbDrop=(e,targetId)=>{e.preventDefault();if(!dragNbId||dragNbId===targetId)return; setNotebooks(prev=>{const a=[...prev];const fi=a.findIndex(n=>n.id===dragNbId);const ti=a.findIndex(n=>n.id===targetId);if(fi<0||ti<0)return prev;const[item]=a.splice(fi,1);a.splice(ti,0,item);return a;}); setDragNbId(null);setDragOverNbId(null);}; const handleNbDragEnd=()=>{setDragNbId(null);setDragOverNbId(null);}; const handleNbContextMenu=(e,nb)=>{e.preventDefault();e.stopPropagation(); setCtxMenu({x:Math.min(e.clientX,window.innerWidth-200),y:Math.min(e.clientY,window.innerHeight-180), items:[{label:"Rename",icon:I.edit,action:()=>setEditingNb(nb)},{label:"Change Cover",icon:I.palette,action:()=>setEditingNb(nb)},{divider:true},{label:"Delete Notebook",icon:I.trash,danger:true,action:()=>requestDeleteNb(nb.id)}]}); }; useEffect(()=>{const l=document.createElement("link");l.href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,300;0,6..72,400;0,6..72,500;0,6..72,600;1,6..72,400&family=DM+Sans:wght@400;500;600;700&display=swap";l.rel="stylesheet";document.head.appendChild(l);},[]); const renderSidebar=()=>(
✏️
InkNote
{I.search} setSearchQuery(e.target.value)} placeholder="Search notes..." style={{border:"none",background:"transparent",outline:"none",flex:1,color:T.text,fontSize:13,fontFamily:"'DM Sans',sans-serif"}}/>
{DEFAULT_FOLDERS.map(f=>( ))}
Notebooks
{notebooks.map(nb=>(
handleNbDragStart(e,nb.id)} onDragOver={e=>handleNbDragOver(e,nb.id)} onDrop={e=>handleNbDrop(e,nb.id)} onDragEnd={handleNbDragEnd} onContextMenu={e=>handleNbContextMenu(e,nb)} style={{display:"flex",alignItems:"center",borderRadius:10,transition:"all .15s",background:dragOverNbId===nb.id?T.accentSoft:activeFolder===nb.id?T.sidebarActive:"transparent",borderTop:dragOverNbId===nb.id?`2px solid ${T.accent}`:"2px solid transparent"}}> {I.grip}
))}
● {syncStatus==="synced"?"All changes synced":syncStatus==="syncing"?"Syncing...":"Offline"}
); const renderDashboard=()=>(

My Notebooks

{notebooks.map(nb=>( n.folderId===nb.id).length} onClick={()=>{setActiveFolder(nb.id);setActiveNote(null);}} onContextMenu={e=>handleNbContextMenu(e,nb)} isActive={activeFolder===nb.id} isDragOver={dragOverNbId===nb.id} draggable onDragStart={e=>handleNbDragStart(e,nb.id)} onDragEnd={handleNbDragEnd} onDragOver={e=>handleNbDragOver(e,nb.id)} onDrop={e=>handleNbDrop(e,nb.id)}/> ))}

Recent Notes

{notes.slice(0,6).map(note=>{const nb=notebooks.find(b=>b.id===note.folderId);return( );})}
); const renderNoteList=()=>{ const fname=(()=>{const sf=DEFAULT_FOLDERS.find(f=>f.id===activeFolder);if(sf)return sf.name;const nb=notebooks.find(n=>n.id===activeFolder);return nb?nb.name:"Notes";})(); return(

{fname}

{filteredNotes.length===0?(
📝
No notes yet
):filteredNotes.map(note=>( ))}
);}; const renderEditor=()=>{ if(!currentNote)return(
✏️
Select a note to edit
); return(
updateNote(currentNote.id,{title:e.target.value})} style={{flex:1,border:"none",background:"transparent",outline:"none",fontSize:17,fontWeight:600,color:T.text,fontFamily:"'Newsreader',Georgia,serif"}}/>
updateNote(currentNote.id,u)} dark={dark}/>
);}; return(
{renderSidebar()} {activeFolder==="dashboard"?renderDashboard():(<>{renderNoteList()}{renderEditor()})} {showNewNb&&setShowNewNb(false)} onCreate={createNb} dark={dark}/>} {editingNb&&setEditingNb(null)} onSave={u=>updateNb(editingNb.id,u)} dark={dark}/>} {confirmDialog&&setConfirmDialog(null)} dark={dark}/>} {ctxMenu&&setCtxMenu(null)} dark={dark}/>}
); }