Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Nexus Notes - Modern Note Taking</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <script> | |
| tailwind.config = { | |
| darkMode: 'class', | |
| theme: { | |
| extend: { | |
| colors: { | |
| primary: { | |
| 50: '#f0f9ff', | |
| 100: '#e0f2fe', | |
| 200: '#bae6fd', | |
| 300: '#7dd3fc', | |
| 400: '#38bdf8', | |
| 500: '#0ea5e9', | |
| 600: '#0284c7', | |
| 700: '#0369a1', | |
| 800: '#075985', | |
| 900: '#0c4a6e', | |
| }, | |
| secondary: { | |
| 50: '#f8fafc', | |
| 100: '#f1f5f9', | |
| 200: '#e2e8f0', | |
| 300: '#cbd5e1', | |
| 400: '#94a3b8', | |
| 500: '#64748b', | |
| 600: '#475569', | |
| 700: '#334155', | |
| 800: '#1e293b', | |
| 900: '#0f172a', | |
| } | |
| }, | |
| fontFamily: { | |
| sans: ['Inter', 'system-ui', 'sans-serif'], | |
| mono: ['Fira Code', 'monospace'] | |
| } | |
| } | |
| } | |
| } | |
| </script> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Fira+Code:wght@400;500&display=swap'); | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| } | |
| .markdown-content h1 { | |
| @apply text-2xl font-bold my-4 text-gray-900 dark:text-white; | |
| } | |
| .markdown-content h2 { | |
| @apply text-xl font-bold my-3 text-gray-800 dark:text-gray-200; | |
| } | |
| .markdown-content h3 { | |
| @apply text-lg font-bold my-2 text-gray-700 dark:text-gray-300; | |
| } | |
| .markdown-content p { | |
| @apply my-2 text-gray-700 dark:text-gray-300; | |
| } | |
| .markdown-content ul { | |
| @apply list-disc pl-5 my-2 text-gray-700 dark:text-gray-300; | |
| } | |
| .markdown-content ol { | |
| @apply list-decimal pl-5 my-2 text-gray-700 dark:text-gray-300; | |
| } | |
| .markdown-content pre { | |
| @apply bg-gray-100 dark:bg-gray-700 p-3 rounded my-2 overflow-x-auto text-gray-800 dark:text-gray-200 font-mono text-sm; | |
| } | |
| .markdown-content code { | |
| @apply bg-gray-100 dark:bg-gray-700 px-1 rounded text-sm text-gray-800 dark:text-gray-200 font-mono; | |
| } | |
| .markdown-content blockquote { | |
| @apply border-l-4 border-primary-500 pl-4 italic my-2 text-gray-600 dark:text-gray-400; | |
| } | |
| .markdown-content a { | |
| @apply text-primary-600 dark:text-primary-400 hover:underline; | |
| } | |
| .markdown-content img { | |
| @apply max-w-full rounded my-2; | |
| } | |
| .markdown-content table { | |
| @apply w-full border-collapse my-3; | |
| } | |
| .markdown-content th, | |
| .markdown-content td { | |
| @apply border border-gray-300 dark:border-gray-600 px-3 py-2 text-left; | |
| } | |
| .markdown-content th { | |
| @apply bg-gray-100 dark:bg-gray-700 font-medium; | |
| } | |
| /* Custom scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 6px; | |
| height: 6px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| @apply bg-gray-100 dark:bg-gray-800; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| @apply bg-gray-300 dark:bg-gray-600 rounded; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| @apply bg-gray-400 dark:bg-gray-500; | |
| } | |
| /* Animation for note cards */ | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .note-card { | |
| animation: fadeIn 0.3s ease-out forwards; | |
| } | |
| /* Mobile menu transition */ | |
| .mobile-menu { | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| .mobile-menu.hidden { | |
| transform: translateX(-100%); | |
| opacity: 0; | |
| } | |
| .mobile-menu.shown { | |
| transform: translateX(0); | |
| opacity: 1; | |
| } | |
| /* Editor height */ | |
| .editor-container { | |
| height: calc(100vh - 180px); | |
| } | |
| @media (min-width: 768px) { | |
| .editor-container { | |
| height: calc(100vh - 140px); | |
| } | |
| } | |
| /* Bottom navigation */ | |
| .bottom-nav { | |
| box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05); | |
| @apply dark:shadow-none dark:border-t dark:border-gray-700; | |
| } | |
| /* Floating action button */ | |
| .fab { | |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); | |
| @apply transition-transform hover:scale-105 active:scale-95; | |
| } | |
| /* Note card hover effect */ | |
| .note-card:hover { | |
| transform: translateY(-2px); | |
| @apply transition-transform duration-200; | |
| } | |
| /* Active tab indicator */ | |
| .tab-indicator { | |
| @apply absolute bottom-0 left-0 h-0.5 bg-primary-500 transition-all duration-300; | |
| } | |
| /* Markdown toolbar */ | |
| .markdown-toolbar { | |
| @apply sticky top-0 bg-white dark:bg-gray-800 z-10 border-b border-gray-200 dark:border-gray-700 px-2 py-1 flex flex-wrap items-center gap-1; | |
| } | |
| .markdown-toolbar button { | |
| @apply p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 hover:text-primary-500 dark:hover:text-primary-400; | |
| min-width: 32px; | |
| min-height: 32px; | |
| } | |
| .markdown-toolbar button.active { | |
| @apply bg-primary-100 dark:bg-gray-700 text-primary-600 dark:text-primary-400; | |
| } | |
| /* Preview pane */ | |
| .preview-pane { | |
| @apply border-l border-gray-200 dark:border-gray-700 h-full overflow-y-auto; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 text-gray-800 dark:bg-gray-900 dark:text-gray-100 transition-colors duration-200"> | |
| <div class="flex flex-col min-h-screen"> | |
| <!-- Header --> | |
| <header class="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-4 shadow-sm sticky top-0 z-10"> | |
| <div class="container mx-auto flex justify-between items-center"> | |
| <div class="flex items-center space-x-3"> | |
| <div class="w-8 h-8 rounded-md bg-primary-500 flex items-center justify-center"> | |
| <i class="fas fa-cube text-white"></i> | |
| </div> | |
| <h1 class="text-xl font-bold hidden sm:block">Nexus Notes</h1> | |
| </div> | |
| <div class="flex items-center space-x-4"> | |
| <button id="darkModeToggle" class="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition"> | |
| <i class="fas fa-moon dark:hidden text-gray-600"></i> | |
| <i class="fas fa-sun hidden dark:block text-yellow-400"></i> | |
| </button> | |
| <button id="newNoteBtn" class="hidden md:flex items-center space-x-2 bg-primary-500 hover:bg-primary-600 text-white px-4 py-2 rounded-md font-medium transition"> | |
| <i class="fas fa-plus"></i> | |
| <span>New Note</span> | |
| </button> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main Content - Split View --> | |
| <div class="flex flex-1 overflow-hidden"> | |
| <!-- Left Pane - Note List --> | |
| <div class="w-full md:w-1/3 lg:w-1/4 xl:w-1/5 bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 flex-col overflow-y-auto"> | |
| <div class="p-4 sticky top-0 bg-white dark:bg-gray-800 z-10"> | |
| <div class="relative mb-4"> | |
| <input type="text" id="searchNotes" placeholder="Search notes..." | |
| class="w-full p-2 pl-10 border border-gray-200 dark:border-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500"> | |
| <i class="fas fa-search absolute left-3 top-3 text-gray-400"></i> | |
| </div> | |
| <div class="flex justify-between items-center"> | |
| <h2 class="font-semibold text-gray-700 dark:text-gray-300">My Notes</h2> | |
| <div class="flex space-x-2"> | |
| <button id="sortDateBtn" class="p-1 text-primary-500 dark:text-primary-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded" title="Sort by date"> | |
| <i class="fas fa-calendar"></i> | |
| </button> | |
| <button id="sortAlphaBtn" class="p-1 text-gray-500 dark:text-gray-400 hover:text-primary-500 dark:hover:text-primary-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded" title="Sort alphabetically"> | |
| <i class="fas fa-sort-alpha-down"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="noteList" class="divide-y divide-gray-100 dark:divide-gray-700 flex-1 overflow-y-auto"> | |
| <!-- Notes will be dynamically added here --> | |
| </div> | |
| </div> | |
| <!-- Right Pane - Side by Side Editor/Preview --> | |
| <div class="hidden md:flex flex-1 flex-col md:flex-row bg-white dark:bg-gray-800 overflow-hidden border-l border-gray-200 dark:border-gray-700"> | |
| <!-- Editor Pane --> | |
| <div id="editorPane" class="flex-1 flex flex-col border-r border-gray-200 dark:border-gray-700"> | |
| <div class="p-4 border-b border-gray-200 dark:border-gray-700 sticky top-0 bg-white dark:bg-gray-800 z-10 flex justify-between items-center"> | |
| <input type="text" id="noteTitle" placeholder="Note title" | |
| class="text-xl font-bold w-full focus:outline-none bg-transparent text-gray-800 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500"> | |
| <div class="flex space-x-2"> | |
| <button id="saveNoteBtn" class="p-2 text-gray-500 dark:text-gray-400 hover:text-primary-500 dark:hover:text-primary-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition" title="Save"> | |
| <i class="fas fa-save"></i> | |
| </button> | |
| <button id="deleteNoteBtn" class="p-2 text-gray-500 dark:text-gray-400 hover:text-red-500 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition" title="Delete"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="markdown-toolbar"> | |
| <button class="format-btn" data-format="bold"><i class="fas fa-bold"></i></button> | |
| <button class="format-btn" data-format="italic"><i class="fas fa-italic"></i></button> | |
| <button class="format-btn" data-format="header1"><i class="fas fa-heading"><sub>1</sub></i></button> | |
| <button class="format-btn" data-format="header2"><i class="fas fa-heading"><sub>2</sub></i></button> | |
| <button class="format-btn" data-format="header3"><i class="fas fa-heading"><sub>3</sub></i></button> | |
| <button class="format-btn" data-format="quote"><i class="fas fa-quote-right"></i></button> | |
| <button class="format-btn" data-format="code"><i class="fas fa-code"></i></button> | |
| <button class="format-btn" data-format="link"><i class="fas fa-link"></i></button> | |
| <button class="format-btn" data-format="image"><i class="fas fa-image"></i></button> | |
| <button class="format-btn" data-format="list"><i class="fas fa-list-ul"></i></button> | |
| <button class="format-btn" data-format="olist"><i class="fas fa-list-ol"></i></button> | |
| </div> | |
| <div class="flex-1 overflow-auto"> | |
| <textarea id="noteContent" class="w-full h-full p-4 focus:outline-none resize-none font-mono bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500" | |
| placeholder="Start writing your note here... Markdown is supported!"></textarea> | |
| </div> | |
| <div class="p-2 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700 text-sm text-gray-500 dark:text-gray-400 flex justify-between items-center"> | |
| <div id="wordCount" class="flex items-center space-x-2"> | |
| <i class="fas fa-font text-xs"></i> | |
| <span>0 words</span> | |
| </div> | |
| <div id="lastSaved" class="text-xs flex items-center space-x-1"> | |
| <i class="fas fa-clock text-xs"></i> | |
| <span>Not saved yet</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Preview Pane --> | |
| <div id="previewPane" class="preview-pane hidden lg:block lg:w-1/2"> | |
| <div class="p-4 border-b border-gray-200 dark:border-gray-700 sticky top-0 bg-white dark:bg-gray-800 z-10"> | |
| <h2 class="text-xl font-bold text-gray-800 dark:text-white">Preview</h2> | |
| </div> | |
| <div id="notePreviewContent" class="p-4 markdown-content bg-white dark:bg-gray-800"> | |
| <!-- Preview content will be rendered here --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Mobile View (Single Pane) --> | |
| <div id="mobileEditor" class="md:hidden flex-1 flex flex-col bg-white dark:bg-gray-800 overflow-hidden"> | |
| <!-- Empty State --> | |
| <div id="emptyState" class="h-full flex flex-col items-center justify-center text-gray-500 dark:text-gray-400 p-4"> | |
| <div class="w-16 h-16 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center mb-4"> | |
| <i class="fas fa-sticky-note text-2xl opacity-50"></i> | |
| </div> | |
| <h2 class="text-xl font-medium mb-2">No Note Selected</h2> | |
| <p class="mb-6 text-center max-w-md">Select a note from the sidebar or create a new one to get started</p> | |
| <button id="emptyNewNoteBtn" class="bg-primary-500 hover:bg-primary-600 text-white px-6 py-3 rounded-md font-medium transition flex items-center space-x-2"> | |
| <i class="fas fa-plus"></i> | |
| <span>Create New Note</span> | |
| </button> | |
| </div> | |
| <!-- Editor (Mobile) --> | |
| <div id="mobileNoteEditor" class="h-full hidden flex-col"> | |
| <div class="p-4 border-b border-gray-200 dark:border-gray-700 sticky top-0 bg-white dark:bg-gray-800 z-10 flex justify-between items-center"> | |
| <input type="text" id="mobileNoteTitle" placeholder="Note title" | |
| class="text-xl font-bold w-full focus:outline-none bg-transparent text-gray-800 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500"> | |
| <div class="flex space-x-2"> | |
| <button id="mobileSaveNoteBtn" class="p-2 text-gray-500 dark:text-gray-400 hover:text-primary-500 dark:hover:text-primary-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition" title="Save"> | |
| <i class="fas fa-save"></i> | |
| </button> | |
| <button id="mobileDeleteNoteBtn" class="p-2 text-gray-500 dark:text-gray-400 hover:text-red-500 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition" title="Delete"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="flex border-b border-gray-200 dark:border-gray-700 relative"> | |
| <button id="writeTab" class="px-4 py-3 font-medium text-primary-500 dark:text-primary-400 relative"> | |
| <i class="fas fa-pen mr-2"></i> | |
| <span>Write</span> | |
| </button> | |
| <button id="previewTab" class="px-4 py-3 font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300"> | |
| <i class="fas fa-eye mr-2"></i> | |
| <span>Preview</span> | |
| </button> | |
| <div id="tabIndicator" class="tab-indicator w-1/2"></div> | |
| </div> | |
| <div class="editor-container flex flex-col"> | |
| <div id="writePane" class="flex-1 overflow-auto"> | |
| <textarea id="mobileNoteContent" class="w-full h-full p-4 focus:outline-none resize-none font-mono bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500" | |
| placeholder="Start writing your note here... Markdown is supported!"></textarea> | |
| </div> | |
| <div id="mobilePreviewPane" class="flex-1 overflow-auto p-4 hidden markdown-content bg-white dark:bg-gray-800"> | |
| <!-- Markdown preview will be rendered here --> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Bottom Navigation (Mobile) --> | |
| <nav class="md:hidden bottom-nav bg-white dark:bg-gray-800 fixed bottom-0 left-0 right-0 z-50"> | |
| <div class="flex justify-around items-center p-2"> | |
| <button id="mobileNotesBtn" class="p-3 rounded-full text-primary-500 dark:text-primary-400 bg-primary-50 dark:bg-gray-700"> | |
| <i class="fas fa-sticky-note text-lg"></i> | |
| </button> | |
| <button id="mobileNewNoteBtn" class="fab bg-primary-500 text-white p-4 rounded-full -mt-6"> | |
| <i class="fas fa-plus text-xl"></i> | |
| </button> | |
| <button id="mobilePreviewToggle" class="p-3 rounded-full text-gray-500 dark:text-gray-400 hover:text-primary-500 dark:hover:text-primary-400"> | |
| <i class="fas fa-eye text-lg"></i> | |
| </button> | |
| </div> | |
| </nav> | |
| <!-- Modal for markdown elements --> | |
| <div id="mdModal" class="fixed inset-0 z-50 hidden items-center justify-center bg-black bg-opacity-50"> | |
| <div class="bg-white dark:bg-gray-800 rounded-lg p-4 w-full max-w-md mx-4"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-lg font-semibold text-gray-800 dark:text-white" id="mdModalTitle">Add Element</h3> | |
| <button id="closeMdModal" class="text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div id="mdModalContent" class="mb-4"> | |
| <!-- Content will be dynamically added here --> | |
| </div> | |
| <div class="flex justify-end space-x-2"> | |
| <button id="cancelMdModal" class="px-4 py-2 rounded-md border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700"> | |
| Cancel | |
| </button> | |
| <button id="insertMdElement" class="px-4 py-2 rounded-md bg-primary-500 text-white hover:bg-primary-600"> | |
| Insert | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // DOM Elements | |
| const darkModeToggle = document.getElementById('darkModeToggle'); | |
| const newNoteBtn = document.getElementById('newNoteBtn'); | |
| const emptyNewNoteBtn = document.getElementById('emptyNewNoteBtn'); | |
| const mobileNewNoteBtn = document.getElementById('mobileNewNoteBtn'); | |
| const searchNotes = document.getElementById('searchNotes'); | |
| const sortDateBtn = document.getElementById('sortDateBtn'); | |
| const sortAlphaBtn = document.getElementById('sortAlphaBtn'); | |
| const noteList = document.getElementById('noteList'); | |
| const emptyState = document.getElementById('emptyState'); | |
| const mobileEditor = document.getElementById('mobileEditor'); | |
| const mobileNoteEditor = document.getElementById('mobileNoteEditor'); | |
| // Desktop Editor Elements | |
| const editorPane = document.getElementById('editorPane'); | |
| const previewPane = document.getElementById('previewPane'); | |
| const noteTitle = document.getElementById('noteTitle'); | |
| const noteContent = document.getElementById('noteContent'); | |
| const saveNoteBtn = document.getElementById('saveNoteBtn'); | |
| const deleteNoteBtn = document.getElementById('deleteNoteBtn'); | |
| const wordCount = document.getElementById('wordCount'); | |
| const lastSaved = document.getElementById('lastSaved'); | |
| const notePreviewContent = document.getElementById('notePreviewContent'); | |
| // Mobile Editor Elements | |
| const mobileNotesBtn = document.getElementById('mobileNotesBtn'); | |
| const mobilePreviewToggle = document.getElementById('mobilePreviewToggle'); | |
| const mobileNoteTitle = document.getElementById('mobileNoteTitle'); | |
| const mobileNoteContent = document.getElementById('mobileNoteContent'); | |
| const mobileSaveNoteBtn = document.getElementById('mobileSaveNoteBtn'); | |
| const mobileDeleteNoteBtn = document.getElementById('mobileDeleteNoteBtn'); | |
| const writeTab = document.getElementById('writeTab'); | |
| const previewTab = document.getElementById('previewTab'); | |
| const writePane = document.getElementById('writePane'); | |
| const mobilePreviewPane = document.getElementById('mobilePreviewPane'); | |
| const tabIndicator = document.getElementById('tabIndicator'); | |
| // Formatting Elements | |
| const mdModal = document.getElementById('mdModal'); | |
| const mdModalTitle = document.getElementById('mdModalTitle'); | |
| const mdModalContent = document.getElementById('mdModalContent'); | |
| const cancelMdModal = document.getElementById('cancelMdModal'); | |
| const insertMdElement = document.getElementById('insertMdElement'); | |
| const closeMdModal = document.getElementById('closeMdModal'); | |
| const formatButtons = document.querySelectorAll('.format-btn'); | |
| // State | |
| let notes = JSON.parse(localStorage.getItem('nexus-notes')) || []; | |
| let currentNoteId = null; | |
| let sortByDate = true; | |
| let darkMode = localStorage.getItem('darkMode') === 'true'; | |
| let currentFormat = null; | |
| let selectionStart = 0; | |
| let selectionEnd = 0; | |
| let isMobile = window.innerWidth < 768; | |
| let previewVisible = false; | |
| // Initialize | |
| if (darkMode) { | |
| document.documentElement.classList.add('dark'); | |
| } | |
| renderNoteList(); | |
| updateSortButtons(); | |
| // Detect mobile/desktop on resize | |
| window.addEventListener('resize', () => { | |
| const newMobileState = window.innerWidth < 768; | |
| if (newMobileState !== isMobile) { | |
| isMobile = newMobileState; | |
| if (currentNoteId) { | |
| openNote(currentNoteId); | |
| } | |
| } | |
| }); | |
| // Sample notes if empty (for demo purposes) | |
| if (notes.length === 0) { | |
| notes = [ | |
| { | |
| id: '1', | |
| title: 'Welcome to Nexus Notes', | |
| content: '# Welcome to Nexus Notes\n\nNexus Notes is a **markdown** note taking app with **real-time preview**.\n\n## Features\n\n- 📝 **Rich Markdown Editing**\n- 👁️ **Live Preview**\n- 🎨 **Dark/Light Mode**\n- 🔍 **Search Notes**\n- 📱 **Mobile Friendly**\n\nTry creating a new note!', | |
| createdAt: new Date().toISOString(), | |
| updatedAt: new Date().toISOString() | |
| }, | |
| { | |
| id: '2', | |
| title: 'Markdown Cheatsheet', | |
| content: '# Markdown Cheatsheet\n\n## Headers\n\n```\n# H1\n## H2\n### H3\n```\n\n## Text Styles\n\n```\n**bold**\n*italic*\n~~strikethrough~~\n```\n\n## Lists\n\n```\n- Unordered list\n - Sub item\n\n1. Ordered list\n 1. Sub item\n```', | |
| createdAt: new Date(Date.now() - 86400000).toISOString(), | |
| updatedAt: new Date(Date.now() - 86400000).toISOString() | |
| } | |
| ]; | |
| saveNotes(); | |
| renderNoteList(); | |
| } | |
| // Event Listeners | |
| darkModeToggle.addEventListener('click', toggleDarkMode); | |
| newNoteBtn.addEventListener('click', createNewNote); | |
| emptyNewNoteBtn.addEventListener('click', createNewNote); | |
| mobileNewNoteBtn.addEventListener('click', createNewNote); | |
| searchNotes.addEventListener('input', filterNotes); | |
| sortDateBtn.addEventListener('click', () => { | |
| sortByDate = true; | |
| renderNoteList(); | |
| updateSortButtons(); | |
| }); | |
| sortAlphaBtn.addEventListener('click', () => { | |
| sortByDate = false; | |
| renderNoteList(); | |
| updateSortButtons(); | |
| }); | |
| // Desktop Editor Events | |
| saveNoteBtn.addEventListener('click', saveCurrentNote); | |
| deleteNoteBtn.addEventListener('click', deleteCurrentNote); | |
| // Mobile Editor Events | |
| mobileSaveNoteBtn.addEventListener('click', saveCurrentNote); | |
| mobileDeleteNoteBtn.addEventListener('click', deleteCurrentNote); | |
| mobileNotesBtn.addEventListener('click', () => { | |
| if (currentNoteId) { | |
| // If already viewing a note, close it to show the list | |
| closeCurrentNote(); | |
| } else { | |
| // Show mobile sidebar (in a real app you'd implement this) | |
| alert('Show mobile sidebar with notes list'); | |
| } | |
| }); | |
| mobilePreviewToggle.addEventListener('click', togglePreviewPane); | |
| writeTab.addEventListener('click', showWritePane); | |
| previewTab.addEventListener('click', showPreviewPane); | |
| // Format buttons | |
| formatButtons.forEach(button => { | |
| button.addEventListener('click', function() { | |
| currentFormat = this.dataset.format; | |
| showFormatModal(); | |
| }); | |
| }); | |
| // Markdown modal events | |
| cancelMdModal.addEventListener('click', hideMdModal); | |
| closeMdModal.addEventListener('click', hideMdModal); | |
| insertMdElement.addEventListener('click', insertFormatting); | |
| // Auto-save when typing | |
| let saveTimeout; | |
| function setupAutoSave(element) { | |
| element.addEventListener('input', () => { | |
| updateWordCount(); | |
| updatePreview(); | |
| clearTimeout(saveTimeout); | |
| saveTimeout = setTimeout(saveCurrentNote, 2000); | |
| }); | |
| } | |
| setupAutoSave(noteContent); | |
| setupAutoSave(mobileNoteContent); | |
| // Save selection | |
| noteContent.addEventListener('click', saveSelection); | |
| noteContent.addEventListener('keyup', saveSelection); | |
| mobileNoteContent.addEventListener('click', saveSelection); | |
| mobileNoteContent.addEventListener('keyup', saveSelection); | |
| // Functions | |
| function toggleDarkMode() { | |
| darkMode = !darkMode; | |
| localStorage.setItem('darkMode', darkMode); | |
| if (darkMode) { | |
| document.documentElement.classList.add('dark'); | |
| } else { | |
| document.documentElement.classList.remove('dark'); | |
| } | |
| } | |
| function updateSortButtons() { | |
| if (sortByDate) { | |
| sortDateBtn.classList.remove('text-gray-500', 'dark:text-gray-400'); | |
| sortDateBtn.classList.add('text-primary-500', 'dark:text-primary-400'); | |
| sortAlphaBtn.classList.remove('text-primary-500', 'dark:text-primary-400'); | |
| sortAlphaBtn.classList.add('text-gray-500', 'dark:text-gray-400'); | |
| } else { | |
| sortAlphaBtn.classList.remove('text-gray-500', 'dark:text-gray-400'); | |
| sortAlphaBtn.classList.add('text-primary-500', 'dark:text-primary-400'); | |
| sortDateBtn.classList.remove('text-primary-500', 'dark:text-primary-400'); | |
| sortDateBtn.classList.add('text-gray-500', 'dark:text-gray-400'); | |
| } | |
| } | |
| function createNewNote() { | |
| const newNote = { | |
| id: Date.now().toString(), | |
| title: 'Untitled Note', | |
| content: '', | |
| createdAt: new Date().toISOString(), | |
| updatedAt: new Date().toISOString() | |
| }; | |
| notes.unshift(newNote); | |
| saveNotes(); | |
| renderNoteList(); | |
| // Open the new note | |
| openNote(newNote.id); | |
| // Focus on title field | |
| setTimeout(() => { | |
| if (isMobile) { | |
| mobileNoteTitle.focus(); | |
| mobileNoteTitle.select(); | |
| } else { | |
| noteTitle.focus(); | |
| noteTitle.select(); | |
| } | |
| }, 100); | |
| } | |
| function saveNotes() { | |
| localStorage.setItem('nexus-notes', JSON.stringify(notes)); | |
| } | |
| function renderNoteList() { | |
| let filteredNotes = getFilteredNotes(); | |
| if (filteredNotes.length === 0) { | |
| noteList.innerHTML = ` | |
| <div class="p-4 text-center text-gray-500 dark:text-gray-400"> | |
| ${searchNotes.value ? 'No notes found matching your search.' : 'No notes yet. Create your first note!'} | |
| </div> | |
| `; | |
| return; | |
| } | |
| noteList.innerHTML = filteredNotes.map(note => createNoteCardHTML(note)).join(''); | |
| } | |
| function getFilteredNotes() { | |
| let filteredNotes = [...notes]; | |
| // Filter by search | |
| const searchTerm = searchNotes.value.toLowerCase(); | |
| if (searchTerm) { | |
| filteredNotes = filteredNotes.filter(note => | |
| note.title.toLowerCase().includes(searchTerm) || | |
| note.content.toLowerCase().includes(searchTerm) | |
| ); | |
| } | |
| // Sort | |
| if (sortByDate) { | |
| filteredNotes.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)); | |
| } else { | |
| filteredNotes.sort((a, b) => a.title.localeCompare(b.title)); | |
| } | |
| return filteredNotes; | |
| } | |
| function createNoteCardHTML(note) { | |
| const isActive = currentNoteId === note.id; | |
| return ` | |
| <div class="note-card cursor-pointer rounded-lg transition ${isActive ? 'bg-primary-50 dark:bg-gray-700 border border-primary-200 dark:border-gray-600' : 'bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 border border-gray-100 dark:border-gray-700'}" | |
| data-id="${note.id}" onclick="openNote('${note.id}')"> | |
| <div class="p-3"> | |
| <h3 class="font-medium truncate ${isActive ? 'text-primary-600 dark:text-primary-400' : 'text-gray-800 dark:text-gray-200'}">${note.title || 'Untitled Note'}</h3> | |
| <p class="text-sm ${isActive ? 'text-primary-500 dark:text-primary-300' : 'text-gray-500 dark:text-gray-400'} truncate mt-1">${note.content.substring(0, 100)}${note.content.length > 100 ? '...' : ''}</p> | |
| <div class="flex justify-between items-center mt-2 text-xs ${isActive ? 'text-primary-400 dark:text-primary-300' : 'text-gray-400 dark:text-gray-500'}"> | |
| <span>${formatDate(note.updatedAt)}</span> | |
| <span>${countWords(note.content)} words</span> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| function openNote(id) { | |
| const note = notes.find(n => n.id === id); | |
| if (!note) return; | |
| currentNoteId = id; | |
| if (isMobile) { | |
| // Mobile view - show editor in single pane mode | |
| emptyState.classList.add('hidden'); | |
| mobileNoteEditor.classList.remove('hidden'); | |
| editorPane.classList.add('hidden'); | |
| previewPane.classList.add('hidden'); | |
| mobileNoteTitle.value = note.title; | |
| mobileNoteContent.value = note.content; | |
| updateWordCount(); | |
| updatePreview(); | |
| // Show write pane by default on mobile | |
| showWritePane(); | |
| } else { | |
| // Desktop view - show split pane editor/preview | |
| emptyState.classList.add('hidden'); | |
| editorPane.classList.remove('hidden'); | |
| previewPane.classList.remove('hidden'); | |
| mobileNoteEditor.classList.add('hidden'); | |
| noteTitle.value = note.title; | |
| noteContent.value = note.content; | |
| updateWordCount(); | |
| updatePreview(); | |
| lastSaved.textContent = `Last saved: ${formatDate(note.updatedAt)}`; | |
| } | |
| // Highlight active note in list | |
| highlightActiveNote(); | |
| } | |
| function closeCurrentNote() { | |
| currentNoteId = null; | |
| if (isMobile) { | |
| mobileNoteEditor.classList.add('hidden'); | |
| emptyState.classList.remove('hidden'); | |
| } else { | |
| editorPane.classList.add('hidden'); | |
| previewPane.classList.add('hidden'); | |
| emptyState.classList.remove('hidden'); | |
| } | |
| highlightActiveNote(); | |
| } | |
| function highlightActiveNote() { | |
| document.querySelectorAll('.note-card').forEach(card => { | |
| if (card.dataset.id === currentNoteId) { | |
| card.classList.add('bg-primary-50', 'dark:bg-gray-700', 'border-primary-200', 'dark:border-gray-600'); | |
| card.classList.remove('hover:bg-gray-50', 'dark:hover:bg-gray-700', 'border-gray-100', 'dark:border-gray-700'); | |
| card.querySelector('h3').classList.add('text-primary-600', 'dark:text-primary-400'); | |
| card.querySelector('p').classList.add('text-primary-500', 'dark:text-primary-300'); | |
| card.querySelector('div').classList.add('text-primary-400', 'dark:text-primary-300'); | |
| } else { | |
| card.classList.remove('bg-primary-50', 'dark:bg-gray-700', 'border-primary-200', 'dark:border-gray-600'); | |
| card.classList.add('hover:bg-gray-50', 'dark:hover:bg-gray-700', 'border-gray-100', 'dark:border-gray-700'); | |
| card.querySelector('h3').classList.remove('text-primary-600', 'dark:text-primary-400'); | |
| card.querySelector('p').classList.remove('text-primary-500', 'dark:text-primary-300'); | |
| card.querySelector('div').classList.remove('text-primary-400', 'dark:text-primary-300'); | |
| } | |
| }); | |
| } | |
| function saveCurrentNote() { | |
| if (!currentNoteId) return; | |
| const noteIndex = notes.findIndex(n => n.id === currentNoteId); | |
| if (noteIndex === -1) return; | |
| // Get values from the appropriate editor based on view | |
| const title = isMobile ? mobileNoteTitle.value : noteTitle.value; | |
| const content = isMobile ? mobileNoteContent.value : noteContent.value; | |
| notes[noteIndex] = { | |
| ...notes[noteIndex], | |
| title: title, | |
| content: content, | |
| updatedAt: new Date().toISOString() | |
| }; | |
| saveNotes(); | |
| renderNoteList(); | |
| if (isMobile) { | |
| // Update mobile view | |
| } else { | |
| lastSaved.textContent = `Last saved: ${formatDate(new Date().toISOString())}`; | |
| } | |
| // Update preview | |
| updatePreview(); | |
| } | |
| function deleteCurrentNote() { | |
| if (!currentNoteId) return; | |
| if (confirm('Are you sure you want to delete this note?')) { | |
| notes = notes.filter(note => note.id !== currentNoteId); | |
| saveNotes(); | |
| renderNoteList(); | |
| closeCurrentNote(); | |
| } | |
| } | |
| function filterNotes() { | |
| renderNoteList(); | |
| } | |
| function updateWordCount() { | |
| const text = isMobile ? mobileNoteContent.value : noteContent.value; | |
| wordCount.querySelector('span').textContent = `${countWords(text)} words`; | |
| } | |
| function countWords(text) { | |
| return text.trim() === '' ? 0 : text.trim().split(/\s+/).length; | |
| } | |
| function formatDate(dateString) { | |
| const date = new Date(dateString); | |
| const now = new Date(); | |
| if (date.toDateString() === now.toDateString()) { | |
| return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); | |
| } | |
| if (date.getFullYear() === now.getFullYear()) { | |
| return date.toLocaleDateString([], { month: 'short', day: 'numeric' }); | |
| } | |
| return date.toLocaleDateString([], { year: 'numeric', month: 'short', day: 'numeric' }); | |
| } | |
| function showWritePane() { | |
| if (isMobile) { | |
| writePane.classList.remove('hidden'); | |
| mobilePreviewPane.classList.add('hidden'); | |
| writeTab.classList.add('text-primary-500', 'dark:text-primary-400'); | |
| writeTab.classList.remove('text-gray-500', 'dark:text-gray-400'); | |
| previewTab.classList.remove('text-primary-500', 'dark:text-primary-400'); | |
| previewTab.classList.add('text-gray-500', 'dark:text-gray-400'); | |
| tabIndicator.style.transform = 'translateX(0)'; | |
| } | |
| } | |
| function showPreviewPane() { | |
| if (isMobile) { | |
| writePane.classList.add('hidden'); | |
| mobilePreviewPane.classList.remove('hidden'); | |
| previewTab.classList.add('text-primary-500', 'dark:text-primary-400'); | |
| previewTab.classList.remove('text-gray-500', 'dark:text-gray-400'); | |
| writeTab.classList.remove('text-primary-500', 'dark:text-primary-400'); | |
| writeTab.classList.add('text-gray-500', 'dark:text-gray-400'); | |
| tabIndicator.style.transform = 'translateX(100%)'; | |
| updatePreview(); | |
| } | |
| } | |
| function togglePreviewPane() { | |
| previewVisible = !previewVisible; | |
| if (previewVisible) { | |
| previewPane.classList.remove('hidden'); | |
| mobilePreviewToggle.classList.add('text-primary-500', 'dark:text-primary-400'); | |
| } else { | |
| previewPane.classList.add('hidden'); | |
| mobilePreviewToggle.classList.remove('text-primary-500', 'dark:text-primary-400'); | |
| } | |
| } | |
| function renderMarkdown(text) { | |
| // Simple markdown rendering | |
| return text | |
| .replace(/^# (.*$)/gm, '<h1>$1</h1>') | |
| .replace(/^## (.*$)/gm, '<h2>$1</h2>') | |
| .replace(/^### (.*$)/gm, '<h3>$1</h3>') | |
| .replace(/^#### (.*$)/gm, '<h4>$1</h4>') | |
| .replace(/^##### (.*$)/gm, '<h5>$1</h5>') | |
| .replace(/^###### (.*$)/gm, '<h6>$1</h6>') | |
| .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>') | |
| .replace(/\*(.*?)\*/g, '<em>$1</em>') | |
| .replace(/~~(.*?)~~/g, '<del>$1</del>') | |
| .replace(/`{3}([^`]+)`{3}/g, '<pre><code>$1</code></pre>') | |
| .replace(/`([^`]+)`/g, '<code>$1</code>') | |
| .replace(/!\[(.*?)\]\((.*?)\)/g, '<img alt="$1" src="$2">') | |
| .replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2">$1</a>') | |
| .replace(/^>\s(.*$)/gm, '<blockquote>$1</blockquote>') | |
| .replace(/^\s*\n\*/gm, '<ul>\n*') | |
| .replace(/^(\*.+)\s*\n([^\*])/gm, '$1\n</ul>\n\n$2') | |
| .replace(/^\*(.+)/gm, '<li>$1</li>') | |
| .replace(/^\s*\n\d+\./gm, '<ol>\n1.') | |
| .replace(/^(\d\..+)\s*\n([^\d\.])/gm, '$1\n</ol>\n\n$2') | |
| .replace(/^\d\.(.+)/gm, '<li>$1</li>') | |
| .replace(/\n/g, '<br>'); | |
| } | |
| function updatePreview() { | |
| const content = isMobile ? mobileNoteContent.value : noteContent.value; | |
| if (isMobile && !mobilePreviewPane.classList.contains('hidden')) { | |
| mobilePreviewPane.innerHTML = renderMarkdown(content); | |
| } | |
| if (!isMobile) { | |
| notePreviewContent.innerHTML = renderMarkdown(content); | |
| } | |
| } | |
| function showFormatModal() { | |
| // Save current selection | |
| saveSelection(); | |
| mdModalTitle.textContent = getModalTitle(); | |
| mdModalContent.innerHTML = getModalContent(); | |
| mdModal.classList.remove('hidden'); | |
| // Focus on first input if exists | |
| const firstInput = mdModalContent.querySelector('input'); | |
| if (firstInput) { | |
| firstInput.focus(); | |
| } | |
| } | |
| function hideMdModal() { | |
| mdModal.classList.add('hidden'); | |
| } | |
| function getModalTitle() { | |
| switch(currentFormat) { | |
| case 'bold': return 'Insert Bold Text'; | |
| case 'italic': return 'Insert Italic Text'; | |
| case 'header1': return 'Insert Heading 1'; | |
| case 'header2': return 'Insert Heading 2'; | |
| case 'header3': return 'Insert Heading 3'; | |
| case 'quote': return 'Insert Blockquote'; | |
| case 'code': return 'Insert Code'; | |
| case 'link': return 'Insert Link'; | |
| case 'image': return 'Insert Image'; | |
| case 'list': return 'Insert List'; | |
| case 'olist': return 'Insert Ordered List'; | |
| default: return 'Insert Element'; | |
| } | |
| } | |
| function getModalContent() { | |
| switch(currentFormat) { | |
| case 'bold': | |
| case 'italic': | |
| case 'header1': | |
| case 'header2': | |
| case 'header3': | |
| case 'quote': | |
| return '<input type="text" id="mdInputText" class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200" placeholder="Enter text" value="' + getSelectedText() + '">'; | |
| case 'code': | |
| return ` | |
| <div class="space-y-2"> | |
| <input type="text" id="mdInputCode" class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200" placeholder="Enter code"> | |
| <div class="flex items-center"> | |
| <input type="checkbox" id="mdInlineCode" class="mr-2"> | |
| <label for="mdInlineCode" class="text-sm text-gray-700 dark:text-gray-300">Inline code</label> | |
| </div> | |
| </div> | |
| `; | |
| case 'link': | |
| return ` | |
| <div class="space-y-2"> | |
| <input type="text" id="mdLinkText" class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200" placeholder="Link text" value="${getSelectedText()}"> | |
| <input type="text" id="mdLinkUrl" class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200" placeholder="URL (https://)"> | |
| </div> | |
| `; | |
| case 'image': | |
| return ` | |
| <div class="space-y-2"> | |
| <input type="text" id="mdImageAlt" class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200" placeholder="Alternative text"> | |
| <input type="text" id="mdImageUrl" class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200" placeholder="Image URL (https://)"> | |
| </div> | |
| `; | |
| case 'list': | |
| case 'olist': | |
| return ` | |
| <div class="space-y-2"> | |
| <textarea id="mdListItems" class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200" rows="4" placeholder="Enter list items, one per line"></textarea> | |
| </div> | |
| `; | |
| default: | |
| return '<p>Select a formatting option</p>'; | |
| } | |
| } | |
| function insertFormatting() { | |
| const activeTextarea = isMobile ? mobileNoteContent : noteContent; | |
| let content = activeTextarea.value; | |
| let textBefore = content.substring(0, selectionStart); | |
| let textAfter = content.substring(selectionEnd); | |
| let selectedText = content.substring(selectionStart, selectionEnd); | |
| let insertText = ''; | |
| switch(currentFormat) { | |
| case 'bold': | |
| const boldText = document.getElementById('mdInputText').value; | |
| insertText = `**${boldText}**`; | |
| break; | |
| case 'italic': | |
| const italicText = document.getElementById('mdInputText').value; | |
| insertText = `*${italicText}*`; | |
| break; | |
| case 'header1': | |
| const header1Text = document.getElementById('mdInputText').value; | |
| insertText = `# ${header1Text}`; | |
| break; | |
| case 'header2': | |
| const header2Text = document.getElementById('mdInputText').value; | |
| insertText = `## ${header2Text}`; | |
| break; | |
| case 'header3': | |
| const header3Text = document.getElementById('mdInputText').value; | |
| insertText = `### ${header3Text}`; | |
| break; | |
| case 'quote': | |
| const quoteText = document.getElementById('mdInputText').value; | |
| insertText = `> ${quoteText}`; | |
| break; | |
| case 'code': | |
| const codeText = document.getElementById('mdInputCode').value; | |
| const isInline = document.getElementById('mdInlineCode').checked; | |
| if (isInline) { | |
| insertText = `\`${codeText}\``; | |
| } else { | |
| insertText = `\`\`\`\n${codeText}\n\`\`\``; | |
| } | |
| break; | |
| case 'link': | |
| const linkText = document.getElementById('mdLinkText').value; | |
| const linkUrl = document.getElementById('mdLinkUrl').value; | |
| insertText = `[${linkText}](${linkUrl})`; | |
| break; | |
| case 'image': | |
| const altText = document.getElementById('mdImageAlt').value; | |
| const imageUrl = document.getElementById('mdImageUrl').value; | |
| insertText = ``; | |
| break; | |
| case 'list': | |
| const listItems = document.getElementById('mdListItems').value.split('\n'); | |
| insertText = listItems.filter(item => item.trim()).map(item => `- ${item}`).join('\n'); | |
| break; | |
| case 'olist': | |
| const olistItems = document.getElementById('mdListItems').value.split('\n'); | |
| insertText = olistItems.filter((item, index) => item.trim()).map((item, index) => `${index + 1}. ${item}`).join('\n'); | |
| break; | |
| } | |
| // Update content | |
| activeTextarea.value = textBefore + insertText + textAfter; | |
| // Set cursor position after inserted text | |
| const newCursorPos = selectionStart + insertText.length; | |
| activeTextarea.setSelectionRange(newCursorPos, newCursorPos); | |
| activeTextarea.focus(); | |
| // Update preview and save | |
| updatePreview(); | |
| // Auto-save after formatting | |
| saveCurrentNote(); | |
| hideMdModal(); | |
| } | |
| function saveSelection() { | |
| const activeTextarea = isMobile ? mobileNoteContent : noteContent; | |
| selectionStart = activeTextarea.selectionStart; | |
| selectionEnd = activeTextarea.selectionEnd; | |
| } | |
| function getSelectedText() { | |
| const activeTextarea = isMobile ? mobileNoteContent : noteContent; | |
| return activeTextarea.value.substring(selectionStart, selectionEnd); | |
| } | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=yasarefe/nexus-notes" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |