<script>
  import FileTreeRow from './FileTreeRow.svelte'
  import FileBreadcrumbs from './lib/FileBreadcrumbs.svelte'
  import FilePreviewModal from './lib/FilePreviewModal.svelte'
  import ConfirmModal from './lib/ConfirmModal.svelte'
  import { onMount, onDestroy } from 'svelte'
  import { canPreviewFile, needsBase64Preview, needsTextPreview } from './lib/fileUtils.js'

  // ===== Wails v2 API call helper =====
  // In production: window['go']['main']['App']['MethodName'](...args)
  // In dev without Wails: fallback to mock data
  function wailsCall(method, ...args) {
    try {
      if (window['go'] && window['go']['main'] && window['go']['main']['App']) {
        const fn = window['go']['main']['App'][method]
        if (typeof fn === 'function') {
          return fn(...args)
        }
      }
    } catch (e) {
      console.error('Wails call error:', method, e)
    }
    return Promise.reject(new Error('Wails not connected: ' + method))
  }

  // ===== State =====
  let sections = []
  let nodes = []
  let todayDashboard = null
  let activityFeed = []
  let activityOffset = 0
  let activityHasMore = true
  let activityLoading = false
  let caseActivity = []
  let version = ''
  let error = ''
  let selectedSection = ''
  let selectedNode = null
  let activeTab = 'overview'
  let notes = []
  let noteEditor = null
  let files = []
  let actions = []
  let worklog = []
  let worklogMinutes = ''
  let worklogSummary = ''
  let showCreateNode = false
  let newNodeTitle = ''
  let newNodeSection = 'clients'
  let newNodeTemplate = ''
  let templates = []
  let showCreateNote = false
  let newNoteTitle = ''
  let showCreateAction = false
  let newActionTitle = ''
  let newActionKind = 'open_url'
  let newActionData = ''
  let actionKinds = [
    { id: 'open_url', label: 'Открыть URL' },
    { id: 'open_file', label: 'Открыть файл' },
    { id: 'open_folder', label: 'Открыть папку' },
    { id: 'run_command', label: 'Запустить команду' },
    { id: 'run_script', label: 'Запустить скрипт' },
    { id: 'open_terminal', label: 'Открыть терминал' },
    { id: 'launch_app', label: 'Запустить приложение' },
  ]
  let loading = true
  let importing = false
  let importSummary = null
  let showImportDialog = false
  let pendingImportPath = ''
  let pendingImportMode = 'copy'
  let treeItems = []
  let expanded = {}
  let childrenMap = {}
  let loadingFiles = false
  let currentFolderId = null
  let folderStack = []
  let fileItems = []
  let previewItem = null
  let previewContent = ''
  let previewLoading = false
  let previewError = ''
  let clipboard = { items: [], mode: 'copy' }
  let selectedIds = []
  let dragIds = []

  let showConfirm = false
  let confirmTitle = ''
  let confirmMessage = ''
  let confirmDanger = false
  let confirmText = 'Удалить'
  let confirmAction = null
  let cancelAction = null

  let showRename = false
  let renameId = ''
  let renameValue = ''
  let renameError = ''

  // ===== Sync state =====
  let showSettings = false
  let syncStatus = null
  let syncLoading = false
  let syncServerUrl = ''
  let syncUsername = ''
  let syncPassword = ''
  let syncInterval = 0
  let syncResult = ''

  const tabs = [
    { id: 'overview', label: 'Обзор' },
    { id: 'notes', label: 'Заметки' },
    { id: 'files', label: 'Файлы' },
    { id: 'actions', label: 'Действия' },
    { id: 'worklog', label: 'Журнал' },
    { id: 'activity', label: 'Активность' },
  ]

  let unlistenDrop = null

  // ===== Lifecycle =====
  onMount(async () => {
    try {
      version = await wailsCall('VerstakVersion') || 'verstak-gui/v2'
      sections = await wailsCall('ListSections') || []
    } catch (e) {
      error = String(e)
      // Fallback: show sections from known list
      sections = [
        { id: 'today', label: 'Сегодня' },
        { id: 'inbox', label: 'Неразобранное' },
        { id: 'activity', label: 'Активность' },
        { id: 'clients', label: 'Клиенты' },
        { id: 'projects', label: 'Проекты' },
        { id: 'recipes', label: 'Рецепты' },
        { id: 'documents', label: 'Документы' },
        { id: 'archive', label: 'Архив' },
      ]
    }

    // Listen for file drops from OS file manager.
    if (window.runtime && window.runtime.EventsOn) {
      window.runtime.EventsOn('files-dropped', onFilesDropped)
      unlistenDrop = () => window.runtime.EventsOff('files-dropped')
    }

    window.addEventListener('keydown', handleKeydown)

    loading = false
    loadSyncStatus()
  })

  onDestroy(() => {
    if (unlistenDrop) unlistenDrop()
    window.removeEventListener('keydown', handleKeydown)
  })

  // ===== Section / Node selection =====
  async function selectSection(id) {
    selectedSection = id
    selectedNode = null
    activeTab = 'overview'
    notes = []
    files = []
    actions = []
    worklog = []
    showCreateNode = false
    error = ''
    todayDashboard = null
    activityFeed = []
    activityOffset = 0
    activityHasMore = true
    nodes = []
    try {
      if (id === 'today') {
        todayDashboard = await wailsCall('ListTodayView') || { cases: [] }
      } else if (id === 'activity') {
        activityFeed = await wailsCall('ListActivityFeed', 50, 0) || []
        activityOffset = activityFeed.length
        activityHasMore = activityFeed.length === 50
      } else {
        nodes = await wailsCall('ListNodesBySection', id) || []
      }
    } catch (e) {
      error = String(e)
      nodes = []
      todayDashboard = { cases: [] }
      activityFeed = []
    }
  }

  async function selectNode(node) {
    selectedNode = node
    activeTab = 'overview'
    notes = []
    files = []
    actions = []
    worklog = []
    treeItems = []
    expanded = {}
    childrenMap = {}
    fileItems = []
    folderStack = []
    currentFolderId = null
    previewItem = null
    previewContent = ''
    selectedIds = []
    dragIds = []
    noteEditor = null
    showCreateNode = false
    showCreateNote = false
    error = ''
    caseActivity = []
    await loadTabData(node.id)
  }

  async function loadTabData(nodeID) {
    try { notes = await wailsCall('ListNotes', nodeID) || [] } catch(e) {}
    try { files = await wailsCall('ListFiles', nodeID) || [] } catch(e) {}
    try { actions = await wailsCall('ListActions', nodeID) || [] } catch(e) {}
    try { worklog = await wailsCall('ListWorklog', nodeID) || [] } catch(e) {}
    try { caseActivity = await wailsCall('ListActivityByNode', nodeID, 50, 0) || [] } catch(e) {}
  }

  async function loadTree(nodeID) {
    loadingFiles = true
    try {
      treeItems = await wailsCall('ListItems', nodeID) || []
    } catch (e) {
      treeItems = []
    }
    loadingFiles = false
  }

  // ===== Folder navigation =====

  async function loadFolder(folderId) {
    loadingFiles = true
    try {
      let items = await wailsCall('ListItems', folderId) || []
      items.sort((a, b) => {
        if (a.type !== b.type) return a.type === 'folder' ? -1 : 1
        return (a.name || '').localeCompare(b.name || '')
      })
      fileItems = items
    } catch (e) {
      fileItems = []
    }
    loadingFiles = false
  }

  async function navigateToFolder(folderId) {
    if (!selectedNode) return
    // Get the folder node name for breadcrumbs
    try {
      const node = await wailsCall('GetNodeDetail', folderId)
      if (node) {
        folderStack = [...folderStack, { id: folderId, name: node.title }]
      }
    } catch (e) {
      folderStack = [...folderStack, { id: folderId, name: '...' }]
    }
    currentFolderId = folderId
    await loadFolder(folderId)
  }

  function navigateBack() {
    if (folderStack.length < 2) {
      // Go back to root
      folderStack = []
      currentFolderId = null
      loadFolder(selectedNode.id)
    } else {
      const target = folderStack[folderStack.length - 2]
      folderStack = folderStack.slice(0, -1)
      currentFolderId = target.id
      loadFolder(target.id)
    }
  }

  function navigateToBreadcrumb(index) {
    const target = folderStack[index]
    folderStack = folderStack.slice(0, index + 1)
    currentFolderId = target.id
    loadFolder(target.id)
  }

  // ===== File preview =====

  async function openPreview(item) {
    previewItem = item
    previewContent = ''
    previewError = ''
    previewLoading = true

    try {
      if (needsBase64Preview(item)) {
        previewContent = await wailsCall('GetFileBase64', item.fileId) || ''
      } else if (needsTextPreview(item)) {
        previewContent = await wailsCall('ReadFileText', item.fileId) || ''
      }
    } catch (e) {
      previewError = String(e)
    }
    previewLoading = false
  }

  function closePreview() {
    previewItem = null
    previewContent = ''
    previewError = ''
  }

  // ===== File operations =====

  async function createFile() {
    const name = prompt('Введите имя файла:')
    if (!name || !name.trim()) return
    try {
      const parentId = currentFolderId || selectedNode.id
      await wailsCall('CreateEmptyFile', parentId, name.trim())
      await loadFolder(parentId)
    } catch (e) {
      error = String(e)
    }
  }

  async function duplicateItem(id) {
    try {
      await wailsCall('DuplicateNode', id)
      const parentId = currentFolderId || selectedNode.id
      await loadFolder(parentId)
    } catch (e) {
      error = String(e)
    }
  }

  function renameItem(id) {
    const item = fileItems.find(x => x.id === id)
    if (item) openRename(item.id, item.name)
  }

  function cutItem(id) {
    clipboard = { items: [id], mode: 'cut' }
  }

  function copyItem(id) {
    clipboard = { items: [id], mode: 'copy' }
  }

  async function pasteItem() {
    if (clipboard.items.length === 0) return
    const targetId = currentFolderId || selectedNode.id
    try {
      if (clipboard.mode === 'copy') {
        for (const id of clipboard.items) {
          await wailsCall('DuplicateNode', id)
        }
      } else {
        for (const id of clipboard.items) {
          await wailsCall('MoveNode', id, targetId)
        }
      }
      clipboard = { items: [], mode: 'copy' }
      await loadFolder(targetId)
    } catch (e) {
      error = String(e)
    }
  }

  // ===== Selection =====

  function toggleSelection(id) {
    if (selectedIds.includes(id)) {
      selectedIds = selectedIds.filter(x => x !== id)
    } else {
      selectedIds = [...selectedIds, id]
    }
  }

  function selectOne(id) {
    selectedIds = [id]
  }

  function selectAll() {
    selectedIds = fileItems.map(x => x.id)
  }

  function rangeSelect(id) {
    if (fileItems.length === 0) return
    const lastId = selectedIds.length > 0 ? selectedIds[selectedIds.length - 1] : fileItems[0].id
    const lastIdx = fileItems.findIndex(x => x.id === lastId)
    const curIdx = fileItems.findIndex(x => x.id === id)
    if (lastIdx === -1 || curIdx === -1) return
    const start = Math.min(lastIdx, curIdx)
    const end = Math.max(lastIdx, curIdx)
    const range = fileItems.slice(start, end + 1).map(x => x.id)
    const set = new Set(selectedIds)
    range.forEach(x => set.add(x))
    selectedIds = [...set]
  }

  function clearSelection() {
    selectedIds = []
  }

  function getTargetIds(ids) {
    return ids.length > 0 ? ids : fileItems.map(x => x.id)
  }

  async function deleteSelected() {
    const ids = getTargetIds(selectedIds)
    const label = ids.length === 1 && fileItems.find(x => x.id === ids[0])?.type === 'folder' ? 'папку' : `файлов (${ids.length})`
    openConfirm({
      title: 'Удаление',
      message: `Удалить ${label}?`,
      confirmText: 'Удалить',
      danger: true,
      onConfirm: async () => {
        for (const id of ids) {
          try {
            await wailsCall('DeleteFileOrFolder', id)
          } catch (e) { error = String(e) }
        }
        selectedIds = []
        const reloadId = currentFolderId || selectedNode.id
        await loadFolder(reloadId)
      }
    })
  }

  function cutSelected() {
    const ids = getTargetIds(selectedIds)
    clipboard = { items: ids, mode: 'cut' }
    selectedIds = []
  }

  function copySelected() {
    const ids = getTargetIds(selectedIds)
    clipboard = { items: ids, mode: 'copy' }
    selectedIds = []
  }

  // ===== Drag-and-drop =====

  function onDragStart(e, id) {
    const ids = selectedIds.includes(id) ? selectedIds : [id]
    dragIds = ids
    e.dataTransfer.effectAllowed = 'move'
    e.dataTransfer.setData('text/plain', ids.join(','))
  }

  function onDragOver(e, folderId) {
    const item = fileItems.find(x => x.id === folderId)
    if (item && item.type === 'folder') {
      e.preventDefault()
      e.dataTransfer.dropEffect = 'move'
    }
  }

  async function onDrop(e, folderId) {
    e.preventDefault()
    if (dragIds.length === 0) return
    for (const id of dragIds) {
      try {
        await wailsCall('MoveNode', id, folderId)
      } catch (e) { error = String(e) }
    }
    dragIds = []
    selectedIds = []
    await loadFolder(currentFolderId || selectedNode.id)
  }

  // ===== Keyboard =====

  function handleKeydown(e) {
    if (activeTab !== 'files') return
    if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return

    if (e.ctrlKey || e.metaKey) {
      if (e.key === 'c' || e.key === 'C') { e.preventDefault(); copySelected() }
      else if (e.key === 'x' || e.key === 'X') { e.preventDefault(); cutSelected() }
      else if (e.key === 'v' || e.key === 'V') { e.preventDefault(); pasteItem() }
      else if (e.key === 'a' || e.key === 'A') { e.preventDefault(); selectAll() }
      else if (e.key === 'o' || e.key === 'O') { e.preventDefault(); openSelectedExternal() }
      else if (e.key === 'Enter') { e.preventDefault(); openSelected() }
    } else if (e.key === 'Enter') {
      e.preventDefault()
      openSelected()
    } else if (e.key === 'Delete' || e.key === 'Backspace') {
      if (previewItem) { e.preventDefault(); closePreview(); return }
      if (selectedIds.length > 0) { e.preventDefault(); deleteSelected(); return }
      // Backspace without selection → navigate up
      if (e.key === 'Backspace' && folderStack.length > 0) {
        e.preventDefault()
        navigateBack()
        return
      }
    } else if (e.key === 'Escape') {
      if (previewItem) { closePreview(); return }
      if (selectedIds.length > 0) { clearSelection(); return }
    } else if (e.key === 'F2') {
      e.preventDefault()
      openRenameForSelection()
    }
  }

  function openSelected() {
    if (selectedIds.length === 1) {
      const item = fileItems.find(x => x.id === selectedIds[0])
      if (item) {
        if (item.type === 'folder') {
          navigateToFolder(item.id)
        } else {
          openPreview(item)
        }
      }
    }
  }

  function openSelectedExternal() {
    if (selectedIds.length === 1) {
      const item = fileItems.find(x => x.id === selectedIds[0])
      if (item && item.fileId) {
        wailsCall('OpenFile', item.fileId)
      }
    }
  }

  // ===== Rename modal =====

  function openRename(id, currentName) {
    renameId = id
    renameValue = currentName
    renameError = ''
    showRename = true
  }

  function openRenameForSelection() {
    if (selectedIds.length === 1) {
      const item = fileItems.find(x => x.id === selectedIds[0])
      if (item) {
        openRename(item.id, item.name)
      }
    }
  }

  async function submitRename() {
    const name = renameValue.trim()
    if (!name) { renameError = 'Имя не может быть пустым'; return }
    // Validate name via backend
    try {
      await wailsCall('ValidateName', name)
    } catch (e) {
      renameError = 'Недопустимое имя'
      return
    }
    showRename = false
    renameId = ''
    try {
      await wailsCall('RenameNode', renameId, name)
      const parentId = currentFolderId || selectedNode.id
      await loadFolder(parentId)
    } catch (e) {
      error = String(e)
    }
  }

  function cancelRename() {
    showRename = false
    renameId = ''
    renameValue = ''
    renameError = ''
  }

  function onRenameKeydown(e) {
    if (e.key === 'Enter') submitRename()
    else renameError = ''
  }

  // ===== Confirm modal =====

  function openConfirm(opts) {
    confirmTitle = opts.title || 'Подтверждение'
    confirmMessage = opts.message || ''
    confirmDanger = opts.danger !== undefined ? opts.danger : true
    confirmText = opts.confirmText || 'Удалить'
    confirmAction = opts.onConfirm || null
    cancelAction = opts.onCancel || null
    showConfirm = true
  }

  function closeConfirm() {
    showConfirm = false
    confirmAction = null
    cancelAction = null
  }

  function handleConfirm() {
    if (confirmAction) confirmAction()
    closeConfirm()
  }

  function handleCancel() {
    if (cancelAction) cancelAction()
    closeConfirm()
  }

  // ===== Node creation =====
  function openCreateNode() {
    showCreateNode = true
    newNodeTitle = ''
    newNodeSection = selectedSection || 'clients'
    newNodeTemplate = ''
    wailsCall('ListTemplates').then(t => { templates = t || [] }).catch(() => { templates = [] })
  }
  function cancelCreateNode() { showCreateNode = false; newNodeTitle = '' }
  async function submitCreateNode() {
    if (!newNodeTitle.trim()) return
    try {
      let node
      if (newNodeTemplate) {
        node = await wailsCall('FromTemplate', '', 'case', newNodeTitle.trim(), newNodeSection, newNodeTemplate)
      } else {
        node = await wailsCall('CreateNode', '', 'case', newNodeTitle.trim(), newNodeSection)
      }
      showCreateNode = false
      newNodeTitle = ''
      newNodeTemplate = ''
      await selectSection(newNodeSection)
    } catch (e) { error = String(e) }
  }

  // ===== Notes =====
  function openCreateNote() { showCreateNote = true; newNoteTitle = '' }
  function cancelCreateNote() { showCreateNote = false; newNoteTitle = '' }
  async function submitCreateNote() {
    if (!newNoteTitle.trim() || !selectedNode) return
    try {
      const note = await wailsCall('CreateNote', selectedNode.id, newNoteTitle.trim())
      notes = [...notes, (note && note.id) ? note : { id: Date.now().toString(), title: newNoteTitle.trim(), createdAt: new Date().toISOString() }]
      showCreateNote = false
      newNoteTitle = ''
    } catch (e) {
      // Fallback: create note locally
      const newNote = { id: Date.now().toString(), title: newNoteTitle.trim(), createdAt: new Date().toISOString() }
      notes = [...notes, newNote]
      showCreateNote = false
      newNoteTitle = ''
    }
  }

  async function openNote(note) {
    if (noteEditor && noteEditor.dirty) {
      openConfirm({
        title: 'Несохранённые изменения',
        message: 'Закрыть редактор? Все несохранённые изменения будут потеряны.',
        confirmText: 'Закрыть',
        danger: false,
        onConfirm: async () => {
          await doOpenNote(note)
        }
      })
      return
    }
    await doOpenNote(note)
  }

  async function doOpenNote(note) {
    try {
      const content = await wailsCall('ReadNote', note.id)
      noteEditor = { id: note.id, title: note.title, content: content || '', dirty: false }
    } catch (e) {
      noteEditor = { id: note.id, title: note.title, content: '# ' + note.title + '\n\n', dirty: false }
    }
  }

  function closeNoteEditor() {
    if (noteEditor && noteEditor.dirty) {
      openConfirm({
        title: 'Несохранённые изменения',
        message: 'Закрыть редактор? Все несохранённые изменения будут потеряны.',
        confirmText: 'Закрыть',
        danger: false,
        onConfirm: () => { noteEditor = null }
      })
      return
    }
    noteEditor = null
  }

  function updateNoteContent(e) {
    if (noteEditor) { noteEditor.content = e.target.value; noteEditor.dirty = true }
  }

  async function saveCurrentNote() {
    if (!noteEditor) return
    try {
      await wailsCall('SaveNote', noteEditor.id, noteEditor.content)
      noteEditor.dirty = false
    } catch (e) {
      // Saved locally only
      noteEditor.dirty = false
    }
  }

  // ===== Worklog =====
  async function submitWorklog() {
    const mins = parseInt(worklogMinutes, 10)
    if (!worklogSummary.trim() || isNaN(mins) || mins <= 0 || !selectedNode) return
    try {
      const entry = await wailsCall('CreateWorklog', selectedNode.id, worklogSummary.trim(), mins)
      worklog = [...worklog, (entry && entry.id) ? entry : { id: Date.now().toString(), nodeId: selectedNode.id, summary: worklogSummary.trim(), minutes: mins, createdAt: new Date().toISOString() }]
    } catch (e) {
      worklog = [...worklog, { id: Date.now().toString(), nodeId: selectedNode.id, summary: worklogSummary.trim(), minutes: mins, createdAt: new Date().toISOString() }]
    }
    worklogSummary = ''
    worklogMinutes = ''
  }

  // ===== Files =====
  async function addFile() {
    const path = await wailsCall('PickFile')
    if (!path) return
    await startImport(selectedNode.id, path)
  }

  async function addFolder() {
    const path = await wailsCall('PickDirectory')
    if (!path) return
    await startImport(selectedNode.id, path)
  }

  async function startImport(nodeID, sourcePath) {
    importing = true
    try {
      const summary = await wailsCall('PreviewImport', sourcePath)
      importSummary = summary
      pendingImportPath = sourcePath
      showImportDialog = true
    } catch (e) {
      error = String(e)
    }
    importing = false
  }

  async function confirmImport(mode) {
    try {
      const result = mode === 'copy'
        ? await wailsCall('AddPathCopy', selectedNode.id, pendingImportPath)
        : await wailsCall('AddPathLink', selectedNode.id, pendingImportPath)
      showImportDialog = false
      importSummary = null
      folderStack = []
      currentFolderId = null
      await Promise.all([
        loadTabData(selectedNode.id),
        loadFolder(selectedNode.id),
      ])
    } catch (e) {
      error = String(e)
    }
  }

  function cancelImport() {
    showImportDialog = false
    importSummary = null
  }

  async function deleteFile({ id, type }) {
    const label = type === 'folder' ? 'папку' : 'файл'
    openConfirm({
      title: 'Удаление',
      message: `Удалить ${label}?`,
      confirmText: 'Удалить',
      danger: true,
      onConfirm: async () => {
        try {
          await wailsCall('DeleteFileOrFolder', id)
          files = files.filter(f => f.nodeId !== id)
          const reloadId = currentFolderId || selectedNode.id
          await loadFolder(reloadId)
        } catch (e) {
          error = String(e)
        }
      }
    })
  }

  async function openSelectedFile(fileID) {
    try {
      await wailsCall('OpenFile', fileID)
    } catch (e) {
      error = String(e)
    }
  }

  // ===== Drag-and-drop =====
  async function onFilesDropped(paths) {
    if (!paths || paths.length === 0) return
    if (!selectedNode) {
      error = 'Сначала выберите дело для добавления файлов'
      return
    }
    const path = paths[0]
    await startImport(selectedNode.id, path)
  }

  // ===== Helpers =====
  function tabClass(id) { return activeTab === id ? 'tab active' : 'tab' }
  function eventLabel(type) {
    const labels = {
      'note_created': 'Заметка создана',
      'note_updated': 'Заметка изменена',
      'file_added': 'Файл добавлен',
      'file_deleted': 'Файл удалён',
      'file_renamed': 'Файл переименован',
      'file_copied': 'Файл скопирован',
      'file_moved': 'Файл перемещён',
      'folder_added': 'Папка добавлена',
      'folder_deleted': 'Папка удалена',
      'folder_renamed': 'Папка переименована',
      'node_created': 'Дело создано',
      'node_updated': 'Дело изменено',
    }
    return labels[type] || type
  }
  function eventIcon(type) {
    if (type === 'note_created' || type === 'file_added' || type === 'folder_added' || type === 'node_created') return '+'
    if (type === 'file_deleted' || type === 'folder_deleted') return '×'
    if (type === 'file_renamed' || type === 'folder_renamed' || type === 'note_updated' || type === 'node_updated') return '~'
    if (type === 'file_copied') return '⧉'
    if (type === 'file_moved') return '→'
    return '•'
  }
  function formatTime(str) {
    if (!str) return ''
    try { return new Date(str).toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' }) } catch (e) { return '' }
  }
  function formatDate(str) {
    if (!str) return ''
    try { return new Date(str).toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' }) } catch (e) { return str }
  }
  function nodeKindLabel(kind) {
    const labels = { 'project': 'Проект', 'client': 'Клиент', 'document': 'Документ', 'recipe': 'Рецепт', 'archive': 'Архив', 'case': 'Дело' }
    return labels[kind] || kind || 'Дело'
  }
  function pluralize(n, one, few, many) {
    n = Math.abs(n) % 100
    if (n >= 5 && n <= 20) return many
    n %= 10
    if (n === 1) return one
    if (n >= 2 && n <= 4) return few
    return many
  }
  // ===== Actions =====
  function openCreateAction() {
    showCreateAction = true
    newActionTitle = ''
    newActionKind = 'open_url'
    newActionData = ''
  }
  function cancelCreateAction() { showCreateAction = false; newActionTitle = ''; newActionData = '' }
  async function submitCreateAction() {
    if (!newActionTitle.trim() || !newActionData.trim() || !selectedNode) return
    try {
      const action = await wailsCall('CreateAction', selectedNode.id, newActionKind, newActionTitle.trim(), newActionData.trim())
      if (action && action.id) {
        actions = [...actions, action]
      }
      showCreateAction = false
      newActionTitle = ''
      newActionData = ''
    } catch (e) { error = String(e) }
  }
  async function deleteAction(id) {
    try {
      await wailsCall('DeleteAction', id)
      actions = actions.filter(a => a.id !== id)
    } catch (e) { error = String(e) }
  }
  function actionKindLabel(kind) {
    const k = actionKinds.find(k => k.id === kind)
    return k ? k.label : kind
  }

  async function openNodeById(id) {
    try {
      const node = await wailsCall('GetNodeDetail', id)
      if (node) selectNode(node)
    } catch (e) {
      error = String(e)
    }
  }

  // ===== Sync =====
  async function loadSyncStatus() {
    try {
      syncStatus = await wailsCall('SyncStatus')
    } catch (e) {
      syncStatus = { configured: false, serverUrl: '', deviceId: '', unpushedOps: 0, lastSyncAt: '', syncInterval: 0 }
    }
  }

  function openSettings() {
    showSettings = true
    syncServerUrl = syncStatus?.serverUrl || ''
    syncUsername = ''
    syncPassword = ''
    syncInterval = syncStatus?.syncInterval || 0
    syncResult = ''
  }

  function closeSettings() {
    showSettings = false
    syncResult = ''
  }

  async function saveSyncConfig() {
    syncLoading = true
    syncResult = ''
    try {
      await wailsCall('SyncConfigure', syncServerUrl, syncUsername, syncPassword)
      if (syncInterval > 0) {
        await wailsCall('SyncSetInterval', syncInterval)
      }
      syncResult = 'ok'
      await loadSyncStatus()
    } catch (e) {
      syncResult = 'err: ' + String(e)
    }
    syncLoading = false
  }

  async function testConnection() {
    syncLoading = true
    syncResult = ''
    try {
      await wailsCall('SyncTestConnection', syncServerUrl, syncUsername, syncPassword)
      syncResult = 'connection ok'
    } catch (e) {
      syncResult = 'connection failed: ' + String(e)
    }
    syncLoading = false
  }

  async function runSyncNow() {
    syncLoading = true
    syncResult = ''
    try {
      const r = await wailsCall('SyncNow')
      syncResult = 'pushed ' + r.pushed + ', pulled ' + r.pulled + ' (rev ' + r.serverRevision + ')'
      await loadSyncStatus()
    } catch (e) {
      syncResult = 'err: ' + String(e)
    }
    syncLoading = false
  }
</script>

<div class="app">
  <!-- Sidebar -->
  <aside class="sidebar">
    <div class="sidebar-brand">
      <span class="logo">&#9874;</span>
      <span class="brand-name">Верстак</span>
    </div>
    <nav class="sidebar-nav">
      <div class="nav-group">
        <div class="nav-label">Разделы</div>
        {#each sections as section}
          <button class="nav-item {selectedSection === section.id ? 'selected' : ''}"
                  on:click={() => selectSection(section.id)}>
            {section.label}
          </button>
        {/each}
      </div>
      {#if selectedSection && selectedSection !== 'today' && selectedSection !== 'inbox' && selectedSection !== 'activity'}
        <div class="nav-group">
          <div class="nav-label">Дела {#if nodes.length > 0}({nodes.length}){/if}</div>
          {#each nodes as node}
            <button class="nav-item {selectedNode && selectedNode.id === node.id ? 'selected' : ''}"
                    on:click={() => selectNode(node)}>
              {node.title}
            </button>
          {/each}
          {#if nodes.length === 0}<div class="nav-empty">Нет дел</div>{/if}
        </div>
      {/if}
    </nav>
    <div class="sidebar-footer">
      <button class="sidebar-sync-btn" on:click={openSettings} title="Настройки синхронизации">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
        <span class="sync-dot" class:active={syncStatus?.configured}></span>
        <span class="sidebar-sync-label">Синхронизация</span>
      </button>
      <span class="version">{version}</span>
    </div>
  </aside>

  <!-- Main -->
  <main class="main">
    <header class="header">
      <div class="header-left">
        {#if selectedNode}
          <span class="crumb">{selectedNode.title}</span>
          <span class="crumb-type">{selectedNode.type}</span>
        {:else if selectedSection}
          <span class="crumb">{#each sections as s}{s.id === selectedSection ? s.label : ''}{/each}</span>
        {:else}
          <span class="crumb placeholder">Выберите раздел или дело</span>
        {/if}
      </div>
      <div class="header-right">
        {#if syncStatus?.configured}
          <button class="header-sync-btn" on:click={runSyncNow} disabled={syncLoading} title="Синхронизировать">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
            {#if syncStatus.unpushedOps > 0}
              <span class="sync-badge">{syncStatus.unpushedOps}</span>
            {/if}
          </button>
        {/if}
      </div>
    </header>

    {#if error}
      <div class="error-banner" on:click={() => error = ''}>
        {error}
        <button class="dismiss-btn" on:click|stopPropagation={() => error = ''} aria-label="Dismiss">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
            <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
          </svg>
        </button>
      </div>
    {/if}

    {#if noteEditor}
      <!-- Note editor -->
      <div class="note-editor">
        <div class="note-editor-header">
          <span class="note-title">{noteEditor.title}</span>
          {#if noteEditor.dirty}<span class="dirty-mark">●</span>{/if}
          <div class="note-editor-actions">
            <button class="btn btn-primary" on:click={saveCurrentNote}>Сохранить</button>
            <button class="btn" on:click={closeNoteEditor}>Закрыть</button>
          </div>
        </div>
        <textarea class="note-textarea" bind:value={noteEditor.content}
                  on:input={updateNoteContent} placeholder="Начните писать..."></textarea>
      </div>

    {:else if selectedNode}
      <!-- Tabs -->
      <div class="tabs">
        {#each tabs as tab}
          <button class={tabClass(tab.id)} on:click={() => { activeTab = tab.id; if (tab.id === 'files' && selectedNode && fileItems.length === 0 && !currentFolderId) loadFolder(selectedNode.id) }}>{tab.label}</button>
        {/each}
      </div>
      <div class="tab-content">
        {#if activeTab === 'overview'}
          <div class="overview">
            <h2>{selectedNode.title}</h2>
            <div class="meta-grid">
              <div class="meta-item"><span class="meta-label">Тип</span><span>{selectedNode.type}</span></div>
              <div class="meta-item"><span class="meta-label">Раздел</span><span>{selectedNode.section || '—'}</span></div>
              <div class="meta-item"><span class="meta-label">Создано</span><span>{formatDate(selectedNode.createdAt)}</span></div>
            </div>
            <div class="quick-actions">
              <button class="qa-btn" on:click={() => { activeTab = 'notes'; openCreateNote() }}>
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
                Новая заметка
              </button>
              <button class="qa-btn" on:click={() => { activeTab = 'files'; addFile() }}>
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
                Добавить файл
              </button>
              <button class="qa-btn" on:click={openCreateAction}>
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
                Добавить действие
              </button>
              <button class="qa-btn" on:click={() => activeTab = 'worklog'}>
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
                Записать время
              </button>
            </div>
            {#if notes.length > 0}
              <div class="recent-section">
                <h3>Последние заметки</h3>
                {#each notes.slice(0, 5) as note}
                  <div class="recent-note" on:click={() => openNote(note)}>
                    <span>{note.title}</span><span class="recent-date">{formatDate(note.createdAt)}</span>
                  </div>
                {/each}
              </div>
            {/if}
            {#if worklog.length > 0}
              <div class="recent-section">
                <h3>Последние записи</h3>
                {#each worklog.slice(0, 3) as e}
                  <div class="recent-entry">{e.summary} ({e.minutes} мин)</div>
                {/each}
              </div>
            {/if}
          </div>

        {:else if activeTab === 'notes'}
          <div class="notes-tab">
            <div class="tab-toolbar">
              <button class="btn btn-primary" on:click={openCreateNote}>+ Добавить заметку</button>
            </div>
            {#if showCreateNote}
              <div class="create-form">
                <input type="text" placeholder="Название заметки" bind:value={newNoteTitle}
                       on:keydown={(e) => e.key === 'Enter' && submitCreateNote()} />
                <div class="form-actions">
                  <button class="btn btn-primary" on:click={submitCreateNote}>Создать</button>
                  <button class="btn" on:click={cancelCreateNote}>Отмена</button>
                </div>
              </div>
            {/if}
            {#if notes.length === 0 && !showCreateNote}
              <div class="empty-state"><p>Нет заметок</p><p class="hint">Создайте первую заметку для этого дела.</p></div>
            {:else}
              <div class="notes-list">
                {#each notes as note}
                  <div class="note-card" on:click={() => openNote(note)}>
                    <div class="note-card-title">{note.title}</div>
                    <div class="note-card-date">{formatDate(note.createdAt)}</div>
                  </div>
                {/each}
              </div>
            {/if}
          </div>

        {:else if activeTab === 'files'}
          <div class="files-tab">
            <div class="tab-toolbar">
              <button class="btn btn-primary" on:click={addFile} disabled={importing}>+ Добавить файл</button>
              <button class="btn" on:click={addFolder} disabled={importing}>+ Добавить папку</button>
              <button class="btn" on:click={createFile}>+ Новый файл</button>
              {#if clipboard.items.length > 0}
                <button class="btn" on:click={pasteItem}>Вставить {clipboard.items.length}</button>
              {/if}
            </div>

            {#if loadingFiles}
              <div class="empty-state">
                <p>Загрузка...</p>
              </div>
            {:else}
              {#if folderStack.length > 0}
                <FileBreadcrumbs crumbs={[{ name: 'Файлы' }, ...folderStack]} on:navigate={(e) => {
                  const i = e.detail
                  if (i === 0) {
                    folderStack = []
                    currentFolderId = null
                    loadFolder(selectedNode.id)
                  } else {
                    navigateToBreadcrumb(i - 1)
                  }
                }}/>
                <button class="btn btn-sm back-btn" on:click={navigateBack}>
                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
                  Back
                </button>
              {:else}
                <FileBreadcrumbs crumbs={[{ name: 'Файлы' }]}/>
              {/if}

              {#if fileItems.length === 0}
                <div class="empty-state">
                  <div class="empty-icon">
                    <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
                      <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
                      <polyline points="14 2 14 8 20 8"/>
                    </svg>
                  </div>
                  <p>{folderStack.length > 0 ? 'В этой папке пока нет файлов' : 'В этом проекте пока нет файлов'}</p>
                  <p class="hint">Добавьте файл или папку, чтобы сохранить материалы проекта.</p>
                  <div class="empty-actions">
                    <button class="btn btn-primary" on:click={addFile}>Добавить файл</button>
                    <button class="btn" on:click={addFolder}>Добавить папку</button>
                  </div>
                </div>
              {:else}
                <div class="file-list">
                  {#each fileItems as item (item.id)}
                    <FileTreeRow
                      {item}
                      selected={selectedIds.includes(item.id)}
                      {onDragStart}
                      {onDragOver}
                      {onDrop}
                      on:navigate={(e) => navigateToFolder(e.detail)}
                      on:preview={(e) => openPreview(e.detail)}
                      on:openExternal={(e) => wailsCall('OpenFile', e.detail)}
                      on:showInFolder={(e) => wailsCall('OpenFolder', e.detail)}
                      on:delete={(e) => deleteFile(e.detail)}
                      on:rename={(e) => renameItem(e.detail.id)}
                      on:duplicate={(e) => duplicateItem(e.detail)}
                      on:cut={(e) => cutItem(e.detail)}
                      on:copy={(e) => copyItem(e.detail)}
                      on:selectOne={(e) => selectOne(e.detail)}
                      on:toggleSelect={(e) => toggleSelection(e.detail)}
                      on:rangeSelect={(e) => rangeSelect(e.detail)}
                    />
                  {/each}
                </div>
              {/if}
            {/if}

            {#if importing && !showImportDialog}
              <div class="empty-state"><p>Сканирование...</p></div>
            {/if}
          </div>

          {#if previewItem}
            <FilePreviewModal
              item={previewItem}
              content={previewContent}
              loading={previewLoading}
              error={previewError}
              on:close={closePreview}
              on:openExternal={(e) => wailsCall('OpenFile', e.detail)}
            />
          {/if}

        {:else if activeTab === 'actions'}
          <div class="actions-tab">
            <div class="tab-toolbar">
              <button class="btn btn-primary" on:click={openCreateAction}>+ Добавить действие</button>
            </div>
            {#if actions.length === 0}
              <div class="empty-state"><p>Действий пока нет</p></div>
            {:else}
              {#each actions as action}
                <div class="action-card">
                  <div class="action-info">
                    <span class="action-title">{action.title}</span>
                    <span class="action-type">{actionKindLabel(action.type)}</span>
                    <span class="action-data">{action.data}</span>
                  </div>
                  <div class="action-btns">
                    <button class="btn btn-sm" on:click={() => wailsCall('RunAction', action.id)}>Запустить</button>
                    <button class="btn btn-sm btn-danger" on:click={() => deleteAction(action.id)}>
                      <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
                    </button>
                  </div>
                </div>
              {/each}
            {/if}
          </div>

        {:else if activeTab === 'worklog'}
          <div class="worklog-tab">
            <div class="worklog-form">
              <input type="text" placeholder="Что сделано" bind:value={worklogSummary} />
              <input type="number" placeholder="Мин" bind:value={worklogMinutes} min="1" />
              <button class="btn btn-primary" on:click={submitWorklog}
                      disabled={!worklogSummary.trim() || !worklogMinutes}>Записать</button>
            </div>
            {#if worklog.length === 0}
              <div class="empty-state"><p>Записей работы пока нет</p></div>
            {:else}
              {#each worklog as e}
                <div class="worklog-entry">
                  <div>{e.summary}</div>
                  <div class="wl-meta">{e.minutes} мин · {formatDate(e.createdAt)}</div>
                </div>
              {/each}
            {/if}
          </div>

        {:else if activeTab === 'activity'}
          <div class="activity-tab">
            {#if caseActivity.length === 0}
              <div class="empty-state"><p>Активность пока не зафиксирована</p></div>
            {:else}
              <div class="activity-events">
                {#each caseActivity as ev}
                  <div class="activity-event">
                    <span class="activity-event-icon">{eventIcon(ev.eventType)}</span>
                    <span class="activity-event-title">{ev.title}</span>
                    <span class="activity-event-type">{eventLabel(ev.eventType)}</span>
                    {#if ev.targetType}<span class="activity-event-target">{ev.targetType}</span>{/if}
                    <span class="activity-event-time">{formatTime(ev.createdAt)}</span>
                  </div>
                {/each}
              </div>
            {/if}
          </div>
        {/if}
      </div>

    {:else if selectedSection === 'today' && todayDashboard}
      <div class="today-dashboard">
        <div class="today-header">
          <h2>Сегодня</h2>
          <span class="today-date">{todayDashboard.date}</span>
        </div>
        {#if todayDashboard.summary}
          <div class="today-summary">
            {#if todayDashboard.summary.changedCases > 0}<span class="summary-chip">{todayDashboard.summary.changedCases} {pluralize(todayDashboard.summary.changedCases, 'дело', 'дела', 'дел')}</span>{/if}
            {#if todayDashboard.summary.notes > 0}<span class="summary-chip">{todayDashboard.summary.notes} {pluralize(todayDashboard.summary.notes, 'заметка', 'заметки', 'заметок')}</span>{/if}
            {#if todayDashboard.summary.files > 0}<span class="summary-chip">{todayDashboard.summary.files} {pluralize(todayDashboard.summary.files, 'файл', 'файла', 'файлов')}</span>{/if}
          </div>
        {/if}

        {#if todayDashboard.groups && todayDashboard.groups.length > 0}
          {#each todayDashboard.groups as group}
            <div class="today-case">
              <div class="today-case-header" role="button" tabindex="0" on:click={() => openNodeById(group.nodeId)} on:keydown={(e) => e.key === 'Enter' && openNodeById(group.nodeId)}>
                <span class="today-case-title">{group.nodeTitle}</span>
                <span class="today-case-type">{nodeKindLabel(group.nodeKind)}</span>
                {#if group.events}<span class="today-case-count">{group.events.length} {pluralize(group.events.length, 'событие', 'события', 'событий')}</span>{/if}
                <span class="today-case-time">{formatTime(group.lastActivityAt)}</span>
              </div>
              {#if group.events && group.events.length > 0}
                <div class="today-events">
                  {#each group.events as ev}
                    <div class="today-event" role="button" tabindex="0" on:click={() => openNodeById(group.nodeId)} on:keydown={(e) => e.key === 'Enter' && openNodeById(group.nodeId)}>
                      <span class="today-event-icon">{eventIcon(ev.eventType)}</span>
                      <span class="today-event-title">{ev.title}</span>
                      <span class="today-event-type">{eventLabel(ev.eventType)}</span>
                      {#if ev.targetType}<span class="activity-event-target">{ev.targetType}</span>{/if}
                      <span class="today-event-time">{formatTime(ev.createdAt)}</span>
                    </div>
                  {/each}
                </div>
              {:else}
                <div class="today-events-empty">Изменён сегодня</div>
              {/if}
            </div>
          {/each}

          {#if todayDashboard.events && todayDashboard.events.length > 0}
            <div class="today-timeline">
              <h3>Лента за сегодня</h3>
              {#each todayDashboard.events as ev}
                <div class="timeline-event" role="button" tabindex="0" on:click={() => openNodeById(ev.nodeId)} on:keydown={(e) => e.key === 'Enter' && openNodeById(ev.nodeId)}>
                  <span class="timeline-dot"></span>
                  <span class="timeline-title">{ev.title}</span>
                  <span class="timeline-type">{eventLabel(ev.eventType)}</span>
                  <span class="timeline-time">{formatTime(ev.createdAt)}</span>
                </div>
              {/each}
            </div>
          {/if}
        {:else}
          <div class="today-empty">
            <p>Сегодня пока тихо</p>
            <p class="hint">Здесь появятся дела, заметки, файлы и действия, с которыми вы работали сегодня.</p>
          </div>
        {/if}
      </div>

    {:else if selectedSection === 'activity'}
      <div class="activity-feed">
        <div class="activity-feed-header">
          <h2>Активность</h2>
        </div>
        {#if activityFeed.length === 0}
          <div class="empty-state"><p>Активность пока не зафиксирована</p></div>
        {:else}
          <div class="activity-feed-events">
            {#each activityFeed as ev}
              <div class="activity-feed-event" role="button" tabindex="0" on:click={() => openNodeById(ev.nodeId)} on:keydown={(e) => e.key === 'Enter' && openNodeById(ev.nodeId)}>
                <span class="activity-feed-icon">{eventIcon(ev.eventType)}</span>
                <div class="activity-feed-body">
                  <span class="activity-feed-title">{ev.title}</span>
                  <div class="activity-feed-meta">
                    <span class="activity-feed-type">{eventLabel(ev.eventType)}</span>
                    {#if ev.targetType}<span class="activity-feed-target">{ev.targetType}</span>{/if}
                    <span class="activity-feed-time">{formatDate(ev.createdAt)} {formatTime(ev.createdAt)}</span>
                  </div>
                </div>
              </div>
            {/each}
          </div>
        {/if}
      </div>

    {:else}
      <div class="welcome">
        <h2>Верстак</h2>
        {#if loading}<p>Загрузка...</p>
        {:else if sections.length > 0}
          <p>Выберите раздел в боковой панели.</p>
          <p class="hint">Или создайте новое дело кнопкой «+».</p>
        {:else if error}<p class="error-text">Ошибка: {error}</p>{/if}
      </div>
    {/if}

    {#if !noteEditor && !selectedNode && selectedSection !== 'today' && selectedSection !== 'inbox' && selectedSection !== 'activity'}
      <div class="fab" on:click={openCreateNode} title="Добавить дело">+</div>
    {/if}

    {#if showCreateNode}
      <div class="modal-overlay" on:click|self={cancelCreateNode}>
        <div class="modal">
          <h3>Новое дело</h3>
          <div class="form-group">
            <label>Название</label>
            <input type="text" placeholder="Название дела" bind:value={newNodeTitle}
                   on:keydown={(e) => e.key === 'Enter' && submitCreateNode()} autofocus />
          </div>
          <div class="form-group">
            <label>Раздел</label>
            <select bind:value={newNodeSection}>
              {#each sections.filter(s => s.id !== 'today' && s.id !== 'inbox' && s.id !== 'activity') as s}
                <option value={s.id}>{s.label}</option>
              {/each}
            </select>
          </div>
          {#if templates.length > 0}
            <div class="form-group">
              <label>Шаблон (опционально)</label>
              <select bind:value={newNodeTemplate}>
                <option value="">Без шаблона</option>
                {#each templates as t}
                  <option value={t.name}>{t.name}{t.description ? ' — ' + t.description : ''}</option>
                {/each}
              </select>
            </div>
          {/if}
          <div class="modal-actions">
            <button class="btn btn-primary" on:click={submitCreateNode}>Создать</button>
            <button class="btn" on:click={cancelCreateNode}>Отмена</button>
          </div>
        </div>
      </div>
    {/if}

    {#if showCreateAction}
      <div class="modal-overlay" on:click|self={cancelCreateAction}>
        <div class="modal">
          <h3>Новое действие</h3>
          <div class="form-group">
            <label>Название</label>
            <input type="text" placeholder="Например: Открыть сайт" bind:value={newActionTitle}
                   on:keydown={(e) => e.key === 'Enter' && submitCreateAction()} autofocus />
          </div>
          <div class="form-group">
            <label>Тип</label>
            <select bind:value={newActionKind}>
              {#each actionKinds as k}
                <option value={k.id}>{k.label}</option>
              {/each}
            </select>
          </div>
          <div class="form-group">
            <label>{newActionKind === 'open_url' ? 'URL' : newActionKind === 'open_folder' || newActionKind === 'open_file' ? 'Путь' : 'Команда'}</label>
            <input type="text" placeholder={newActionKind === 'open_url' ? 'https://example.com' : newActionKind === 'open_folder' || newActionKind === 'open_file' ? '/path/to/file' : 'команда'}
                   bind:value={newActionData}
                   on:keydown={(e) => e.key === 'Enter' && submitCreateAction()} />
          </div>
          <div class="modal-actions">
            <button class="btn btn-primary" on:click={submitCreateAction}>Создать</button>
            <button class="btn" on:click={cancelCreateAction}>Отмена</button>
          </div>
        </div>
      </div>
    {/if}

    {#if showImportDialog && importSummary}
      <div class="modal-overlay" on:click|self={cancelImport}>
        <div class="modal">
          <h3>Добавить в «{selectedNode ? selectedNode.title : ''}»</h3>
          <div class="import-summary">
            <div class="summary-row"><span>Файлов:</span><span>{importSummary.files}</span></div>
            <div class="summary-row"><span>Папок:</span><span>{importSummary.folders}</span></div>
            <div class="summary-row"><span>Размер:</span><span>{(importSummary.totalBytes / 1024).toFixed(1)} KB</span></div>
            {#if importSummary.isDangerous}
              <div class="summary-warn">
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
                {importSummary.dangerReason}
              </div>
            {/if}
          </div>
          <div class="modal-actions">
            <button class="btn btn-primary" on:click={() => confirmImport('copy')}>Скопировать</button>
            <button class="btn" on:click={() => confirmImport('link')}>Привязать</button>
            <button class="btn" on:click={cancelImport}>Отмена</button>
          </div>
        </div>
      </div>
    {/if}

    {#if showRename}
      <div class="modal-overlay" on:click|self={cancelRename}>
        <div class="modal">
          <h3>Переименовать</h3>
          <div class="form-group">
            <label>Новое имя</label>
            <input type="text" bind:value={renameValue}
                   on:keydown={onRenameKeydown} />
          </div>
          {#if renameError}
            <div class="rename-error">{renameError}</div>
          {/if}
          <div class="modal-actions">
            <button class="btn btn-primary" on:click={submitRename}>Переименовать</button>
            <button class="btn" on:click={cancelRename}>Отмена</button>
          </div>
        </div>
      </div>
    {/if}

    {#if showConfirm}
      <ConfirmModal
        title={confirmTitle}
        message={confirmMessage}
        confirmText={confirmText}
        danger={confirmDanger}
        on:confirm={handleConfirm}
        on:cancel={handleCancel}
      />
    {/if}

    {#if showSettings}
      <div class="modal-overlay" on:click|self={closeSettings}>
        <div class="modal modal-sync">
          <h3>Настройки синхронизации</h3>
          {#if syncStatus}
            <div class="sync-status">
              <div class="sync-row"><span class="sync-label">Статус</span><span class="sync-value">{syncStatus.configured ? 'Включена' : 'Отключена'}</span></div>
              <div class="sync-row"><span class="sync-label">Сервер</span><span class="sync-value mono">{syncStatus.serverUrl || '—'}</span></div>
              <div class="sync-row"><span class="sync-label">Устройство</span><span class="sync-value mono">{syncStatus.deviceId || '—'}</span></div>
              <div class="sync-row"><span class="sync-label">Неотправлено</span><span class="sync-value">{syncStatus.unpushedOps}</span></div>
              <div class="sync-row"><span class="sync-label">Последняя синх.</span><span class="sync-value">{syncStatus.lastSyncAt || '—'}</span></div>
            </div>
          {/if}
          <div class="form-group">
            <label>URL сервера</label>
            <input type="text" placeholder="https://example.com:47732" bind:value={syncServerUrl} />
          </div>
          <div class="form-group">
            <label>Логин</label>
            <input type="text" placeholder="username" bind:value={syncUsername} />
          </div>
          <div class="form-group">
            <label>Пароль</label>
            <input type="password" placeholder="password" bind:value={syncPassword} />
          </div>
          <div class="form-group">
            <label>Автосинхронизация (мин)</label>
            <input type="number" placeholder="0 = отключено" bind:value={syncInterval} min="0" />
          </div>
          {#if syncResult}
            <div class="sync-result">{syncResult}</div>
          {/if}
          <div class="modal-actions">
            <button class="btn" on:click={testConnection} disabled={syncLoading || !syncServerUrl}>Проверить</button>
            <button class="btn btn-primary" on:click={saveSyncConfig} disabled={syncLoading}>Подключиться</button>
            <button class="btn" on:click={runSyncNow} disabled={syncLoading || !syncStatus?.configured}>Синхронизировать</button>
            <button class="btn" on:click={closeSettings}>Закрыть</button>
          </div>
        </div>
      </div>
    {/if}
  </main>
</div>

<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
.app { display: flex; width: 100vw; height: 100vh; overflow: hidden; background: #13131f; color: #e4e4ef; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 14px; }

/* Sidebar */
.sidebar { width: 260px; min-width: 200px; height: 100vh; display: flex; flex-direction: column; background: #1a1a28; border-right: 1px solid #2a2a3c; flex-shrink: 0; overflow: hidden; }
.sidebar-brand { padding: 16px 20px; display: flex; align-items: center; gap: 10px; border-bottom: 1px solid #2a2a3c; flex-shrink: 0; }
.logo { font-size: 20px; line-height: 1; }
.brand-name { font-size: 16px; font-weight: 600; }
.sidebar-nav { flex: 1; overflow-y: auto; padding: 12px 0; }
.nav-group { margin-bottom: 16px; }
.nav-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px; color: #666; padding: 4px 20px; margin-bottom: 4px; }
.nav-item { display: block; width: 100%; padding: 8px 20px; border: none; background: none; color: #ccc; font-size: 13px; text-align: left; cursor: pointer; border-radius: 0; font-family: inherit; }
.nav-item:hover { background: #222233; }
.nav-item.selected { background: #2a2a4a; color: #fff; font-weight: 500; }
.nav-empty { padding: 8px 20px; color: #555; font-size: 12px; }
.sidebar-footer { padding: 8px 12px; border-top: 1px solid #2a2a3c; flex-shrink: 0; display: flex; flex-direction: column; gap: 4px; }
.version { font-size: 11px; color: #555; text-align: center; }

/* Main */
.main { flex: 1; display: flex; flex-direction: column; height: 100vh; min-width: 0; overflow: hidden; background: #13131f; }
.header { padding: 12px 24px; border-bottom: 1px solid #2a2a3c; display: flex; align-items: center; flex-shrink: 0; min-height: 48px; }
.header-left { display: flex; align-items: center; gap: 8px; flex: 1; }
.header-right { display: flex; align-items: center; gap: 8px; }
.header-sync-btn { background: #1a1a28; border: 1px solid #2a2a3c; border-radius: 8px; padding: 6px 12px; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; color: #b0b0c0; font-family: inherit; font-size: 13px; position: relative; }
.header-sync-btn:hover { background: #222233; color: #e4e4ef; border-color: #6366f1; }
.header-sync-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.sync-badge { background: #6366f1; color: #fff; font-size: 10px; border-radius: 50%; width: 16px; height: 16px; display: inline-flex; align-items: center; justify-content: center; position: absolute; top: -6px; right: -6px; }
.crumb { font-size: 14px; font-weight: 500; }
.crumb.placeholder { color: #666; }
.crumb-type { font-size: 11px; color: #555; background: #1e1e2e; padding: 2px 8px; border-radius: 10px; margin-left: 8px; }
.error-banner { background: #3a2222; color: #ff8888; padding: 8px 24px; font-size: 12px; border-bottom: 1px solid #4a2222; flex-shrink: 0; cursor: pointer; display: flex; justify-content: space-between; align-items: center; }
.dismiss-btn { background: none; border: none; color: #ff6666; cursor: pointer; padding: 2px; display: flex; align-items: center; border-radius: 2px; }
.dismiss-btn:hover { color: #ff4444; }

/* Tabs */
.tabs { display: flex; border-bottom: 1px solid #2a2a3c; flex-shrink: 0; padding: 0 24px; }
.tab { padding: 10px 16px; border: none; background: none; color: #888; font-size: 13px; cursor: pointer; border-bottom: 2px solid transparent; font-family: inherit; }
.tab:hover { color: #ccc; }
.tab.active { color: #e4e4ef; border-bottom-color: #6366f1; }

/* Tab content */
.tab-content { flex: 1; overflow-y: auto; }

/* Note editor */
.note-editor { flex: 1; display: flex; flex-direction: column; height: 100%; }
.note-editor-header { padding: 12px 24px; border-bottom: 1px solid #2a2a3c; display: flex; align-items: center; gap: 12px; flex-shrink: 0; }
.note-title { font-size: 16px; font-weight: 500; }
.dirty-mark { color: #f59e0b; font-size: 10px; }
.note-editor-actions { margin-left: auto; display: flex; gap: 8px; }
.note-textarea { flex: 1; width: 100%; border: none; outline: none; background: #13131f; color: #e4e4ef; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 14px; line-height: 1.6; padding: 24px; resize: none; }

/* Overview */
.overview { padding: 24px; }
.overview h2 { font-size: 24px; margin-bottom: 16px; }
.meta-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; margin-bottom: 24px; }
.meta-item { background: #1a1a28; padding: 12px 16px; border-radius: 8px; }
.meta-label { display: block; font-size: 11px; color: #666; margin-bottom: 4px; text-transform: uppercase; }
.quick-actions { display: flex; gap: 8px; margin-bottom: 24px; flex-wrap: wrap; }
.qa-btn { padding: 10px 16px; border: 1px solid #2a2a3c; background: #1a1a28; color: #ccc; border-radius: 8px; cursor: pointer; font-size: 13px; font-family: inherit; display: inline-flex; align-items: center; gap: 6px; }
.qa-btn:hover { background: #222233; }
.qa-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.recent-section { margin-bottom: 24px; }
.recent-section h3 { font-size: 13px; color: #666; text-transform: uppercase; margin-bottom: 8px; }
.recent-note { padding: 8px 12px; border-radius: 6px; cursor: pointer; display: flex; justify-content: space-between; }
.recent-note:hover { background: #1a1a28; }
.recent-date { font-size: 11px; color: #555; }
.recent-entry { padding: 6px 0; font-size: 13px; color: #888; border-bottom: 1px solid #1a1a28; }

/* Notes tab */
.notes-tab { padding: 24px; }
.tab-toolbar { margin-bottom: 16px; }
.create-form { background: #1a1a28; padding: 16px; border-radius: 8px; margin-bottom: 16px; }
.create-form input { width: 100%; padding: 8px 12px; border: 1px solid #2a2a3c; background: #13131f; color: #e4e4ef; border-radius: 4px; font-size: 14px; font-family: inherit; margin-bottom: 8px; }
.create-form input:focus { outline: none; border-color: #6366f1; }
.form-actions { display: flex; gap: 8px; }
.notes-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
.note-card { background: #1a1a28; border: 1px solid #2a2a3c; border-radius: 8px; padding: 16px; cursor: pointer; }
.note-card:hover { border-color: #3a3a5c; }
.note-card-title { font-size: 14px; font-weight: 500; margin-bottom: 4px; }
.note-card-date { font-size: 11px; color: #555; }

/* Worklog tab */
.worklog-tab { padding: 24px; }
.worklog-form { display: flex; gap: 8px; margin-bottom: 24px; align-items: center; }
.worklog-form input { padding: 8px 12px; border: 1px solid #2a2a3c; background: #13131f; color: #e4e4ef; border-radius: 4px; font-size: 14px; font-family: inherit; }
.worklog-form input:focus { outline: none; border-color: #6366f1; }
.worklog-form input[type="text"] { flex: 1; }
.worklog-form input[type="number"] { width: 70px; }
.worklog-entry { padding: 12px 0; border-bottom: 1px solid #1a1a28; }
.wl-meta { font-size: 11px; color: #555; margin-top: 2px; }

/* Actions */
.actions-tab { padding: 24px; }
.action-card { background: #1a1a28; padding: 12px 16px; border-radius: 8px; display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
.action-info { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; }
.action-title { font-weight: 500; }
.action-type { font-size: 11px; color: #888; background: #222233; padding: 2px 8px; border-radius: 10px; white-space: nowrap; }
.action-data { font-size: 11px; color: #555; font-family: 'SF Mono', 'Fira Code', monospace; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 200px; }
.action-btns { display: flex; gap: 4px; flex-shrink: 0; }
.action-btns .btn-danger { color: #ff6b6b; border-color: #4a2222; padding: 4px 8px; }
.action-btns .btn-danger:hover { background: #3a2222; }

/* Empty states */
.empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 48px 24px; text-align: center; }
.empty-state p { margin: 0; font-size: 14px; color: #666; }
.empty-state .empty-icon { margin-bottom: 12px; color: #444; }
.empty-state .hint { font-size: 12px; color: #555; margin-top: 6px; }
.empty-state .empty-actions { display: flex; gap: 8px; justify-content: center; margin-top: 16px; }
.empty-note { font-size: 12px; color: #444; margin-top: 16px; }

/* Welcome */
.welcome { padding: 48px 24px; text-align: center; }
.welcome h2 { font-size: 32px; font-weight: 300; color: #8888a4; margin-bottom: 16px; }
.welcome p { color: #666; font-size: 14px; }
.error-text { color: #ff8888; }

/* FAB */
.fab { position: fixed; bottom: 24px; right: 24px; width: 56px; height: 56px; border-radius: 50%; background: #6366f1; color: #fff; font-size: 28px; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4); }
.fab:hover { background: #4f46e5; }

/* Modal */
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center; z-index: 100; }
.modal { background: #1a1a28; border: 1px solid #2a2a3c; border-radius: 12px; padding: 24px; width: 400px; max-width: 90vw; }
.modal h3 { font-size: 18px; margin-bottom: 16px; }
.form-group { margin-bottom: 12px; }
.form-group label { display: block; font-size: 12px; color: #666; margin-bottom: 4px; }
.form-group input, .form-group select { width: 100%; padding: 8px 12px; border: 1px solid #2a2a3c; background: #13131f; color: #e4e4ef; border-radius: 4px; font-size: 14px; font-family: inherit; }
.form-group select { appearance: none; -webkit-appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23888' d='M2 4l4 4 4-4'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 10px center; padding-right: 32px; }
.form-group input:focus, .form-group select:focus { outline: none; border-color: #6366f1; }
.modal-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }

/* Buttons */
.btn { padding: 8px 16px; border: 1px solid #2a2a3c; background: #1a1a28; color: #ccc; border-radius: 6px; cursor: pointer; font-size: 13px; font-family: inherit; }
.btn:hover { background: #222233; }
.btn-primary { background: #6366f1; border-color: #6366f1; color: #fff; }
.btn-primary:hover { background: #4f46e5; }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
.btn-sm { padding: 4px 10px; font-size: 12px; }
.btn-danger { color: #ff6b6b; border-color: #4a2222; }
.btn-danger:hover { background: #3a2222; }

/* Files tab */
.files-tab { padding: 20px; }
.files-tab .tab-toolbar { display: flex; gap: 8px; align-items: center; margin-bottom: 16px; }
.file-list { display: flex; flex-direction: column; }
.back-btn { margin-bottom: 4px; display: inline-flex; align-items: center; gap: 4px; }

/* Import summary */
.import-summary { margin-bottom: 16px; }
.summary-row { display: flex; justify-content: space-between; padding: 6px 0; font-size: 14px; border-bottom: 1px solid #2a2a3c; }
.summary-warn { margin-top: 8px; padding: 8px 12px; background: #3a2a22; border-radius: 6px; color: #ffaa66; font-size: 13px; }

.rename-error { color: #ff6b6b; font-size: 12px; margin-top: 4px; }

/* Today Dashboard */
.today-dashboard { padding: 24px; overflow-y: auto; flex: 1; }
.today-header { display: flex; align-items: baseline; gap: 12px; margin-bottom: 16px; }
.today-header h2 { font-size: 24px; }
.today-date { font-size: 13px; color: #666; }
.today-summary { display: flex; gap: 8px; margin-bottom: 20px; flex-wrap: wrap; }
.summary-chip { font-size: 12px; color: #b0b0c0; background: #1a1a28; border: 1px solid #2a2a3c; padding: 4px 12px; border-radius: 16px; }
.today-case { background: #1a1a28; border: 1px solid #2a2a3c; border-radius: 8px; margin-bottom: 12px; overflow: hidden; }
.today-case-header { padding: 12px 16px; display: flex; align-items: center; gap: 8px; border-bottom: 1px solid #2a2a3c; cursor: pointer; }
.today-case-header:hover { background: #1e1e30; }
.today-case-title { font-weight: 500; }
.today-case-type { font-size: 11px; color: #888; background: #222233; padding: 2px 8px; border-radius: 10px; }
.today-case-count { font-size: 11px; color: #6366f1; margin-left: 4px; }
.today-case-time { font-size: 11px; color: #555; margin-left: auto; }
.today-events { padding: 8px 16px; }
.today-event { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 13px; color: #b0b0c0; cursor: pointer; }
.today-event:hover { color: #e4e4ef; }
.today-event-icon { width: 18px; text-align: center; color: #6366f1; font-size: 13px; }
.today-event-title { flex: 1; }
.today-event-type { font-size: 11px; color: #666; }
.today-event-time { font-size: 11px; color: #555; margin-left: auto; }
.today-events-empty { padding: 8px 16px; font-size: 13px; color: #666; font-style: italic; }
.today-empty { padding: 48px 24px; text-align: center; }
.today-empty p { color: #666; font-size: 14px; margin: 0; }
.today-empty .hint { font-size: 12px; color: #555; margin-top: 8px; }
.today-timeline { margin-top: 24px; }
.today-timeline h3 { font-size: 13px; color: #666; text-transform: uppercase; margin-bottom: 12px; }
.timeline-event { display: flex; align-items: center; gap: 10px; padding: 6px 0; font-size: 13px; color: #b0b0c0; border-left: 2px solid #2a2a3c; padding-left: 16px; margin-left: 4px; cursor: pointer; }
.timeline-event:hover { color: #e4e4ef; }
.timeline-dot { width: 6px; height: 6px; border-radius: 50%; background: #6366f1; margin-left: -19px; flex-shrink: 0; }
.timeline-title { flex: 1; }
.timeline-type { font-size: 11px; color: #666; }
.timeline-time { font-size: 11px; color: #555; }

/* Activity tab (per-case) */
.activity-tab { padding: 24px; }
.activity-events { display: flex; flex-direction: column; gap: 2px; }
.activity-event { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 6px; font-size: 13px; color: #b0b0c0; cursor: pointer; }
.activity-event:hover { background: #1a1a28; color: #e4e4ef; }
.activity-event-icon { width: 18px; text-align: center; color: #6366f1; font-size: 13px; flex-shrink: 0; }
.activity-event-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.activity-event-type { font-size: 11px; color: #666; flex-shrink: 0; }
.activity-event-target { font-size: 10px; color: #555; background: #1e1e2e; padding: 1px 6px; border-radius: 8px; flex-shrink: 0; }
.activity-event-time { font-size: 11px; color: #555; margin-left: 8px; flex-shrink: 0; }

/* Activity feed (global section) */
.activity-feed { padding: 24px; overflow-y: auto; flex: 1; }
.activity-feed-header { margin-bottom: 20px; }
.activity-feed-header h2 { font-size: 24px; }
.activity-feed-events { display: flex; flex-direction: column; gap: 2px; }
.activity-feed-event { display: flex; align-items: flex-start; gap: 10px; padding: 8px 12px; border-radius: 8px; cursor: pointer; }
.activity-feed-event:hover { background: #1a1a28; color: #e4e4ef; }
.activity-feed-icon { width: 20px; text-align: center; color: #6366f1; font-size: 14px; flex-shrink: 0; margin-top: 1px; }
.activity-feed-body { flex: 1; min-width: 0; }
.activity-feed-title { font-size: 14px; color: #e4e4ef; }
.activity-feed-meta { display: flex; align-items: center; gap: 8px; margin-top: 2px; }
.activity-feed-type { font-size: 11px; color: #666; }
.activity-feed-target { font-size: 10px; color: #555; background: #1e1e2e; padding: 1px 6px; border-radius: 8px; }
.activity-feed-time { font-size: 11px; color: #555; }

/* Sync */
.sidebar-sync-btn { background: #1a1a28; border: 1px solid #2a2a3c; border-radius: 8px; padding: 8px 12px; cursor: pointer; width: 100%; display: flex; align-items: center; gap: 8px; color: #888; font-family: inherit; font-size: 13px; }
.sidebar-sync-btn:hover { background: #222233; color: #e4e4ef; border-color: #6366f1; }
.sidebar-sync-label { flex: 1; text-align: left; }
.sync-dot { width: 8px; height: 8px; border-radius: 50%; background: #4a4a4a; flex-shrink: 0; }
.sync-dot.active { background: #4ade80; box-shadow: 0 0 6px rgba(74,222,128,0.5); }
.modal-sync { width: 460px; }
.sync-status { background: #13131f; border-radius: 8px; padding: 12px; margin-bottom: 16px; }
.sync-row { display: flex; justify-content: space-between; padding: 4px 0; font-size: 13px; }
.sync-label { color: #666; }
.sync-value { color: #e4e4ef; }
.sync-value.mono { font-family: 'SF Mono', 'Fira Code', monospace; font-size: 12px; }
.sync-result { font-size: 12px; color: #6366f1; padding: 4px 0; }
</style>
