javascriptノートアプリの開発

index.html

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ノート管理</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
    <link rel="icon" type="image/svg+xml" href="favicon.svg">
    <link rel="stylesheet" href="style.css">
</head>

<body>
    <div class="app-container">
        <!-- Header -->
        <header class="app-header">
            <div class="header-content">
                <h1 class="app-title">
                    <svg class="title-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                        <polyline points="14 2 14 8 20 8"></polyline>
                        <line x1="16" y1="13" x2="8" y2="13"></line>
                        <line x1="16" y1="17" x2="8" y2="17"></line>
                        <polyline points="10 9 9 9 8 9"></polyline>
                    </svg>
                    ノート管理
                </h1>
                <div class="header-actions">
                    <button id="viewModeBtn" class="compact-action-btn" title="閲覧モード (Esc)">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <path d="M15 3h6v6"></path>
                            <path d="M9 21H3v-6"></path>
                            <path d="M21 3l-7 7"></path>
                            <path d="M3 21l7-7"></path>
                        </svg>
                    </button>
                    <button id="editModeBtn" class="compact-action-btn" title="編集モード">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
                            <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
                        </svg>
                    </button>
                    <button id="newNoteBtn" class="compact-action-btn" title="新規ノート">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                            <polyline points="14 2 14 8 20 8"></polyline>
                        </svg>
                        <span class="plus-icon">+</span>
                    </button>
                    <button id="newFolderBtn" class="compact-action-btn" title="新規フォルダ">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z">
                            </path>
                        </svg>
                        <span class="plus-icon">+</span>
                    </button>
                    <div class="toolbar-separator"></div>
                    <div class="formatting-toolbar">

                        <button id="heading2Btn" class="text-btn" title="見出し2">
                            <span>h2</span>
                        </button>
                        <button id="heading3Btn" class="text-btn" title="見出し3">
                            <span>h3</span>
                        </button>
                        <button id="heading4Btn" class="text-btn" title="見出し4">
                            <span>h4</span>
                        </button>
                        <div class="toolbar-separator"></div>
                        <button id="grayTextBtn" class="text-btn" title="グレー文字">
                            <span>灰</span>
                        </button>
                        <button id="redTextBtn" class="text-btn" title="赤文字">
                            <span>赤</span>
                        </button>
                        <button id="blackTextBtn" class="text-btn" title="黒文字">
                            <span>黒</span>
                        </button>
                        <button id="boldTextBtn" class="text-btn" title="太字">
                            <span>B</span>
                        </button>
                        <div class="toolbar-separator"></div>
                        <button id="insertBoxBtn" class="icon-btn" title="グレーボックス">
                            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
                            </svg>
                        </button>
                        <button id="insertBoxBlueBtn" class="icon-btn" title="青ボックス">
                            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <rect x="3" y="3" width="18" height="18" rx="2" ry="2" fill="rgba(99, 102, 241, 0.2)">
                                </rect>
                            </svg>
                        </button>
                        <button id="insertGrayBoxBtn" class="icon-btn" title="グレーボックス2">
                            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <rect x="3" y="3" width="18" height="18" rx="2" ry="2" fill="rgba(250, 250, 250, 0.5)">
                                </rect>
                                <text x="12" y="16" font-size="10" text-anchor="middle" fill="currentColor"
                                    font-weight="bold">2</text>
                            </svg>
                        </button>
                        <button id="insertCheckHeadingBtn" class="icon-btn" title="チェック見出し">
                            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <polyline points="20 6 9 17 4 12"></polyline>
                            </svg>
                        </button>
                        <button id="insertEmptyLineBtn" class="icon-btn" title="空行挿入">
                            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <path d="M8 12h8m-8 4h8m-8-8h8"></path>
                                <polyline points="17 9 20 12 17 15"></polyline>
                            </svg>
                        </button>
                        <div class="toolbar-separator"></div>
                        <button id="insertLinkBtn" class="icon-btn" title="リンク挿入">
                            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
                                <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
                            </svg>
                        </button>
                        <button id="insertImageBtn" class="icon-btn" title="画像挿入">
                            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
                                <circle cx="8.5" cy="8.5" r="1.5"></circle>
                                <polyline points="21 15 16 10 5 21"></polyline>
                            </svg>
                        </button>
                        <input type="file" id="imageInput" accept="image/*" style="display: none;">
                        <div class="toolbar-separator"></div>
                        <select id="folderSelect" class="folder-select-header">
                            <option value="">フォルダ...</option>
                        </select>
                        <div class="toolbar-separator"></div>
                        <button id="codeEditorBtn" class="icon-btn" title="HTMLコード編集">
                            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <polyline points="16 18 22 12 16 6"></polyline>
                                <polyline points="8 6 2 12 8 18"></polyline>
                            </svg>
                        </button>
                        <button id="viewCodeBtn" class="text-icon-btn" title="選択範囲をコード表示">
                            <span>🧩</span>
                        </button>
                        <button id="paragraphBtn" class="icon-btn" title="段落にする">
                            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <path d="M13 4h7"></path>
                                <path d="M13 8h7"></path>
                                <path d="M13 12h7"></path>
                                <path d="M3 4h6"></path>
                                <path d="M3 8h6"></path>
                                <path d="M3 12h6"></path>
                                <path d="M3 16h16"></path>
                                <path d="M3 20h16"></path>
                            </svg>
                        </button>
                        <button id="clearFormatBtn" class="icon-btn" title="書式をクリア">
                            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <polyline points="4 7 4 4 20 4 20 7"></polyline>
                                <line x1="9" y1="20" x2="15" y2="20"></line>
                                <line x1="12" y1="4" x2="12" y2="20"></line>
                            </svg>
                        </button>

                    </div>
                    <div class="export-dropdown">
                        <button id="exportBtn" class="icon-btn" title="エクスポート">
                            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
                                <polyline points="7 10 12 15 17 10"></polyline>
                                <line x1="12" y1="15" x2="12" y2="3"></line>
                            </svg>
                        </button>
                        <div id="exportMenu" class="export-menu hidden">
                            <button id="exportMarkdownBtn" class="export-menu-item">
                                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                    <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                                    <polyline points="14 2 14 8 20 8"></polyline>
                                </svg>
                                Markdownでエクスポート
                            </button>
                            <button id="exportHTMLBtn" class="export-menu-item">
                                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                    <polyline points="16 18 22 12 16 6"></polyline>
                                    <polyline points="8 6 2 12 8 18"></polyline>
                                </svg>
                                HTMLでエクスポート
                            </button>
                            <button id="exportPDFBtn" class="export-menu-item">
                                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                    <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                                    <polyline points="14 2 14 8 20 8"></polyline>
                                    <line x1="9" y1="15" x2="15" y2="15"></line>
                                </svg>
                                PDFでエクスポート
                            </button>
                            <div class="export-menu-divider"></div>
                            <button id="exportAllBtn" class="export-menu-item">
                                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                    <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
                                    <polyline points="17 8 12 3 7 8"></polyline>
                                    <line x1="12" y1="3" x2="12" y2="15"></line>
                                </svg>
                                全ノートをZIPでエクスポート
                            </button>
                        </div>
                    </div>
                    <button id="themeToggle" class="icon-btn" title="テーマ切替">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <circle cx="12" cy="12" r="5"></circle>
                            <line x1="12" y1="1" x2="12" y2="3"></line>
                            <line x1="12" y1="21" x2="12" y2="23"></line>
                            <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
                            <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
                            <line x1="1" y1="12" x2="3" y2="12"></line>
                            <line x1="21" y1="12" x2="23" y2="12"></line>
                            <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
                            <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
                        </svg>
                    </button>
                </div>
            </div>
        </header>

        <div class="main-layout">
            <!-- Sidebar -->
            <aside class="sidebar" id="sidebar">
                <div class="notes-list" id="notesList">
                    <!-- Notes will be dynamically added here -->
                </div>
                <div class="sidebar-resizer" id="sidebarResizer"></div>
            </aside>

            <!-- Main Content -->
            <main class="main-content">
                <button id="exitViewBtn" class="exit-view-btn hidden" title="閲覧モード終了 (Esc)">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                        <line x1="18" y1="6" x2="6" y2="18"></line>
                        <line x1="6" y1="6" x2="18" y2="18"></line>
                    </svg>
                </button>
                <div class="editor-container">
                    <div class="editor-header">
                        <div class="content-centered-wrapper">
                            <input type="text" id="noteTitle" class="note-title-input" placeholder="タイトルを入力...">
                        </div>
                    </div>
                    <div id="editor" class="editor" contenteditable="true" data-placeholder="ここに入力を開始..."></div>
                </div>
                <div class="empty-state" id="emptyState">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                        <polyline points="14 2 14 8 20 8"></polyline>
                    </svg>
                    <h2>ノートを選択するか、新規作成してください</h2>
                    <p>左のサイドバーから既存のノートを選択するか、「新規ノート」ボタンをクリックして新しいノートを作成できます。</p>
                </div>
            </main>
        </div>
    </div>

    <!-- Floating Edit Toolbar -->
    <div id="floatingToolbar" class="floating-toolbar hidden">
        <button id="floatingUndoBtn" class="floating-btn" title="元に戻す">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                <path d="M3 7v6h6"></path>
                <path d="M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"></path>
            </svg>
        </button>
        <button id="floatingRedoBtn" class="floating-btn" title="やり直す">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                <path d="M21 7v6h-6"></path>
                <path d="M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3l3 3.7"></path>
            </svg>
        </button>
        <div class="floating-separator"></div>
        <button id="floatingMoveUpBtn" class="floating-btn" title="上に移動">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                <polyline points="18 15 12 9 6 15"></polyline>
            </svg>
        </button>
        <button id="floatingMoveDownBtn" class="floating-btn" title="下に移動">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                <polyline points="6 9 12 15 18 9"></polyline>
            </svg>
        </button>
    </div>

    <!-- Load JavaScript modules in order -->
    <script src="js/database.js"></script>
    <script src="js/image.js"></script>
    <script src="js/ui.js"></script>
    <script src="js/editor.js"></script>
    <script src="js/export.js"></script>
    <script src="js/main.js"></script>
</body>

</html>

app.js

// ===== IndexedDB Setup =====
const DB_NAME = 'NotesDB';
const DB_VERSION = 3;
const STORE_NAME = 'notes';
const FOLDER_STORE = 'folders';
const DEFAULT_FOLDER_ID = 1;

let db;
let currentNoteId = null;
let currentFolderId = null;
let saveTimeout = null;
let collapsedFolders = new Set();

// Initialize IndexedDB
function initDB() {
    return new Promise((resolve, reject) => {
        const request = indexedDB.open(DB_NAME, DB_VERSION);

        request.onerror = () => reject(request.error);
        request.onsuccess = () => {
            db = request.result;
            resolve(db);
        };

        request.onupgradeneeded = (event) => {
            db = event.target.result;
            const oldVersion = event.oldVersion;

            // Notes store
            if (!db.objectStoreNames.contains(STORE_NAME)) {
                const notesStore = db.createObjectStore(STORE_NAME, { keyPath: 'id', autoIncrement: true });
                notesStore.createIndex('folderId', 'folderId', { unique: false });
                notesStore.createIndex('updatedAt', 'updatedAt', { unique: false }); // Changed from timestamp
            } else if (oldVersion < 3) { // Upgrade existing notes store for version 3
                const transaction = event.target.transaction;
                const notesStore = transaction.objectStore(STORE_NAME);
                if (!notesStore.indexNames.contains('folderId')) {
                    notesStore.createIndex('folderId', 'folderId', { unique: false });
                }
                // If 'timestamp' index exists, it might need to be removed or replaced,
                // but for simplicity, we'll just add 'updatedAt' if it doesn't exist.
                if (!notesStore.indexNames.contains('updatedAt')) {
                    notesStore.createIndex('updatedAt', 'updatedAt', { unique: false });
                }
            }

            // Folders store
            if (!db.objectStoreNames.contains(FOLDER_STORE)) {
                const folderStore = db.createObjectStore(FOLDER_STORE, { keyPath: 'id', autoIncrement: true });
                folderStore.createIndex('timestamp', 'timestamp', { unique: false });

                // Add default folder
                folderStore.add({
                    id: DEFAULT_FOLDER_ID,
                    name: '未分類',
                    timestamp: Date.now()
                });
            }

            // Images store (new in version 3)
            if (!db.objectStoreNames.contains('images')) {
                const imagesStore = db.createObjectStore('images', { keyPath: 'id', autoIncrement: true });
                imagesStore.createIndex('noteId', 'noteId', { unique: false });
            }
        };
    });
}

// ===== CRUD Operations =====

// Create a new note
async function createNote(folderId = DEFAULT_FOLDER_ID) {
    const note = {
        title: '新しいノート',
        content: '',
        folderId: folderId,
        timestamp: Date.now()
    };

    return new Promise((resolve, reject) => {
        const transaction = db.transaction([STORE_NAME], 'readwrite');
        const objectStore = transaction.objectStore(STORE_NAME);
        const request = objectStore.add(note);

        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

// Get all notes
async function getAllNotes() {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction([STORE_NAME], 'readonly');
        const objectStore = transaction.objectStore(STORE_NAME);
        const request = objectStore.getAll();

        request.onsuccess = () => {
            const notes = request.result;
            notes.sort((a, b) => b.timestamp - a.timestamp);
            resolve(notes);
        };
        request.onerror = () => reject(request.error);
    });
}

// Get a single note by ID
async function getNote(id) {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction([STORE_NAME], 'readonly');
        const objectStore = transaction.objectStore(STORE_NAME);
        const request = objectStore.get(id);

        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

// Update a note
async function updateNote(id, updates) {
    const note = await getNote(id);
    if (!note) return;

    const updatedNote = { ...note, ...updates, timestamp: Date.now() };

    return new Promise((resolve, reject) => {
        const transaction = db.transaction([STORE_NAME], 'readwrite');
        const objectStore = transaction.objectStore(STORE_NAME);
        const request = objectStore.put(updatedNote);

        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

// Delete a note
async function deleteNote(id) {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction([STORE_NAME], 'readwrite');
        const objectStore = transaction.objectStore(STORE_NAME);
        const request = objectStore.delete(id);

        request.onsuccess = () => resolve();
        request.onerror = () => reject(request.error);
    });
}

// ===== Folder CRUD Operations =====

// Create a new folder
async function createFolder(name = '新しいフォルダ') {
    const folder = {
        name: name,
        timestamp: Date.now()
    };

    return new Promise((resolve, reject) => {
        const transaction = db.transaction([FOLDER_STORE], 'readwrite');
        const objectStore = transaction.objectStore(FOLDER_STORE);
        const request = objectStore.add(folder);

        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

// Get all folders
async function getAllFolders() {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction([FOLDER_STORE], 'readonly');
        const objectStore = transaction.objectStore(FOLDER_STORE);
        const request = objectStore.getAll();

        request.onsuccess = () => {
            const folders = request.result;
            folders.sort((a, b) => {
                if (a.id === DEFAULT_FOLDER_ID) return -1;
                if (b.id === DEFAULT_FOLDER_ID) return 1;
                return a.timestamp - b.timestamp;
            });
            resolve(folders);
        };
        request.onerror = () => reject(request.error);
    });
}

// Get folder by ID
async function getFolder(id) {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction([FOLDER_STORE], 'readonly');
        const objectStore = transaction.objectStore(FOLDER_STORE);
        const request = objectStore.get(id);

        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

// Update folder
async function updateFolder(id, updates) {
    const folder = await getFolder(id);
    if (!folder) return;

    const updatedFolder = { ...folder, ...updates, timestamp: Date.now() };

    return new Promise((resolve, reject) => {
        const transaction = db.transaction([FOLDER_STORE], 'readwrite');
        const objectStore = transaction.objectStore(FOLDER_STORE);
        const request = objectStore.put(updatedFolder);

        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

// Delete folder
async function deleteFolder(id) {
    if (id === DEFAULT_FOLDER_ID) {
        alert('デフォルトフォルダは削除できません。');
        return;
    }

    // Move all notes in this folder to default folder
    const notes = await getAllNotes();
    const notesInFolder = notes.filter(note => note.folderId === id);
    for (const note of notesInFolder) {
        await updateNote(note.id, { folderId: DEFAULT_FOLDER_ID });
    }

    return new Promise((resolve, reject) => {
        const transaction = db.transaction([FOLDER_STORE], 'readwrite');
        const objectStore = transaction.objectStore(FOLDER_STORE);
        const request = objectStore.delete(id);

        request.onsuccess = () => resolve();
        request.onerror = () => reject(request.error);
    });
}

// Get notes by folder
async function getNotesByFolder(folderId) {
    const allNotes = await getAllNotes();
    return allNotes.filter(note => (note.folderId || DEFAULT_FOLDER_ID) === folderId);
}

// ===== Image Storage Functions =====
function saveImage(blob, noteId) {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['images'], 'readwrite');
        const store = transaction.objectStore('images');
        const image = {
            blob: blob,
            noteId: noteId || currentNoteId,
            createdAt: new Date().toISOString()
        };
        const request = store.add(image);
        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

function getImage(id) {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['images'], 'readonly');
        const store = transaction.objectStore('images');
        const request = store.get(id);
        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

function deleteImagesByNoteId(noteId) {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['images'], 'readwrite');
        const store = transaction.objectStore('images');
        const index = store.index('noteId');
        const request = index.openCursor(IDBKeyRange.only(noteId));

        request.onsuccess = (event) => {
            const cursor = event.target.result;
            if (cursor) {
                cursor.delete();
                cursor.continue();
            } else {
                resolve();
            }
        };
        request.onerror = () => reject(request.error);
    });
}

// ===== UI Functions =====

// Render folders and notes list
async function renderFoldersAndNotes() {
    const notesList = document.getElementById('notesList');
    const folders = await getAllFolders();

    notesList.innerHTML = '';

    for (const folder of folders) {
        const folderElement = createFolderElement(folder);
        notesList.appendChild(folderElement);

        const notes = await getNotesByFolder(folder.id);
        const notesContainer = document.createElement('div');
        notesContainer.className = 'folder-notes';
        notesContainer.dataset.folderId = folder.id;

        if (collapsedFolders.has(folder.id)) {
            notesContainer.classList.add('collapsed');
        }

        notes.forEach(note => {
            const noteItem = createNoteElement(note);
            notesContainer.appendChild(noteItem);
        });

        notesList.appendChild(notesContainer);
    }
}

// Create folder element
function createFolderElement(folder) {
    const div = document.createElement('div');
    div.className = 'folder-item';
    div.dataset.id = folder.id;

    const isCollapsed = collapsedFolders.has(folder.id);
    const noteCount = 0; // Will be updated by CSS counter or manually

    div.innerHTML = `
        <div class="folder-item-header">
            <div class="folder-item-left">
                <svg class="folder-chevron ${isCollapsed ? 'collapsed' : ''}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                    <polyline points="6 9 12 15 18 9"></polyline>
                </svg>
                <svg class="folder-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                    <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
                </svg>
                <div class="folder-item-title">${escapeHtml(folder.name)}</div>
            </div>
            <div class="folder-item-actions">
                ${folder.id !== DEFAULT_FOLDER_ID ? `
                    <button class="note-action-btn rename" title="名前を変更">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
                            <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
                        </svg>
                    </button>
                    <button class="note-action-btn delete" title="削除">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <polyline points="3 6 5 6 21 6"></polyline>
                            <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
                        </svg>
                    </button>
                ` : ''}
            </div>
        </div>
    `;

    // Toggle collapse
    const header = div.querySelector('.folder-item-header');
    header.addEventListener('click', (e) => {
        if (!e.target.closest('.note-action-btn')) {
            toggleFolder(folder.id);
        }
    });

    // Rename button
    if (folder.id !== DEFAULT_FOLDER_ID) {
        div.querySelector('.rename').addEventListener('click', (e) => {
            e.stopPropagation();
            startFolderRename(div, folder.id);
        });

        // Delete button
        div.querySelector('.delete').addEventListener('click', async (e) => {
            e.stopPropagation();
            if (confirm('このフォルダを削除しますか?\n(中のノートは「未分類」に移動されます)')) {
                await deleteFolder(folder.id);
                await renderFoldersAndNotes();
            }
        });
    }

    return div;
}

// Toggle folder collapse
function toggleFolder(folderId) {
    if (collapsedFolders.has(folderId)) {
        collapsedFolders.delete(folderId);
    } else {
        collapsedFolders.add(folderId);
    }
    renderFoldersAndNotes();
}

// Start renaming a folder
function startFolderRename(folderElement, folderId) {
    const titleElement = folderElement.querySelector('.folder-item-title');
    const currentTitle = titleElement.textContent;

    const input = document.createElement('input');
    input.type = 'text';
    input.className = 'note-rename-input';
    input.value = currentTitle;

    titleElement.replaceWith(input);
    input.focus();
    input.select();

    const finishRename = async () => {
        const newTitle = input.value.trim() || '無題のフォルダ';
        await updateFolder(folderId, { name: newTitle });
        await renderFoldersAndNotes();
    };

    input.addEventListener('blur', finishRename);
    input.addEventListener('keydown', (e) => {
        if (e.key === 'Enter') {
            e.preventDefault();
            input.blur();
        } else if (e.key === 'Escape') {
            renderFoldersAndNotes();
        }
    });
}

// Render notes list (legacy - now using renderFoldersAndNotes)
async function renderNotesList() {
    await renderFoldersAndNotes();
}

// Create note element
function createNoteElement(note) {
    const div = document.createElement('div');
    div.className = 'note-item';
    if (currentNoteId === note.id) {
        div.classList.add('active');
    }
    div.dataset.id = note.id;

    const date = new Date(note.timestamp);
    const dateStr = date.toLocaleDateString('ja-JP', { month: 'short', day: 'numeric' });

    div.innerHTML = `
        <div class="note-item-header">
            <div class="note-item-title">${escapeHtml(note.title)}</div>
            <div class="note-item-actions">
                <button class="note-action-btn rename" title="名前を変更">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                        <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
                        <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
                    </svg>
                </button>
                <button class="note-action-btn delete" title="削除">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                        <polyline points="3 6 5 6 21 6"></polyline>
                        <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
                    </svg>
                </button>
            </div>
        </div>
        <div class="note-item-date">${dateStr}</div>
    `;

    // Click to select note
    div.addEventListener('click', (e) => {
        if (!e.target.closest('.note-action-btn')) {
            loadNote(note.id);
        }
    });

    // Rename button
    div.querySelector('.rename').addEventListener('click', (e) => {
        e.stopPropagation();
        startRename(div, note.id);
    });

    // Delete button
    div.querySelector('.delete').addEventListener('click', async (e) => {
        e.stopPropagation();
        if (confirm('このノートを削除しますか?')) {
            await deleteNote(note.id);
            if (currentNoteId === note.id) {
                currentNoteId = null;
                showEmptyState();
            }
            await renderNotesList();
        }
    });

    return div;
}

// Start renaming a note
function startRename(noteElement, noteId) {
    const titleElement = noteElement.querySelector('.note-item-title');
    const currentTitle = titleElement.textContent;

    const input = document.createElement('input');
    input.type = 'text';
    input.className = 'note-rename-input';
    input.value = currentTitle;

    titleElement.replaceWith(input);
    input.focus();
    input.select();

    const finishRename = async () => {
        const newTitle = input.value.trim() || '無題のノート';
        await updateNote(noteId, { title: newTitle });
        await renderNotesList();
        if (currentNoteId === noteId) {
            document.getElementById('noteTitle').value = newTitle;
        }
    };

    input.addEventListener('blur', finishRename);
    input.addEventListener('keydown', (e) => {
        if (e.key === 'Enter') {
            e.preventDefault();
            input.blur();
        } else if (e.key === 'Escape') {
            renderNotesList();
        }
    });
}

// Load a note into the editor
async function loadNote(noteId) {
    const note = await getNote(noteId);
    if (!note) return;

    currentNoteId = noteId;
    document.getElementById('noteTitle').value = note.title;

    const editor = document.getElementById('editor');
    editor.innerHTML = note.content;

    // Load images from IndexedDB
    const images = editor.querySelectorAll('img[data-image-id]');
    for (const img of images) {
        const imageId = parseInt(img.getAttribute('data-image-id'));
        try {
            const imageData = await getImage(imageId);
            if (imageData && imageData.blob) {
                const blobUrl = URL.createObjectURL(imageData.blob);
                img.src = blobUrl;
            }
        } catch (error) {
            console.error('Failed to load image:', error);
        }
    }

    // Update folder select
    const folderSelect = document.getElementById('folderSelect');
    if (note.folderId) {
        folderSelect.value = note.folderId;
    } else {
        folderSelect.value = '';
    }

    // Highlight active note
    document.querySelectorAll('.note-item').forEach(item => {
        item.classList.remove('active');
    });
    const activeItem = document.querySelector(`[data-id="${noteId}"]`); // Changed data-note-id to data-id based on createNoteElement
    if (activeItem) {
        activeItem.classList.add('active');
    }

    // Show editor and hide empty state
    document.querySelector('.editor-container').classList.add('active');
    document.getElementById('emptyState').classList.add('hidden');

    await renderNotesList();
    await updateFolderSelect();
}

// Show empty state
function showEmptyState() {
    const editorContainer = document.querySelector('.editor-container');
    const emptyState = document.getElementById('emptyState');

    editorContainer.classList.remove('active');
    emptyState.classList.remove('hidden');

    document.getElementById('noteTitle').value = '';
    document.getElementById('editor').innerHTML = '';
}

// Auto-save with debounce
function autoSave() {
    clearTimeout(saveTimeout);
    saveTimeout = setTimeout(async () => {
        if (!currentNoteId) return;

        const title = document.getElementById('noteTitle').value.trim() || '無題のノート';
        const editor = document.getElementById('editor');

        // Clone editor content to preserve image IDs
        const tempDiv = document.createElement('div');
        tempDiv.innerHTML = editor.innerHTML;

        // Convert blob URLs back to data-image-id for storage
        const images = tempDiv.querySelectorAll('img[data-image-id]');
        images.forEach(img => {
            // Keep only the data-image-id attribute, remove src
            const imageId = img.getAttribute('data-image-id');
            img.removeAttribute('src');
            img.setAttribute('data-image-id', imageId);
        });

        const content = tempDiv.innerHTML;

        await updateNote(currentNoteId, { title, content });
        await renderNotesList();
    }, 500);
}

// Insert image
async function insertImage(file) {
    if (!file || !file.type.startsWith('image/')) {
        alert('有効な画像ファイルを選択してください。');
        return;
    }

    try {
        // Save image to IndexedDB
        const imageId = await saveImage(file, currentNoteId);

        // Create blob URL for display
        const blobUrl = URL.createObjectURL(file);

        const img = document.createElement('img');
        img.src = blobUrl;
        img.setAttribute('data-image-id', imageId);
        img.style.maxWidth = '100%';
        img.style.height = 'auto';

        const editor = document.getElementById('editor');
        const selection = window.getSelection();

        if (selection.rangeCount > 0) {
            const range = selection.getRangeAt(0);
            range.insertNode(img);
            range.setStartAfter(img);
            range.collapse(true);
        } else {
            editor.appendChild(img);
        }

        autoSave();
    } catch (error) {
        console.error('Failed to insert image:', error);
        alert('画像の挿入に失敗しました。');
    }
}

// Escape HTML
function escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
}

// ===== Formatting Functions =====

// Format selected text as heading
function formatHeading(level) {
    const editor = document.getElementById('editor');
    const selection = window.getSelection();

    if (selection.rangeCount === 0) {
        alert('テキストを選択してください。');
        return;
    }

    const range = selection.getRangeAt(0);
    const selectedText = range.toString();

    if (!selectedText) {
        alert('テキストを選択してください。');
        return;
    }

    // Create heading element
    const heading = document.createElement(`h${level}`);
    heading.textContent = selectedText;

    // Replace selection with heading
    range.deleteContents();
    range.insertNode(heading);

    // Add a line break after heading
    const br = document.createElement('br');
    heading.after(br);

    // Move cursor after the heading
    range.setStartAfter(br);
    range.collapse(true);
    selection.removeAllRanges();
    selection.addRange(range);

    autoSave();
}

// Insert text box
function insertTextBox() {
    const editor = document.getElementById('editor');
    const selection = window.getSelection();

    // Create text box
    const textBox = document.createElement('div');
    textBox.className = 'text-box';
    textBox.contentEditable = 'true';
    textBox.textContent = 'ここに入力...';

    // Insert at cursor or append
    if (selection.rangeCount > 0) {
        const range = selection.getRangeAt(0);
        range.insertNode(textBox);

        // Add line break after box
        const br = document.createElement('br');
        textBox.after(br);

        // Move cursor after the box
        range.setStartAfter(br);
        range.collapse(true);
        selection.removeAllRanges();
        selection.addRange(range);
    } else {
        editor.appendChild(textBox);
        editor.appendChild(document.createElement('br'));
    }

    // Focus on the text box
    textBox.focus();

    // Select placeholder text
    const textRange = document.createRange();
    textRange.selectNodeContents(textBox);
    selection.removeAllRanges();
    selection.addRange(textRange);

    autoSave();
}

// Insert blue text box
function insertTextBoxBlue() {
    const editor = document.getElementById('editor');
    const selection = window.getSelection();

    // Create blue text box
    const textBox = document.createElement('div');
    textBox.className = 'text-box-blue';
    textBox.contentEditable = 'true';
    textBox.textContent = 'ここに入力...';

    // Insert at cursor or append
    if (selection.rangeCount > 0) {
        const range = selection.getRangeAt(0);
        range.insertNode(textBox);

        // Add line break after box
        const br = document.createElement('br');
        textBox.after(br);

        // Move cursor after the box
        range.setStartAfter(br);
        range.collapse(true);
        selection.removeAllRanges();
        selection.addRange(range);
    } else {
        editor.appendChild(textBox);
        editor.appendChild(document.createElement('br'));
    }

    // Focus on the text box
    textBox.focus();

    // Select placeholder text
    const textRange = document.createRange();
    textRange.selectNodeContents(textBox);
    selection.removeAllRanges();
    selection.addRange(textRange);

    autoSave();
}

// Insert link
function insertLink() {
    const selection = window.getSelection();

    if (selection.rangeCount === 0) {
        alert('リンクにするテキストを選択してください。');
        return;
    }

    const range = selection.getRangeAt(0);
    const selectedText = range.toString();

    if (!selectedText) {
        alert('リンクにするテキストを選択してください。');
        return;
    }

    // Prompt for URL
    const url = prompt('リンク先のURLを入力してください:', 'https://');

    if (!url || url === 'https://') {
        return;
    }

    // Create link element
    const link = document.createElement('a');
    link.href = url;
    link.textContent = selectedText;
    link.target = '_blank';
    link.rel = 'noopener noreferrer';

    // Replace selection with link
    range.deleteContents();
    range.insertNode(link);

    // Move cursor after the link
    range.setStartAfter(link);
    range.collapse(true);
    selection.removeAllRanges();
    selection.addRange(range);

    autoSave();
}

// Clear formatting from selected text (line-based)
function clearFormatting() {
    const selection = window.getSelection();
    const editor = document.getElementById('editor');

    if (selection.rangeCount === 0) {
        return;
    }

    const range = selection.getRangeAt(0);

    // If no selection, clear formatting of the current block element
    if (range.collapsed) {
        let node = range.startContainer;

        // Find the parent block element
        while (node && node !== editor) {
            if (node.nodeType === Node.ELEMENT_NODE &&
                (node.tagName === 'H2' || node.tagName === 'H3' || node.tagName === 'H4' ||
                    node.classList.contains('text-box') || node.classList.contains('text-box-blue'))) {

                // Get text content
                const textContent = node.textContent;

                // Create plain paragraph
                const p = document.createElement('p');
                p.textContent = textContent;

                // Replace the element
                node.parentNode.replaceChild(p, node);

                // Move cursor into the new paragraph
                const newRange = document.createRange();
                newRange.setStart(p, 0);
                newRange.collapse(true);
                selection.removeAllRanges();
                selection.addRange(newRange);

                autoSave();
                return;
            }
            node = node.parentNode;
        }

        return;
    }

    // Get the common ancestor container
    let container = range.commonAncestorContainer;

    // If it's a text node, get its parent element
    if (container.nodeType === Node.TEXT_NODE) {
        container = container.parentElement;
    }

    // Find all text nodes in the selection
    const walker = document.createTreeWalker(
        container,
        NodeFilter.SHOW_TEXT,
        {
            acceptNode: function (node) {
                const nodeRange = document.createRange();
                nodeRange.selectNodeContents(node);
                return range.intersectsNode(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
            }
        }
    );

    let textContent = '';
    let node;
    while (node = walker.nextNode()) {
        textContent += node.textContent;
    }

    if (!textContent.trim()) {
        return;
    }

    // Create a plain paragraph with the text
    const p = document.createElement('p');
    p.textContent = textContent;

    // Replace the selection with the plain paragraph
    range.deleteContents();
    range.insertNode(p);

    // Move cursor after the paragraph
    range.setStartAfter(p);
    range.collapse(true);
    selection.removeAllRanges();
    selection.addRange(range);

    autoSave();
}

// ===== Export Functions =====

// Helper function to escape HTML
function escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
}

// Helper function to download file
function downloadFile(blob, filename) {
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
}

// Sanitize filename
function sanitizeFilename(filename) {
    return filename.replace(/[<>:"/\\|?*]/g, '_');
}

// Export note as HTML
async function exportNoteAsHTML(noteId) {
    const note = await getNote(noteId);
    if (!note) return;

    const html = `<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${escapeHtml(note.title)}</title>
    <style>
        body {
            font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            max-width: 900px;
            margin: 0 auto;
            padding: 2rem;
            line-height: 1.7;
            color: #e0e0e0;
            background: #1a1a2e;
        }
        h1 {
            color: #ffffff;
            border-bottom: 3px solid #6366f1;
            padding-bottom: 0.75rem;
            margin-bottom: 2rem;
        }
        h2 {
            font-size: 1.6rem;
            color: #333;
            background: #f5f6f7;
            padding: 20px;
            margin-bottom: 40px;
            font-weight: 600;
        }
        h3 {
            border-left: 7px solid #888;
            border-right: 1px solid #ddd;
            border-top: 1px solid #ddd;
            border-bottom: 1px solid #ddd;
            font-size: 1.4rem;
            padding: 11px 20px;
            margin-bottom: 40px;
            font-weight: 600;
            color: #333;
        }
        h4 {
            border-top: 2px solid #ddd;
            border-bottom: 2px solid #ddd;
            margin-bottom: 1.62em;
            font-size: 1.2rem;
            padding: 9px 10px;
            font-weight: 600;
            color: #333;
        }
        .text-box {
            background: #f3f4f6;
            border: 1px solid #ddd;
            border-radius: 8px;
            padding: 1rem;
            margin: 1rem 0;
        }
        .text-box-blue {
            background: rgba(99, 102, 241, 0.1);
            border: 1px solid rgba(99, 102, 241, 0.3);
            border-radius: 8px;
            padding: 1rem;
            margin: 1rem 0;
        }
        img {
            max-width: 100%;
            height: auto;
            border-radius: 8px;
            margin: 1rem 0;
        }
        p {
            margin-bottom: 1rem;
        }
        a {
            color: #6366f1;
            text-decoration: underline;
        }
    </style>
</head>
<body>
    <h1>${escapeHtml(note.title)}</h1>
    ${note.content}
</body>
</html>`;

    const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
    downloadFile(blob, `${sanitizeFilename(note.title)}.html`);
}

// Export note as PDF
async function exportNotePDF(noteId) {
    const note = await getNote(noteId);
    if (!note) return;

    // Create a temporary print window
    const printWindow = window.open('', '_blank');

    const html = `<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>${escapeHtml(note.title)}</title>
    <style>
        @media print {
            @page {
                margin: 2cm;
            }
        }
        body {
            font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 2rem;
            line-height: 1.7;
            color: #333;
        }
        h1 {
            color: #1a1a2e;
            border-bottom: 3px solid #6366f1;
            padding-bottom: 0.75rem;
            margin-bottom: 2rem;
        }
        h2 {
            font-size: 1.6rem;
            color: #333;
            background: #f5f6f7;
            padding: 20px;
            margin-bottom: 40px;
            font-weight: 600;
        }
        h3 {
            border-left: 7px solid #888;
            border-right: 1px solid #ddd;
            border-top: 1px solid #ddd;
            border-bottom: 1px solid #ddd;
            font-size: 1.4rem;
            padding: 11px 20px;
            margin-bottom: 40px;
            font-weight: 600;
            color: #333;
        }
        h4 {
            border-top: 2px solid #ddd;
            border-bottom: 2px solid #ddd;
            margin-bottom: 1.62em;
            font-size: 1.2rem;
            padding: 9px 10px;
            font-weight: 600;
            color: #333;
        }
        .text-box {
            background: #f3f4f6;
            border: 1px solid #ddd;
            border-radius: 8px;
            padding: 1rem;
            margin: 1rem 0;
        }
        .text-box-blue {
            background: rgba(99, 102, 241, 0.1);
            border: 1px solid rgba(99, 102, 241, 0.3);
            border-radius: 8px;
            padding: 1rem;
            margin: 1rem 0;
        }
        img {
            max-width: 100%;
            height: auto;
            border-radius: 8px;
            margin: 1rem 0;
        }
        p {
            margin-bottom: 1rem;
        }
        a {
            color: #6366f1;
            text-decoration: underline;
        }
    </style>
</head>
<body>
    <h1>${escapeHtml(note.title)}</h1>
    ${note.content}
</body>
</html>`;

    printWindow.document.write(html);
    printWindow.document.close();

    // Wait for content to load, then print
    printWindow.onload = function () {
        printWindow.print();
        // Close after printing or canceling
        printWindow.onafterprint = function () {
            printWindow.close();
        };
    };
}

// Export all notes as ZIP
async function exportAllNotes() {
    if (typeof JSZip === 'undefined') {
        alert('JSZipライブラリが読み込まれていません。');
        return;
    }

    const notes = await getAllNotes();
    if (notes.length === 0) {
        alert('エクスポートするノートがありません。');
        return;
    }

    const zip = new JSZip();

    for (const note of notes) {
        const html = `<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>${escapeHtml(note.title)}</title>
</head>
<body>
    <h1>${escapeHtml(note.title)}</h1>
    ${note.content}
</body>
</html>`;
        zip.file(`${sanitizeFilename(note.title)}.html`, html);
    }

    const blob = await zip.generateAsync({ type: 'blob' });
    downloadFile(blob, 'notes.zip');
}

// ===== Formatting Functions =====

// Convert HTML to Markdown (simple conversion)
function htmlToMarkdown(html) {
    let markdown = html;

    // Convert images
    markdown = markdown.replace(/<img[^>]+src="([^"]+)"[^>]*alt="([^"]*)"[^>]*>/gi, '![$2]($1)');
    markdown = markdown.replace(/<img[^>]+src="([^"]+)"[^>]*>/gi, '![]($1)');

    // Convert headings
    markdown = markdown.replace(/<h1[^>]*>(.*?)<\/h1>/gi, '# $1\n\n');
    markdown = markdown.replace(/<h2[^>]*>(.*?)<\/h2>/gi, '## $1\n\n');
    markdown = markdown.replace(/<h3[^>]*>(.*?)<\/h3>/gi, '### $1\n\n');

    // Convert paragraphs
    markdown = markdown.replace(/<p[^>]*>(.*?)<\/p>/gi, '$1\n\n');

    // Convert line breaks
    markdown = markdown.replace(/<br\s*\/?>/gi, '\n');

    // Convert bold and italic
    markdown = markdown.replace(/<strong[^>]*>(.*?)<\/strong>/gi, '**$1**');
    markdown = markdown.replace(/<b[^>]*>(.*?)<\/b>/gi, '**$1**');
    markdown = markdown.replace(/<em[^>]*>(.*?)<\/em>/gi, '*$1*');
    markdown = markdown.replace(/<i[^>]*>(.*?)<\/i>/gi, '*$1*');

    // Convert lists
    markdown = markdown.replace(/<li[^>]*>(.*?)<\/li>/gi, '- $1\n');
    markdown = markdown.replace(/<\/?[uo]l[^>]*>/gi, '\n');

    // Remove remaining HTML tags
    markdown = markdown.replace(/<[^>]+>/g, '');

    // Decode HTML entities
    const textarea = document.createElement('textarea');
    textarea.innerHTML = markdown;
    markdown = textarea.value;

    // Clean up extra newlines
    markdown = markdown.replace(/\n{3,}/g, '\n\n').trim();

    return markdown;
}

// Export note as Markdown
async function exportNoteAsMarkdown(noteId) {
    const note = await getNote(noteId);
    if (!note) return;

    const markdown = `# ${note.title}\n\n${htmlToMarkdown(note.content)}`;

    const blob = new Blob([markdown], { type: 'text/markdown;charset=utf-8' });
    downloadFile(blob, `${sanitizeFilename(note.title)}.md`);
}

// Export note as HTML
async function exportNoteAsHTML(noteId) {
    const note = await getNote(noteId);
    if (!note) return;

    const html = `<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${escapeHtml(note.title)}</title>
    <style>
        body {
            font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 2rem;
            line-height: 1.7;
            color: #333;
            background: #f9fafb;
        }
        h1 {
            color: #1a1a2e;
            border-bottom: 3px solid #6366f1;
            padding-bottom: 0.75rem;
            margin-bottom: 2rem;
        }
        h2 {
            font-size: 1.5rem;
            font-weight: 600;
            color: #1a1a2e;
            margin: 1.25rem 0 0.75rem 0;
            padding-left: 0.5rem;
            border-left: 4px solid #6366f1;
        }
        h3 {
            font-size: 1.25rem;
            font-weight: 600;
            color: #8b5cf6;
            margin: 1rem 0 0.5rem 0;
        }
        .text-box {
            background: rgba(99, 102, 241, 0.1);
            border-left: 4px solid #6366f1;
            border-radius: 4px;
            padding: 1rem;
            margin: 1rem 0;
            color: #1a1a2e;
        }
        img {
            max-width: 100%;
            height: auto;
            border-radius: 8px;
            margin: 1rem 0;
            box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
        }
        p {
            margin-bottom: 1rem;
        }
    </style>
</head>
<body>
    <h1>${escapeHtml(note.title)}</h1>
    <div class="content">
        ${note.content}
    </div>
</body>
</html>`;

    const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
    downloadFile(blob, `${sanitizeFilename(note.title)}.html`);
}

// Export note as PDF
async function exportNotePDF(noteId) {
    const note = await getNote(noteId);
    if (!note) return;

    // Create a temporary print window
    const printWindow = window.open('', '_blank');

    const html = `<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>${escapeHtml(note.title)}</title>
    <style>
        @media print {
            @page {
                margin: 2cm;
            }
        }
        body {
            font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 2rem;
            line-height: 1.7;
            color: #333;
        }
        h1 {
            color: #1a1a2e;
            border-bottom: 3px solid #6366f1;
            padding-bottom: 0.75rem;
            margin-bottom: 2rem;
        }
        h2 {
            font-size: 1.6rem;
            color: #333;
            background: #f5f6f7;
            background-size: 4px 4px;
            padding: 20px 20px 20px;
            margin-bottom: 40px;
            font-weight: 600;
        }
        h3 {
            border-left: 7px solid #888;
            border-right: 1px solid #ddd;
            border-top: 1px solid #ddd;
            border-bottom: 1px solid #ddd;
            font-size: 1.4rem;
            padding: 11px 20px;
            margin-bottom: 40px;
            font-weight: 600;
            color: #333;
        }
        h4 {
            border-top: 2px solid #ddd;
            border-bottom: 2px solid #ddd;
            margin-bottom: 1.62em;
            font-size: 1.2rem;
            padding: 9px 10px;
            font-weight: 600;
            color: #333;
        }
        .text-box {
            background: #f3f4f6;
            border: 1px solid #ddd;
            border-radius: 8px;
            padding: 1rem;
            margin: 1rem 0;
        }
        .text-box-blue {
            background: rgba(99, 102, 241, 0.1);
            border: 1px solid rgba(99, 102, 241, 0.3);
            border-radius: 8px;
            padding: 1rem;
            margin: 1rem 0;
        }
        img {
            max-width: 100%;
            height: auto;
            border-radius: 8px;
            margin: 1rem 0;
        }
        p {
            margin-bottom: 1rem;
        }
        a {
            color: #6366f1;
            text-decoration: underline;
        }
    </style>
</head>
<body>
    <h1>${escapeHtml(note.title)}</h1>
    ${note.content}
</body>
</html>`;

    printWindow.document.write(html);
    printWindow.document.close();

    // Wait for content to load, then print
    printWindow.onload = function () {
        printWindow.print();
        // Close after printing or canceling
        printWindow.onafterprint = function () {
            printWindow.close();
        };
    };
}

// Export all notes as ZIP
async function exportAllNotes() {
    if (typeof JSZip === 'undefined') {
        alert('エクスポート機能を使用するには、ページをリロードしてください。');
        return;
    }

    const zip = new JSZip();
    const folders = await getAllFolders();

    for (const folder of folders) {
        const notes = await getNotesByFolder(folder.id);
        const folderName = sanitizeFilename(folder.name);

        for (const note of notes) {
            const markdown = `# ${note.title}\n\n${htmlToMarkdown(note.content)}`;
            const filename = `${sanitizeFilename(note.title)}.md`;
            zip.file(`${folderName}/${filename}`, markdown);
        }
    }

    const blob = await zip.generateAsync({ type: 'blob' });
    const date = new Date().toISOString().split('T')[0].replace(/-/g, '');
    downloadFile(blob, `notes_export_${date}.zip`);
}

// Sanitize filename
function sanitizeFilename(filename) {
    return filename.replace(/[<>:"/\\|?*]/g, '_').substring(0, 200);
}

// Download file
function downloadFile(blob, filename) {
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
}

// ===== Event Listeners =====

// New note button
document.getElementById('newNoteBtn').addEventListener('click', async () => {
    const noteId = await createNote();
    await loadNote(noteId);
});

// Title input
document.getElementById('noteTitle').addEventListener('input', autoSave);

// Editor input
document.getElementById('editor').addEventListener('input', autoSave);

// Image insert button
document.getElementById('insertImageBtn').addEventListener('click', () => {
    document.getElementById('imageInput').click();
});

// Image file input
document.getElementById('imageInput').addEventListener('change', (e) => {
    const file = e.target.files[0];
    if (file && file.type.startsWith('image/')) {
        insertImage(file);
    }
    e.target.value = '';
});

// Paste image
document.getElementById('editor').addEventListener('paste', (e) => {
    const items = e.clipboardData.items;
    for (let i = 0; i < items.length; i++) {
        if (items[i].type.startsWith('image/')) {
            e.preventDefault();
            const file = items[i].getAsFile();
            insertImage(file);
            break;
        }
    }
});

// Handle link clicks with Ctrl key
document.getElementById('editor').addEventListener('click', (e) => {
    if (e.target.tagName === 'A' && (e.ctrlKey || e.metaKey)) {
        e.preventDefault();
        window.open(e.target.href, '_blank', 'noopener,noreferrer');
    }
});

// Code editor toggle
let isCodeEditorMode = false;

function formatHTML(html) {
    // Add line breaks after closing block tags
    const blockTags = ['h2', 'h3', 'h4', 'p', 'div', 'img', 'br'];
    let formatted = html;

    blockTags.forEach(tag => {
        // Add line break after closing tags
        formatted = formatted.replace(new RegExp(`</${tag}>`, 'gi'), `</${tag}>\n`);
        // Add line break after self-closing img tags
        if (tag === 'img' || tag === 'br') {
            formatted = formatted.replace(new RegExp(`<${tag}([^>]*)>`, 'gi'), `<${tag}$1>\n`);
        }
    });

    // Clean up multiple consecutive line breaks
    formatted = formatted.replace(/\n{3,}/g, '\n\n');

    return formatted.trim();
}

document.getElementById('codeEditorBtn').addEventListener('click', () => {
    const editor = document.getElementById('editor');

    if (!isCodeEditorMode) {
        // Switch to code editor mode
        const htmlContent = editor.innerHTML;
        const formattedHTML = formatHTML(htmlContent);
        editor.textContent = formattedHTML;
        editor.style.fontFamily = 'monospace';
        editor.style.whiteSpace = 'pre-wrap';
        editor.style.fontSize = '0.9rem';
        isCodeEditorMode = true;
        document.getElementById('codeEditorBtn').style.background = 'var(--accent-primary)';
        document.getElementById('codeEditorBtn').style.color = 'white';
    } else {
        // Switch back to visual editor mode
        const codeContent = editor.textContent;
        editor.innerHTML = codeContent;
        editor.style.fontFamily = '';
        editor.style.whiteSpace = '';
        editor.style.fontSize = '';
        isCodeEditorMode = false;
        document.getElementById('codeEditorBtn').style.background = '';
        document.getElementById('codeEditorBtn').style.color = '';

        // Reload images after switching back
        if (currentNoteId) {
            loadNote(currentNoteId);
        }
    }
});

// View code for selected range with modal editor
document.getElementById('viewCodeBtn').addEventListener('click', () => {
    const selection = window.getSelection();

    if (selection.rangeCount === 0 || selection.isCollapsed) {
        alert('コード表示する範囲を選択してください。');
        return;
    }

    const range = selection.getRangeAt(0);
    const container = document.createElement('div');
    container.appendChild(range.cloneContents());

    const html = formatHTML(container.innerHTML);

    // Create modal
    const modal = document.createElement('div');
    modal.style.cssText = `
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.7);
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 10000;
    `;

    const modalContent = document.createElement('div');
    modalContent.style.cssText = `
        background: var(--bg-secondary);
        border-radius: 12px;
        padding: 2rem;
        width: 90%;
        max-width: 800px;
        max-height: 80vh;
        display: flex;
        flex-direction: column;
        box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
    `;

    const title = document.createElement('h3');
    title.textContent = '選択範囲のHTMLコード';
    title.style.cssText = `
        margin: 0 0 1rem 0;
        color: var(--text-primary);
        font-size: 1.25rem;
    `;

    const textarea = document.createElement('textarea');
    textarea.value = html;
    textarea.style.cssText = `
        flex: 1;
        font-family: 'Consolas', 'Monaco', monospace;
        font-size: 0.9rem;
        padding: 1rem;
        border: 1px solid var(--border-color);
        border-radius: 8px;
        background: var(--bg-primary);
        color: var(--text-primary);
        resize: none;
        outline: none;
        margin-bottom: 1rem;
    `;

    const buttonContainer = document.createElement('div');
    buttonContainer.style.cssText = `
        display: flex;
        gap: 1rem;
        justify-content: flex-end;
    `;

    const applyBtn = document.createElement('button');
    applyBtn.textContent = '適用';
    applyBtn.style.cssText = `
        padding: 0.75rem 1.5rem;
        background: var(--accent-primary);
        color: white;
        border: none;
        border-radius: 8px;
        font-weight: 600;
        cursor: pointer;
        transition: all 0.2s;
    `;
    applyBtn.onmouseover = () => applyBtn.style.background = 'var(--accent-secondary)';
    applyBtn.onmouseout = () => applyBtn.style.background = 'var(--accent-primary)';

    const cancelBtn = document.createElement('button');
    cancelBtn.textContent = 'キャンセル';
    cancelBtn.style.cssText = `
        padding: 0.75rem 1.5rem;
        background: var(--bg-tertiary);
        color: var(--text-primary);
        border: 1px solid var(--border-color);
        border-radius: 8px;
        font-weight: 600;
        cursor: pointer;
        transition: all 0.2s;
    `;
    cancelBtn.onmouseover = () => cancelBtn.style.background = 'var(--bg-hover)';
    cancelBtn.onmouseout = () => cancelBtn.style.background = 'var(--bg-tertiary)';

    applyBtn.onclick = () => {
        const newHTML = textarea.value;
        const tempDiv = document.createElement('div');
        tempDiv.innerHTML = newHTML;

        // Replace selection with new content
        const newRange = document.createRange();
        newRange.setStart(range.startContainer, range.startOffset);
        newRange.setEnd(range.endContainer, range.endOffset);
        newRange.deleteContents();

        while (tempDiv.firstChild) {
            newRange.insertNode(tempDiv.lastChild);
        }

        document.body.removeChild(modal);
        autoSave();
    };

    cancelBtn.onclick = () => {
        document.body.removeChild(modal);
    };

    buttonContainer.appendChild(cancelBtn);
    buttonContainer.appendChild(applyBtn);

    modalContent.appendChild(title);
    modalContent.appendChild(textarea);
    modalContent.appendChild(buttonContainer);
    modal.appendChild(modalContent);
    document.body.appendChild(modal);

    // Focus textarea
    textarea.focus();
    textarea.select();
});

// Heading format buttons
document.getElementById('heading2Btn').addEventListener('click', () => {
    formatHeading(2);
});

document.getElementById('heading3Btn').addEventListener('click', () => {
    formatHeading(3);
});

document.getElementById('heading4Btn').addEventListener('click', () => {
    formatHeading(4);
});

// Insert text box button
document.getElementById('insertBoxBtn').addEventListener('click', () => {
    insertTextBox();
});

// Insert blue text box button
document.getElementById('insertBoxBlueBtn').addEventListener('click', () => {
    insertTextBoxBlue();
});

// Insert link button
document.getElementById('insertLinkBtn').addEventListener('click', () => {
    insertLink();
});

// Clear formatting button
document.getElementById('clearFormatBtn').addEventListener('click', () => {
    clearFormatting();
});

// Theme toggle (optional enhancement)
document.getElementById('themeToggle').addEventListener('click', () => {
    // Could implement light/dark theme toggle here
    console.log('Theme toggle clicked');
});

// New folder button
document.getElementById('newFolderBtn').addEventListener('click', async () => {
    const folderId = await createFolder();
    await renderFoldersAndNotes();
});

// Export button and menu
const exportBtn = document.getElementById('exportBtn');
const exportMenu = document.getElementById('exportMenu');

exportBtn.addEventListener('click', (e) => {
    e.stopPropagation();
    exportMenu.classList.toggle('hidden');
});

// Close export menu when clicking outside
document.addEventListener('click', (e) => {
    if (!exportBtn.contains(e.target) && !exportMenu.contains(e.target)) {
        exportMenu.classList.add('hidden');
    }
});

// Export Markdown
document.getElementById('exportMarkdownBtn').addEventListener('click', async () => {
    if (currentNoteId) {
        await exportNoteAsMarkdown(currentNoteId);
        exportMenu.classList.add('hidden');
    } else {
        alert('エクスポートするノートを選択してください。');
    }
});

// Export HTML
document.getElementById('exportHTMLBtn').addEventListener('click', async () => {
    if (currentNoteId) {
        await exportNoteAsHTML(currentNoteId);
        exportMenu.classList.add('hidden');
    } else {
        alert('エクスポートするノートを選択してください。');
    }
});

// Export all notes
document.getElementById('exportAllBtn').addEventListener('click', async () => {
    await exportAllNotes();
    exportMenu.classList.add('hidden');
});

// Export PDF
document.getElementById('exportPDFBtn').addEventListener('click', async () => {
    if (currentNoteId) {
        await exportNotePDF(currentNoteId);
        exportMenu.classList.add('hidden');
    } else {
        alert('エクスポートするノートを選択してください。');
    }
});

// Folder select change
document.getElementById('folderSelect').addEventListener('change', async (e) => {
    if (!currentNoteId) return;

    const folderId = parseInt(e.target.value);
    if (folderId) {
        await updateNote(currentNoteId, { folderId });
        await renderFoldersAndNotes();
    }
});

// Update folder select options
async function updateFolderSelect() {
    const folderSelect = document.getElementById('folderSelect');
    const folders = await getAllFolders();

    folderSelect.innerHTML = '<option value="">フォルダ...</option>';
    folders.forEach(folder => {
        const option = document.createElement('option');
        option.value = folder.id;
        option.textContent = folder.name;
        folderSelect.appendChild(option);
    });

    // Set current folder if note is loaded
    if (currentNoteId) {
        const note = await getNote(currentNoteId);
        if (note && note.folderId) {
            folderSelect.value = note.folderId;
        }
    }
}

// Theme toggle
function initTheme() {
    const savedTheme = localStorage.getItem('theme') || 'dark';
    document.documentElement.setAttribute('data-theme', savedTheme);
}

function toggleTheme() {
    const currentTheme = document.documentElement.getAttribute('data-theme');
    const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
    document.documentElement.setAttribute('data-theme', newTheme);
    localStorage.setItem('theme', newTheme);
}

document.getElementById('themeToggle').addEventListener('click', toggleTheme);

// ===== Initialize App =====
async function initApp() {
    try {
        initTheme();
        await initDB();
        await updateFolderSelect();
        await renderNotesList();

        const notes = await getAllNotes();
        if (notes.length > 0) {
            await loadNote(notes[0].id);
        } else {
            showEmptyState();
        }
    } catch (error) {
        console.error('Failed to initialize app:', error);
        alert('アプリケーションの初期化に失敗しました。');
    }
}

// Start the app when DOM is ready
if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initApp);
} else {
    initApp();
}

 database.js

// ===== IndexedDB Setup =====
const DB_NAME = 'NotesDB';
const DB_VERSION = 4;
const STORE_NAME = 'notes';
const FOLDER_STORE = 'folders';
const DEFAULT_FOLDER_ID = 1;

let db;

// Initialize IndexedDB
function initDB() {
    return new Promise((resolve, reject) => {
        console.log('Initializing database:', DB_NAME, 'version:', DB_VERSION);
        const request = indexedDB.open(DB_NAME, DB_VERSION);

        request.onerror = () => {
            console.error('Database initialization failed:', request.error);
            reject(request.error);
        };

        request.onsuccess = () => {
            db = request.result;
            console.log('Database opened successfully. Version:', db.version);
            console.log('Available object stores:', Array.from(db.objectStoreNames));

            // Check if images store exists
            if (!db.objectStoreNames.contains('images')) {
                console.error('Images store not found! Database needs to be upgraded.');
                console.log('Current database version:', db.version);
                console.log('Expected version:', DB_VERSION);

                // Close the database and try to delete and recreate
                db.close();

                if (confirm('画像ストアが見つかりません。データベースを再作成しますか?\n(既存のノートは保持されます)')) {
                    const deleteRequest = indexedDB.deleteDatabase(DB_NAME);
                    deleteRequest.onsuccess = () => {
                        console.log('Database deleted successfully');
                        location.reload();
                    };
                    deleteRequest.onerror = () => {
                        console.error('Failed to delete database');
                        reject(new Error('Failed to delete database'));
                    };
                } else {
                    reject(new Error('Images store not found'));
                }
                return;
            }

            resolve(db);
        };

        request.onupgradeneeded = (event) => {
            console.log('Database upgrade needed. Old version:', event.oldVersion, 'New version:', event.newVersion);
            db = event.target.result;
            const transaction = event.target.transaction;

            // 1. Notes Store
            let notesStore;
            if (!db.objectStoreNames.contains(STORE_NAME)) {
                console.log('Creating notes store');
                notesStore = db.createObjectStore(STORE_NAME, { keyPath: 'id', autoIncrement: true });
                notesStore.createIndex('folderId', 'folderId', { unique: false });
                notesStore.createIndex('updatedAt', 'updatedAt', { unique: false });
            } else {
                // Store exists, check for upgrades
                console.log('Checking notes store for upgrades');
                notesStore = transaction.objectStore(STORE_NAME);
                if (!notesStore.indexNames.contains('folderId')) {
                    notesStore.createIndex('folderId', 'folderId', { unique: false });
                }
                if (!notesStore.indexNames.contains('updatedAt')) {
                    notesStore.createIndex('updatedAt', 'updatedAt', { unique: false });
                }
            }

            // 2. Folders Store
            let folderStore;
            if (!db.objectStoreNames.contains(FOLDER_STORE)) {
                console.log('Creating folders store');
                folderStore = db.createObjectStore(FOLDER_STORE, { keyPath: 'id', autoIncrement: true });
                folderStore.createIndex('timestamp', 'timestamp', { unique: false });
                folderStore.createIndex('parentId', 'parentId', { unique: false }); // Add parentId index

                // Add default folder
                folderStore.add({
                    id: DEFAULT_FOLDER_ID,
                    name: '未分類',
                    timestamp: Date.now(),
                    parentId: null // Top level
                });
            } else {
                // Upgrade folders store if needed
                folderStore = transaction.objectStore(FOLDER_STORE);
                if (!folderStore.indexNames.contains('parentId')) {
                    console.log('Adding parentId index to folders store');
                    folderStore.createIndex('parentId', 'parentId', { unique: false });

                    // Note: Existing folders will have undefined parentId, which is treated as null/top-level usually
                    // or we might need to iterate and set them. But undefined in a query for 'null' might be tricky.
                    // However, we only need to query if specific parentId.
                    // Top level folders will have parentId == null (or undefined).
                }
            }

            // 3. Images Store
            if (!db.objectStoreNames.contains('images')) {
                console.log('Creating images store');
                const imagesStore = db.createObjectStore('images', { keyPath: 'id', autoIncrement: true });
                imagesStore.createIndex('noteId', 'noteId', { unique: false });
            }

            console.log('Database upgrade completed');
        };
    });
}

// ===== Note CRUD Operations =====

// Create a new note
async function createNote(folderId = null) {
    const note = {
        title: '無題のノート',
        content: '<p><br></p>',
        folderId: folderId || DEFAULT_FOLDER_ID,
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString()
    };

    return new Promise((resolve, reject) => {
        const transaction = db.transaction([STORE_NAME], 'readwrite');
        const objectStore = transaction.objectStore(STORE_NAME);
        const request = objectStore.add(note);

        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

// Get all notes
function getAllNotes() {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction([STORE_NAME], 'readonly');
        const objectStore = transaction.objectStore(STORE_NAME);
        const request = objectStore.getAll();

        request.onsuccess = () => {
            const notes = request.result.sort((a, b) =>
                new Date(b.updatedAt) - new Date(a.updatedAt)
            );
            resolve(notes);
        };
        request.onerror = () => reject(request.error);
    });
}

// Get a single note by ID
function getNote(id) {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction([STORE_NAME], 'readonly');
        const objectStore = transaction.objectStore(STORE_NAME);
        const request = objectStore.get(id);

        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

// Update a note
async function updateNote(id, updates) {
    const note = await getNote(id);
    if (!note) return;

    const updatedNote = {
        ...note,
        ...updates,
        updatedAt: new Date().toISOString()
    };

    return new Promise((resolve, reject) => {
        const transaction = db.transaction([STORE_NAME], 'readwrite');
        const objectStore = transaction.objectStore(STORE_NAME);
        const request = objectStore.put(updatedNote);

        request.onsuccess = () => resolve();
        request.onerror = () => reject(request.error);
    });
}

// Delete a note
function deleteNote(id) {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction([STORE_NAME], 'readwrite');
        const objectStore = transaction.objectStore(STORE_NAME);
        const request = objectStore.delete(id);

        request.onsuccess = () => resolve();
        request.onerror = () => reject(request.error);
    });
}

// ===== Folder CRUD Operations =====

// Get all folders
function getAllFolders() {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction([FOLDER_STORE], 'readonly');
        const objectStore = transaction.objectStore(FOLDER_STORE);
        const request = objectStore.getAll();

        request.onsuccess = () => {
            const folders = request.result.sort((a, b) => a.timestamp - b.timestamp);
            resolve(folders);
        };
        request.onerror = () => reject(request.error);
    });
}

// Create a new folder
// Create new folder
async function createFolder(name = '新規フォルダ', parentId = null) {
    let folderName = name;
    // If using default name, prompt user (legacy behavior preserved but flexible)
    if (name === '新規フォルダ' && !parentId) { // Prompt only if triggered from main button without arg, approx
        // Actually better to handle prompt in UI. But for now let's keep it adaptable.
        // If called with just '新規フォルダ', it behaves like before?
        // The prompt was: const folderName = prompt...
    }

    // We'll move the prompt to UI layer mostly, but here:
    if (name === '新規フォルダ' && arguments.length === 0) {
        folderName = prompt('フォルダ名を入力してください:', '新しいフォルダ');
    }

    if (!folderName || !folderName.trim()) return null;

    const folder = {
        name: folderName.trim(),
        timestamp: Date.now(),
        parentId: parentId
    };

    return new Promise((resolve, reject) => {
        const transaction = db.transaction([FOLDER_STORE], 'readwrite');
        const objectStore = transaction.objectStore(FOLDER_STORE);
        const request = objectStore.add(folder);

        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

// Update folder name
async function updateFolderName(id, newName) {
    const folders = await getAllFolders();
    const folder = folders.find(f => f.id === id);
    if (!folder) return;

    // If newName is not provided, prompt for it
    if (!newName) {
        newName = prompt('フォルダ名を入力してください:', folder.name);
    }

    if (!newName || !newName.trim()) return;

    folder.name = newName.trim();

    return new Promise((resolve, reject) => {
        const transaction = db.transaction([FOLDER_STORE], 'readwrite');
        const objectStore = transaction.objectStore(FOLDER_STORE);
        const request = objectStore.put(folder);

        request.onsuccess = () => resolve();
        request.onerror = () => reject(request.error);
    });
}

// Delete a folder
async function deleteFolder(id) {
    if (id === DEFAULT_FOLDER_ID) {
        alert('デフォルトフォルダは削除できません。');
        return;
    }

    if (!confirm('このフォルダを削除しますか?フォルダ内のノートは「未分類」に移動されます。')) {
        return;
    }

    const notes = await getNotesByFolder(id);
    for (const note of notes) {
        await updateNote(note.id, { folderId: DEFAULT_FOLDER_ID });
    }

    return new Promise((resolve, reject) => {
        const transaction = db.transaction([FOLDER_STORE], 'readwrite');
        const objectStore = transaction.objectStore(FOLDER_STORE);
        const request = objectStore.delete(id);

        request.onsuccess = () => resolve();
        request.onerror = () => reject(request.error);
    });
}

// Get notes by folder
async function getNotesByFolder(folderId) {
    const allNotes = await getAllNotes();
    return allNotes.filter(note => (note.folderId || DEFAULT_FOLDER_ID) === folderId);
}

 editor.js

// ===== Editor Functions =====

// Custom Undo/Redo Stack
let undoStack = [];
let redoStack = [];
let isUndoRedoOperation = false;
const MAX_UNDO_STACK = 50;

function saveToUndoStack() {
    if (isUndoRedoOperation) return;

    const editor = document.getElementById('editor');
    if (!editor) return;

    const state = {
        html: editor.innerHTML,
        timestamp: Date.now()
    };

    undoStack.push(state);
    if (undoStack.length > MAX_UNDO_STACK) {
        undoStack.shift();
    }

    // Clear redo stack when new action is performed
    redoStack = [];
}

function performUndo() {
    if (undoStack.length === 0) return;

    const editor = document.getElementById('editor');
    if (!editor) return;

    // Save current state to redo stack
    redoStack.push({
        html: editor.innerHTML,
        timestamp: Date.now()
    });

    // Restore previous state
    const previousState = undoStack.pop();
    isUndoRedoOperation = true;
    editor.innerHTML = previousState.html;
    isUndoRedoOperation = false;

    // Reload images if needed
    if (typeof loadImagesInEditor === 'function') {
        loadImagesInEditor();
    }
}

function performRedo() {
    if (redoStack.length === 0) return;

    const editor = document.getElementById('editor');
    if (!editor) return;

    // Save current state to undo stack
    undoStack.push({
        html: editor.innerHTML,
        timestamp: Date.now()
    });

    // Restore redo state
    const redoState = redoStack.pop();
    isUndoRedoOperation = true;
    editor.innerHTML = redoState.html;
    isUndoRedoOperation = false;

    // Reload images if needed
    if (typeof loadImagesInEditor === 'function') {
        loadImagesInEditor();
    }
}

// Auto-save with debounce
function autoSave() {
    clearTimeout(saveTimeout);
    saveTimeout = setTimeout(async () => {
        if (!currentNoteId) return;
        // Don't auto-save if in code editor mode to prevent saving escaped HTML
        if (isCodeEditorMode) return;

        const title = document.getElementById('noteTitle').value.trim() || '無題のノート';
        const editor = document.getElementById('editor');

        // Clone editor content to preserve image IDs
        const tempDiv = document.createElement('div');
        tempDiv.innerHTML = editor.innerHTML;

        // Convert blob URLs back to data-image-id for storage
        const images = tempDiv.querySelectorAll('img[data-image-id]');
        images.forEach(img => {
            // Keep only the data-image-id attribute, remove src
            const imageId = img.getAttribute('data-image-id');
            img.removeAttribute('src');
            img.setAttribute('data-image-id', imageId);
        });

        const content = tempDiv.innerHTML;

        await updateNote(currentNoteId, { title, content });
        await renderNotesList();
    }, 500);
}

// ... (skipping lines)

// Code editor toggle
let isCodeEditorMode = false;

function toggleCodeEditor() {
    const editor = document.getElementById('editor');

    if (!isCodeEditorMode) {
        // Switch to code editor mode
        const htmlContent = editor.innerHTML;
        const formattedHTML = formatHTML(htmlContent);
        editor.textContent = formattedHTML;
        editor.style.fontFamily = 'monospace';
        editor.style.whiteSpace = 'pre-wrap';
        editor.style.fontSize = '0.9rem';
        isCodeEditorMode = true;
        document.getElementById('codeEditorBtn').style.background = 'var(--accent-primary)';
        document.getElementById('codeEditorBtn').style.color = 'white';
    } else {
        // Switch back to visual editor mode
        const codeContent = editor.textContent;
        editor.innerHTML = codeContent;
        editor.style.fontFamily = '';
        editor.style.whiteSpace = '';
        editor.style.fontSize = '';
        isCodeEditorMode = false;
        document.getElementById('codeEditorBtn').style.background = '';
        document.getElementById('codeEditorBtn').style.color = '';

        // Reload images after switching back (blobs need to be restored)
        if (typeof loadImagesInEditor === 'function') {
            loadImagesInEditor();
        }

        // Save the updated HTML
        autoSave();
    }
}

// ===== Formatting Functions =====

// Format selected text or current line as heading
function formatHeading(level) {
    const editor = document.getElementById('editor');
    const selection = window.getSelection();

    if (selection.rangeCount === 0) {
        return;
    }

    const range = selection.getRangeAt(0);
    let selectedText = range.toString();

    // If no text is selected, get the current block element
    if (!selectedText) {
        let node = range.startContainer;

        // If we're in a text node, get its parent
        if (node.nodeType === Node.TEXT_NODE) {
            node = node.parentElement;
        }

        // Find the closest block element
        while (node && node !== editor) {
            if (node.nodeType === Node.ELEMENT_NODE &&
                (node.tagName === 'P' || node.tagName === 'DIV' ||
                    node.tagName === 'H1' || node.tagName === 'H2' ||
                    node.tagName === 'H3' || node.tagName === 'H4' ||
                    node.classList.contains('text-box') ||
                    node.classList.contains('text-box-blue'))) {

                selectedText = node.textContent;

                // Create heading element
                const heading = document.createElement(`h${level}`);
                heading.textContent = selectedText;

                // Replace the block element with heading
                node.parentNode.replaceChild(heading, node);

                // Add a line break after heading
                const br = document.createElement('br');
                heading.after(br);

                // Move cursor to the heading
                const newRange = document.createRange();
                newRange.setStart(heading, 0);
                newRange.collapse(true);
                selection.removeAllRanges();
                selection.addRange(newRange);

                autoSave();
                return;
            }
            node = node.parentElement;
        }

        // If we couldn't find a block element, just return
        return;
    }

    // Create heading element
    const heading = document.createElement(`h${level}`);
    heading.textContent = selectedText;

    // Replace selection with heading
    range.deleteContents();
    range.insertNode(heading);

    // Add a line break after heading
    const br = document.createElement('br');
    heading.after(br);

    // Move cursor after the heading
    range.setStartAfter(br);
    range.collapse(true);
    selection.removeAllRanges();
    selection.addRange(range);

    autoSave();
}

// Insert empty line
// Insert empty line
function insertEmptyLine() {
    const selection = window.getSelection();
    if (!selection.rangeCount) return;

    const editor = document.getElementById('editor');
    const range = selection.getRangeAt(0);
    let container = range.commonAncestorContainer;

    if (container.nodeType === Node.TEXT_NODE) {
        container = container.parentNode;
    }

    // Find the closest block element
    let block = container.closest('p, h1, h2, h3, h4, li, .text-box, .text-box-blue, .graybox, .check');

    // If no specific block found, or if the block is the editor itself (which shouldn't happen with above selector but safe check)
    // We should check if the found block is actually inside the editor
    if (block && !editor.contains(block)) {
        block = null;
    }

    // Create paragraph
    const p = document.createElement('p');
    // We must use <br> for the paragraph to be visible and focusable in contenteditable
    // An empty <p></p> has 0 height and cannot hold a cursor in many browsers
    p.innerHTML = '<br>';

    if (block) {
        block.after(p);
    } else {
        // Fallback: insert at cursor position or append to editor
        // If the cursor is strictly inside the editor but not in a block (e.g. text node directly in editor)
        // We can just insert the p node at the range.
        range.insertNode(p);

        // After insertion, we might want to ensure it's a block behavior (breaks line)
        // Since p is block, it breaks automatically.
        // We don't need to do anything else.
    }

    // Move cursor to new line
    const newRange = document.createRange();
    newRange.setStart(p, 0);
    newRange.collapse(true);
    selection.removeAllRanges();
    selection.addRange(newRange);

    autoSave();
}

// Format selected text as paragraph
function formatParagraph() {
    const editor = document.getElementById('editor');
    const selection = window.getSelection();

    if (selection.rangeCount === 0) {
        return;
    }

    const range = selection.getRangeAt(0);
    const selectedText = range.toString();

    if (!selectedText) {
        alert('テキストを選択してください。');
        return;
    }

    // Create paragraph element
    const paragraph = document.createElement('p');
    paragraph.textContent = selectedText;

    // Replace selection with paragraph
    range.deleteContents();
    range.insertNode(paragraph);

    // Add a line break after paragraph
    const br = document.createElement('br');
    paragraph.after(br);

    // Move cursor after the paragraph
    range.setStartAfter(br);
    range.collapse(true);
    selection.removeAllRanges();
    selection.addRange(range);

    autoSave();
}

// Insert text box
function insertTextBox() {
    const editor = document.getElementById('editor');
    const selection = window.getSelection();

    // Create text box
    const textBox = document.createElement('div');
    textBox.className = 'text-box';
    textBox.contentEditable = 'true';
    textBox.textContent = 'ここに入力...';

    // Insert at cursor or append
    if (selection.rangeCount > 0) {
        const range = selection.getRangeAt(0);
        range.insertNode(textBox);

        // Add line break after box
        const br = document.createElement('br');
        textBox.after(br);

        // Move cursor after the box
        range.setStartAfter(br);
        range.collapse(true);
        selection.removeAllRanges();
        selection.addRange(range);
    } else {
        editor.appendChild(textBox);
        editor.appendChild(document.createElement('br'));
    }

    // Focus on the text box
    textBox.focus();

    // Select placeholder text
    const textRange = document.createRange();
    textRange.selectNodeContents(textBox);
    selection.removeAllRanges();
    selection.addRange(textRange);

    autoSave();
}

// Insert blue text box
function insertTextBoxBlue() {
    const editor = document.getElementById('editor');
    const selection = window.getSelection();

    // Create blue text box
    const textBox = document.createElement('div');
    textBox.className = 'text-box-blue';
    textBox.contentEditable = 'true';
    textBox.textContent = 'ここに入力...';

    // Insert at cursor or append
    if (selection.rangeCount > 0) {
        const range = selection.getRangeAt(0);
        range.insertNode(textBox);

        // Add line break after box
        const br = document.createElement('br');
        textBox.after(br);

        // Move cursor after the box
        range.setStartAfter(br);
        range.collapse(true);
        selection.removeAllRanges();
        selection.addRange(range);
    } else {
        editor.appendChild(textBox);
        editor.appendChild(document.createElement('br'));
    }

    // Focus on the text box
    textBox.focus();

    // Select placeholder text
    const textRange = document.createRange();
    textRange.selectNodeContents(textBox);
    selection.removeAllRanges();
    selection.addRange(textRange);

    autoSave();
}

// Insert gray box (div)
function insertGrayBox() {
    const editor = document.getElementById('editor');
    const selection = window.getSelection();

    // Create gray box (div)
    const grayBox = document.createElement('div');
    grayBox.className = 'graybox';
    grayBox.textContent = 'グレーボックス';

    // Insert at cursor or append
    if (selection.rangeCount > 0) {
        const range = selection.getRangeAt(0);

        // Check if we are inside another block to prevent nesting issues
        let container = range.commonAncestorContainer;
        if (container.nodeType === Node.TEXT_NODE) {
            container = container.parentNode;
        }

        // If inside a p tag or similar, try to insert after it
        if (container.closest('p, h1, h2, h3, h4, .text-box, .text-box-blue, .graybox, .check')) {
            const block = container.closest('p, h1, h2, h3, h4, .text-box, .text-box-blue, .graybox, .check');
            block.after(grayBox);
        } else {
            range.insertNode(grayBox);
        }

        // Add line break after box
        const br = document.createElement('br');
        grayBox.after(br);

        // Move cursor inside the box
        const newRange = document.createRange();
        newRange.selectNodeContents(grayBox);
        selection.removeAllRanges();
        selection.addRange(newRange);
    } else {
        editor.appendChild(grayBox);
        editor.appendChild(document.createElement('br'));
    }

    autoSave();
}

// Insert check heading
function insertCheckHeading() {
    const editor = document.getElementById('editor');
    const selection = window.getSelection();

    // Create check heading
    const checkHeading = document.createElement('div');
    checkHeading.className = 'check';
    checkHeading.contentEditable = 'true';
    checkHeading.textContent = 'チェック見出し';

    // Insert at cursor or append
    if (selection.rangeCount > 0) {
        const range = selection.getRangeAt(0);
        range.insertNode(checkHeading);

        // Add line break after
        const br = document.createElement('br');
        checkHeading.after(br);

        // Move cursor after
        range.setStartAfter(br);
        range.collapse(true);
        selection.removeAllRanges();
        selection.addRange(range);
    } else {
        editor.appendChild(checkHeading);
        editor.appendChild(document.createElement('br'));
    }

    // Focus on the heading
    checkHeading.focus();

    // Select placeholder text
    const textRange = document.createRange();
    textRange.selectNodeContents(checkHeading);
    selection.removeAllRanges();
    selection.addRange(textRange);

    autoSave();
}

// Insert link
function insertLink() {
    const selection = window.getSelection();

    if (selection.rangeCount === 0) {
        alert('リンクにするテキストを選択してください。');
        return;
    }

    const range = selection.getRangeAt(0);
    const selectedText = range.toString();

    if (!selectedText) {
        alert('リンクにするテキストを選択してください。');
        return;
    }

    // Prompt for URL
    const url = prompt('リンク先のURLを入力してください:', 'https://');

    if (!url || url === 'https://') {
        return;
    }

    // Create link element
    const link = document.createElement('a');
    link.href = url;
    link.textContent = selectedText;
    link.target = '_blank';
    link.rel = 'noopener noreferrer';

    // Replace selection with link
    range.deleteContents();
    range.insertNode(link);

    // Move cursor after the link
    range.setStartAfter(link);
    range.collapse(true);
    selection.removeAllRanges();
    selection.addRange(range);

    autoSave();
}



// Clear formatting from selected text (line-based)
function clearFormatting() {
    const selection = window.getSelection();
    const editor = document.getElementById('editor');

    if (selection.rangeCount === 0) {
        return;
    }

    // Save current state to undo stack
    saveToUndoStack();

    const range = selection.getRangeAt(0);

    // If no selection, clear formatting of the current block element
    if (range.collapsed) {
        let node = range.startContainer;

        // Find the parent block element
        while (node && node !== editor) {
            if (node.nodeType === Node.ELEMENT_NODE &&
                (node.tagName === 'H2' || node.tagName === 'H3' || node.tagName === 'H4' ||
                    node.classList.contains('text-box') || node.classList.contains('text-box-blue'))) {

                // Get text content
                const textContent = node.textContent;

                // Create plain paragraph
                const p = document.createElement('p');
                p.textContent = textContent;

                // Replace the element
                node.parentNode.replaceChild(p, node);

                // Move cursor into the new paragraph
                const newRange = document.createRange();
                newRange.setStart(p, 0);
                newRange.collapse(true);
                selection.removeAllRanges();
                selection.addRange(newRange);

                autoSave();
                return;
            }
            node = node.parentNode;
        }

        return;
    }

    // For selected text, extract contents and strip formatting
    const fragment = range.extractContents();
    const tempDiv = document.createElement('div');
    tempDiv.appendChild(fragment);

    // Recursively remove all span.red, span.gray, strong, b tags
    function unwrapFormattingTags(element) {
        const toUnwrap = element.querySelectorAll('span.red, span.gray, strong, b, em, i');
        toUnwrap.forEach(tag => {
            const parent = tag.parentNode;
            while (tag.firstChild) {
                parent.insertBefore(tag.firstChild, tag);
            }
            parent.removeChild(tag);
        });
    }

    unwrapFormattingTags(tempDiv);

    // Create a new fragment from the cleaned content
    const cleanedFragment = document.createDocumentFragment();
    while (tempDiv.firstChild) {
        cleanedFragment.appendChild(tempDiv.firstChild);
    }

    // Insert the cleaned fragment back
    range.insertNode(cleanedFragment);

    autoSave();
}

// ===== Code Editor Modal =====

// Format HTML with line breaks
function formatHTML(html) {
    // Add line breaks after closing block tags
    const blockTags = ['h2', 'h3', 'h4', 'p', 'div', 'img', 'br'];
    let formatted = html;

    blockTags.forEach(tag => {
        // Add line break after closing tags
        formatted = formatted.replace(new RegExp(`</${tag}>`, 'gi'), `</${tag}>\n`);
        // Add line break after self-closing img tags
        if (tag === 'img' || tag === 'br') {
            formatted = formatted.replace(new RegExp(`<${tag}([^>]*)>`, 'gi'), `<${tag}$1>\n`);
        }
    });

    // Clean up multiple consecutive line breaks
    formatted = formatted.replace(/\n{3,}/g, '\n\n');

    return formatted.trim();
}

// View and edit code for selected range
function showCodeEditorModal() {
    const selection = window.getSelection();
    let savedRange = null;

    let container = document.createElement('div');
    let targetNode = null;
    let isOuterHTML = false;

    if (selection.rangeCount === 0 || selection.isCollapsed) {
        // If no selection, try to get the current line/block
        if (selection.rangeCount > 0) {
            const range = selection.getRangeAt(0);
            let currentNode = range.startContainer;
            if (currentNode.nodeType === Node.TEXT_NODE) {
                currentNode = currentNode.parentElement;
            }

            // Find the closest block element or box
            // Priority: surrounding box > heading/p
            targetNode = currentNode.closest('.text-box, .text-box-blue, .graybox, .check, p, h1, h2, h3, h4, div:not(.editor), li') || currentNode;

            // If we found a specific target node (not just the editor div), use its Outer HTML
            if (targetNode && targetNode.id !== 'editor') {
                isOuterHTML = true;
                // We use outerHTML to allow editing the tag attributes and class
                container.textContent = targetNode.outerHTML; // Store raw HTML in textContent to avoid parsing
            } else {
                alert('コード表示する範囲を選択してください。');
                return;
            }
        } else {
            alert('コード表示する範囲を選択してください。');
            return;
        }
    } else {
        // Range selection
        savedRange = selection.getRangeAt(0).cloneRange();

        // Check if the common ancestor is a box
        let commonAncestor = savedRange.commonAncestorContainer;
        if (commonAncestor.nodeType === Node.TEXT_NODE) {
            commonAncestor = commonAncestor.parentElement;
        }

        const box = commonAncestor.closest('.text-box, .text-box-blue, .graybox, .check');
        if (box) {
            // If selection is inside a box, edit the WHOLE box without confirmation
            targetNode = box;
            isOuterHTML = true;
            container.textContent = targetNode.outerHTML;
        } else {
            container.appendChild(savedRange.cloneContents());
        }
    }

    // Get HTML content
    let html;
    if (isOuterHTML) {
        html = formatHTML(container.textContent);
    } else {
        html = formatHTML(container.innerHTML);
    }

    // Create modal
    const modal = document.createElement('div');
    modal.style.cssText = `
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.7);
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 10000;
    `;

    const modalContent = document.createElement('div');
    modalContent.style.cssText = `
        background: var(--bg-secondary);
        border-radius: 12px;
        padding: 2rem;
        width: 90%;
        max-width: 800px;
        max-height: 80vh;
        display: flex;
        flex-direction: column;
        box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
    `;

    const title = document.createElement('h3');
    title.textContent = isOuterHTML ? '要素のHTMLコード (外側も編集可能)' : '選択範囲のHTMLコード';
    title.style.cssText = `
        margin: 0 0 1rem 0;
        color: var(--text-primary);
        font-size: 1.25rem;
    `;

    const textarea = document.createElement('textarea');
    textarea.value = html;
    textarea.rows = 20; // Set number of visible rows
    textarea.style.cssText = `
        flex: 1;
        font-family: 'Consolas', 'Monaco', monospace;
        font-size: 0.9rem;
        padding: 1rem;
        border: 1px solid var(--border-color);
        border-radius: 8px;
        background: var(--bg-primary);
        color: var(--text-primary);
        resize: vertical;
        outline: none;
        margin-bottom: 1rem;
        min-height: 400px;
    `;

    const buttonContainer = document.createElement('div');
    buttonContainer.style.cssText = `
        display: flex;
        gap: 1rem;
        justify-content: flex-end;
    `;

    const applyBtn = document.createElement('button');
    applyBtn.textContent = '適用';
    applyBtn.style.cssText = `
        padding: 0.75rem 1.5rem;
        background: var(--accent-primary);
        color: white;
        border: none;
        border-radius: 8px;
        font-weight: 600;
        cursor: pointer;
        transition: all 0.2s;
    `;
    applyBtn.onmouseover = () => applyBtn.style.background = 'var(--accent-secondary)';
    applyBtn.onmouseout = () => applyBtn.style.background = 'var(--accent-primary)';

    const cancelBtn = document.createElement('button');
    cancelBtn.textContent = 'キャンセル';
    cancelBtn.style.cssText = `
        padding: 0.75rem 1.5rem;
        background: var(--bg-tertiary);
        color: var(--text-primary);
        border: 1px solid var(--border-color);
        border-radius: 8px;
        font-weight: 600;
        cursor: pointer;
        transition: all 0.2s;
    `;
    cancelBtn.onmouseover = () => cancelBtn.style.background = 'var(--bg-hover)';
    cancelBtn.onmouseout = () => cancelBtn.style.background = 'var(--bg-tertiary)';

    applyBtn.onclick = () => {
        const newHTML = textarea.value;

        if (targetNode && isOuterHTML) {
            // Replace the outerHTML of the target node
            // Since we can't set outerHTML directly to a text string effectively if it has multiple siblings or invalid structure easily without parsing
            // We create a temp div
            const tempDiv = document.createElement('div');
            tempDiv.innerHTML = newHTML;

            // Check if parsing worked
            if (tempDiv.childNodes.length === 0 && newHTML.trim() !== '') {
                // Maybe text only?
                targetNode.outerHTML = newHTML;
            } else {
                targetNode.replaceWith(...tempDiv.childNodes);
            }

        } else if (targetNode) {
            // Should not happen with current logic but fallback
            const tempDiv = document.createElement('div');
            tempDiv.innerHTML = newHTML;
            targetNode.replaceWith(...tempDiv.childNodes);
        } else if (savedRange) {
            // If we edited a selection range logic (fallback)
            // Use the saved range
            savedRange.deleteContents();

            const tempDiv = document.createElement('div');
            tempDiv.innerHTML = newHTML;

            const fragment = document.createDocumentFragment();
            while (tempDiv.firstChild) {
                fragment.appendChild(tempDiv.firstChild);
            }

            savedRange.insertNode(fragment);
        }

        document.body.removeChild(modal);
        autoSave();
    };

    cancelBtn.onclick = () => {
        document.body.removeChild(modal);
    };

    buttonContainer.appendChild(cancelBtn);
    buttonContainer.appendChild(applyBtn);

    modalContent.appendChild(title);
    modalContent.appendChild(textarea);
    modalContent.appendChild(buttonContainer);
    modal.appendChild(modalContent);
    document.body.appendChild(modal);

    // Focus textarea
    textarea.focus();
    textarea.select();
}

// ===== Text Formatting Functions =====

// Helper function to apply a class to all text nodes while preserving structure
function applyClassToTextNodes(element, className) {
    const walker = document.createTreeWalker(
        element,
        NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,
        null
    );

    const nodesToWrap = [];
    let node;

    while (node = walker.nextNode()) {
        if (node.nodeType === Node.TEXT_NODE && node.textContent.trim()) {
            nodesToWrap.push(node);
        }
    }

    // Wrap each text node in a span with the class
    nodesToWrap.forEach(textNode => {
        const span = document.createElement('span');
        span.className = className;
        span.textContent = textNode.textContent;
        textNode.parentNode.replaceChild(span, textNode);
    });
}

// Format selected text as gray
function formatGrayText() {
    const selection = window.getSelection();

    if (selection.rangeCount === 0 || selection.isCollapsed) {
        alert('テキストを選択してください。');
        return;
    }

    // Save current state to undo stack
    saveToUndoStack();

    const range = selection.getRangeAt(0);

    // Extract the selected content preserving structure
    const fragment = range.cloneContents();
    const tempDiv = document.createElement('div');
    tempDiv.appendChild(fragment);

    // Apply gray class to all text nodes while preserving structure
    applyClassToTextNodes(tempDiv, 'gray');

    // Create a document fragment to hold the formatted content
    const formattedFragment = document.createDocumentFragment();
    while (tempDiv.firstChild) {
        formattedFragment.appendChild(tempDiv.firstChild);
    }

    // Delete the selected content and insert the formatted fragment
    range.deleteContents();
    range.insertNode(formattedFragment);

    autoSave();
}

// Format selected text as red
function formatRedText() {
    const selection = window.getSelection();

    if (selection.rangeCount === 0 || selection.isCollapsed) {
        alert('テキストを選択してください。');
        return;
    }

    // Save current state to undo stack
    saveToUndoStack();

    const range = selection.getRangeAt(0);

    // Extract the selected content preserving structure
    const fragment = range.cloneContents();
    const tempDiv = document.createElement('div');
    tempDiv.appendChild(fragment);

    // Apply red class to all text nodes while preserving structure
    applyClassToTextNodes(tempDiv, 'red');

    // Create a document fragment to hold the formatted content
    const formattedFragment = document.createDocumentFragment();
    while (tempDiv.firstChild) {
        formattedFragment.appendChild(tempDiv.firstChild);
    }

    // Delete the selected content and insert the formatted fragment
    range.deleteContents();
    range.insertNode(formattedFragment);

    autoSave();
}

// Format selected text as black
function formatBlackText() {
    const selection = window.getSelection();

    if (selection.rangeCount === 0 || selection.isCollapsed) {
        alert('テキストを選択してください。');
        return;
    }

    // Save current state to undo stack
    saveToUndoStack();

    const range = selection.getRangeAt(0);

    // Extract the selected content preserving structure
    const fragment = range.cloneContents();
    const tempDiv = document.createElement('div');
    tempDiv.appendChild(fragment);

    // Apply black class to all text nodes while preserving structure
    applyClassToTextNodes(tempDiv, 'black');

    // Create a document fragment to hold the formatted content
    const formattedFragment = document.createDocumentFragment();
    while (tempDiv.firstChild) {
        formattedFragment.appendChild(tempDiv.firstChild);
    }

    // Delete the selected content and insert the formatted fragment
    range.deleteContents();
    range.insertNode(formattedFragment);

    autoSave();
}

// Format selected text as bold (with toggle)
function formatBoldText() {
    const selection = window.getSelection();

    if (selection.rangeCount === 0 || selection.isCollapsed) {
        alert('テキストを選択してください。');
        return;
    }

    // Save current state to undo stack
    saveToUndoStack();

    // Use execCommand for bold which automatically handles toggle and undo/redo
    document.execCommand('bold', false, null);

    autoSave();
}

// Code editor toggle moved to top to fix autoSave dep
// (Removed duplicate)

// ===== Element Movement Functions =====

// Move element up
function moveElementUp() {
    const selection = window.getSelection();
    if (!selection.rangeCount) return;

    const editor = document.getElementById('editor');
    const range = selection.getRangeAt(0);
    let currentNode = range.startContainer;

    // If we're in a text node, get its parent
    if (currentNode.nodeType === Node.TEXT_NODE) {
        currentNode = currentNode.parentElement;
    }

    // Find the closest block element
    const element = currentNode.closest('p, h1, h2, h3, h4, .text-box, .text-box-blue, .graybox, .check, div:not(#editor)');

    if (!element || element === editor) return;

    // Get the previous sibling (skip text nodes that are just whitespace)
    let previousSibling = element.previousElementSibling;

    // Skip BR elements
    while (previousSibling && previousSibling.tagName === 'BR') {
        previousSibling = previousSibling.previousElementSibling;
    }

    if (!previousSibling) return; // Already at the top

    // Save to undo stack before making changes
    saveToUndoStack();

    // Move the element before its previous sibling
    element.parentNode.insertBefore(element, previousSibling);

    // Restore selection/cursor to the moved element
    const newRange = document.createRange();
    newRange.selectNodeContents(element);
    newRange.collapse(true);
    selection.removeAllRanges();
    selection.addRange(newRange);

    autoSave();
}

// Move element down
function moveElementDown() {
    const selection = window.getSelection();
    if (!selection.rangeCount) return;

    const editor = document.getElementById('editor');
    const range = selection.getRangeAt(0);
    let currentNode = range.startContainer;

    // If we're in a text node, get its parent
    if (currentNode.nodeType === Node.TEXT_NODE) {
        currentNode = currentNode.parentElement;
    }

    // Find the closest block element
    const element = currentNode.closest('p, h1, h2, h3, h4, .text-box, .text-box-blue, .graybox, .check, div:not(#editor)');

    if (!element || element === editor) return;

    // Get the next sibling (skip text nodes that are just whitespace)
    let nextSibling = element.nextElementSibling;

    // Skip BR elements
    while (nextSibling && nextSibling.tagName === 'BR') {
        nextSibling = nextSibling.nextElementSibling;
    }

    if (!nextSibling) return; // Already at the bottom

    // Save to undo stack before making changes
    saveToUndoStack();

    // Move the element after its next sibling
    if (nextSibling.nextSibling) {
        element.parentNode.insertBefore(element, nextSibling.nextSibling);
    } else {
        element.parentNode.appendChild(element);
    }

    // Restore selection/cursor to the moved element
    const newRange = document.createRange();
    newRange.selectNodeContents(element);
    newRange.collapse(true);
    selection.removeAllRanges();
    selection.addRange(newRange);

    autoSave();
}


 export.js

// ===== Export Functions =====

// Helper function to download file
function downloadFile(blob, filename) {
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
}

// Sanitize filename
function sanitizeFilename(filename) {
    return filename.replace(/[<>:"/\\|?*]/g, '_');
}

// Convert blob to base64
function blobToBase64(blob) {
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onloadend = () => resolve(reader.result);
        reader.onerror = reject;
        reader.readAsDataURL(blob);
    });
}

// Process content to embed images as base64
async function processContentForExport(content) {
    const tempDiv = document.createElement('div');
    tempDiv.innerHTML = content;

    // Find all images with data-image-id
    const images = tempDiv.querySelectorAll('img[data-image-id]');

    for (const img of images) {
        const imageId = parseInt(img.getAttribute('data-image-id'));
        try {
            const imageData = await getImage(imageId);
            if (imageData && imageData.blob) {
                // Convert blob to base64
                const base64 = await blobToBase64(imageData.blob);
                img.src = base64;
                // Remove data-image-id attribute for export
                img.removeAttribute('data-image-id');
            }
        } catch (error) {
            console.error('Failed to load image for export:', imageId, error);
        }
    }

    return tempDiv.innerHTML;
}

// Export note as HTML
async function exportNoteAsHTML(noteId) {
    const note = await getNote(noteId);
    if (!note) return;

    // Process content to embed images as base64
    const processedContent = await processContentForExport(note.content);

    const html = `<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${escapeHtml(note.title)}</title>
    <style>
        body {
            font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            max-width: 900px;
            margin: 0 auto;
            padding: 2rem;
            line-height: 1.7;
            color: #333;
            background: #ffffff;
        }
        h1 {
            color: #1a1a2e;
            text-align: center;
            padding-bottom: 0.75rem;
            margin-bottom: 2rem;
            font-size: 2rem;
        }
        h2 {
            font-size: 1.6rem;
            color: #333;
            background: #f5f6f7;
            padding: 20px;
            margin-bottom: 40px;
            font-weight: 600;
        }
        h3 {
            border-left: 7px solid #888;
            border-right: 1px solid #ddd;
            border-top: 1px solid #ddd;
            border-bottom: 1px solid #ddd;
            font-size: 1.4rem;
            padding: 11px 20px;
            margin-bottom: 40px;
            font-weight: 600;
            color: #333;
        }
        h4 {
            border-top: 2px solid #ddd;
            border-bottom: 2px solid #ddd;
            margin-bottom: 40px;
            font-size: 1.2rem;
            padding: 9px 10px;
            font-weight: 600;
            color: #333;
        }
        .text-box {
            background: #f3f4f6;
            border: 1px solid #ddd;
            border-radius: 2px;
            padding: 1rem;
            margin: 1rem 0;
        }
        .text-box-blue {
            background: rgba(99, 102, 241, 0.1);
            border: 1px solid rgba(99, 102, 241, 0.3);
            border-radius: 2px;
            padding: 1rem;
            margin: 1rem 0;
        }
        /* Gray Box (User Custom) */
        .graybox {
            background-color: rgba(250, 250, 250, 0.48);
            outline: 1px solid rgba(228, 228, 228, 0.87);
            color: #444;
            overflow: auto;
            display: block;
            padding: 20px 20px 25px;
            margin-bottom: 40px;
            line-height: 1.7;
            border-radius: 2px;
        }
        .graybox p {
            margin-bottom: 0;
        }
        /* Check Heading */
        .check {
            position: relative;
            padding-left: 32px;
            margin: 32px 0 40px;
            font-weight: 600;
            font-size: 1.2rem;
            color: #333;
            line-height: 1.5;
        }
        .check::before {
            content: "";
            position: absolute;
            left: 0;
            top: 50%;
            width: 14px;
            height: 8px;
            border-left: 3px solid #4b6cb7;
            border-bottom: 3px solid #4b6cb7;
            transform: translateY(-65%) rotate(-45deg);
        }
        img {
            max-width: 100%;
            height: auto;
            border-radius: 8px;
            margin: 1rem 0;
        }
        p {
            margin-bottom: 40px;
        }
        a {
            color: #6366f1;
            text-decoration: underline;
        }
    </style>
</head>
<body>
    <h1>${escapeHtml(note.title)}</h1>
    ${processedContent}
</body>
</html>`;

    const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
    downloadFile(blob, `${sanitizeFilename(note.title)}.html`);
}

// Export note as PDF
async function exportNotePDF(noteId) {
    const note = await getNote(noteId);
    if (!note) return;

    // Process content to embed images as base64
    const processedContent = await processContentForExport(note.content);

    // Create a temporary print window
    const printWindow = window.open('', '_blank');

    const html = `<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>${escapeHtml(note.title)}</title>
    <style>
        @media print {
            @page {
                margin-top: 00mm;
                margin-bottom: 10mm;
                margin-left: 20mm;
                margin-right: 20mm;
            }
            body {
                padding-top: 0;
                margin-top: 0;
            }
        }
        body {
            font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 2rem;
            line-height: 1.7;
            color: #333;
            background: #ffffff;
        }
        h1 {
            color: #1a1a2e;
            text-align: center;
            padding-bottom: 0.75rem;
            margin-bottom: 2rem;
            font-size: 2rem;
        }
        h2 {
            font-size: 1.6rem;
            color: #333;
            background: #f5f6f7;
            padding: 20px;
            margin-bottom: 40px;
            font-weight: 600;
            -webkit-print-color-adjust: exact;
            print-color-adjust: exact;
        }
        h3 {
            border-left: 7px solid #888;
            border-right: 1px solid #ddd;
            border-top: 1px solid #ddd;
            border-bottom: 1px solid #ddd;
            font-size: 1.4rem;
            padding: 11px 20px;
            margin-bottom: 40px;
            font-weight: 600;
            color: #333;
            -webkit-print-color-adjust: exact;
            print-color-adjust: exact;
        }
        h4 {
            border-top: 2px solid #ddd;
            border-bottom: 2px solid #ddd;
            margin-bottom: 1.62em;
            font-size: 1.2rem;
            padding: 9px 10px;
            font-weight: 600;
            color: #333;
            -webkit-print-color-adjust: exact;
            print-color-adjust: exact;
        }
        .text-box {
            background: #f3f4f6;
            border: 1px solid #ddd;
            border-radius: 8px;
            padding: 1rem;
            margin: 1rem 0;
            -webkit-print-color-adjust: exact;
            print-color-adjust: exact;
        }
        .text-box-blue {
            background: rgba(99, 102, 241, 0.1);
            border: 1px solid rgba(99, 102, 241, 0.3);
            border-radius: 8px;
            padding: 1rem;
            margin: 1rem 0;
            -webkit-print-color-adjust: exact;
            print-color-adjust: exact;
        }
        img {
            max-width: 100%;
            height: auto;
            border-radius: 8px;
            margin: 1rem 0;
        }
        p {
            margin-bottom: 1rem;
        }
        a {
            color: #6366f1;
            text-decoration: underline;
        }
    </style>
</head>
<body>
    <h1>${escapeHtml(note.title)}</h1>
    ${processedContent}
</body>
</html>`;

    printWindow.document.write(html);
    printWindow.document.close();

    // Wait for content to load, then print
    printWindow.onload = function () {
        printWindow.print();
        // Close after printing or canceling
        printWindow.onafterprint = function () {
            printWindow.close();
        };
    };
}

// Export all notes as ZIP
async function exportAllNotes() {
    if (typeof JSZip === 'undefined') {
        alert('JSZipライブラリが読み込まれていません。');
        return;
    }

    const notes = await getAllNotes();
    if (notes.length === 0) {
        alert('エクスポートするノートがありません。');
        return;
    }

    const zip = new JSZip();

    for (const note of notes) {
        const html = `<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>${escapeHtml(note.title)}</title>
</head>
<body>
    <h1>${escapeHtml(note.title)}</h1>
    ${note.content}
</body>
</html>`;
        zip.file(`${sanitizeFilename(note.title)}.html`, html);
    }

    const blob = await zip.generateAsync({ type: 'blob' });
    downloadFile(blob, 'notes.zip');
}

 image.js

// ===== Image Storage Functions =====

// Save image to IndexedDB
function saveImage(blob, noteId) {
    console.log('saveImage called with:', { blob, noteId, currentNoteId });

    if (!db) {
        console.error('Database not initialized');
        return Promise.reject(new Error('Database not initialized'));
    }

    return new Promise((resolve, reject) => {
        try {
            const transaction = db.transaction(['images'], 'readwrite');
            const store = transaction.objectStore('images');
            const image = {
                blob: blob,
                noteId: noteId || currentNoteId,
                createdAt: new Date().toISOString()
            };

            console.log('Saving image to IndexedDB:', image);
            const request = store.add(image);

            request.onsuccess = () => {
                console.log('Image saved successfully with ID:', request.result);
                resolve(request.result);
            };
            request.onerror = () => {
                console.error('Error saving image:', request.error);
                reject(request.error);
            };
        } catch (error) {
            console.error('Exception in saveImage:', error);
            reject(error);
        }
    });
}

// Get image from IndexedDB
function getImage(id) {
    console.log('getImage called with ID:', id);

    if (!db) {
        console.error('Database not initialized');
        return Promise.reject(new Error('Database not initialized'));
    }

    return new Promise((resolve, reject) => {
        try {
            const transaction = db.transaction(['images'], 'readonly');
            const store = transaction.objectStore('images');
            const request = store.get(id);

            request.onsuccess = () => {
                console.log('Image retrieved:', request.result);
                resolve(request.result);
            };
            request.onerror = () => {
                console.error('Error getting image:', request.error);
                reject(request.error);
            };
        } catch (error) {
            console.error('Exception in getImage:', error);
            reject(error);
        }
    });
}

// Delete all images for a note
function deleteImagesByNoteId(noteId) {
    console.log('deleteImagesByNoteId called with noteId:', noteId);

    if (!db) {
        console.error('Database not initialized');
        return Promise.reject(new Error('Database not initialized'));
    }

    return new Promise((resolve, reject) => {
        try {
            const transaction = db.transaction(['images'], 'readwrite');
            const store = transaction.objectStore('images');
            const index = store.index('noteId');
            const request = index.openCursor(IDBKeyRange.only(noteId));

            request.onsuccess = (event) => {
                const cursor = event.target.result;
                if (cursor) {
                    console.log('Deleting image:', cursor.value);
                    cursor.delete();
                    cursor.continue();
                } else {
                    console.log('All images deleted for note:', noteId);
                    resolve();
                }
            };
            request.onerror = () => {
                console.error('Error deleting images:', request.error);
                reject(request.error);
            };
        } catch (error) {
            console.error('Exception in deleteImagesByNoteId:', error);
            reject(error);
        }
    });
}

// ===== Image Insertion Functions =====

// Insert image into editor
async function insertImage(file) {
    console.log('insertImage called with file:', file);

    if (!file || !file.type.startsWith('image/')) {
        console.error('Invalid file type:', file);
        alert('有効な画像ファイルを選択してください。');
        return;
    }

    if (!currentNoteId) {
        console.error('No note selected');
        alert('ノートを選択してから画像を挿入してください。');
        return;
    }

    try {
        console.log('Starting image insertion process...');

        // Save image to IndexedDB
        const imageId = await saveImage(file, currentNoteId);
        console.log('Image saved with ID:', imageId);

        // Create blob URL for display
        const blobUrl = URL.createObjectURL(file);
        console.log('Blob URL created:', blobUrl);

        const img = document.createElement('img');
        img.src = blobUrl;
        img.setAttribute('data-image-id', imageId);
        img.style.maxWidth = '100%';
        img.style.height = 'auto';
        console.log('Image element created:', img);

        const editor = document.getElementById('editor');
        const selection = window.getSelection();

        if (selection.rangeCount > 0) {
            const range = selection.getRangeAt(0);
            range.insertNode(img);
            range.setStartAfter(img);
            range.collapse(true);
            console.log('Image inserted at cursor position');
        } else {
            editor.appendChild(img);
            console.log('Image appended to editor');
        }

        autoSave();
        console.log('Image insertion completed successfully');
    } catch (error) {
        console.error('Failed to insert image:', error);
        console.error('Error stack:', error.stack);
        alert('画像の挿入に失敗しました: ' + error.message);
    }
}

// Load images in editor content
async function loadImagesInEditor() {
    console.log('loadImagesInEditor called');

    const editor = document.getElementById('editor');
    const images = editor.querySelectorAll('img[data-image-id]');

    console.log('Found', images.length, 'images to load');

    for (const img of images) {
        const imageId = parseInt(img.getAttribute('data-image-id'));
        console.log('Loading image with ID:', imageId);

        try {
            const imageData = await getImage(imageId);
            if (imageData && imageData.blob) {
                const blobUrl = URL.createObjectURL(imageData.blob);
                img.src = blobUrl;
                console.log('Image loaded successfully:', imageId);
            } else {
                console.warn('Image data not found for ID:', imageId);
            }
        } catch (error) {
            console.error('Failed to load image:', imageId, error);
        }
    }
}

 main.js

// ===== Global Variables =====
let currentNoteId = null;
let currentFolderId = null;
let saveTimeout = null;
let collapsedFolders = new Set();
let isEditMode = false;

// ===== Theme Functions =====

// Initialize theme
function initTheme() {
    const savedTheme = localStorage.getItem('theme') || 'dark';
    document.documentElement.setAttribute('data-theme', savedTheme);
}

// Toggle theme
function toggleTheme() {
    const currentTheme = document.documentElement.getAttribute('data-theme');
    const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
    document.documentElement.setAttribute('data-theme', newTheme);
    localStorage.setItem('theme', newTheme);
}

// ===== Edit Mode Functions =====

// Toggle edit mode
function toggleEditMode() {
    isEditMode = !isEditMode;
    const toolbar = document.getElementById('floatingToolbar');
    const editModeBtn = document.getElementById('editModeBtn');

    if (isEditMode) {
        toolbar.classList.remove('hidden');
        editModeBtn.classList.add('active');
    } else {
        toolbar.classList.add('hidden');
        editModeBtn.classList.remove('active');
    }

    // Save state to localStorage
    localStorage.setItem('editMode', isEditMode);
}

// ===== Event Listeners =====

// New note button
document.getElementById('newNoteBtn').addEventListener('click', async () => {
    const noteId = await createNote(currentFolderId);
    await loadNote(noteId);
    await renderNotesList();
});

// New folder button
document.getElementById('newFolderBtn').addEventListener('click', async () => {
    const folderId = await createFolder();
    await renderFoldersAndNotes();
});

// Note title input
document.getElementById('noteTitle').addEventListener('input', () => {
    autoSave();
});

// Editor input
document.getElementById('editor').addEventListener('input', () => {
    autoSave();
});

// Image click handler - open in new tab
document.getElementById('editor').addEventListener('click', (e) => {
    if (e.target.tagName === 'IMG') {
        window.open(e.target.src, '_blank');
    }
});

// Paste handling - prevent pasting images as base64
document.getElementById('editor').addEventListener('paste', (e) => {
    const items = e.clipboardData.items;
    for (const item of items) {
        if (item.type.startsWith('image/')) {
            e.preventDefault();
            const file = item.getAsFile();
            insertImage(file);
            return;
        }
    }
});

// Link click handling (Ctrl+Click to open)
document.getElementById('editor').addEventListener('click', (e) => {
    if (e.target.tagName === 'A' && (e.ctrlKey || e.metaKey)) {
        e.preventDefault();
        window.open(e.target.href, '_blank', 'noopener,noreferrer');
    }
});

// Image insert button
document.getElementById('insertImageBtn').addEventListener('click', () => {
    document.getElementById('imageInput').click();
});

// Image upload button
document.getElementById('imageInput').addEventListener('change', (e) => {
    const file = e.target.files[0];
    if (file) {
        insertImage(file);
        e.target.value = ''; // Reset input
    }
});

// Code editor toggle button
document.getElementById('codeEditorBtn').addEventListener('click', () => {
    toggleCodeEditor();
});

// View code button
document.getElementById('viewCodeBtn').addEventListener('click', () => {
    showCodeEditorModal();
});



// Heading format buttons
document.getElementById('heading2Btn').addEventListener('click', () => {
    formatHeading(2);
});

document.getElementById('heading3Btn').addEventListener('click', () => {
    formatHeading(3);
});

document.getElementById('heading4Btn').addEventListener('click', () => {
    formatHeading(4);
});

// Paragraph format button
document.getElementById('paragraphBtn').addEventListener('click', () => {
    formatParagraph();
});

// Insert text box button
document.getElementById('insertBoxBtn').addEventListener('click', () => {
    insertTextBox();
});

// Insert blue text box button
document.getElementById('insertBoxBlueBtn').addEventListener('click', () => {
    insertTextBoxBlue();
});

// Insert gray box button
document.getElementById('insertGrayBoxBtn').addEventListener('click', () => {
    insertGrayBox();
});

// Insert check heading button
document.getElementById('insertCheckHeadingBtn').addEventListener('click', () => {
    insertCheckHeading();
});

// Insert empty line button
document.getElementById('insertEmptyLineBtn').addEventListener('click', () => {
    insertEmptyLine();
});

// Disable Ctrl+S (Browser Save)
document.addEventListener('keydown', (e) => {
    if ((e.ctrlKey || e.metaKey) && e.key === 's') {
        e.preventDefault();
        // Since auto-save is implemented, we can just notify or do nothing
        // autoSave() is called on input, so no need to call it explicitly here unless needed
    }
});

// Insert link button
document.getElementById('insertLinkBtn').addEventListener('click', () => {
    insertLink();
});

// Clear formatting button
document.getElementById('clearFormatBtn').addEventListener('click', () => {
    clearFormatting();
});

// Gray text button
document.getElementById('grayTextBtn').addEventListener('click', () => {
    formatGrayText();
});

// Red text button
document.getElementById('redTextBtn').addEventListener('click', () => {
    formatRedText();
});

// Black text button
document.getElementById('blackTextBtn').addEventListener('click', () => {
    formatBlackText();
});

// Bold text button
document.getElementById('boldTextBtn').addEventListener('click', () => {
    formatBoldText();
});

// Edit mode toggle
document.getElementById('editModeBtn').addEventListener('click', () => {
    toggleEditMode();
});

// Floating toolbar buttons
document.getElementById('floatingUndoBtn').addEventListener('click', () => {
    performUndo();
});

document.getElementById('floatingRedoBtn').addEventListener('click', () => {
    performRedo();
});

document.getElementById('floatingMoveUpBtn').addEventListener('click', () => {
    moveElementUp();
});

document.getElementById('floatingMoveDownBtn').addEventListener('click', () => {
    moveElementDown();
});

// Theme toggle
document.getElementById('themeToggle').addEventListener('click', () => {
    toggleTheme();
});

// Export button and menu
const exportBtn = document.getElementById('exportBtn');
const exportMenu = document.getElementById('exportMenu');

exportBtn.addEventListener('click', (e) => {
    e.stopPropagation();
    exportMenu.classList.toggle('hidden');
});

// Close export menu when clicking outside
document.addEventListener('click', (e) => {
    if (!exportBtn.contains(e.target) && !exportMenu.contains(e.target)) {
        exportMenu.classList.add('hidden');
    }
});

// Export HTML
document.getElementById('exportHTMLBtn').addEventListener('click', async () => {
    if (currentNoteId) {
        await exportNoteAsHTML(currentNoteId);
        exportMenu.classList.add('hidden');
    } else {
        alert('エクスポートするノートを選択してください。');
    }
});

// Export all notes
document.getElementById('exportAllBtn').addEventListener('click', async () => {
    await exportAllNotes();
    exportMenu.classList.add('hidden');
});

// Export PDF
document.getElementById('exportPDFBtn').addEventListener('click', async () => {
    if (currentNoteId) {
        await exportNotePDF(currentNoteId);
        exportMenu.classList.add('hidden');
    } else {
        alert('エクスポートするノートを選択してください。');
    }
});

// Folder select change
document.getElementById('folderSelect').addEventListener('change', async (e) => {
    if (!currentNoteId) return;

    const folderId = parseInt(e.target.value);
    if (folderId) {
        await updateNote(currentNoteId, { folderId });
        await renderFoldersAndNotes();
    }
});

// Sidebar Resize Logic
if (typeof setupSidebarResize === 'function') {
    setupSidebarResize();
} else {
    // Wait for ui.js to load if script order is tricky, but here modules are loaded.
    // However, ui.js functions might not be global unless attached to window or imported.
    // Since we are using standard script tags without type=module, functions are global.
    // But setupSidebarResize is defined in ui.js which loads BEFORE main.js? 
    // Check index.html: ui.js (259) -> main.js (262). Yes, it should be available.
}

// ===== Initialize App =====

// Initialize editor to use <p> tags instead of <div>
function initEditor() {
    const editor = document.getElementById('editor');

    // Set default paragraph separator to <p>
    document.execCommand('defaultParagraphSeparator', false, 'p');

    // Handle Enter key to ensure <p> tags are used
    editor.addEventListener('keydown', (e) => {
        if (e.key === 'Enter' && !e.shiftKey) {
            const selection = window.getSelection();
            if (!selection.rangeCount) return;

            const range = selection.getRangeAt(0);
            let currentNode = range.startContainer;

            // If we're in a text node, get its parent
            if (currentNode.nodeType === Node.TEXT_NODE) {
                currentNode = currentNode.parentElement;
            }

            // Check if we're in a heading or other special element
            const isInHeading = currentNode.closest('h1, h2, h3, h4, h5, h6');
            const isInTextBox = currentNode.closest('.text-box, .text-box-blue');

            if (isInHeading || isInTextBox) {
                // For headings and text boxes, create a new paragraph after
                e.preventDefault();

                const p = document.createElement('p');
                p.innerHTML = '<br>';

                const elementToInsertAfter = isInHeading || isInTextBox;
                elementToInsertAfter.parentNode.insertBefore(p, elementToInsertAfter.nextSibling);

                // Move cursor to the new paragraph
                const newRange = document.createRange();
                newRange.setStart(p, 0);
                newRange.collapse(true);
                selection.removeAllRanges();
                selection.addRange(newRange);

                autoSave();
                return;
            }

            // For normal cases, let the browser handle it with defaultParagraphSeparator
            // This will create <p> tags automatically
        }
    });
}

async function initApp() {
    try {
        initTheme();
        initEditor(); // Initialize editor settings

        // Restore collapsed folders state
        try {
            const savedCollapsed = localStorage.getItem('collapsedFolders');
            if (savedCollapsed) {
                collapsedFolders = new Set(JSON.parse(savedCollapsed));
            }
        } catch (e) {
            console.error('Failed to restore collapsed folders:', e);
        }

        // Restore edit mode state
        try {
            const savedEditMode = localStorage.getItem('editMode');
            if (savedEditMode === 'true') {
                isEditMode = true;
                document.getElementById('floatingToolbar').classList.remove('hidden');
                document.getElementById('editModeBtn').classList.add('active');
            }
        } catch (e) {
            console.error('Failed to restore edit mode:', e);
        }

        await initDB();
        await updateFolderSelect();
        await renderNotesList();

        // Restore last opened note
        const savedNoteId = localStorage.getItem('currentNoteId');
        if (savedNoteId) {
            const noteExists = await getNote(parseInt(savedNoteId));
            if (noteExists) {
                await loadNote(parseInt(savedNoteId));
            } else {
                localStorage.removeItem('currentNoteId');
                // Fallback to first note if saved note doesn't exist
                const notes = await getAllNotes();
                if (notes.length > 0) {
                    await loadNote(notes[0].id);
                } else {
                    showEmptyState();
                }
            }
        } else {
            const notes = await getAllNotes();
            if (notes.length > 0) {
                await loadNote(notes[0].id);
            } else {
                showEmptyState();
            }
        }
    } catch (error) {
        console.error('Failed to initialize app:', error);
        alert(`アプリケーションの初期化に失敗しました。\nエラー詳細: ${error.message || error}`);
    }
}

// Start the app when DOM is ready
if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initApp);
} else {
    initApp();
}

 ui.js

// ===== UI Functions =====

// Escape HTML helper
function escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
}

// Render folders and notes list
async function renderFoldersAndNotes() {
    setupViewMode(); // Initialize View Mode listeners
    const notesList = document.getElementById('notesList');
    const allFolders = await getAllFolders();

    // Create map for easy lookup
    const folderMap = new Map();
    allFolders.forEach(f => {
        f.children = [];
        folderMap.set(f.id, f);
    });

    // Build tree
    const rootFolders = [];
    allFolders.forEach(f => {
        if (f.parentId && folderMap.has(f.parentId)) {
            folderMap.get(f.parentId).children.push(f);
        } else {
            rootFolders.push(f);
        }
    });

    notesList.innerHTML = '';

    // Recursive render function
    const renderFolderTree = async (folder, level = 0) => {
        const notes = await getNotesByFolder(folder.id);

        // Hide Default folder if simple empty (no notes, no subfolders)
        if (folder.id === DEFAULT_FOLDER_ID && notes.length === 0 && (!folder.children || folder.children.length === 0)) {
            return null;
        }

        const folderContainer = document.createElement('div');
        folderContainer.className = 'folder-container';

        const folderElement = createFolderElement(folder, level);
        folderContainer.appendChild(folderElement);

        const contentContainer = document.createElement('div');
        contentContainer.className = 'folder-content';
        contentContainer.dataset.folderId = folder.id;

        if (collapsedFolders.has(folder.id)) {
            contentContainer.classList.add('collapsed');
            folderElement.querySelector('.folder-chevron').classList.add('collapsed');
        }

        // Render subfolders
        if (folder.children && folder.children.length > 0) {
            folder.children.sort((a, b) => a.timestamp - b.timestamp);
            for (const child of folder.children) {
                const childNode = await renderFolderTree(child, level + 1);
                if (childNode) {
                    contentContainer.appendChild(childNode);
                }
            }
        }

        // Render notes
        if (notes.length > 0) {
            const notesContainer = document.createElement('div');
            notesContainer.className = 'folder-notes';
            notesContainer.dataset.folderId = folder.id; // Keep strict for drag/drop if added later

            // Note: In new recursive struct, folder-notes is inside contentContainer
            notes.forEach(note => {
                const noteItem = createNoteElement(note);
                // Add indentation
                noteItem.style.paddingLeft = `${(level + 1) * 1.2 + 0.5}rem`;
                notesContainer.appendChild(noteItem);
            });
            contentContainer.appendChild(notesContainer);
        }

        folderContainer.appendChild(contentContainer);
        return folderContainer;
    };

    // Render roots
    rootFolders.sort((a, b) => a.id === DEFAULT_FOLDER_ID ? -1 : a.timestamp - b.timestamp);

    for (const folder of rootFolders) {
        const folderNode = await renderFolderTree(folder, 0);
        if (folderNode) {
            notesList.appendChild(folderNode);
        }
    }
}

// Create folder element
function createFolderElement(folder, level) {
    const div = document.createElement('div');
    div.className = 'folder-item';
    div.dataset.id = folder.id;
    div.style.paddingLeft = `${level * 1.2 + 0.5}rem`;

    const isCollapsed = collapsedFolders.has(folder.id);

    div.innerHTML = `
        <div class="folder-item-header">
            <div class="folder-item-left">
                <svg class="folder-chevron ${isCollapsed ? 'collapsed' : ''}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                    <polyline points="6 9 12 15 18 9"></polyline>
                </svg>
                <svg class="folder-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                    <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
                </svg>
                <div class="folder-item-title">${escapeHtml(folder.name)}</div>
            </div>
            <div class="folder-item-actions">
                <button class="note-action-btn add-subfolder" title="サブフォルダ作成">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                         <path d="M12 5v14M5 12h14"></path>
                    </svg>
                </button>
                ${folder.id !== DEFAULT_FOLDER_ID ? `
                    <button class="note-action-btn rename" title="名前を変更">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
                            <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
                        </svg>
                    </button>
                    <button class="note-action-btn delete" title="削除">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <polyline points="3 6 5 6 21 6"></polyline>
                            <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
                        </svg>
                    </button>
                ` : ''}
            </div>
        </div>
    `;

    // Toggle collapse
    const header = div.querySelector('.folder-item-header');
    header.addEventListener('click', (e) => {
        if (!e.target.closest('.note-action-btn')) {
            toggleFolder(folder.id);
        }
    });

    // Add Subfolder
    div.querySelector('.add-subfolder').addEventListener('click', async (e) => {
        e.stopPropagation();
        const subName = prompt('サブフォルダ名を入力してください:');
        if (subName) {
            await createFolder(subName, folder.id);
            await renderFoldersAndNotes();
        }
    });

    // Rename button
    if (folder.id !== DEFAULT_FOLDER_ID) {
        div.querySelector('.rename').addEventListener('click', (e) => {
            e.stopPropagation();
            startFolderRename(div, folder.id);
        });

        // Delete button
        div.querySelector('.delete').addEventListener('click', async (e) => {
            e.stopPropagation();
            if (confirm('このフォルダを削除しますか?\n(中のノートとサブフォルダは「未分類」または保持されます)')) {
                await deleteFolder(folder.id);
                await renderFoldersAndNotes();
            }
        });
    }

    return div;
}

// Toggle folder collapse
function toggleFolder(folderId) {
    if (collapsedFolders.has(folderId)) {
        collapsedFolders.delete(folderId);
    } else {
        collapsedFolders.add(folderId);
    }
    // Save state to localStorage
    localStorage.setItem('collapsedFolders', JSON.stringify([...collapsedFolders]));
    renderFoldersAndNotes();
}

// Start renaming a folder
function startFolderRename(folderElement, folderId) {
    const titleElement = folderElement.querySelector('.folder-item-title');
    const currentTitle = titleElement.textContent;

    const input = document.createElement('input');
    input.type = 'text';
    input.className = 'note-rename-input';
    input.value = currentTitle;

    titleElement.replaceWith(input);
    input.focus();
    input.select();

    const finishRename = async () => {
        const newTitle = input.value.trim() || '無題のフォルダ';
        await updateFolderName(folderId, newTitle);
        await renderFoldersAndNotes();
    };

    input.addEventListener('blur', finishRename);
    input.addEventListener('keydown', (e) => {
        if (e.key === 'Enter') {
            e.preventDefault();
            input.blur();
        } else if (e.key === 'Escape') {
            renderFoldersAndNotes();
        }
    });
}

// Render notes list (legacy - now using renderFoldersAndNotes)
async function renderNotesList() {
    await renderFoldersAndNotes();
}

// Create note element
function createNoteElement(note) {
    const div = document.createElement('div');
    div.className = 'note-item';
    if (currentNoteId === note.id) {
        div.classList.add('active');
    }
    div.dataset.id = note.id;

    const date = new Date(note.updatedAt || note.createdAt);
    const dateStr = date.toLocaleDateString('ja-JP', { month: 'short', day: 'numeric' });

    div.innerHTML = `
        <div class="note-item-header">
            <div class="note-item-title">${escapeHtml(note.title)}</div>
            <div class="note-item-actions">
                <button class="note-action-btn rename" title="名前を変更">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                        <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
                        <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
                    </svg>
                </button>
                <button class="note-action-btn delete" title="削除">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                        <polyline points="3 6 5 6 21 6"></polyline>
                        <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
                    </svg>
                </button>
            </div>
        </div>
        <div class="note-item-date">${dateStr}</div>
    `;

    // Click to select note
    div.addEventListener('click', (e) => {
        if (!e.target.closest('.note-action-btn')) {
            loadNote(note.id);
        }
    });

    // Rename button
    div.querySelector('.rename').addEventListener('click', (e) => {
        e.stopPropagation();
        startRename(div, note.id);
    });

    // Delete button
    div.querySelector('.delete').addEventListener('click', async (e) => {
        e.stopPropagation();
        if (confirm('このノートを削除しますか?')) {
            await deleteNote(note.id);
            if (currentNoteId === note.id) {
                currentNoteId = null;
                showEmptyState();
            }
            await renderNotesList();
        }
    });

    return div;
}

// Start renaming a note
function startRename(noteElement, noteId) {
    const titleElement = noteElement.querySelector('.note-item-title');
    const currentTitle = titleElement.textContent;

    const input = document.createElement('input');
    input.type = 'text';
    input.className = 'note-rename-input';
    input.value = currentTitle;

    titleElement.replaceWith(input);
    input.focus();
    input.select();

    const finishRename = async () => {
        const newTitle = input.value.trim() || '無題のノート';
        await updateNote(noteId, { title: newTitle });

        // If the renamed note is the current one, update tab title
        if (currentNoteId === noteId) {
            document.title = newTitle;
        }

        await renderNotesList();
    };

    input.addEventListener('blur', finishRename);
    input.addEventListener('keydown', (e) => {
        if (e.key === 'Enter') {
            e.preventDefault();
            input.blur();
        } else if (e.key === 'Escape') {
            renderNotesList();
        }
    });
}

// Load a note into the editor
async function loadNote(noteId) {
    const note = await getNote(noteId);
    if (!note) return;

    currentNoteId = noteId;
    localStorage.setItem('currentNoteId', noteId); // Save current note ID
    document.title = note.title; // Update tab title
    document.getElementById('noteTitle').value = note.title;

    const editor = document.getElementById('editor');
    // If content is empty or only whitespace, start with a paragraph tag
    if (!note.content || note.content.trim() === '') {
        editor.innerHTML = '<p><br></p>';
    } else {
        editor.innerHTML = note.content;
    }

    // Load images from IndexedDB using the image.js function
    await loadImagesInEditor();

    // Update folder select
    const folderSelect = document.getElementById('folderSelect');
    if (note.folderId) {
        folderSelect.value = note.folderId;
    } else {
        folderSelect.value = '';
    }

    // Highlight active note
    document.querySelectorAll('.note-item').forEach(item => {
        item.classList.remove('active');
    });
    const activeItem = document.querySelector(`[data-id="${noteId}"]`);
    if (activeItem) {
        activeItem.classList.add('active');
    }

    // Show editor and hide empty state
    document.querySelector('.editor-container').classList.add('active');
    document.getElementById('emptyState').classList.add('hidden');

    await renderNotesList();
    await updateFolderSelect();
}

// Show empty state
function showEmptyState() {
    document.querySelector('.editor-container').classList.remove('active');
    document.getElementById('emptyState').classList.remove('hidden');
    document.getElementById('noteTitle').value = '';
    document.getElementById('editor').innerHTML = '<p><br></p>';
    document.title = 'ノート管理'; // Reset tab title
    localStorage.removeItem('currentNoteId'); // Clear saved note ID
}

// Update folder select options
async function updateFolderSelect() {
    const folderSelect = document.getElementById('folderSelect');
    const folders = await getAllFolders();

    folderSelect.innerHTML = '<option value="">フォルダ...</option>';
    folders.forEach(folder => {
        const option = document.createElement('option');
        option.value = folder.id;
        option.textContent = folder.name;
        folderSelect.appendChild(option);
    });

    // Set current folder if note is loaded
    if (currentNoteId) {
        const note = await getNote(currentNoteId);
        if (note && note.folderId) {
            folderSelect.value = note.folderId;
        }
    }
}

// Sidebar Resize Logic
function setupSidebarResize() {
    const sidebar = document.getElementById('sidebar');
    const resizer = document.getElementById('sidebarResizer');

    // Restore width
    const savedWidth = localStorage.getItem('sidebarWidth');
    if (savedWidth) {
        sidebar.style.width = `${savedWidth}px`;
    }

    let isResizing = false;

    resizer.addEventListener('mousedown', (e) => {
        isResizing = true;
        resizer.classList.add('resizing');
        document.body.style.cursor = 'col-resize';
        document.body.style.userSelect = 'none'; // Prevent text selection
    });

    document.addEventListener('mousemove', (e) => {
        if (!isResizing) return;

        let newWidth = e.clientX;
        // Limits
        if (newWidth < 200) newWidth = 200;
        if (newWidth > 600) newWidth = 600;

        sidebar.style.width = `${newWidth}px`;
    });

    document.addEventListener('mouseup', () => {
        if (isResizing) {
            isResizing = false;
            resizer.classList.remove('resizing');
            document.body.style.cursor = '';
            document.body.style.userSelect = '';

            // Save width
            localStorage.setItem('sidebarWidth', sidebar.style.width.replace('px', ''));
        }
    });
}

// Setup View Mode
function setupViewMode() {
    const viewModeBtn = document.getElementById('viewModeBtn');
    const exitViewBtn = document.getElementById('exitViewBtn');

    if (viewModeBtn) {
        // Remove old listener to avoid duplicates if re-running
        const newBtn = viewModeBtn.cloneNode(true);
        viewModeBtn.parentNode.replaceChild(newBtn, viewModeBtn);

        newBtn.addEventListener('click', () => {
            document.body.classList.add('view-mode');
            if (exitViewBtn) exitViewBtn.classList.remove('hidden');
        });
    }

    if (exitViewBtn) {
        const newExitBtn = exitViewBtn.cloneNode(true);
        exitViewBtn.parentNode.replaceChild(newExitBtn, exitViewBtn);

        newExitBtn.addEventListener('click', () => {
            document.body.classList.remove('view-mode');
            newExitBtn.classList.add('hidden');
        });
    }

    // Keyboard shortcut (Esc)
    document.addEventListener('keydown', (e) => {
        if (e.key === 'Escape' && document.body.classList.contains('view-mode')) {
            document.body.classList.remove('view-mode');
            if (exitViewBtn) exitViewBtn.classList.add('hidden');
        }
    });
}

 style.css

/* ===== CSS Variables & Reset ===== */
:root {
    --transition-fast: 0.15s ease;
    --transition-normal: 0.3s ease;
    --transition-slow: 0.5s ease;
}

/* Light Theme */
[data-theme="light"] {
    --bg-primary: #ffffff;
    --bg-secondary: #f9fafb;
    --bg-tertiary: #f3f4f6;
    --bg-hover: #e5e7eb;
    --accent-primary: #6366f1;
    --accent-secondary: #8b5cf6;
    --accent-gradient: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
    --text-primary: #1f2937;
    --text-secondary: #6b7280;
    --text-muted: #9ca3af;
    --border-color: rgba(0, 0, 0, 0.1);
    --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.1);
    --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.15);
    --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.2);
    --glass-bg: rgba(255, 255, 255, 0.8);
    --glass-border: rgba(0, 0, 0, 0.1);
}

/* Dark Theme (Default) */
[data-theme="dark"] {
    --bg-primary: #0f0f1a;
    --bg-secondary: #1a1a2e;
    --bg-tertiary: #252538;
    --bg-hover: #2d2d42;
    --accent-primary: #6366f1;
    --accent-secondary: #8b5cf6;
    --accent-gradient: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
    --text-primary: #e4e4e7;
    --text-secondary: #a1a1aa;
    --text-muted: #71717a;
    --border-color: rgba(255, 255, 255, 0.1);
    --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3);
    --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4);
    --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.5);
    --glass-bg: rgba(26, 26, 46, 0.7);
    --glass-border: rgba(255, 255, 255, 0.1);
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    background: var(--bg-primary);
    color: var(--text-primary);
    overflow: hidden;
    height: 100vh;
}

/* ===== App Container ===== */
.app-container {
    display: flex;
    flex-direction: column;
    height: 100vh;
}

/* ===== Header ===== */
.app-header {
    background: var(--glass-bg);
    backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--glass-border);
    box-shadow: var(--shadow-sm);
    position: sticky;
    top: 0;
    z-index: 100;
}

.header-content {
    max-width: 1400px;
    margin: 0 auto;
    padding: 0.75rem 1.5rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.app-title {
    font-size: 1.25rem;
    font-weight: 700;
    background: var(--accent-gradient);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0;
    writing-mode: horizontal-tb;
    white-space: nowrap;
    flex-shrink: 0;
}

.title-icon {
    width: 24px;
    height: 24px;
    stroke: var(--accent-primary);
    -webkit-text-fill-color: initial;
}

.header-actions {
    display: flex;
    gap: 0.4rem;
    align-items: center;

    overflow-x: visible;
    /* Changed from auto to visible to prevent clipping dropdown */
    overflow-y: visible;
    /* Changed from hidden to visible */
    flex-wrap: nowrap;
}

.formatting-toolbar {
    display: flex;
    gap: 0.5rem;
    padding-right: 0.5rem;
    border-right: 1px solid var(--border-color);
    margin-right: 0.5rem;
}

.icon-btn {
    width: 27px;
    /* Reduced from 34 */
    height: 30px;
    /* Reduced from 34 */
    border: none;
    background: var(--bg-tertiary);
    color: var(--text-secondary);
    border-radius: 6px;
    /* Slightly tighter radius */
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all var(--transition-fast);
    border: 1px solid var(--border-color);
}

.icon-btn:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
    transform: translateY(-1px);
    /* More subtle hover */
    box-shadow: var(--shadow-sm);
}

.icon-btn svg {
    width: 16px;
    /* Reduced from 20 */
    height: 16px;
}

/* Text Button (for h2, h3, h4) */
.text-btn {
    min-width: 27px;
    height: 30px;
    padding: 0 0.55rem;
    border: none;
    background: var(--bg-tertiary);
    color: var(--text-secondary);
    border-radius: 8px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all var(--transition-fast);
    border: 1px solid var(--border-color);
    font-weight: 600;
    font-size: 0.7rem;
}

.text-btn:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
    transform: translateY(-2px);
    box-shadow: var(--shadow-sm);
}

/* Text + Icon Button (for HTML編集) */
.text-icon-btn {
    height: 34px;
    padding: 0 0.875rem;
    border: none;
    background: var(--bg-tertiary);
    color: var(--text-secondary);
    border-radius: 8px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    transition: all var(--transition-fast);
    border: 1px solid var(--border-color);
    font-weight: 500;
    font-size: 0.875rem;
}

.text-icon-btn:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
    transform: translateY(-2px);
    box-shadow: var(--shadow-sm);
}

.text-icon-btn svg {
    width: 18px;
    height: 18px;
}

/* Compact Action Buttons (New Note, New Folder) */
.compact-action-btn {
    width: 30px;
    height: 30px;
    border: none;
    background: var(--bg-tertiary);
    color: var(--text-secondary);
    border-radius: 8px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    transition: all var(--transition-fast);
    border: 1px solid var(--border-color);
}

.compact-action-btn:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
    transform: translateY(-2px);
    box-shadow: var(--shadow-sm);
}

.compact-action-btn:active {
    transform: translateY(0);
}

.compact-action-btn svg {
    width: 20px;
    height: 20px;
}

.compact-action-btn .plus-icon {
    position: absolute;
    bottom: -2px;
    right: -2px;
    width: 16px;
    height: 16px;
    background: var(--accent-primary);
    color: white;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
    font-weight: bold;
    line-height: 1;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}

/* ===== Main Layout ===== */
.main-layout {
    display: flex;
    flex: 1;
    overflow: hidden;
}

/* ===== Sidebar ===== */
.sidebar {
    width: 280px;
    /* Default, will be overridden by style attribute */
    background: var(--bg-secondary);
    border-right: 1px solid var(--border-color);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    position: relative;
    /* For resizer */
    min-width: 200px;
    max-width: 600px;
    flex-shrink: 0;
}

.sidebar-resizer {
    position: absolute;
    top: 0;
    right: 0;
    width: 4px;
    height: 100%;
    cursor: col-resize;
    background: transparent;
    transition: background var(--transition-fast);
    z-index: 10;
}

.sidebar-resizer:hover,
.sidebar-resizer.resizing {
    background: var(--accent-primary);
}

.sidebar-header {
    padding: 1rem;
    border-bottom: 1px solid var(--border-color);
    display: flex;
    gap: 0.5rem;
}

.new-note-btn,
.new-folder-btn {
    flex: 1;
    width: auto;
    height: 40px;
    padding: 0;
    background: var(--accent-gradient);
    color: white;
    border: none;
    border-radius: 8px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all var(--transition-normal);
    box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
}

.new-note-btn:hover,
.new-folder-btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 20px rgba(99, 102, 241, 0.4);
}

.new-note-btn:active,
.new-folder-btn:active {
    transform: translateY(0);
}

.new-note-btn svg,
.new-folder-btn svg {
    width: 20px;
    height: 20px;
}

.notes-list {
    flex: 1;
    overflow-y: auto;
    padding: 0.5rem;
}

/* Custom Scrollbar */
.notes-list::-webkit-scrollbar,
.editor::-webkit-scrollbar {
    width: 8px;
}

.notes-list::-webkit-scrollbar-track,
.editor::-webkit-scrollbar-track {
    background: transparent;
}

.notes-list::-webkit-scrollbar-thumb,
.editor::-webkit-scrollbar-thumb {
    background: var(--bg-hover);
    border-radius: 4px;
}

.notes-list::-webkit-scrollbar-thumb:hover,
.editor::-webkit-scrollbar-thumb:hover {
    background: var(--text-muted);
}

/* Note Item */
.note-item {
    padding: 0.25rem 0.5rem;
    /* Reduced padding */
    margin-bottom: 0.1rem;
    /* Reduced margin */
    background: var(--bg-tertiary);
    border: 1px solid var(--border-color);
    border-radius: 6px;
    /* Slightly smaller radius */
    cursor: pointer;
    transition: all var(--transition-fast);
    position: relative;
    font-size: 0.85rem;
    /* Slightly smaller text */
}

.note-item:hover {
    background: var(--bg-hover);
    transform: translateX(4px);
    border-color: var(--accent-primary);
}

.note-item.active {
    background: var(--accent-gradient);
    border-color: transparent;
    box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
}

.note-item-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.5rem;
}

.note-item-title {
    font-weight: 500;
    font-size: 0.875rem;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 1;
}

.note-item.active .note-item-title {
    color: white;
}

.note-item-actions {
    display: flex;
    gap: 0.25rem;
    opacity: 0;
    transition: opacity var(--transition-fast);
}

.note-item:hover .note-item-actions {
    opacity: 1;
}

.note-action-btn {
    width: 24px;
    height: 24px;
    border: none;
    background: rgba(255, 255, 255, 0.1);
    color: var(--text-secondary);
    border-radius: 4px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all var(--transition-fast);
}

.note-action-btn:hover {
    background: rgba(255, 255, 255, 0.2);
    color: var(--text-primary);
}

.note-action-btn.delete:hover {
    background: #ef4444;
    color: white;
}

.note-action-btn svg {
    width: 12px;
    height: 12px;
}

.note-item-date {
    display: none;
}

/* ===== Main Content ===== */
.main-content {
    flex: 1;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    position: relative;
    /* Added for view mode absolute elements */
    transition: width var(--transition-normal);
}

/* Editor Container */
.editor-container {
    flex: 1;
    display: flex;
    flex-direction: column;
    background: var(--bg-primary);
    overflow-y: auto;
    /* Scroll parent */
}

/* Scrollable Container Content */
.editor-container.active {
    display: flex;
}

.editor-header {
    padding: 1.5rem 0rem 0.5rem;
    /* Reduced bottom padding, no horiz padding here (in wrapper) */
    border-bottom: none;
    /* Remove border */
    background: transparent;
    /* Transparent */
    display: flex;
    justify-content: center;
    /* Center wrapper */
    flex-shrink: 0;
}

.content-centered-wrapper {
    width: 100%;
    max-width: 900px;
    padding: 0 2rem;
    /* Match editor padding */
    display: flex;
    flex-direction: column;
}

.note-title-input {
    width: 100%;
    padding: 0.4rem 0;
    background: transparent;
    border: none;
    color: var(--text-primary);
    font-size: 2rem;
    /* Larger title */
    font-weight: 700;
    outline: none;
    margin-bottom: 10px;
    line-height: 1.3;
    text-align: center;
}

.note-title-input::placeholder {
    color: var(--text-muted);
}

/* ===== View Mode / Focus Mode ===== */
body.view-mode .app-header {
    display: none;
}

body.view-mode .sidebar {
    display: none;
}

body.view-mode .floating-toolbar {
    display: none;
}

/* Exit Button */
.exit-view-btn {
    position: absolute;
    top: 1rem;
    right: 1.5rem;
    z-index: 50;
    background: rgba(0, 0, 0, 0.05);
    border: none;
    cursor: pointer;
    border-radius: 50%;
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-secondary);
    transition: all var(--transition-fast);
}

.exit-view-btn:hover {
    background: rgba(0, 0, 0, 0.1);
    color: var(--text-primary);
}

.exit-view-btn.hidden {
    display: none;
}

body.view-mode .exit-view-btn.hidden {
    display: flex;
    /* Show when in view mode */
}

/* Toolbar Separator */
.toolbar-separator {
    width: 1px;
    height: 28px;
    background: var(--border-color);
    align-self: center;
}

/* Folder Select in Header */
.folder-select-header {
    padding: 0.3rem 0.35rem;
    background: var(--bg-tertiary);
    color: var(--text-secondary);
    border: 1px solid var(--border-color);
    border-radius: 6px;
    font-size: 0.6rem;
    font-weight: 500;
    cursor: pointer;
    transition: all var(--transition-fast);
    outline: none;
    min-width: 80px;
}

.folder-select-header:hover,
.folder-select-header:focus {
    background: var(--bg-hover);
    color: var(--text-primary);
    border-color: var(--accent-primary);
}

.folder-select-header option {
    background: var(--bg-secondary);
    color: var(--text-primary);
}

/* Editor */
.editor {
    flex: 1;
    padding: 0 2rem 4rem;
    /* Match header padding, extra bottom space */
    overflow-y: visible;
    /* Let parent scroll */
    background: transparent;
    color: var(--text-primary);
    font-size: 1rem;
    line-height: 1.7;
    outline: none;
    max-width: 900px;
    margin: 0 auto;
    width: 100%;
}

.editor:empty:before {
    content: attr(data-placeholder);
    color: var(--text-muted);
    pointer-events: none;
}

.editor img {
    max-width: 100%;
    height: auto;
    border-radius: 12px;
    margin: 1rem 0;
    box-shadow: var(--shadow-md);
    cursor: pointer;
    transition: box-shadow var(--transition-normal);
}

.editor img:hover {
    box-shadow: var(--shadow-lg);
}

.editor p {
    margin-bottom: 1rem;
}

/* Editor Headings */
.editor h2 {
    font-size: 1.6rem;
    color: #333;
    background: #f5f6f7;
    background-size: 4px 4px;
    padding: 20px 20px 20px;
    margin-bottom: 40px;
    font-weight: 600;
}

.editor h3 {
    border-left: 7px solid #888;
    border-right: 1px solid #ddd;
    border-top: 1px solid #ddd;
    border-bottom: 1px solid #ddd;
    font-size: 1.4rem;
    padding: 11px 20px;
    margin-bottom: 40px;
    font-weight: 600;
    color: var(--text-primary);
}

.editor h4 {
    border-top: 2px solid #ddd;
    border-bottom: 2px solid #ddd;
    margin-bottom: 40px;
    font-size: 1.2rem;
    padding: 9px 10px;
    font-weight: 600;
    color: var(--text-primary);
}

.editor p {
    margin-bottom: 40px;
}



/* Text Box - Gray */
.text-box {
    background: var(--bg-tertiary);
    border: 1px solid var(--border-color);
    border-radius: 2px;
    padding: 1rem;
    margin: 1rem 0;
    color: var(--text-primary);
    transition: all var(--transition-fast);
    outline: none;
}

.text-box:hover {
    background: var(--bg-hover);
    border-color: var(--text-muted);
}

.text-box:focus {
    background: var(--bg-hover);
    border-color: var(--accent-primary);
    box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.1);
}

/* Text Box - Blue */
.text-box-blue {
    background: rgba(99, 102, 241, 0.1);
    border: 1px solid rgba(99, 102, 241, 0.3);
    border-radius: 2px;
    padding: 1rem;
    margin: 1rem 0;
    color: var(--text-primary);
    transition: all var(--transition-fast);
    outline: none;
}

.text-box-blue:hover {
    background: rgba(99, 102, 241, 0.15);
    border-color: rgba(99, 102, 241, 0.5);
}

.text-box-blue:focus {
    background: rgba(99, 102, 241, 0.2);
    border-color: var(--accent-primary);
    box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
}

/* Gray Box (User Custom) */
.graybox {
    background-color: rgba(250, 250, 250, .48);
    outline: 1px solid rgba(228, 228, 228, .8705882353);
    color: #444;
    overflow: auto;
    display: block;
    padding: 20px 20px 25px;
    margin-bottom: 40px;
    line-height: 1.7;
    border-radius: 2px;
    /* Added for consistency */
}

.graybox p {
    margin-bottom: 0;
}

/* Check Heading (User Custom) */
.check {
    position: relative;
    padding-left: 32px;
    margin: 32px 0 40px;
    font-weight: 600;
    font-size: 1.2rem;
    color: var(--text-primary);
    /* Ensure visibility */
    line-height: 1.5;
}

/* Check Mark */
.check::before {
    content: "";
    position: absolute;
    left: 0;
    top: 50%;
    /* Adjusted centering */
    width: 14px;
    height: 8px;
    border-left: 3px solid #4b6cb7;
    border-bottom: 3px solid #4b6cb7;
    transform: translateY(-65%) rotate(-45deg);
    /* Adjusted transform */
}

/* Links */
.editor a {
    color: var(--accent-primary);
    text-decoration: underline;
    transition: color var(--transition-fast);
}

.editor a:hover {
    color: var(--accent-secondary);
}

/* Toolbar Divider */
.toolbar-divider {
    width: 1px;
    height: 24px;
    background: var(--border-color);
    margin: 0 0.25rem;
}

/* Text Formatting Styles */
.gray {
    color: #888888;
}

.red {
    color: #ef4444;
}

.black {
    color: #222;
}


/* Empty State */
.empty-state {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 2rem;
    color: var(--text-secondary);
}

.empty-state.hidden {
    display: none;
}

.empty-state svg {
    width: 80px;
    height: 80px;
    margin-bottom: 1.5rem;
    opacity: 0.3;
}

.empty-state h2 {
    font-size: 1.5rem;
    font-weight: 600;
    margin-bottom: 0.5rem;
    color: var(--text-primary);
}

.empty-state p {
    font-size: 1rem;
    max-width: 400px;
}

/* Rename Input */
.note-rename-input {
    width: 100%;
    padding: 0.25rem 0.5rem;
    background: rgba(255, 255, 255, 0.1);
    border: 1px solid var(--accent-primary);
    border-radius: 4px;
    color: var(--text-primary);
    font-size: 0.95rem;
    font-weight: 500;
    outline: none;
}

/* Animations */
@keyframes fadeIn {
    from {
        opacity: 0;
        transform: translateY(10px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.note-item {
    animation: fadeIn var(--transition-normal);
}

/* Responsive */
@media (max-width: 1200px) {
    .header-content {
        padding: 0.75rem 1rem;
    }

    .formatting-toolbar {
        gap: 0.25rem;
    }

    .header-actions {
        gap: 0.25rem;
    }
}

@media (max-width: 768px) {
    .sidebar {
        width: 240px;
    }

    .app-header {
        padding: 0.5rem;
    }

    .header-content {
        padding: 0.5rem;
        gap: 0.5rem;
    }

    /* Hide title text on small screens, keep only icon */
    .app-title {
        font-size: 0;
        gap: 0;
    }

    .title-icon {
        width: 28px;
        height: 28px;
    }

    .header-actions {
        flex: 1;
        justify-content: flex-start;
        gap: 0.25rem;
        overflow-x: auto;
        overflow-y: hidden;
        flex-wrap: nowrap;
    }

    .formatting-toolbar {
        gap: 0.25rem;
        padding-right: 0.25rem;
        margin-right: 0.25rem;
        flex-wrap: nowrap;
    }

    /* Make buttons slightly smaller on mobile */
    .icon-btn,
    .text-btn,
    .compact-action-btn {
        width: 36px;
        height: 36px;
        min-width: 36px;
        flex-shrink: 0;
    }

    .text-icon-btn {
        height: 36px;
        padding: 0 0.5rem;
        font-size: 0.75rem;
        flex-shrink: 0;
    }

    .text-icon-btn svg {
        width: 16px;
        height: 16px;
    }

    .editor {
        padding: 1.5rem;
    }

    .toolbar-separator {
        height: 24px;
        flex-shrink: 0;
    }

    /* Hide some less critical buttons on very narrow screens */
    #clearFormatBtn,
    #paragraphBtn {
        display: none;
    }

    .folder-select-header {
        min-width: 100px;
        font-size: 0.75rem;
        padding: 0.4rem 0.5rem;
    }
}

@media (max-width: 480px) {
    .sidebar {
        width: 200px;
    }

    .editor {
        padding: 1rem;
    }

    .header-actions {
        gap: 0.2rem;
    }

    .formatting-toolbar {
        gap: 0.2rem;
    }

    /* Further reduce button sizes on very small screens */
    .icon-btn,
    .text-btn,
    .compact-action-btn {
        width: 32px;
        height: 32px;
        min-width: 32px;
    }

    .icon-btn svg,
    .compact-action-btn svg {
        width: 16px;
        height: 16px;
    }

    .text-btn {
        font-size: 0.75rem;
        padding: 0 0.5rem;
    }

    .text-icon-btn {
        height: 32px;
        padding: 0 0.4rem;
        font-size: 0.7rem;
    }

    .text-icon-btn svg {
        width: 14px;
        height: 14px;
    }

    /* Hide more buttons on very small screens */
    #insertBoxBlueBtn,
    #codeEditorBtn {
        display: none;
    }

    .folder-select-header {
        min-width: 80px;
        font-size: 0.7rem;
    }
}

/* ===== Export Dropdown ===== */
.export-dropdown {
    position: relative;
    z-index: 1001;
    /* Ensure dropdown container is above other elements */
}

.export-menu {
    position: absolute;
    top: calc(100% + 0.5rem);
    right: 0;
    background: var(--bg-secondary);
    border: 1px solid var(--border-color);
    border-radius: 8px;
    box-shadow: var(--shadow-lg);
    min-width: 250px;
    overflow: visible;
    /* Changed from hidden to visible */
    z-index: 1002;
    /* Increased z-index to ensure it's above everything */
    backdrop-filter: blur(20px);
}

.export-menu.hidden {
    display: none;
}

.export-menu-item {
    width: 100%;
    padding: 0.875rem 1rem;
    background: transparent;
    border: none;
    color: var(--text-primary);
    font-size: 0.875rem;
    font-weight: 500;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    transition: all var(--transition-fast);
    text-align: left;
}

.export-menu-item:hover {
    background: var(--bg-hover);
    color: var(--accent-primary);
}

.export-menu-item svg {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
}

.export-menu-divider {
    height: 1px;
    background: var(--border-color);
    margin: 0.25rem 0;
}

/* ===== Folder Styles ===== */
.new-folder-btn {
    width: 100%;
    padding: 0.75rem 1rem;
    background: var(--bg-tertiary);
    color: var(--text-secondary);
    border: 1px solid var(--border-color);
    border-radius: 8px;
    font-size: 0.875rem;
    font-weight: 600;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    transition: all var(--transition-normal);
    margin-top: 0.5rem;
}

.new-folder-btn:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
    border-color: var(--accent-primary);
    transform: translateY(-2px);
}

.new-folder-btn svg {
    width: 18px;
    height: 18px;
}

.folder-item {
    margin-bottom: 0.25rem;
    animation: fadeIn var(--transition-normal);
}

.folder-item-header {
    padding: 0.5rem 0.75rem;
    background: var(--bg-tertiary);
    border: 1px solid var(--border-color);
    border-radius: 8px;
    cursor: pointer;
    transition: all var(--transition-fast);
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.folder-item-header:hover {
    background: var(--bg-hover);
    border-color: var(--accent-primary);
}

.folder-item-left {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex: 1;
    min-width: 0;
}

.folder-chevron {
    width: 16px;
    height: 16px;
    color: var(--text-secondary);
    transition: transform var(--transition-fast);
    flex-shrink: 0;
}

.folder-chevron.collapsed {
    transform: rotate(-90deg);
}

.folder-icon {
    width: 18px;
    height: 18px;
    color: var(--accent-primary);
    flex-shrink: 0;
}

.folder-item-title {
    font-weight: 600;
    font-size: 0.9rem;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.folder-item-actions {
    display: flex;
    gap: 0.25rem;
    opacity: 0;
    transition: opacity var(--transition-fast);
}

.folder-item-header:hover .folder-item-actions {
    opacity: 1;
}

.folder-notes {
    margin-left: 1.5rem;
    margin-top: 0;
    max-height: 1000px;
    overflow: hidden;
    transition: max-height var(--transition-normal), opacity var(--transition-normal);
}

.folder-notes.collapsed {
    max-height: 0;
    opacity: 0;
    margin-top: 0;
}

.folder-notes .note-item {
    margin-left: 0.5rem;
    border-left: 2px solid var(--border-color);
}

/* ===== Folder Select ===== */
.folder-select {
    padding: 0.625rem 1rem;
    background: var(--bg-tertiary);
    color: var(--text-secondary);
    border: 1px solid var(--border-color);
    border-radius: 8px;
    font-size: 0.875rem;
    font-weight: 500;
    cursor: pointer;
    transition: all var(--transition-fast);
    outline: none;
}

.folder-select:hover,
.folder-select:focus {
    background: var(--bg-hover);
    color: var(--text-primary);
    border-color: var(--accent-primary);
}

.folder-select option {
    background: var(--bg-secondary);
    color: var(--text-primary);
}

.folder-content {
    transition: all var(--transition-fast);
}

.folder-content.collapsed {
    display: none;
}

/* ===== Floating Edit Toolbar ===== */
.floating-toolbar {
    position: fixed;
    right: 20px;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    flex-direction: column;
    gap: 8px;
    background: var(--bg-secondary);
    border: 1px solid var(--border-color);
    border-radius: 12px;
    padding: 12px 8px;
    box-shadow: var(--shadow-md);
    z-index: 1000;
    transition: opacity 0.3s, transform 0.3s;
}

.floating-toolbar.hidden {
    opacity: 0;
    pointer-events: none;
    transform: translateY(-50%) translateX(20px);
}

.floating-btn {
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-tertiary);
    border: 1px solid var(--border-color);
    border-radius: 8px;
    cursor: pointer;
    color: var(--text-secondary);
    transition: all 0.2s;
}

.floating-btn:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
    transform: translateX(-2px);
    box-shadow: var(--shadow-sm);
}

.floating-btn:active {
    transform: translateX(0);
}

.floating-btn svg {
    width: 20px;
    height: 20px;
}

.floating-separator {
    height: 1px;
    background: var(--border-color);
    margin: 4px 0;
}

/* Edit Mode Button Active State */
.compact-action-btn.active {
    background: var(--accent-primary);
    color: white;
}

.compact-action-btn.active:hover {
    background: var(--accent-secondary);
}

 favicon.svg

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
  <!-- 付箋本体 -->
  <rect x="2" y="2" width="60" height="60" rx="8"
        fill="#ffe66d" stroke="#111111" stroke-width="3"/>

  <!-- 文字っぽい罫線 -->
  <path d="M10 22h44M10 32h44M10 42h34"
        stroke="#111111" stroke-width="4" stroke-linecap="round"/>

  <!-- 右下めくれ -->
  <path d="M44 62c8-2 14-8 16-16"
        fill="none" stroke="#111111" stroke-width="3" opacity="0.7"/>
</svg>