diff --git a/teams/Krantikari/README.md b/teams/Krantikari/README.md
new file mode 100644
index 0000000..33d13e5
--- /dev/null
+++ b/teams/Krantikari/README.md
@@ -0,0 +1,36 @@
+# Zerolat
+
+## Krantikari
+
+## Team Members
+- Divyansh Bhargav (GitHub: @divyansh0655)
+- Virottam Dutt Raturi (GitHub: @vduttrat)
+- Manasvi Sharma (GitHub: @manasvisharma2231)
+- Aryan Kumar (GitHub: @IronKommander)
+- Lakshya Kumar (GitHub: @lakshya117-kr)
+
+## Idea Chosen
+Offline Markdown to HTML Converter
+
+## Problem Statement
+The application allows the users to easily record notes and convert Markdown to HTML in real time without requiring internet access. It is an offline first application which locally stores the user's notes allowing for fast and lightweight note taking.
+
+## Tech Stack
+- React
+- TailwindCSS
+- indexedDB
+- draft.js
+- Zustand
+
+## How to Run Locally
+```
+git clone https://github.com/Sky-walkerX/DevStakes
+cd DevStakes/teams/Krantikari
+npm i
+npm run dev
+```
+
+## Live Demo
+https://krantikari-dev-stakes.vercel.app/
+
+## Screenshots / Demo
diff --git a/teams/Krantikari/index.html b/teams/Krantikari/index.html
new file mode 100644
index 0000000..004096f
--- /dev/null
+++ b/teams/Krantikari/index.html
@@ -0,0 +1,21 @@
+
+
+
+
+ {/* TOC Sidebar */}
+ {showToc && toc.length > 0 && (
+
+ )}
+
+ {/* Preview Content */}
+
+ {/* Preview Header */}
+
+
+ Preview
+
+
+ {toc.length > 0 && (
+ setShowToc(v => !v)}
+ title="Toggle Table of Contents"
+ className="p-1.5 rounded-lg transition-all text-xs flex items-center gap-1"
+ style={{
+ background: showToc ? 'rgba(6,182,212,0.15)' : 'rgba(255,255,255,0.04)',
+ color: showToc ? '#06B6D4' : '#64748B',
+ border: `1px solid ${showToc ? 'rgba(6,182,212,0.3)' : 'rgba(255,255,255,0.07)'}`,
+ }}
+ >
+
+
+ )}
+
+ {copied ? : }
+ {copied ? 'Copied!' : 'HTML'}
+
+
+
+
+ {/* Scrollable content */}
+
+ {html ? (
+
/g, '
') }}
+ />
+ ) : (
+
+
👁
+
+ Type something to see the live preview
+
+
+ )}
+
+
+
+ );
+}
diff --git a/teams/Krantikari/src/components/sidebar/Sidebar.jsx b/teams/Krantikari/src/components/sidebar/Sidebar.jsx
new file mode 100644
index 0000000..f2a5da7
--- /dev/null
+++ b/teams/Krantikari/src/components/sidebar/Sidebar.jsx
@@ -0,0 +1,536 @@
+import React, { useEffect, useRef, useState, useCallback } from 'react';
+import { ContentState, convertToRaw } from 'draft-js';
+import { useNoteStore } from '../../store/noteStore';
+import { useFolderStore } from '../../store/folderStore';
+import { useKeyboardShortcuts } from '../../hooks/useKeyboardShortcuts';
+import {
+ FilePlus, FileText, Trash2, Moon, Sun, Search, Upload,
+ Folder, FolderPlus, ChevronRight, ChevronDown, Pin, PinOff,
+ Palette, Command, Zap, X
+} from 'lucide-react';
+import { clsx } from 'clsx';
+import { twMerge } from 'tailwind-merge';
+import StreakCounter from '../StreakCounter';
+
+export function cn(...inputs) {
+ return twMerge(clsx(inputs));
+}
+
+const NOTE_COLORS = [
+ { id: 'default', label: 'Default', hex: '#ffffff15' },
+ { id: 'purple', label: 'Aurora', hex: '#7C3AED' },
+ { id: 'cyan', label: 'Slate', hex: '#4F7676' },
+ { id: 'emerald', label: 'Forest', hex: '#10B981' },
+ { id: 'amber', label: 'Blaze', hex: '#F59E0B' },
+ { id: 'rose', label: 'Cherry', hex: '#F43F5E' },
+];
+
+export default function Sidebar({ theme, toggleTheme, onOpenCommandPalette, onToggleFocusMode, focusMode }) {
+ const { notes, activeNoteId, loadNotes, createNote, setActiveNote, removeNote, pinNote, unpinNote, setNoteColor } = useNoteStore();
+ const { folders, loadFolders, createFolder, deleteFolder } = useFolderStore();
+ const [searchQuery, setSearchQuery] = useState('');
+ const [expandedFolders, setExpandedFolders] = useState(new Set());
+ const [colorPickerNoteId, setColorPickerNoteId] = useState(null);
+ const fileInputRef = useRef(null);
+ const filmstripRef = useRef(null);
+
+ const handleImport = async (e) => {
+ const file = e.target.files?.[0];
+ if (!file) return;
+ const text = await file.text();
+ const contentState = ContentState.createFromText(text);
+ const rawContent = JSON.stringify(convertToRaw(contentState));
+ const rawTitle = file.name.replace(/\.md$/i, '');
+ await createNote(rawTitle, rawContent);
+ e.target.value = '';
+ };
+
+ useKeyboardShortcuts({
+ newNote: createNote,
+ focusSearch: () => document.querySelector('.search-input')?.focus(),
+ commandPalette: onOpenCommandPalette,
+ focusMode: onToggleFocusMode,
+ });
+
+
+ useEffect(() => {
+ const el = filmstripRef.current;
+ if (!el) return;
+ const handler = (e) => {
+ if (e.shiftKey) {
+ e.preventDefault();
+ el.scrollLeft += e.deltaY;
+ }
+ };
+ el.addEventListener('wheel', handler, { passive: false });
+ return () => el.removeEventListener('wheel', handler);
+ }, []);
+
+ useEffect(() => {
+ loadNotes();
+ loadFolders();
+ }, [loadNotes, loadFolders]);
+
+ const filteredNotes = notes.filter(note => {
+ if (!searchQuery) return true;
+ const query = searchQuery.toLowerCase();
+ return (note.title?.toLowerCase().includes(query)) ||
+ (note.content?.toLowerCase().includes(query));
+ });
+
+ const statelessNotes = filteredNotes.filter(n => !n.folderId);
+ const groupedFolders = folders.map(f => ({
+ ...f,
+ notes: filteredNotes.filter(n => n.folderId === f.id)
+ }));
+
+ const toggleFolder = (e, id) => {
+ e.stopPropagation();
+ const newSet = new Set(expandedFolders);
+ if (newSet.has(id)) newSet.delete(id); else newSet.add(id);
+ setExpandedFolders(newSet);
+ };
+
+ const handleCreateFolder = () => {
+ const name = window.prompt('Enter folder name:');
+ if (name) createFolder(name);
+ };
+
+ const handleColorSelect = (noteId, colorId) => {
+ setNoteColor(noteId, colorId);
+ setColorPickerNoteId(null);
+ };
+
+ return (
+
+ {/* Toolbar */}
+
+
+ {/* Logo Section */}
+
+
+ {/* Selector Section (Streak) */}
+
+
+
+
+ {/* Search Section */}
+
+
+ setSearchQuery(e.target.value)}
+ className="search-input w-56 pl-10 pr-4 py-2 text-xs rounded-xl outline-none transition-all"
+ style={{
+ background: 'rgba(0,0,0,0.2)',
+ border: '1px solid var(--border)',
+ boxShadow: 'var(--shadow-recessed)',
+ color: 'var(--foreground)',
+ fontFamily: 'Inter, sans-serif',
+ }}
+ onFocus={e => e.target.style.borderColor = 'var(--primary)'}
+ onBlur={e => e.target.style.borderColor = 'var(--border)'}
+ />
+
+
+
+ {/* Right: action buttons */}
+
+
}
+ onClick={onOpenCommandPalette}
+ title="Command Palette (Ctrl+K)"
+ />
+
}
+ onClick={onToggleFocusMode}
+ title="Focus Mode (Ctrl+F)"
+ active={focusMode}
+ />
+
+
:
}
+ onClick={toggleTheme}
+ title="Toggle Theme"
+ />
+
}
+ onClick={() => fileInputRef.current?.click()}
+ title="Import .md Note"
+ />
+
+
}
+ onClick={handleCreateFolder}
+ title="New Folder"
+ />
+
createNote()}
+ title="New Note (Ctrl+N)"
+ className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-semibold transition-all duration-200 hover:scale-105"
+ style={{
+ background: 'linear-gradient(135deg, #7C3AED, #6D28D9)',
+ color: '#fff',
+ boxShadow: '0 0 16px rgba(124,58,237,0.4)',
+ border: '1px solid rgba(124,58,237,0.3)',
+ }}
+ onMouseEnter={e => e.currentTarget.style.boxShadow = '0 0 24px rgba(124,58,237,0.6)'}
+ onMouseLeave={e => e.currentTarget.style.boxShadow = '0 0 16px rgba(124,58,237,0.4)'}
+ >
+
+ New
+
+
+
+
+ {/* Film Strip */}
+
+ {filteredNotes.length === 0 && folders.length === 0 ? (
+
+
📝
+
+ {searchQuery ? `No matches for "${searchQuery}"` : 'No notes yet'}
+
+ {!searchQuery && (
+
createNote()}
+ className="text-xs px-3 py-1.5 rounded-lg font-semibold mt-1 transition-all hover:scale-105"
+ style={{ background: 'rgba(124,58,237,0.2)', color: '#A855F7', border: '1px solid rgba(124,58,237,0.3)' }}
+ >
+ Create your first note →
+
+ )}
+
+ ) : (
+
+ {/* Folders */}
+ {groupedFolders.map((folder) => {
+ const isExpanded = expandedFolders.has(folder.id) || searchQuery !== '';
+ return (
+
+
+ {isExpanded && folder.notes.map(note => (
+ setColorPickerNoteId(note.id)}
+ colorPickerOpen={colorPickerNoteId === note.id}
+ onColorSelect={handleColorSelect}
+ onCloseColorPicker={() => setColorPickerNoteId(null)}
+ />
+ ))}
+
+ );
+ })}
+
+ {/* Stateless Notes */}
+ {statelessNotes.map(note => (
+
setColorPickerNoteId(note.id)}
+ colorPickerOpen={colorPickerNoteId === note.id}
+ onColorSelect={handleColorSelect}
+ onCloseColorPicker={() => setColorPickerNoteId(null)}
+ />
+ ))}
+
+ )}
+
+
+ {/* Keyboard hint */}
+
+
Shift+Scroll to navigate
+
+
+ );
+}
+
+function SidebarButton({ icon, onClick, title, active }) {
+ return (
+
{ if (!active) e.currentTarget.style.color = '#94A3B8'; e.currentTarget.style.background = 'rgba(255,255,255,0.05)'; }}
+ onMouseLeave={e => { e.currentTarget.style.color = active ? '#A855F7' : '#64748B'; e.currentTarget.style.background = active ? 'rgba(124,58,237,0.15)' : 'transparent'; }}
+ >
+ {icon}
+
+ );
+}
+
+function FolderCard({ folder, isExpanded, toggleFolder, deleteFolder }) {
+ return (
+
+ toggleFolder(e, folder.id)}
+ className="h-full flex flex-col justify-center items-center gap-2 px-5 py-2 rounded-xl text-sm transition-all duration-200 hover:scale-[1.02]"
+ style={{
+ minWidth: '90px',
+ background: 'rgba(255,255,255,0.03)',
+ border: '1px solid rgba(255,255,255,0.07)',
+ color: '#64748B',
+ }}
+ onMouseEnter={e => { e.currentTarget.style.background = 'rgba(255,255,255,0.06)'; e.currentTarget.style.borderColor = 'rgba(6,182,212,0.3)'; }}
+ onMouseLeave={e => { e.currentTarget.style.background = 'rgba(255,255,255,0.03)'; e.currentTarget.style.borderColor = 'rgba(255,255,255,0.07)'; }}
+ >
+
+ {folder.name}
+ {isExpanded ? : }
+
+ {
+ e.stopPropagation();
+ if (window.confirm('Delete this folder?')) deleteFolder(folder.id);
+ }}
+ className="absolute top-1.5 right-1.5 p-1 rounded opacity-0 group-hover:opacity-100 transition-all"
+ style={{ color: '#F43F5E', background: 'rgba(244,63,94,0.1)' }}
+ title="Delete Folder"
+ >
+
+
+
+ );
+}
+
+const COLOR_GLOW_MAP = {
+ purple: 'note-glow-purple',
+ cyan: 'note-glow-cyan',
+ emerald: 'note-glow-emerald',
+ amber: 'note-glow-amber',
+ rose: 'note-glow-rose',
+ default: 'note-glow-default',
+};
+
+const COLOR_HEX_MAP = {
+ purple: '#7C3AED',
+ cyan: '#4F7676',
+ emerald: '#10B981',
+ amber: '#F59E0B',
+ rose: '#F43F5E',
+ default: 'rgba(255,255,255,0.08)',
+};
+
+function NoteCard({ note, isActive, setActiveNote, removeNote, pinNote, unpinNote, onColorPicker, colorPickerOpen, onColorSelect, onCloseColorPicker }) {
+ const color = note.color || 'default';
+ const glowClass = COLOR_GLOW_MAP[color] || 'note-glow-default';
+ const accentColor = COLOR_HEX_MAP[color] || 'rgba(255,255,255,0.12)';
+
+ let snippet = '';
+ try {
+ if (note.content) {
+ const raw = JSON.parse(note.content);
+ const blocks = raw.blocks || [];
+ snippet = blocks.map(b => b.text).join(' ').slice(0, 60);
+ }
+ } catch {/* ignore */}
+
+ const timeAgo = getTimeAgo(note.updatedAt);
+
+ return (
+
+
setActiveNote(note.id)}
+ className={cn(
+ 'h-full w-full flex flex-col items-start justify-between rounded-md text-sm transition-all duration-300 text-left overflow-hidden relative',
+ isActive ? [glowClass, 'glass-shine-hover'] : 'note-glow-default',
+ )}
+ style={{
+ background: isActive
+ ? `linear-gradient(165deg, rgba(${hexToRgb(accentColor)}, 0.1), var(--surface-1))`
+ : 'var(--surface-2)',
+ border: isActive
+ ? `1px solid ${accentColor}88`
+ : '1px solid var(--border)',
+ transform: isActive ? 'scale(1.05) translateY(-4px)' : 'scale(1)',
+ perspective: '1000px',
+ boxShadow: isActive ? 'var(--shadow-depth)' : 'var(--shadow-recessed)',
+ }}
+ onMouseEnter={e => { if (!isActive) { e.currentTarget.style.background = 'var(--glass-hover)'; e.currentTarget.style.transform = 'scale(1.02) translateY(-2px)'; }}}
+ onMouseLeave={e => { if (!isActive) { e.currentTarget.style.background = 'var(--surface-2)'; e.currentTarget.style.transform = 'scale(1)'; }}}
+ >
+ {/* Code Palette Indicator (Side bar) */}
+
+
+
+
+ {/* Pin indicator */}
+ {note.isPinned && (
+
+ )}
+
+ {note.title || 'Untitled'}
+
+
+
+
+ {snippet && (
+
+ {snippet}
+
+ )}
+
{timeAgo}
+
+
+
+
+ {/* Actions overlay */}
+
+
:
}
+ onClick={(e) => { e.stopPropagation(); note.isPinned ? unpinNote(note.id) : pinNote(note.id); }}
+ title={note.isPinned ? 'Unpin' : 'Pin'}
+ color="#F59E0B"
+ />
+
}
+ onClick={(e) => { e.stopPropagation(); onColorPicker(); }}
+ title="Set color"
+ color="#A855F7"
+ />
+
}
+ onClick={(e) => { e.stopPropagation(); if (window.confirm('Delete this note?')) removeNote(note.id); }}
+ title="Delete"
+ color="#F43F5E"
+ />
+
+
+ {/* Color picker popover — Now an overlay so it doesn't get clipped by the filmstrip overflow */}
+ {colorPickerOpen && (
+
e.stopPropagation()}
+ >
+
+
+
+ {NOTE_COLORS.map(c => (
+ { e.stopPropagation(); onColorSelect(note.id, c.id); }}
+ title={c.label}
+ className="w-5 h-5 rounded-full transition-transform hover:scale-125 cursor-pointer border-2"
+ style={{
+ background: c.hex,
+ borderColor: note.color === c.id ? '#fff' : 'transparent',
+ }}
+ />
+ ))}
+
+ )}
+
+ );
+}
+
+function ActionButton({ icon, onClick, title, color }) {
+ return (
+
e.currentTarget.style.background = `${color}30`}
+ onMouseLeave={e => e.currentTarget.style.background = `${color}15`}
+ >
+ {icon}
+
+ );
+}
+
+function hexToRgb(hex) {
+ if (hex.startsWith('rgba') || hex.startsWith('rgb')) return '255,255,255';
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+ return result
+ ? `${parseInt(result[1], 16)},${parseInt(result[2], 16)},${parseInt(result[3], 16)}`
+ : '255,255,255';
+}
+
+function getTimeAgo(ts) {
+ if (!ts) return '';
+ const diff = Date.now() - ts;
+ const mins = Math.floor(diff / 60000);
+ if (mins < 1) return 'just now';
+ if (mins < 60) return `${mins}m ago`;
+ const hours = Math.floor(mins / 60);
+ if (hours < 24) return `${hours}h ago`;
+ return `${Math.floor(hours / 24)}d ago`;
+}
diff --git a/teams/Krantikari/src/hooks/useAutoSave.js b/teams/Krantikari/src/hooks/useAutoSave.js
new file mode 100644
index 0000000..f78d6f1
--- /dev/null
+++ b/teams/Krantikari/src/hooks/useAutoSave.js
@@ -0,0 +1,39 @@
+import { useState, useCallback, useEffect } from 'react';
+import { debounce } from '../lib/utils';
+import { useNoteStore } from '../store/noteStore';
+
+export function useAutoSave(delay = 500) {
+ const { updateActiveNote } = useNoteStore();
+ const [isSaving, setIsSaving] = useState(false);
+
+
+ const debouncedSave = useCallback(
+ debounce(async (updates) => {
+ setIsSaving(true);
+ await updateActiveNote(updates);
+ setIsSaving(false);
+ }, delay),
+ [updateActiveNote, delay]
+ );
+
+ useEffect(() => {
+ const handleUnload = () => {
+ debouncedSave.flush();
+ };
+
+ window.addEventListener('beforeunload', handleUnload);
+ const handleVisibility = () => {
+ if (document.visibilityState === 'hidden') {
+ handleUnload();
+ }
+ };
+ window.addEventListener('visibilitychange', handleVisibility);
+
+ return () => {
+ window.removeEventListener('beforeunload', handleUnload);
+ window.removeEventListener('visibilitychange', handleVisibility);
+ };
+ }, [debouncedSave]);
+
+ return { isSaving, triggerSave: debouncedSave };
+}
diff --git a/teams/Krantikari/src/hooks/useFocusMode.js b/teams/Krantikari/src/hooks/useFocusMode.js
new file mode 100644
index 0000000..8f31e44
--- /dev/null
+++ b/teams/Krantikari/src/hooks/useFocusMode.js
@@ -0,0 +1,30 @@
+import { useEffect, useCallback } from 'react';
+import { useKeyboardShortcuts } from './useKeyboardShortcuts';
+import { create } from 'zustand';
+
+
+export const useFocusModeStore = create((set) => ({
+ focusMode: false,
+ setFocusMode: (v) => set({ focusMode: v }),
+ toggleFocusMode: () => set((s) => ({ focusMode: !s.focusMode })),
+}));
+
+export function useFocusMode() {
+ const { focusMode, toggleFocusMode } = useFocusModeStore();
+
+
+ useEffect(() => {
+ if (focusMode) {
+ document.body.classList.add('focus-mode');
+ } else {
+ document.body.classList.remove('focus-mode');
+ }
+ return () => document.body.classList.remove('focus-mode');
+ }, [focusMode]);
+
+ useKeyboardShortcuts({
+ focusMode: toggleFocusMode,
+ });
+
+ return { focusMode, toggleFocusMode };
+}
diff --git a/teams/Krantikari/src/hooks/useKeyboardShortcuts.js b/teams/Krantikari/src/hooks/useKeyboardShortcuts.js
new file mode 100644
index 0000000..0e6d310
--- /dev/null
+++ b/teams/Krantikari/src/hooks/useKeyboardShortcuts.js
@@ -0,0 +1,32 @@
+import { useEffect } from 'react';
+
+const shortcuts = {
+ 'ctrl+n': 'newNote',
+ 'ctrl+s': 'forceSave',
+ 'ctrl+p': 'togglePreview',
+ 'ctrl+shift+f': 'focusSearch',
+ 'ctrl+/': 'showShortcuts',
+ 'ctrl+k': 'commandPalette',
+ 'ctrl+f': 'focusMode',
+};
+
+export function useKeyboardShortcuts(handlers) {
+ useEffect(() => {
+ const handler = (e) => {
+ const key = [
+ e.ctrlKey && 'ctrl',
+ e.metaKey && 'ctrl',
+ e.shiftKey && 'shift',
+ e.key.toLowerCase()
+ ].filter(Boolean).join('+');
+
+ const action = shortcuts[key];
+ if (action && handlers[action]) {
+ e.preventDefault();
+ handlers[action]();
+ }
+ };
+ window.addEventListener('keydown', handler);
+ return () => window.removeEventListener('keydown', handler);
+ }, [handlers]);
+}
diff --git a/teams/Krantikari/src/hooks/useOfflineStatus.js b/teams/Krantikari/src/hooks/useOfflineStatus.js
new file mode 100644
index 0000000..649a852
--- /dev/null
+++ b/teams/Krantikari/src/hooks/useOfflineStatus.js
@@ -0,0 +1,20 @@
+import { useEffect, useState } from 'react';
+
+export function useOfflineStatus() {
+ const [isOffline, setIsOffline] = useState(!navigator.onLine);
+
+ useEffect(() => {
+ const handleOnline = () => setIsOffline(false);
+ const handleOffline = () => setIsOffline(true);
+
+ window.addEventListener('online', handleOnline);
+ window.addEventListener('offline', handleOffline);
+
+ return () => {
+ window.removeEventListener('online', handleOnline);
+ window.removeEventListener('offline', handleOffline);
+ };
+ }, []);
+
+ return isOffline;
+}
diff --git a/teams/Krantikari/src/hooks/useThemeContext.js b/teams/Krantikari/src/hooks/useThemeContext.js
new file mode 100644
index 0000000..36cea1d
--- /dev/null
+++ b/teams/Krantikari/src/hooks/useThemeContext.js
@@ -0,0 +1,23 @@
+import { useState, useEffect } from 'react';
+
+export function useThemeContext() {
+ const [theme, setTheme] = useState(() => {
+ return localStorage.getItem('theme') || 'dark';
+ });
+
+ useEffect(() => {
+ const root = window.document.documentElement;
+ if (theme === 'light') {
+ root.classList.add('light');
+ } else {
+ root.classList.remove('light');
+ }
+ localStorage.setItem('theme', theme);
+ }, [theme]);
+
+ const toggleTheme = () => {
+ setTheme(prev => prev === 'dark' ? 'light' : 'dark');
+ };
+
+ return { theme, toggleTheme };
+}
diff --git a/teams/Krantikari/src/index.css b/teams/Krantikari/src/index.css
new file mode 100644
index 0000000..ae91c02
--- /dev/null
+++ b/teams/Krantikari/src/index.css
@@ -0,0 +1,768 @@
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:ital,wght@0,400;0,500;0,600;1,400&display=swap');
+
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+
+@layer base {
+ :root {
+
+ --s-1: 8px;
+ --s-2: 16px;
+ --s-3: 24px;
+ --s-4: 32px;
+
+
+ --background: #141416;
+ --surface-1: #1A1A1E;
+ --surface-2: #212126;
+ --surface-3: #282830;
+ --card: #1A1A1E;
+ --card-foreground: #E2E8F0;
+ --app-bg: #141416;
+ --app-glow-bg:
+ radial-gradient(ellipse 80% 60% at 10% -5%, rgba(124,58,237,0.12) 0%, transparent 60%),
+ radial-gradient(ellipse 60% 50% at 90% 110%, rgba(79,118,118,0.1) 0%, transparent 60%);
+
+
+ --glass-bg: rgba(255, 255, 255, 0.03);
+ --glass-border: rgba(255, 255, 255, 0.06);
+ --glass-hover: rgba(255, 255, 255, 0.05);
+
+
+ --primary: #7C3AED;
+ --primary-light: #A855F7;
+ --primary-glow: rgba(124, 58, 237, 0.25);
+ --cyan: #4F7676;
+ --cyan-glow: rgba(79, 118, 118, 0.25);
+ --emerald: #10B981;
+ --emerald-glow: rgba(16, 185, 129, 0.2);
+ --amber: #F59E0B;
+ --amber-glow: rgba(245, 158, 11, 0.2);
+ --rose: #F43F5E;
+ --rose-glow: rgba(244, 63, 94, 0.2);
+
+
+ --foreground: #E2E8F0;
+ --muted-foreground: #5A6679;
+ --subtle: #2C3544;
+
+
+ --border: rgba(255, 255, 255, 0.05);
+ --border-strong: rgba(255, 255, 255, 0.1);
+
+
+ --shadow-depth: 0 10px 40px -10px rgba(0,0,0,0.7), 0 0 20px rgba(0,0,0,0.5);
+ --shadow-recessed: inset 0 2px 4px rgba(0,0,0,0.4), inset 0 -1px 0 rgba(255,255,255,0.05);
+
+
+ --tw-bg-opacity: 1;
+
+
+ --color-background: 230 100% 5%;
+ --color-foreground: 213 31% 91%;
+ --color-card: 235 43% 9%;
+ --color-card-foreground: 213 31% 91%;
+ --color-border: 235 20% 14%;
+ --color-primary: 263 69% 57%;
+ --color-primary-foreground: 0 0% 98%;
+ --color-muted: 235 25% 16%;
+ --color-muted-foreground: 215 16% 47%;
+ --color-accent: 235 25% 18%;
+ --color-accent-foreground: 213 31% 91%;
+ --color-destructive: 343 89% 56%;
+ --color-destructive-foreground: 0 0% 98%;
+ }
+
+ .light {
+ --background: #F8FAFC;
+ --surface-1: #FFFFFF;
+ --surface-2: #F1F5F9;
+ --card: #FFFFFF;
+ --card-foreground: #0F172A;
+ --app-bg: #F8FAFC;
+ --app-glow-bg: radial-gradient(ellipse 60% 50% at 20% 0%, rgba(124,58,237,0.04) 0%, transparent 70%),
+ radial-gradient(ellipse 40% 40% at 80% 100%, rgba(6,182,212,0.03) 0%, transparent 70%);
+ --glass-bg: rgba(0, 0, 0, 0.03);
+ --glass-border: rgba(0, 0, 0, 0.08);
+ --glass-hover: rgba(0, 0, 0, 0.05);
+ --foreground: #0F172A;
+ --muted-foreground: #64748B;
+ --border: rgba(0, 0, 0, 0.09);
+ --border-strong: rgba(0, 0, 0, 0.15);
+ --color-background: 210 40% 98%;
+ --color-foreground: 222 84% 5%;
+ --color-card: 0 0% 100%;
+ --color-card-foreground: 222 84% 5%;
+ --color-border: 214 32% 91%;
+ --color-primary: 263 69% 57%;
+ --color-primary-foreground: 0 0% 98%;
+ --color-muted: 210 40% 96%;
+ --color-muted-foreground: 215 16% 47%;
+ --color-accent: 210 40% 96%;
+ --color-accent-foreground: 222 47% 11%;
+
+
+ --shadow-depth: 0 4px 12px rgba(0,0,0,0.06), 0 1px 3px rgba(0,0,0,0.04);
+ --shadow-recessed: inset 0 2px 4px rgba(0,0,0,0.03), 0 1px 0 rgba(255,255,255,0.8);
+ }
+}
+
+
+@layer base {
+ *, *::before, *::after {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ }
+
+ html {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ text-rendering: optimizeLegibility;
+ }
+
+ body {
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
+ background: var(--background);
+ color: var(--foreground);
+ transition: background 0.4s ease, color 0.3s ease;
+ overflow: hidden;
+ }
+
+ body.focus-mode .filmstrip,
+ body.focus-mode .app-header {
+ opacity: 0;
+ pointer-events: none;
+ transform: translateY(100%);
+ transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
+ }
+
+ body.focus-mode .editor-container {
+ border-radius: 0 !important;
+ border: none !important;
+ box-shadow: none !important;
+ }
+
+ ::-webkit-scrollbar {
+ width: 4px;
+ height: 4px;
+ }
+ ::-webkit-scrollbar-track { background: transparent; }
+ ::-webkit-scrollbar-thumb {
+ background: var(--glass-border);
+ border-radius: 99px;
+ }
+ ::-webkit-scrollbar-thumb:hover { background: var(--subtle); }
+}
+
+
+@keyframes aurora-shift {
+ 0%, 100% { background-position: 0% 50%; }
+ 50% { background-position: 100% 50%; }
+}
+
+@keyframes pulse-teal {
+ 0%, 100% {
+ border-color: rgba(79, 118, 118, 0.25);
+ box-shadow: 0 0 0 0 rgba(79, 118, 118, 0);
+ }
+ 50% {
+ border-color: rgba(79, 118, 118, 0.9);
+ box-shadow: 0 0 16px 3px rgba(79, 118, 118, 0.2);
+ }
+}
+
+@keyframes pulse-amber {
+ 0%, 100% {
+ border-color: rgba(245, 158, 11, 0.25);
+ box-shadow: 0 0 0 0 rgba(245, 158, 11, 0);
+ }
+ 50% {
+ border-color: rgba(245, 158, 11, 0.9);
+ box-shadow: 0 0 16px 3px rgba(245, 158, 11, 0.15);
+ }
+}
+
+@keyframes pulse-red {
+ 0%, 100% {
+ border-color: rgba(244, 63, 94, 0.25);
+ box-shadow: 0 0 0 0 rgba(244, 63, 94, 0);
+ }
+ 50% {
+ border-color: rgba(244, 63, 94, 0.9);
+ box-shadow: 0 0 16px 3px rgba(244, 63, 94, 0.2);
+ }
+}
+
+@keyframes shimmer {
+ 0% { background-position: -200% center; }
+ 100% { background-position: 200% center; }
+}
+
+@keyframes drift {
+ 0% { transform: translate(0, 0) scale(1); }
+ 25% { transform: translate(4vw, 6vh) scale(1.08); }
+ 50% { transform: translate(-2vw, 12vh) scale(0.93); }
+ 75% { transform: translate(6vw, -4vh) scale(1.05); }
+ 100% { transform: translate(0, 0) scale(1); }
+}
+
+@keyframes nebula-pulse {
+ 0%, 100% { opacity: 0.6; transform: scale(1); }
+ 50% { opacity: 1; transform: scale(1.08); }
+}
+
+@keyframes sparkle {
+ 0%, 100% { opacity: 0; transform: scale(0.5); }
+ 50% { opacity: 1; transform: scale(1.2); }
+}
+
+@keyframes star-twinkle {
+ 0%, 100% { opacity: 0.15; }
+ 50% { opacity: 0.8; }
+}
+
+@keyframes pulse-halo {
+ 0%, 100% {
+ filter: drop-shadow(0 0 10px rgba(79, 118, 118, 0.4)) drop-shadow(0 0 20px rgba(124, 58, 237, 0.2));
+ transform: scale(1);
+ }
+ 50% {
+ filter: drop-shadow(0 0 25px rgba(79, 118, 118, 0.6)) drop-shadow(0 0 40px rgba(124, 58, 237, 0.4));
+ transform: scale(1.01);
+ }
+}
+
+.image-halo {
+ animation: pulse-halo 4s ease-in-out infinite;
+ border: 1px solid rgba(79, 118, 118, 0.3) !important;
+}
+
+
+.cosmic-bg::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background-image:
+ radial-gradient(1px 1px at 20% 30%, rgba(255,255,255,0.6) 0%, transparent 100%),
+ radial-gradient(1px 1px at 60% 70%, rgba(255,255,255,0.5) 0%, transparent 100%),
+ radial-gradient(1.5px 1.5px at 80% 15%, rgba(255,255,255,0.7) 0%, transparent 100%),
+ radial-gradient(1px 1px at 40% 85%, rgba(255,255,255,0.4) 0%, transparent 100%),
+ radial-gradient(1px 1px at 10% 60%, rgba(255,255,255,0.5) 0%, transparent 100%),
+ radial-gradient(1.5px 1.5px at 90% 45%, rgba(255,255,255,0.6) 0%, transparent 100%),
+ radial-gradient(1px 1px at 35% 20%, rgba(255,255,255,0.4) 0%, transparent 100%),
+ radial-gradient(1px 1px at 70% 55%, rgba(255,255,255,0.3) 0%, transparent 100%),
+ radial-gradient(1px 1px at 50% 5%, rgba(255,255,255,0.5) 0%, transparent 100%),
+ radial-gradient(1px 1px at 15% 90%, rgba(255,255,255,0.4) 0%, transparent 100%);
+ animation: star-twinkle 4s ease-in-out infinite;
+ pointer-events: none;
+}
+
+@keyframes orbit {
+ from { transform: rotate(0deg) translateX(10vw) rotate(0deg); }
+ to { transform: rotate(360deg) translateX(10vw) rotate(-360deg); }
+}
+
+@keyframes glass-shine {
+ from { transform: translateX(-100%) skewX(-15deg); }
+ to { transform: translateX(200%) skewX(-15deg); }
+}
+
+@keyframes float-pane {
+ 0%, 100% { transform: translateY(0); }
+ 50% { transform: translateY(-3px); }
+}
+
+.float-pane {
+ animation: float-pane 6s ease-in-out infinite;
+}
+
+.glass-shine-hover {
+ position: relative;
+ overflow: hidden;
+}
+
+.glass-shine-hover::after {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 50%;
+ height: 100%;
+ background: linear-gradient(
+ 90deg,
+ transparent,
+ rgba(255, 255, 255, 0.1),
+ transparent
+ );
+ transition: none;
+}
+
+.glass-shine-hover:hover::after {
+ animation: glass-shine 0.8s ease-in-out;
+}
+
+@keyframes scale-in {
+ from { opacity: 0; transform: scale(0.94) translateY(10px); }
+ to { opacity: 1; transform: scale(1) translateY(0); }
+}
+
+@keyframes pop-in {
+ 0% { opacity: 0; transform: scale(0.8); }
+ 70% { transform: scale(1.05); }
+ 100% { opacity: 1; transform: scale(1); }
+}
+
+.animate-pop {
+ animation: pop-in 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275) both;
+}
+
+
+.pulse-teal { animation: pulse-teal 3s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
+.pulse-amber { animation: pulse-amber 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
+.pulse-red { animation: pulse-red 1.2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
+
+
+.gap-s4 { gap: var(--s-4); }
+.p-s2 { padding: var(--s-2); }
+
+
+.glass {
+ background: var(--glass-bg);
+ backdrop-filter: blur(20px) saturate(180%);
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
+ border: 1px solid var(--glass-border);
+}
+
+.glass-strong {
+ background: rgba(17, 17, 28, 0.85);
+ backdrop-filter: blur(40px) saturate(200%);
+ -webkit-backdrop-filter: blur(40px) saturate(200%);
+ border: 1px solid var(--glass-border);
+}
+
+.glass-hover:hover {
+ background: var(--glass-hover);
+ border-color: var(--border-strong);
+}
+
+.aurora-text {
+ background: linear-gradient(135deg, #A855F7, #06B6D4, #10B981);
+ background-size: 200% 200%;
+ animation: aurora-shift 6s ease infinite;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.aurora-border {
+ border-image: linear-gradient(135deg, #7C3AED, #06B6D4, #10B981) 1;
+ animation: aurora-shift 6s ease infinite;
+}
+
+.shimmer-text {
+ background: linear-gradient(
+ 90deg,
+ var(--muted-foreground) 0%,
+ var(--foreground) 40%,
+ var(--muted-foreground) 80%
+ );
+ background-size: 200% auto;
+ animation: shimmer 2.5s linear infinite;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+
+.command-palette-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.75);
+ backdrop-filter: blur(8px);
+ z-index: 1000;
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ padding-top: 120px;
+ animation: fadeIn 0.15s ease;
+}
+
+.command-palette-panel {
+ width: 620px;
+ max-width: 90vw;
+ border-radius: 20px;
+ overflow: hidden;
+ animation: scale-in 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
+ box-shadow:
+ 0 0 0 1px rgba(255,255,255,0.08),
+ 0 30px 80px rgba(0,0,0,0.6),
+ 0 0 60px rgba(124, 58, 237, 0.15);
+}
+
+
+.word-count-hud {
+ position: absolute;
+ bottom: 16px;
+ right: 16px;
+ z-index: 10;
+ animation: fadeIn 0.3s ease;
+}
+
+
+.status-banner {
+ background: linear-gradient(
+ 90deg,
+ rgba(124, 58, 237, 0.15),
+ rgba(6, 182, 212, 0.15),
+ rgba(16, 185, 129, 0.15)
+ );
+ background-size: 300% 100%;
+ animation: aurora-shift 4s ease infinite;
+}
+
+.status-banner.offline {
+ background: linear-gradient(
+ 90deg,
+ rgba(245, 158, 11, 0.2),
+ rgba(244, 63, 94, 0.15),
+ rgba(245, 158, 11, 0.2)
+ );
+ background-size: 300% 100%;
+ animation: aurora-shift 2.5s ease infinite;
+}
+
+
+.DraftEditor-root {
+ height: 100%;
+ font-family: 'JetBrains Mono', 'SF Mono', monospace;
+ font-size: 15px;
+ line-height: 1.8;
+ color: var(--foreground);
+}
+
+.DraftEditor-editorContainer,
+.public-DraftEditor-content {
+ height: 100%;
+}
+
+.DraftEditor-root .public-DraftEditorPlaceholder-root {
+ color: var(--muted-foreground);
+ font-style: italic;
+ pointer-events: none;
+}
+
+
+.public-DraftStyleDefault-bold { font-weight: 700; }
+.public-DraftStyleDefault-italic { font-style: italic; }
+.public-DraftStyleDefault-underline { text-decoration: underline; }
+.public-DraftStyleDefault-code {
+ background: rgba(124, 58, 237, 0.12);
+ border: 1px solid rgba(124, 58, 237, 0.25);
+ padding: 0.1em 0.4em;
+ border-radius: 5px;
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.88em;
+ color: #A855F7;
+}
+
+.public-DraftStyleDefault-header-one {
+ font-size: 2em;
+ font-weight: 800;
+ margin: 0.5em 0;
+ color: #FFFFFF;
+ letter-spacing: -0.02em;
+}
+
+.public-DraftStyleDefault-header-two {
+ font-size: 1.5em;
+ font-weight: 700;
+ margin: 0.4em 0;
+ color: #E2E8F0;
+}
+
+.public-DraftStyleDefault-header-three {
+ font-size: 1.2em;
+ font-weight: 700;
+ margin: 0.3em 0;
+ color: #CBD5E1;
+}
+
+.public-DraftStyleDefault-unordered-list-item,
+.public-DraftStyleDefault-ordered-list-item {
+ margin: 0.2em 0;
+ padding-left: 0.5em;
+}
+
+.public-DraftStyleDefault-blockquote {
+ border-left: 4px solid var(--primary);
+ background: rgba(124, 58, 237, 0.1);
+ padding: 0.6em 1.2em;
+ margin: 1em 0;
+ font-style: italic;
+ color: #C4B5FD;
+ border-radius: 0 8px 8px 0;
+}
+
+.light .public-DraftStyleDefault-blockquote {
+ background: rgba(124, 58, 237, 0.05);
+ color: #5B21B6;
+}
+
+.light .public-DraftStyleDefault-header-one,
+.light .public-DraftStyleDefault-header-two,
+.light .public-DraftStyleDefault-header-three {
+ color: #0F172A;
+}
+
+
+.markdown-preview {
+ font-family: 'Inter', sans-serif;
+ font-size: 15px;
+ line-height: 1.85;
+ color: var(--foreground);
+}
+
+.markdown-preview strong,
+.markdown-preview b {
+ font-weight: 700;
+ color: #E2E8F0;
+}
+
+.markdown-preview em,
+.markdown-preview i {
+ font-style: italic;
+ color: #A78BFA;
+}
+
+.markdown-preview u {
+ text-decoration: underline;
+ text-decoration-color: rgba(168, 85, 247, 0.5);
+}
+
+.markdown-preview h1,
+.markdown-preview h2,
+.markdown-preview h3,
+.markdown-preview h4 {
+ font-weight: 700;
+ margin-top: 2em;
+ margin-bottom: 0.6em;
+ line-height: 1.3;
+}
+
+.markdown-preview h1 {
+ font-size: 1.875em;
+ background: linear-gradient(135deg, #E2E8F0, #A855F7);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+ padding-bottom: 0.3em;
+ border-bottom: 1px solid var(--border);
+}
+
+.markdown-preview h2 {
+ font-size: 1.4em;
+ color: #C4B5FD;
+ border-bottom: 1px solid var(--border);
+ padding-bottom: 0.25em;
+}
+
+.markdown-preview h3 {
+ font-size: 1.15em;
+ color: #93C5FD;
+}
+
+.markdown-preview h4 {
+ font-size: 1em;
+ color: #6EE7B7;
+}
+
+.markdown-preview p {
+ margin-bottom: 1.25em;
+ color: #CBD5E1;
+}
+
+.markdown-preview a {
+ color: #7C3AED;
+ text-decoration: underline;
+ text-decoration-color: rgba(124, 58, 237, 0.4);
+ transition: color 0.2s;
+}
+.markdown-preview a:hover {
+ color: #A855F7;
+ text-decoration-color: rgba(168, 85, 247, 0.6);
+}
+
+.markdown-preview ul {
+ list-style: none;
+ padding-left: 1.25em;
+ margin-bottom: 1.25em;
+}
+.markdown-preview ul li::before {
+ content: '▸';
+ color: #7C3AED;
+ margin-left: -1.25em;
+ margin-right: 0.5em;
+ font-size: 0.75em;
+}
+
+.markdown-preview ol {
+ list-style-type: decimal;
+ padding-left: 1.5em;
+ margin-bottom: 1.25em;
+ color: #CBD5E1;
+}
+
+.markdown-preview li {
+ margin-bottom: 0.4em;
+ color: #CBD5E1;
+}
+
+.markdown-preview blockquote {
+ border-left: 3px solid #7C3AED;
+ background: rgba(124, 58, 237, 0.08);
+ padding: 0.75em 1.25em;
+ border-radius: 0 12px 12px 0;
+ margin: 1.5em 0;
+ color: #C4B5FD;
+ font-style: italic;
+}
+
+.markdown-preview code {
+ background: rgba(124, 58, 237, 0.12);
+ border: 1px solid rgba(124, 58, 237, 0.25);
+ padding: 0.15em 0.45em;
+ border-radius: 6px;
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.85em;
+ color: #A855F7;
+}
+
+.markdown-preview pre {
+ background: rgba(0, 0, 0, 0.4);
+ border: 1px solid var(--glass-border);
+ border-radius: 12px;
+ padding: 1.25em 1.5em;
+ overflow-x: auto;
+ margin: 1.5em 0;
+ position: relative;
+}
+
+.markdown-preview pre code {
+ background: none;
+ border: none;
+ padding: 0;
+ color: #E2E8F0;
+ font-size: 0.875em;
+}
+
+.markdown-preview table {
+ width: 100%;
+ border-collapse: collapse;
+ margin: 1.5em 0;
+ border-radius: 10px;
+ overflow: hidden;
+ border: 1px solid var(--border);
+}
+
+.markdown-preview th {
+ background: rgba(124, 58, 237, 0.15);
+ padding: 0.7em 1em;
+ text-align: left;
+ font-weight: 600;
+ color: #C4B5FD;
+ border-bottom: 1px solid var(--border);
+}
+
+.markdown-preview td {
+ padding: 0.65em 1em;
+ border-bottom: 1px solid var(--border);
+ color: #CBD5E1;
+}
+
+.markdown-preview tr:last-child td { border-bottom: none; }
+.markdown-preview tr:hover td { background: rgba(255,255,255,0.02); }
+
+.markdown-preview hr {
+ border: none;
+ height: 1px;
+ background: linear-gradient(90deg, transparent, var(--border-strong), transparent);
+ margin: 2em 0;
+}
+
+.markdown-preview img {
+ max-width: 100%;
+ border-radius: var(--s-2);
+ border: 1px solid var(--border);
+ margin: var(--s-2) 0;
+ transition: all 0.3s ease;
+}
+
+.image-halo-container {
+ position: relative;
+ display: inline-block;
+ margin: var(--s-4) auto;
+ width: 100%;
+ text-align: center;
+}
+
+.image-halo-container::before {
+ content: "";
+ position: absolute;
+ inset: -20px;
+ background: radial-gradient(circle, rgba(79, 118, 118, 0.15), rgba(124, 58, 237, 0.1), transparent 70%);
+ animation: nebula-pulse 6s ease-in-out infinite;
+ z-index: -1;
+ border-radius: 50%;
+}
+
+
+.tag-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ padding: 2px 10px;
+ border-radius: 99px;
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.03em;
+ text-transform: lowercase;
+ transition: all 0.2s ease;
+ cursor: default;
+ border: 1px solid transparent;
+}
+
+
+.note-glow-purple { box-shadow: 0 0 20px -4px rgba(124, 58, 237, 0.5), inset 0 0 0 1px rgba(124, 58, 237, 0.3); }
+.note-glow-cyan { box-shadow: 0 0 20px -4px rgba(6, 182, 212, 0.5), inset 0 0 0 1px rgba(6, 182, 212, 0.3); }
+.note-glow-emerald{ box-shadow: 0 0 20px -4px rgba(16, 185, 129, 0.5), inset 0 0 0 1px rgba(16, 185, 129, 0.3); }
+.note-glow-amber { box-shadow: 0 0 20px -4px rgba(245, 158, 11, 0.5), inset 0 0 0 1px rgba(245, 158, 11, 0.3); }
+.note-glow-rose { box-shadow: 0 0 20px -4px rgba(244, 63, 94, 0.5), inset 0 0 0 1px rgba(244, 63, 94, 0.3); }
+.note-glow-default{ box-shadow: 0 0 20px -4px rgba(255,255,255, 0.06), inset 0 0 0 1px rgba(255,255,255, 0.07); }
+
+.light .note-glow-purple { box-shadow: 0 4px 12px -2px rgba(124, 58, 237, 0.2); }
+.light .note-glow-cyan { box-shadow: 0 4px 12px -2px rgba(79, 118, 118, 0.2); }
+.light .note-glow-emerald{ box-shadow: 0 4px 12px -2px rgba(16, 185, 129, 0.2); }
+.light .note-glow-amber { box-shadow: 0 4px 12px -2px rgba(245, 158, 11, 0.2); }
+.light .note-glow-rose { box-shadow: 0 4px 12px -2px rgba(244, 63, 94, 0.2); }
+.light .note-glow-default{ box-shadow: 0 4px 12px -2px rgba(0,0,0, 0.05); }
+
+.light .DraftEditor-root { color: #0F172A; }
+.light .DraftEditor-root .public-DraftEditorPlaceholder-root { color: #94A3B8; }
+.light .markdown-preview h1 {
+ background: linear-gradient(135deg, #0F172A, #7C3AED);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+.light .markdown-preview h2 { color: #5B21B6; }
+.light .markdown-preview h3 { color: #1D4ED8; }
+.light .markdown-preview p, .light .markdown-preview li { color: #334155; }
+.light .markdown-preview code { background: rgba(124,58,237,0.08); color: #6D28D9; }
+.light .markdown-preview pre { background: #F8FAFC; border-color: #E2E8F0; }
+.light .markdown-preview pre code { color: #1E293B; }
+.light .markdown-preview blockquote { background: rgba(124,58,237,0.05); color: #6D28D9; }
+.light .markdown-preview th { background: rgba(124,58,237,0.08); color: #5B21B6; }
+.light .markdown-preview td { color: #334155; }
diff --git a/teams/Krantikari/src/lib/db.js b/teams/Krantikari/src/lib/db.js
new file mode 100644
index 0000000..d15c375
--- /dev/null
+++ b/teams/Krantikari/src/lib/db.js
@@ -0,0 +1,47 @@
+import Dexie from 'dexie';
+
+export const db = new Dexie('MarkdownNotesDB');
+
+
+db.version(1).stores({
+ notes: 'id, title, folderId, createdAt, updatedAt, isDeleted',
+ folders: 'id, name, parentId, createdAt',
+ syncQueue: '++id, operation, entityType, entityId, timestamp, payload'
+});
+
+
+db.version(2).stores({
+ notes: 'id, title, folderId, createdAt, updatedAt, isDeleted, isPinned, color',
+ folders: 'id, name, parentId, createdAt, color',
+ syncQueue: '++id, operation, entityType, entityId, timestamp, payload'
+}).upgrade(tx => {
+ return tx.table('notes').toCollection().modify(note => {
+ note.isPinned = note.isPinned ?? false;
+ note.color = note.color ?? 'default';
+ note.tags = note.tags ?? [];
+ });
+});
+
+export const addNote = async (note) => {
+ return await db.notes.put(note);
+};
+
+export const getNotes = async () => {
+ return await db.notes.filter(note => !note.isDeleted).reverse().sortBy('updatedAt');
+};
+
+export const deleteNote = async (id) => {
+ return await db.notes.update(id, { isDeleted: true, updatedAt: Date.now() });
+};
+
+export const addFolder = async (folder) => {
+ return await db.folders.put(folder);
+};
+
+export const getFolders = async () => {
+ return await db.folders.toArray();
+};
+
+export const deleteFolder = async (id) => {
+ return await db.folders.delete(id);
+};
diff --git a/teams/Krantikari/src/lib/utils.js b/teams/Krantikari/src/lib/utils.js
new file mode 100644
index 0000000..97ee043
--- /dev/null
+++ b/teams/Krantikari/src/lib/utils.js
@@ -0,0 +1,23 @@
+export function debounce(func, delay) {
+ let timeoutId;
+ let lastArgs;
+
+ const debounced = (...args) => {
+ lastArgs = args;
+ if (timeoutId) clearTimeout(timeoutId);
+ timeoutId = setTimeout(() => {
+ timeoutId = null;
+ func(...args);
+ }, delay);
+ };
+
+ debounced.flush = () => {
+ if (timeoutId && lastArgs) {
+ clearTimeout(timeoutId);
+ timeoutId = null;
+ func(...lastArgs);
+ }
+ };
+
+ return debounced;
+}
diff --git a/teams/Krantikari/src/main.jsx b/teams/Krantikari/src/main.jsx
new file mode 100644
index 0000000..54b39dd
--- /dev/null
+++ b/teams/Krantikari/src/main.jsx
@@ -0,0 +1,10 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import App from './App.jsx'
+import './index.css'
+
+ReactDOM.createRoot(document.getElementById('root')).render(
+
+
+ ,
+)
diff --git a/teams/Krantikari/src/store/folderStore.js b/teams/Krantikari/src/store/folderStore.js
new file mode 100644
index 0000000..1c94ddb
--- /dev/null
+++ b/teams/Krantikari/src/store/folderStore.js
@@ -0,0 +1,29 @@
+import { create } from 'zustand';
+import { db, getFolders, addFolder, deleteFolder as dbDeleteFolder } from '../lib/db';
+
+export const useFolderStore = create((set, get) => ({
+ folders: [],
+
+ loadFolders: async () => {
+ const fetchedFolders = await getFolders();
+ set({ folders: fetchedFolders });
+ },
+
+ createFolder: async (name) => {
+ const newFolder = {
+ id: crypto.randomUUID(),
+ name: name || 'New Folder',
+ parentId: null,
+ createdAt: Date.now()
+ };
+ await addFolder(newFolder);
+ await get().loadFolders();
+ },
+
+ deleteFolder: async (id) => {
+ await dbDeleteFolder(id);
+ set((state) => ({
+ folders: state.folders.filter(f => f.id !== id)
+ }));
+ }
+}));
diff --git a/teams/Krantikari/src/store/noteStore.js b/teams/Krantikari/src/store/noteStore.js
new file mode 100644
index 0000000..a137a57
--- /dev/null
+++ b/teams/Krantikari/src/store/noteStore.js
@@ -0,0 +1,108 @@
+import { create } from 'zustand';
+import { db, getNotes, addNote, deleteNote as dbDeleteNote } from '../lib/db';
+
+export const useNoteStore = create((set, get) => ({
+ notes: [],
+ activeNoteId: null,
+
+ loadNotes: async () => {
+ const fetchedNotes = await getNotes();
+
+ const sorted = [...fetchedNotes].sort((a, b) => {
+ if (a.isPinned && !b.isPinned) return -1;
+ if (!a.isPinned && b.isPinned) return 1;
+ return (b.updatedAt || 0) - (a.updatedAt || 0);
+ });
+ set({ notes: sorted });
+ if (sorted.length > 0 && !get().activeNoteId) {
+ set({ activeNoteId: sorted[0].id });
+ }
+ },
+
+ setActiveNote: (id) => set({ activeNoteId: id }),
+
+ createNote: async (initialTitle = 'Untitled Note', initialContent = '') => {
+ const newNote = {
+ id: crypto.randomUUID(),
+ title: initialTitle,
+ content: initialContent,
+ folderId: null,
+ createdAt: Date.now(),
+ updatedAt: Date.now(),
+ isDeleted: false,
+ isPinned: false,
+ color: 'default',
+ tags: [],
+ };
+ await addNote(newNote);
+ await get().loadNotes();
+ set({ activeNoteId: newNote.id });
+ },
+
+ updateActiveNote: async (updates) => {
+ const { activeNoteId } = get();
+ if (!activeNoteId) return;
+
+ await db.notes.update(activeNoteId, {
+ ...updates,
+ updatedAt: Date.now()
+ });
+
+ set((state) => ({
+ notes: state.notes.map(n =>
+ n.id === activeNoteId ? { ...n, ...updates, updatedAt: Date.now() } : n
+ )
+ }));
+ },
+
+ pinNote: async (id) => {
+ await db.notes.update(id, { isPinned: true, updatedAt: Date.now() });
+ set((state) => {
+ const updated = state.notes.map(n =>
+ n.id === id ? { ...n, isPinned: true } : n
+ );
+ return {
+ notes: updated.sort((a, b) => {
+ if (a.isPinned && !b.isPinned) return -1;
+ if (!a.isPinned && b.isPinned) return 1;
+ return (b.updatedAt || 0) - (a.updatedAt || 0);
+ })
+ };
+ });
+ },
+
+ unpinNote: async (id) => {
+ await db.notes.update(id, { isPinned: false, updatedAt: Date.now() });
+ set((state) => {
+ const updated = state.notes.map(n =>
+ n.id === id ? { ...n, isPinned: false } : n
+ );
+ return {
+ notes: updated.sort((a, b) => {
+ if (a.isPinned && !b.isPinned) return -1;
+ if (!a.isPinned && b.isPinned) return 1;
+ return (b.updatedAt || 0) - (a.updatedAt || 0);
+ })
+ };
+ });
+ },
+
+ setNoteColor: async (id, color) => {
+ await db.notes.update(id, { color, updatedAt: Date.now() });
+ set((state) => ({
+ notes: state.notes.map(n => n.id === id ? { ...n, color } : n)
+ }));
+ },
+
+ removeNote: async (id) => {
+ await dbDeleteNote(id);
+ set((state) => {
+ const newNotes = state.notes.filter(n => n.id !== id);
+ const newActiveNoteId =
+ state.activeNoteId === id
+ ? newNotes.length > 0 ? newNotes[0].id : null
+ : state.activeNoteId;
+ return { notes: newNotes, activeNoteId: newActiveNoteId };
+ });
+ }
+}));
diff --git a/teams/Krantikari/src/tests/setup.js b/teams/Krantikari/src/tests/setup.js
new file mode 100644
index 0000000..b3409ec
--- /dev/null
+++ b/teams/Krantikari/src/tests/setup.js
@@ -0,0 +1,8 @@
+import 'fake-indexeddb/auto';
+import { expect, afterEach } from 'vitest';
+import { cleanup } from '@testing-library/react';
+
+
+afterEach(() => {
+ cleanup();
+});
diff --git a/teams/Krantikari/src/tests/unit/db.test.js b/teams/Krantikari/src/tests/unit/db.test.js
new file mode 100644
index 0000000..abf7518
--- /dev/null
+++ b/teams/Krantikari/src/tests/unit/db.test.js
@@ -0,0 +1,70 @@
+import { describe, it, expect, beforeEach, afterEach } from 'vitest';
+import { db, addNote, getNotes, deleteNote, addFolder, getFolders } from '../../lib/db';
+
+describe('Database (Dexie) Operations', () => {
+ beforeEach(async () => {
+
+ await db.notes.clear();
+ await db.folders.clear();
+ });
+
+ afterEach(async () => {
+ await db.notes.clear();
+ await db.folders.clear();
+ });
+
+ it('adds and retrieves a note correctly', async () => {
+ const mockNote = {
+ id: 'note-123',
+ title: 'Test Note',
+ content: '{"blocks":[]}',
+ folderId: null,
+ createdAt: Date.now(),
+ updatedAt: Date.now(),
+ isDeleted: false
+ };
+
+ await addNote(mockNote);
+
+ const notes = await getNotes();
+ expect(notes.length).toBe(1);
+ expect(notes[0].id).toBe('note-123');
+ expect(notes[0].title).toBe('Test Note');
+ });
+
+ it('soft-deletes a note perfectly mapping isDeleted truth values', async () => {
+ const mockNote = {
+ id: 'note-delete',
+ title: 'To Be Deleted',
+ content: '',
+ folderId: null,
+ createdAt: Date.now(),
+ updatedAt: Date.now(),
+ isDeleted: false
+ };
+
+ await addNote(mockNote);
+ await deleteNote('note-delete');
+
+ const notesAfterDelete = await getNotes();
+
+ expect(notesAfterDelete.length).toBe(0);
+
+
+ const maskedNote = await db.notes.get('note-delete');
+ expect(maskedNote.isDeleted).toBe(true);
+ });
+
+ it('validates folder creation operations', async () => {
+ await addFolder({
+ id: 'folder-1',
+ name: 'Work',
+ parentId: null,
+ createdAt: Date.now()
+ });
+
+ const folders = await getFolders();
+ expect(folders.length).toBe(1);
+ expect(folders[0].name).toBe('Work');
+ });
+});
diff --git a/teams/Krantikari/src/tests/unit/useAutoSave.test.jsx b/teams/Krantikari/src/tests/unit/useAutoSave.test.jsx
new file mode 100644
index 0000000..477af9f
--- /dev/null
+++ b/teams/Krantikari/src/tests/unit/useAutoSave.test.jsx
@@ -0,0 +1,66 @@
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
+import { renderHook, act } from '@testing-library/react';
+import { useAutoSave } from '../../hooks/useAutoSave';
+import { useNoteStore } from '../../store/noteStore';
+
+const { mockUpdateActiveNote } = vi.hoisted(() => ({
+ mockUpdateActiveNote: vi.fn().mockResolvedValue(true)
+}));
+
+
+vi.mock('../../store/noteStore', () => ({
+ useNoteStore: () => ({
+ updateActiveNote: mockUpdateActiveNote
+ })
+}));
+
+describe('useAutoSave hook', () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ mockUpdateActiveNote.mockClear();
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it('initially sets isSaving to false', () => {
+ const { result } = renderHook(() => useAutoSave(500));
+ expect(result.current.isSaving).toBe(false);
+ });
+
+ it('debounces the triggerSave execution', () => {
+ const { result } = renderHook(() => useAutoSave(500));
+ const store = useNoteStore();
+
+ act(() => {
+ result.current.triggerSave({ content: 'test update' });
+ });
+
+
+ expect(mockUpdateActiveNote).not.toHaveBeenCalled();
+
+ act(() => {
+ vi.advanceTimersByTime(200);
+ });
+
+ expect(mockUpdateActiveNote).not.toHaveBeenCalled();
+
+ act(() => {
+ vi.advanceTimersByTime(300);
+ });
+
+ expect(mockUpdateActiveNote).toHaveBeenCalledWith({ content: 'test update' });
+ });
+
+ it('provides a flush command that fires explicitly regardless of timeout', () => {
+ const { result } = renderHook(() => useAutoSave(500));
+
+ act(() => {
+ result.current.triggerSave({ content: 'flushed async event' });
+ result.current.triggerSave.flush();
+ });
+
+ expect(mockUpdateActiveNote).toHaveBeenCalled();
+ });
+});
diff --git a/teams/Krantikari/tailwind.config.js b/teams/Krantikari/tailwind.config.js
new file mode 100644
index 0000000..b0cf961
--- /dev/null
+++ b/teams/Krantikari/tailwind.config.js
@@ -0,0 +1,54 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ darkMode: 'class',
+ content: [
+ "./index.html",
+ "./src/**/*.{js,ts,jsx,tsx}",
+ ],
+ theme: {
+ extend: {
+ fontFamily: {
+ sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
+ mono: ['JetBrains Mono', 'SF Mono', 'Menlo', 'monospace'],
+ },
+ colors: {
+ background: 'hsl(var(--color-background))',
+ foreground: 'hsl(var(--color-foreground))',
+ primary: {
+ DEFAULT: 'hsl(var(--color-primary))',
+ foreground: 'hsl(var(--color-primary-foreground))',
+ },
+ card: {
+ DEFAULT: 'hsl(var(--color-card))',
+ foreground: 'hsl(var(--color-card-foreground))',
+ },
+ muted: {
+ DEFAULT: 'hsl(var(--color-muted))',
+ foreground: 'hsl(var(--color-muted-foreground))',
+ },
+ accent: {
+ DEFAULT: 'hsl(var(--color-accent))',
+ foreground: 'hsl(var(--color-accent-foreground))',
+ },
+ destructive: {
+ DEFAULT: 'hsl(var(--color-destructive))',
+ foreground: 'hsl(var(--color-destructive-foreground))',
+ },
+ border: 'hsl(var(--color-border))',
+ },
+ animation: {
+ 'aurora': 'aurora-shift 6s ease infinite',
+ 'float': 'float 3s ease-in-out infinite',
+ 'shimmer': 'shimmer 2.5s linear infinite',
+ 'spin-slow': 'spin-slow 8s linear infinite',
+ 'scale-in': 'scale-in 0.2s cubic-bezier(0.34, 1.56, 0.64, 1)',
+ 'fade-in': 'fadeIn 0.3s ease',
+ 'slide-up': 'slideUp 0.4s ease',
+ },
+ backdropBlur: {
+ xs: '2px',
+ },
+ },
+ },
+ plugins: [],
+}
diff --git a/teams/Krantikari/vite.config.js b/teams/Krantikari/vite.config.js
new file mode 100644
index 0000000..60c8a98
--- /dev/null
+++ b/teams/Krantikari/vite.config.js
@@ -0,0 +1,58 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+import { VitePWA } from 'vite-plugin-pwa'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ define: {
+ global: 'window',
+ },
+ plugins: [
+ react(),
+ VitePWA({
+ registerType: 'autoUpdate',
+ includeAssets: ['icons/icon.svg'],
+ manifest: {
+ name: 'MarkDown Offline',
+ short_name: 'MDOffline',
+ description: 'Distraction-free offline markdown notes',
+ theme_color: '#6366f1',
+ background_color: '#0f172a',
+ display: 'standalone',
+ start_url: '/',
+ icons: [
+ {
+ src: '/icons/icon.svg',
+ sizes: '192x192 512x512',
+ type: 'image/svg+xml',
+ purpose: 'any maskable'
+ }
+ ]
+ },
+ workbox: {
+ globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
+ runtimeCaching: [
+ {
+ urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
+ handler: 'CacheFirst',
+ options: {
+ cacheName: 'google-fonts-cache',
+ expiration: {
+ maxEntries: 10,
+ maxAgeSeconds: 60 * 60 * 24 * 365
+ },
+ cacheableResponse: {
+ statuses: [0, 200]
+ }
+ }
+ }
+ ]
+ }
+ })
+ ],
+ test: {
+ environment: 'jsdom',
+ globals: true,
+ setupFiles: ['./src/tests/setup.js']
+ }
+})