+ {label}
+
+ )}
+ triggerAriaLabel={`Sort by ${label}`}
+ triggerTitle={`Sort by ${label}`}
+ placeholder="Select sort field"
+ menuMinWidth={200}
+ align="start"
+ positionStrategy="absolute"
+ />
+ );
+};
+
+export default SortFieldQuickMenu;
diff --git a/frontend/src/documents/panel/createDocumentsSurface.js b/frontend/src/documents/panel/createDocumentsSurface.js
new file mode 100644
index 0000000..1124db1
--- /dev/null
+++ b/frontend/src/documents/panel/createDocumentsSurface.js
@@ -0,0 +1,119 @@
+import React from 'react';
+import DetailPanel from '../../detail/DetailPanel';
+import SelectionFloatingActions from '../SelectionFloatingActions';
+import createWorkspaceSurfaceConfig from '../workspaceHeader';
+import DocumentsPanel from './DocumentsPanel';
+import { createDocumentsTableHeaderActions } from './DocumentsToolbar';
+
+const createDocumentsSurface = ({
+ tableProps,
+ parentBreadcrumb,
+ onNavigateParent,
+ renderSidebarToggle,
+ detailProps,
+ detailOpen = false,
+}) => {
+ const {
+ currentFolderName,
+ breadcrumbs,
+ searchResults,
+ isFilterActive,
+ viewMode,
+ onViewModeChange,
+ onRefresh,
+ sortField,
+ sortDirection,
+ onSortFieldChange,
+ onSortDirectionToggle,
+ selectedDocumentIds,
+ selectedFolderIds,
+ onDeleteSelection,
+ onClearSelection,
+ tags,
+ correspondents,
+ documentLookup,
+ tagLookupById,
+ onBulkTagAdd,
+ onBulkTagRemove,
+ onBulkCorrespondentAdd,
+ onBulkCorrespondentRemove,
+ onBulkReanalyze,
+ folderOptions,
+ onMoveDocumentsToFolder,
+ searchIncludeDescendants,
+ onToggleSearchIncludeDescendants,
+ onInspectDocument,
+ } = tableProps;
+
+ const title = Array.isArray(searchResults) ? 'Search results' : currentFolderName;
+ const subtitle = Array.isArray(searchResults)
+ ? `${searchResults.length} matching document${searchResults.length === 1 ? '' : 's'}`
+ : null;
+
+ const documentSelectionCount = Array.isArray(selectedDocumentIds) ? selectedDocumentIds.length : 0;
+ const folderSelectionCount = Array.isArray(selectedFolderIds) ? selectedFolderIds.length : 0;
+ const selectionCount = documentSelectionCount + folderSelectionCount;
+
+ const actions = createDocumentsTableHeaderActions({
+ viewMode,
+ onViewModeChange,
+ onRefresh,
+ sortField,
+ onSortFieldChange,
+ sortDirection,
+ onSortDirectionToggle,
+ isFilterActive,
+ includeDescendants: searchIncludeDescendants,
+ onToggleIncludeDescendants: onToggleSearchIncludeDescendants,
+ });
+
+ const floatingActions = selectionCount > 0
+ ? (
+
+ )
+ : null;
+
+ const sidebarToggle = renderSidebarToggle ? renderSidebarToggle() : null;
+ const detail = detailOpen && detailProps ? : null;
+
+ return createWorkspaceSurfaceConfig({
+ key: 'documents',
+ variant: 'documents',
+ title,
+ subtitle,
+ sidebarToggle,
+ parentBreadcrumb,
+ onNavigateParent,
+ actions,
+ breadcrumbs,
+ selectionLabel: null,
+ floatingActions,
+ content: (
+
+ ),
+ detail,
+ });
+};
+
+export default createDocumentsSurface;
diff --git a/frontend/src/documents/useEntryPointer.js b/frontend/src/documents/useEntryPointer.js
index f2a4eea..826645f 100644
--- a/frontend/src/documents/useEntryPointer.js
+++ b/frontend/src/documents/useEntryPointer.js
@@ -14,11 +14,12 @@ export const isPrimaryPointerEvent = (event) => {
return type === 'click' || type === 'pointerdown' || type === 'pointerup';
};
-export const useEntryPointerHandler = ({
+export const useEntryPointer = ({
resolveDocumentRowKey,
resolveFolderRowKey,
onSelectDocument,
onSelectFolder,
+ onInspectDocument,
}) =>
useCallback(
(entry, event) => {
@@ -39,16 +40,20 @@ export const useEntryPointerHandler = ({
const modifierClick = isPointerModifierEvent(event);
const primaryClick = isPrimaryPointerEvent(event);
+ const metadata = { modifierClick, primaryClick, rowKey };
if (type === 'document') {
if (typeof onSelectDocument === 'function') {
- onSelectDocument(id, event, { modifierClick, primaryClick, rowKey });
+ onSelectDocument(id, event, metadata);
+ }
+ if (!modifierClick && primaryClick && typeof onInspectDocument === 'function') {
+ onInspectDocument(id, metadata);
}
return;
}
if (typeof onSelectFolder === 'function') {
- onSelectFolder(id, event, { modifierClick, primaryClick, rowKey });
+ onSelectFolder(id, event, metadata);
}
},
[
@@ -56,7 +61,8 @@ export const useEntryPointerHandler = ({
resolveFolderRowKey,
onSelectDocument,
onSelectFolder,
+ onInspectDocument,
],
);
-export default useEntryPointerHandler;
+export default useEntryPointer;
diff --git a/frontend/src/hooks/documents/useDocumentUploads.js b/frontend/src/hooks/documents/useDocumentUploads.js
index a04bd22..50b6d2d 100644
--- a/frontend/src/hooks/documents/useDocumentUploads.js
+++ b/frontend/src/hooks/documents/useDocumentUploads.js
@@ -2,6 +2,26 @@ import { useCallback, useRef, useState } from 'react';
import useFileDrop from './useFileDrop';
import { DEFAULT_FOLDER_NAME, hasFiles } from '../../app/appLayoutUtils';
+const mapFilesToEntries = (filesInput) => {
+ if (!filesInput) {
+ return [];
+ }
+ const files = Array.isArray(filesInput) ? filesInput : Array.from(filesInput);
+ return files
+ .filter(Boolean)
+ .map((file) => {
+ const relativePath =
+ typeof file.webkitRelativePath === 'string' ? file.webkitRelativePath : '';
+ const segments = relativePath
+ ? relativePath
+ .split('/')
+ .slice(0, -1)
+ .filter(Boolean)
+ : [];
+ return { file, segments };
+ });
+};
+
const useDocumentUploads = ({
apiClient,
token,
@@ -9,8 +29,6 @@ const useDocumentUploads = ({
currentFolderName,
ensureFolderData,
refreshCurrentFolder,
- notifyApiError,
- setStatusMessage,
setLoading,
shellRef,
}) => {
@@ -20,12 +38,13 @@ const useDocumentUploads = ({
});
const dragCounterRef = useRef(0);
const folderPathCacheRef = useRef(new Map());
+ const queueIdRef = useRef(0);
+ const [uploadQueue, setUploadQueue] = useState([]);
const uploadFile = useCallback(
async (file, targetFolderId) => {
if (!file || file.size === 0) {
- setStatusMessage('Skipped empty file.', 'error');
- return null;
+ return { document: null, duplicate: false, statusCode: null, conflictDocumentId: null };
}
const formData = new FormData();
@@ -37,22 +56,71 @@ const useDocumentUploads = ({
try {
const { data, status } = await apiClient.post('/documents', formData);
const duplicate = data?.reused || status === 200;
- setStatusMessage(
- duplicate
- ? `${file.name} already exists; reused existing document.`
- : `Uploaded ${file.name}`,
- duplicate ? 'info' : 'success',
- );
- return data;
+ const document = data?.document ?? data ?? null;
+ return {
+ document,
+ duplicate,
+ statusCode: status ?? (duplicate ? 200 : 201),
+ conflictDocumentId: null,
+ };
} catch (error) {
+ if (error.response?.status === 409) {
+ const conflictId = error.response?.data?.details?.conflict_document_id ?? null;
+ let conflictDocument = null;
+ if (conflictId) {
+ try {
+ const { data } = await apiClient.get(`/documents/${conflictId}`);
+ conflictDocument = data?.document ?? data ?? null;
+ } catch (fetchError) {
+ console.warn('[Uploads] failed to fetch conflict document', fetchError);
+ }
+ }
+ return {
+ document: conflictDocument,
+ duplicate: true,
+ statusCode: 409,
+ conflictDocumentId: conflictId,
+ };
+ }
const message = error.response?.data?.error || `Failed to upload ${file.name}.`;
- notifyApiError(error, message);
- throw error;
+ const wrapped = Object.assign(new Error(message), { response: error.response });
+ throw wrapped;
}
},
- [apiClient, notifyApiError, setStatusMessage],
+ [apiClient],
);
+ const appendQueueItems = useCallback((entries, targetFolderId) => {
+ const baseId = Date.now();
+ const items = entries.map(({ file }) => {
+ queueIdRef.current += 1;
+ return {
+ id: `upload-${baseId}-${queueIdRef.current}`,
+ name: file?.name || 'Unnamed file',
+ size: file?.size ?? null,
+ folderId: targetFolderId ?? selectedFolder ?? 'root',
+ status: 'pending',
+ error: null,
+ code: null,
+ document: null,
+ conflictDocumentId: null,
+ };
+ });
+ if (items.length) {
+ setUploadQueue((current) => [...current, ...items]);
+ }
+ return items;
+ }, [selectedFolder]);
+
+ const updateQueueItem = useCallback((id, patch) => {
+ if (!id) {
+ return;
+ }
+ setUploadQueue((current) =>
+ current.map((item) => (item.id === id ? { ...item, ...patch } : item)),
+ );
+ }, []);
+
const ensureFolderPathOnServer = useCallback(
async (baseFolderId, segments) => {
const trimmedSegments = segments.map((segment) => segment.trim()).filter(Boolean);
@@ -200,10 +268,25 @@ const useDocumentUploads = ({
return results;
}, []);
- const handleFileDrop = useCallback(
- async (dataTransfer, targetFolderId) => {
+ const uploadFileEntries = useCallback(
+ async (entries, targetFolderId) => {
+ if (!entries || !entries.length) {
+ console.warn('[Uploads] No files to upload.');
+ return;
+ }
+
+ const queueItems = appendQueueItems(entries, targetFolderId);
+
if (!token) {
- setStatusMessage('Please log in before uploading.', 'error');
+ queueItems.forEach((item) => {
+ const patch = {
+ status: 'error',
+ error: 'Please log in before uploading.',
+ code: null,
+ };
+ updateQueueItem(item.id, patch);
+ Object.assign(item, patch);
+ });
return;
}
@@ -212,23 +295,17 @@ const useDocumentUploads = ({
try {
folderPathCacheRef.current.clear();
- let extracted;
- try {
- extracted = await extractFilesFromDataTransfer(dataTransfer);
- } catch (error) {
- const message = error.message || 'Failed to process dropped files.';
- notifyApiError(error, message);
- return;
- }
-
- if (!extracted.length) {
- setStatusMessage('No files to upload.', 'info');
- return;
- }
const baseFolderId =
targetFolderId && targetFolderId !== 'root' ? targetFolderId : null;
- for (const { file, segments } of extracted) {
+ for (let index = 0; index < entries.length; index += 1) {
+ const { file, segments } = entries[index];
+ const queueItem = queueItems[index];
+ if (queueItem) {
+ const patch = { status: 'uploading', error: null, code: null };
+ updateQueueItem(queueItem.id, patch);
+ Object.assign(queueItem, patch);
+ }
// eslint-disable-next-line no-await-in-loop
const destinationId = segments.length
? await ensureFolderPathOnServer(baseFolderId, segments)
@@ -238,8 +315,34 @@ const useDocumentUploads = ({
destinationId ??
(targetFolderId && targetFolderId !== 'root' ? targetFolderId : 'root');
- // eslint-disable-next-line no-await-in-loop
- await uploadFile(file, uploadTarget);
+ try {
+ // eslint-disable-next-line no-await-in-loop
+ const { duplicate, statusCode, document, conflictDocumentId } = await uploadFile(
+ file,
+ uploadTarget,
+ );
+ if (queueItem) {
+ const patch = {
+ status: duplicate ? 'duplicate' : 'success',
+ code: statusCode ?? null,
+ document: document || queueItem.document,
+ conflictDocumentId: conflictDocumentId ?? queueItem.conflictDocumentId,
+ };
+ updateQueueItem(queueItem.id, patch);
+ Object.assign(queueItem, patch);
+ }
+ } catch (error) {
+ if (queueItem) {
+ const patch = {
+ status: 'error',
+ error: error.response?.data?.error || error.message || 'Upload failed.',
+ code: error.response?.status ?? null,
+ };
+ updateQueueItem(queueItem.id, patch);
+ Object.assign(queueItem, patch);
+ }
+ continue;
+ }
}
await refreshCurrentFolder();
@@ -252,25 +355,60 @@ const useDocumentUploads = ({
await ensureFolderData(targetFolderId, { force: true, prefetchDepth: 1 });
}
} catch (error) {
- notifyApiError(error, 'Failed to upload files.');
+ const message = error.message || 'Failed to upload files.';
+ queueItems.forEach((item) => {
+ if (item.status === 'success' || item.status === 'duplicate' || item.status === 'error') {
+ return;
+ }
+ const patch = {
+ status: 'error',
+ error: message,
+ code: error.response?.status ?? null,
+ };
+ updateQueueItem(item.id, patch);
+ Object.assign(item, patch);
+ });
+ console.error('[Uploads] batch failed', error);
} finally {
setLoading(false);
}
},
[
token,
- extractFilesFromDataTransfer,
ensureFolderPathOnServer,
uploadFile,
refreshCurrentFolder,
selectedFolder,
ensureFolderData,
- notifyApiError,
- setStatusMessage,
setLoading,
+ appendQueueItems,
+ updateQueueItem,
],
);
+ const handleFileDrop = useCallback(
+ async (dataTransfer, targetFolderId) => {
+ let extracted;
+ try {
+ extracted = await extractFilesFromDataTransfer(dataTransfer);
+ } catch (error) {
+ console.error('[Uploads] Failed to process dropped files.', error);
+ return;
+ }
+
+ await uploadFileEntries(extracted, targetFolderId);
+ },
+ [extractFilesFromDataTransfer, uploadFileEntries],
+ );
+
+ const handleFileSelection = useCallback(
+ async (files, targetFolderId) => {
+ const entries = mapFilesToEntries(files);
+ await uploadFileEntries(entries, targetFolderId);
+ },
+ [uploadFileEntries],
+ );
+
useFileDrop({
shellRef,
token,
@@ -287,6 +425,11 @@ const useDocumentUploads = ({
const resetUploadsState = useCallback(() => {
dragCounterRef.current = 0;
setDropOverlayState({ active: false, folderName: DEFAULT_FOLDER_NAME });
+ setUploadQueue([]);
+ }, []);
+
+ const clearUploadQueue = useCallback(() => {
+ setUploadQueue([]);
}, []);
return {
@@ -294,9 +437,12 @@ const useDocumentUploads = ({
setDropOverlayState,
dragCounterRef,
handleFileDrop,
+ handleFileSelection,
uploadFile,
extractFilesFromDataTransfer,
resetUploadsState,
+ uploadQueue,
+ clearUploadQueue,
};
};
diff --git a/frontend/src/hooks/documents/useDocumentsWorkspace.js b/frontend/src/hooks/documents/useDocumentsWorkspace.js
index 28eb0cb..f79dc09 100644
--- a/frontend/src/hooks/documents/useDocumentsWorkspace.js
+++ b/frontend/src/hooks/documents/useDocumentsWorkspace.js
@@ -12,7 +12,7 @@ import usePasskeys from '../../settings/usePasskeys';
import { useManagementModals } from '../../app/useManagementModals';
import { api, useAppDispatch, useAppState } from '../../app/appState';
import useWorkspaceSelection from '../../app/useWorkspaceSelection';
-import { useEntryPointerHandler as useEntryPointerCore } from '../../documents/useEntryPointer';
+import { useEntryPointer as useEntryPointerCore } from '../../documents/useEntryPointer';
import { isTagTransferEvent } from '../../documents/tagTransfer';
import useDocumentsSelection from '../../documents/hooks/useDocumentsSelection';
import useBulkDocumentActions from '../../documents/hooks/useBulkDocumentActions';
@@ -65,8 +65,6 @@ const useDocumentsWorkspace = ({
onToggleSearchIncludeDescendants,
onSetSearchIncludeDescendants,
sortRefreshReadyRef,
- deskHelpOpen = false,
- setDeskHelpOpen,
handleDeskExit,
} = {}) => {
const handleDocumentsViewModeChange = onDocumentsViewModeChange || noop;
@@ -74,7 +72,6 @@ const useDocumentsWorkspace = ({
const handleDocumentsSortDirectionToggle = onDocumentsSortDirectionToggle || noop;
const toggleSearchIncludeDescendants = onToggleSearchIncludeDescendants || noop;
const setSearchIncludeDescendants = onSetSearchIncludeDescendants || noop;
- const setDeskHelpOpenSafe = setDeskHelpOpen || noop;
const handleDeskExitSafe = handleDeskExit || noop;
const fallbackSortFieldRef = useRef(documentsSortField);
@@ -533,6 +530,9 @@ const useDocumentsWorkspace = ({
const {
dropOverlayState,
handleFileDrop,
+ handleFileSelection,
+ uploadQueue,
+ clearUploadQueue,
resetUploadsState,
} = useDocumentUploads({
apiClient: api,
@@ -623,6 +623,7 @@ const useDocumentsWorkspace = ({
assetManager.reset();
resetPreviewState();
resetUploadsState();
+ clearUploadQueue();
breadcrumbFetchRef.current = new Set();
detailFolderFetchRef.current = new Set();
bootstrapInitializedRef.current = false;
@@ -653,6 +654,7 @@ const useDocumentsWorkspace = ({
setActivePreviewId,
resetPreviewState,
resetUploadsState,
+ clearUploadQueue,
]);
useEffect(() => {
@@ -1022,9 +1024,27 @@ const useDocumentsWorkspace = ({
setStatusMessage,
});
+ const [settingsOpen, setSettingsOpen] = useState(false);
const openSettings = useCallback(() => {
- navigate('/settings');
- }, [navigate]);
+ setSettingsOpen(true);
+ }, []);
+ const closeSettings = useCallback(() => {
+ setSettingsOpen(false);
+ }, []);
+
+ useEffect(() => {
+ if (!settingsOpen) {
+ return undefined;
+ }
+ const handleKeyDown = (event) => {
+ if (event.key === 'Escape') {
+ event.preventDefault();
+ setSettingsOpen(false);
+ }
+ };
+ window.addEventListener('keydown', handleKeyDown);
+ return () => window.removeEventListener('keydown', handleKeyDown);
+ }, [settingsOpen]);
useEffect(
() => () => {
@@ -1371,6 +1391,8 @@ const useDocumentsWorkspace = ({
currentTenantId,
handleTenantSelect,
openSettings,
+ handleFileSelection,
+ uploadQueue,
});
@@ -1421,8 +1443,6 @@ const useDocumentsWorkspace = ({
searchQuery,
activeCorrespondentFilters,
selectedFolder,
- setDeskHelpOpen: setDeskHelpOpenSafe,
- deskHelpOpen,
openDetailPanel,
});
@@ -1476,6 +1496,8 @@ const useDocumentsWorkspace = ({
openSettings,
detailPanelOpen,
openDetailPanel,
+ uploadQueue,
+ clearUploadQueue,
}),
[
token,
@@ -1526,6 +1548,8 @@ const useDocumentsWorkspace = ({
openSettings,
detailPanelOpen,
openDetailPanel,
+ uploadQueue,
+ clearUploadQueue,
],
);
@@ -1537,6 +1561,8 @@ const useDocumentsWorkspace = ({
dropOverlayState,
managementModals,
contextValue,
+ settingsOpen,
+ closeSettings,
};
};
diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx
index 78a38a0..4e334ad 100644
--- a/frontend/src/index.jsx
+++ b/frontend/src/index.jsx
@@ -3,7 +3,7 @@ import '@fontsource/inter/400.css';
import React from 'react';
import { createRoot } from 'react-dom/client';
import { HashRouter } from 'react-router-dom';
-import './styles.css';
+import './styles/index.css';
import { AppStateProvider } from './app/appState';
import AppRouter from './app/AppRouter';
diff --git a/frontend/src/settings/sections/ApiTokensSection.jsx b/frontend/src/settings/sections/ApiTokensSection.jsx
index fd65511..b3ea68c 100644
--- a/frontend/src/settings/sections/ApiTokensSection.jsx
+++ b/frontend/src/settings/sections/ApiTokensSection.jsx
@@ -365,9 +365,6 @@ const ApiTokensSection = ({
const selectedSet = token?.capability_set_id
? capabilitySetMap[token.capability_set_id]
: null;
- const capabilityList = Array.isArray(token?.capabilities) && token.capabilities.length
- ? token.capabilities
- : selectedSet?.capabilities || [];
const capabilitySetLabel = selectedSet?.label
|| selectedSet?.slug
|| token.capability_set_id
@@ -386,15 +383,6 @@ const ApiTokensSection = ({
{capabilitySetsLoading ? (
Loading capability sets…
) : null}
- {!capabilitySetsLoading && capabilityList.length ? (
-
- {capabilityList.map((value) => (
-
- {formatCapabilityLabel(value)}
-
- ))}
-
- ) : null}
{isRevoked ? (
diff --git a/frontend/src/sidebar/Sidebar.jsx b/frontend/src/sidebar/Sidebar.jsx
index ac576af..84b084c 100644
--- a/frontend/src/sidebar/Sidebar.jsx
+++ b/frontend/src/sidebar/Sidebar.jsx
@@ -16,6 +16,7 @@ import {
SunIcon,
MoonIcon,
DesktopIcon,
+ UploadIcon,
} from '../ui/icons';
import PanelHeader from '../ui/PanelHeader';
import useFloatingMenu from '../ui/useFloatingMenu';
@@ -187,6 +188,7 @@ const Sidebar = ({
activeTenantId = null,
onSelectTenant,
onOpenSettings,
+ onUploadFiles,
}) => {
const {
setCollapsed,
@@ -197,6 +199,7 @@ const Sidebar = ({
cycleThemeMode,
themeModes,
} = useSidebarContext();
+ const uploadInputRef = useRef(null);
const handleCollapse = useCallback(() => {
setCollapsed(true);
}, [setCollapsed]);
@@ -258,6 +261,26 @@ const Sidebar = ({
onCreateFolder?.();
}, [creatingFolder, onCreateFolder]);
+ const handleUploadButtonClick = useCallback(() => {
+ if (!onUploadFiles || !uploadInputRef.current) {
+ return;
+ }
+ uploadInputRef.current.click();
+ }, [onUploadFiles]);
+
+ const handleUploadInputChange = useCallback(
+ (event) => {
+ const files = event.target?.files;
+ if (files && files.length && onUploadFiles) {
+ onUploadFiles(files, selectedFolder);
+ }
+ if (event.target) {
+ event.target.value = '';
+ }
+ },
+ [onUploadFiles, selectedFolder],
+ );
+
const handleNeutralHueReset = useCallback(() => {
resetNeutralHue();
}, [resetNeutralHue]);
@@ -294,6 +317,50 @@ const Sidebar = ({
cycleThemeMode();
}, [cycleThemeMode]);
+ const themeMenuSection =
+ typeof neutralHue === 'number' || typeof neutralHue === 'string'
+ ? (
+
+
+ Theme
+
+
+
+
+
+
+
+ )
+ : null;
+
const handleSearchInputChange = useCallback(
(event) => {
onSearchChange?.(event.target.value);
@@ -324,6 +391,7 @@ const Sidebar = ({
offset: 6,
});
const showTenantList = tenants.length > 1;
+ const menuClassName = `menu${!showTenantList && !themeMenuSection ? ' menu--simple' : ''}`;
const toggleTenantMenu = useCallback(() => {
if (!tenantMenuOpen && tenants.length === 0 && onSelectTenant) {
@@ -402,52 +470,15 @@ const Sidebar = ({
);
const rootNode = folderNodes.get('root');
- const themeControls =
- typeof neutralHue === 'number' || typeof neutralHue === 'string'
- ? (
-
-
- Theme
-
-
-
-
-
-
-
- )
- : null;
-
return (
);
};
diff --git a/frontend/src/sidebar/useSidebarProps.js b/frontend/src/sidebar/useSidebarProps.js
index dd38f75..d305b18 100644
--- a/frontend/src/sidebar/useSidebarProps.js
+++ b/frontend/src/sidebar/useSidebarProps.js
@@ -35,6 +35,8 @@ const useSidebarProps = ({
currentTenantId,
handleTenantSelect,
openSettings,
+ handleFileSelection,
+ uploadQueue,
}) =>
useMemo(
() => ({
@@ -76,8 +78,21 @@ const useSidebarProps = ({
activeTenantId: currentTenantId,
onSelectTenant: handleTenantSelect,
onOpenSettings: openSettings,
+ onUploadFiles: (files, targetFolderId) => {
+ if (!handleFileSelection) {
+ return;
+ }
+ if (!files || files.length === 0) {
+ return;
+ }
+ const folderId = targetFolderId ?? selectedFolder ?? 'root';
+ handleFileSelection(files, folderId);
+ },
+ uploadQueue,
}),
[
+ handleFileSelection,
+ uploadQueue,
activeCorrespondentFilters,
activeTagFilters,
appStatus,
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
deleted file mode 100644
index 5687227..0000000
--- a/frontend/src/styles.css
+++ /dev/null
@@ -1,4273 +0,0 @@
-:root {
- color-scheme: light dark;
-
- --neutral-hue: 180deg;
- --foreground-hue-offset: 10deg;
- --accent-hue-offset: -20deg;
- --dark-hue-offset: 30deg;
- --dark-foreground-hue-offset: 10deg;
-
- /* --- Neutrals --- */
- --bg: oklch(0.97 0.001 var(--neutral-hue));
- --surface: oklch(0.985 0.001 var(--neutral-hue));
- --surface-subtle: oklch(0.96 0.002 var(--neutral-hue));
- --fg: oklch(0.32 0.005 calc(var(--neutral-hue) + var(--foreground-hue-offset)));
- --muted: oklch(0.54 0.008 calc(var(--neutral-hue) + var(--foreground-hue-offset)));
- --sidebar-fg: oklch(0.56 0.007 calc(var(--neutral-hue) + var(--foreground-hue-offset)));
- --border: oklch(0.92 0.002 calc(var(--neutral-hue) + var(--foreground-hue-offset)));
-
- /* --- Accent (primary) --- */
- --accent: oklch(0.61 0.2 calc(var(--neutral-hue) + var(--foreground-hue-offset) + var(--accent-hue-offset)));
- --accent-hover: oklch(0.70 0.02 calc(var(--neutral-hue) + var(--foreground-hue-offset) + var(--accent-hue-offset)));
- --on-accent: oklch(1 0 0);
-
- --folder-icon-back: color-mix(in oklch, var(--accent) 78%, black 8%);
- --folder-icon-mid: color-mix(in oklch, var(--accent) 60%, white 30%);
- --folder-icon-front: color-mix(in oklch, var(--accent) 42%, white 58%);
-
- --success-hue: 140deg;
- --warning-hue: 85deg;
- --danger-hue: 25deg;
-
- --accent-soft: color-mix(in oklch, var(--accent) 12%, transparent);
- --accent-elevated: color-mix(in oklch, var(--accent) 16%, transparent);
- --accent-elevated-strong: color-mix(in oklch, var(--accent) 22%, transparent);
- --accent-outline: color-mix(in oklch, var(--accent) 45%, transparent);
- --accent-outline-strong: color-mix(in oklch, var(--accent) 90%, transparent);
- --accent-focus: color-mix(in oklch, var(--accent) 85%, transparent);
- --surface-overlay: color-mix(in oklch, white 82%, transparent);
-
- /* -- Selection --- */
- --selection: oklch(0.61 0.01 var(--neutral-hue));
-
- /* --- Derived accent states --- */
- --selection-soft: color-mix(in oklch, var(--selection) 12%, transparent);
-
- /* --- Shadows & overlays --- */
- --shadow-faint: color-mix(in oklch, black 6%, transparent);
- --shadow-soft: color-mix(in oklch, black 12%, transparent);
- --shadow-medium: color-mix(in oklch, black 18%, transparent);
- --shadow-strong: color-mix(in oklch, black 28%, transparent);
- --shadow-deep: color-mix(in oklch, black 55%, transparent);
- --shadow-pop: color-mix(in oklch, black 25%, transparent);
- --outline-subtle: color-mix(in oklch, black 6%, transparent);
- --overlay-dark: color-mix(in oklch, black 55%, transparent);
- --overlay-dim: color-mix(in oklch, oklch(0.28 0.01 calc(var(--neutral-hue) + var(--foreground-hue-offset) - 5deg)) 20%, transparent);
- --overlay-darker: color-mix(in oklch, black 75%, transparent);
- --surface-danger-subtle: color-mix(in oklch, var(--danger) 12%, transparent);
- --overlay-accent-subtle: color-mix(in oklch, var(--accent) 12%, transparent);
- --border-strong: color-mix(in oklch, var(--fg) 24%, transparent);
- --surface-hover: color-mix(in oklch, white 10%, transparent);
- --overlay-accent-strong: color-mix(in oklch, var(--accent) 24%, transparent);
- --overlay-backdrop: color-mix(in oklch, oklch(0.22 0.015 calc(var(--neutral-hue) + var(--foreground-hue-offset) - 5deg)) 45%, transparent);
- --overlay-shadow: color-mix(in oklch, oklch(0.22 0.015 calc(var(--neutral-hue) + var(--foreground-hue-offset) - 5deg)) 18%, transparent);
-
- /* --- Semantic colors --- */
- --success: oklch(0.68 0.16 var(--success-hue));
- --warning: oklch(0.76 0.17 var(--warning-hue));
- --danger: oklch(0.64 0.2 var(--danger-hue));
-
- --success-subtle: color-mix(in oklch, var(--success) 12%, transparent);
- --warning-subtle: color-mix(in oklch, var(--warning) 12%, transparent);
- --danger-border: color-mix(in oklch, var(--danger) 45%, transparent);
- --danger-soft: color-mix(in oklch, var(--danger) 12%, transparent);
- --danger-subtle: color-mix(in oklch, var(--danger) 8%, transparent);
-
- /* --- Explorer states --- */
- --row-hover-bg: color-mix(in oklch, var(--selection) 6%, transparent);
- --row-active-bg: color-mix(in oklch, var(--selection) 12%, transparent);
- --sidebar-hover-bg: color-mix(in oklch, var(--selection) 8%, transparent);
- --sidebar-active-bg: color-mix(in oklch, var(--selection) 16%, transparent);
- --selection-ring: oklch(0.78 0.16 calc(var(--neutral-hue) + var(--foreground-hue-offset)));
- --sidebar-active-pill-border: color-mix(in oklch, var(--accent) 64%, transparent);
- --link: var(--accent);
- --link-visited: color-mix(in oklch, var(--accent) 75%, var(--fg) 25%);
- --preview-nav-bg: oklch(0.18 0.05 calc(var(--neutral-hue) + var(--foreground-hue-offset)));
- --preview-nav-bg-hover: oklch(0.14 0.05 calc(var(--neutral-hue) + var(--foreground-hue-offset)));
- --preview-nav-fg: var(--on-accent);
- --text-on-dark: color-mix(in oklch, white 90%, transparent);
- --surface-ink-soft: color-mix(in oklch, var(--fg) 8%, transparent);
-
- font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
- font-size: 15px;
- --font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
-
- --detail-panel-width: 30em;
- --documents-grid-title-size: 0.8rem;
-}
-
-:root[data-theme='light'] {
- color-scheme: light;
-}
-
-:root[data-theme='dark'] {
- color-scheme: dark;
-
- --dark-neutral-hue: calc(var(--neutral-hue) + var(--dark-hue-offset));
- --dark-foreground-hue: calc(var(--neutral-hue) + var(--dark-foreground-hue-offset));
-
- --bg: oklch(0.15 0.01 var(--dark-neutral-hue));
- --surface: oklch(0.19 0.012 var(--dark-neutral-hue));
- --surface-subtle: oklch(0.23 0.012 var(--dark-neutral-hue));
- --fg: oklch(0.89 0.015 var(--dark-foreground-hue));
- --muted: oklch(0.72 0.02 var(--dark-foreground-hue));
- --sidebar-fg: oklch(0.78 0.02 var(--dark-foreground-hue));
- --border: oklch(0.33 0.01 var(--dark-foreground-hue));
-
- --accent: oklch(0.75 0.2 calc(var(--dark-foreground-hue) + var(--accent-hue-offset)));
- --accent-hover: oklch(0.82 0.16 calc(var(--dark-foreground-hue) + var(--accent-hue-offset)));
- --on-accent: oklch(0.15 0.015 var(--dark-foreground-hue));
-
- --folder-icon-back: color-mix(in oklch, var(--accent) 72%, black 12%);
- --folder-icon-mid: color-mix(in oklch, var(--accent) 50%, white 24%);
- --folder-icon-front: color-mix(in oklch, var(--accent) 30%, white 50%);
-
- --accent-soft: color-mix(in oklch, var(--accent) 22%, transparent);
- --accent-elevated: color-mix(in oklch, var(--accent) 28%, transparent);
- --accent-elevated-strong: color-mix(in oklch, var(--accent) 38%, transparent);
- --accent-outline: color-mix(in oklch, var(--accent) 55%, transparent);
- --accent-outline-strong: color-mix(in oklch, var(--accent) 90%, transparent);
- --accent-focus: color-mix(in oklch, var(--accent) 80%, transparent);
- --surface-overlay: color-mix(in oklch, black 60%, transparent);
-
- --selection: oklch(0.44 0.04 220deg);
- --selection-soft: color-mix(in oklch, var(--selection) 18%, transparent);
-
- --shadow-faint: color-mix(in oklch, black 35%, transparent);
- --shadow-soft: color-mix(in oklch, black 50%, transparent);
- --shadow-medium: color-mix(in oklch, black 65%, transparent);
- --shadow-strong: color-mix(in oklch, black 80%, transparent);
- --shadow-deep: color-mix(in oklch, black 90%, transparent);
- --shadow-pop: color-mix(in oklch, black 70%, transparent);
- --outline-subtle: color-mix(in oklch, white 10%, transparent);
- --overlay-dark: color-mix(in oklch, black 70%, transparent);
- --overlay-dim: color-mix(in oklch, oklch(0.42 0.04 var(--dark-neutral-hue)) 35%, transparent);
- --overlay-darker: color-mix(in oklch, black 85%, transparent);
- --surface-danger-subtle: color-mix(in oklch, var(--danger) 26%, transparent);
- --overlay-accent-subtle: color-mix(in oklch, var(--accent) 32%, transparent);
- --border-strong: color-mix(in oklch, var(--fg) 30%, transparent);
- --surface-hover: color-mix(in oklch, white 6%, transparent);
- --overlay-accent-strong: color-mix(in oklch, var(--accent) 55%, transparent);
- --overlay-backdrop: color-mix(in oklch, oklch(0.1 0.015 var(--dark-neutral-hue)) 70%, transparent);
- --overlay-shadow: color-mix(in oklch, oklch(0.12 0.015 var(--dark-neutral-hue)) 55%, transparent);
-
- --success: oklch(0.62 0.16 var(--success-hue));
- --warning: oklch(0.68 0.17 var(--warning-hue));
- --danger: oklch(0.60 0.2 var(--danger-hue));
-
- --success-subtle: color-mix(in oklch, var(--success) 20%, transparent);
- --warning-subtle: color-mix(in oklch, var(--warning) 20%, transparent);
- --danger-border: color-mix(in oklch, var(--danger) 45%, transparent);
- --danger-soft: color-mix(in oklch, var(--danger) 24%, transparent);
- --danger-subtle: color-mix(in oklch, var(--danger) 18%, transparent);
-
- --row-hover-bg: color-mix(in oklch, white 4%, transparent);
- --row-active-bg: color-mix(in oklch, var(--selection) 20%, transparent);
- --sidebar-hover-bg: color-mix(in oklch, white 6%, transparent);
- --sidebar-active-bg: color-mix(in oklch, var(--selection) 26%, transparent);
- --selection-ring: oklch(0.68 0.16 var(--dark-foreground-hue));
- --sidebar-active-pill-border: color-mix(in oklch, var(--accent) 72%, transparent);
- --link: color-mix(in oklch, var(--accent) 92%, transparent);
- --link-visited: color-mix(in oklch, var(--accent) 70%, white 20%);
- --preview-nav-bg: oklch(0.3 0.04 var(--dark-neutral-hue));
- --preview-nav-bg-hover: oklch(0.35 0.04 var(--dark-neutral-hue));
- --preview-nav-fg: var(--fg);
- --text-on-dark: color-mix(in oklch, white 92%, transparent);
- --surface-ink-soft: color-mix(in oklch, white 12%, transparent);
-}
-
-@media (prefers-color-scheme: dark) {
- :root:not([data-theme='light']) {
- color-scheme: dark;
-
- --dark-neutral-hue: calc(var(--neutral-hue) + var(--dark-hue-offset));
- --dark-foreground-hue: calc(var(--neutral-hue) + var(--dark-foreground-hue-offset));
-
- --bg: oklch(0.15 0.01 var(--dark-neutral-hue));
- --surface: oklch(0.19 0.012 var(--dark-neutral-hue));
- --surface-subtle: oklch(0.23 0.012 var(--dark-neutral-hue));
- --fg: oklch(0.89 0.015 var(--dark-foreground-hue));
- --muted: oklch(0.72 0.02 var(--dark-foreground-hue));
- --sidebar-fg: oklch(0.78 0.02 var(--dark-foreground-hue));
- --border: oklch(0.33 0.01 var(--dark-foreground-hue));
-
- --accent: oklch(0.75 0.2 calc(var(--dark-foreground-hue) + var(--accent-hue-offset)));
- --accent-hover: oklch(0.82 0.16 calc(var(--dark-foreground-hue) + var(--accent-hue-offset)));
- --on-accent: oklch(0.15 0.015 var(--dark-foreground-hue));
-
- --folder-icon-back: color-mix(in oklch, var(--accent) 72%, black 12%);
- --folder-icon-mid: color-mix(in oklch, var(--accent) 50%, white 24%);
- --folder-icon-front: color-mix(in oklch, var(--accent) 30%, white 50%);
-
- --accent-soft: color-mix(in oklch, var(--accent) 22%, transparent);
- --accent-elevated: color-mix(in oklch, var(--accent) 28%, transparent);
- --accent-elevated-strong: color-mix(in oklch, var(--accent) 38%, transparent);
- --accent-outline: color-mix(in oklch, var(--accent) 55%, transparent);
- --accent-outline-strong: color-mix(in oklch, var(--accent) 90%, transparent);
- --accent-focus: color-mix(in oklch, var(--accent) 80%, transparent);
- --surface-overlay: color-mix(in oklch, black 60%, transparent);
-
- --selection: oklch(0.44 0.04 220deg);
- --selection-soft: color-mix(in oklch, var(--selection) 18%, transparent);
-
- --shadow-faint: color-mix(in oklch, black 35%, transparent);
- --shadow-soft: color-mix(in oklch, black 50%, transparent);
- --shadow-medium: color-mix(in oklch, black 65%, transparent);
- --shadow-strong: color-mix(in oklch, black 80%, transparent);
- --shadow-deep: color-mix(in oklch, black 90%, transparent);
- --shadow-pop: color-mix(in oklch, black 70%, transparent);
- --outline-subtle: color-mix(in oklch, white 10%, transparent);
- --overlay-dark: color-mix(in oklch, black 70%, transparent);
- --overlay-dim: color-mix(in oklch, oklch(0.42 0.04 var(--dark-neutral-hue)) 35%, transparent);
- --overlay-darker: color-mix(in oklch, black 85%, transparent);
- --surface-danger-subtle: color-mix(in oklch, var(--danger) 26%, transparent);
- --overlay-accent-subtle: color-mix(in oklch, var(--accent) 32%, transparent);
- --border-strong: color-mix(in oklch, var(--fg) 30%, transparent);
- --surface-hover: color-mix(in oklch, white 6%, transparent);
- --overlay-accent-strong: color-mix(in oklch, var(--accent) 55%, transparent);
- --overlay-backdrop: color-mix(in oklch, oklch(0.1 0.015 var(--dark-neutral-hue)) 70%, transparent);
- --overlay-shadow: color-mix(in oklch, oklch(0.12 0.015 var(--dark-neutral-hue)) 55%, transparent);
-
- --success: oklch(0.62 0.16 var(--success-hue));
- --warning: oklch(0.68 0.17 var(--warning-hue));
- --danger: oklch(0.60 0.2 var(--danger-hue));
-
- --success-subtle: color-mix(in oklch, var(--success) 20%, transparent);
- --warning-subtle: color-mix(in oklch, var(--warning) 20%, transparent);
- --danger-border: color-mix(in oklch, var(--danger) 45%, transparent);
- --danger-soft: color-mix(in oklch, var(--danger) 24%, transparent);
- --danger-subtle: color-mix(in oklch, var(--danger) 18%, transparent);
-
- --row-hover-bg: color-mix(in oklch, white 4%, transparent);
- --row-active-bg: color-mix(in oklch, var(--selection) 20%, transparent);
- --sidebar-hover-bg: color-mix(in oklch, white 6%, transparent);
- --sidebar-active-bg: color-mix(in oklch, var(--selection) 26%, transparent);
- --selection-ring: oklch(0.68 0.16 var(--dark-foreground-hue));
- --sidebar-active-pill-border: color-mix(in oklch, var(--accent) 72%, transparent);
- --link: color-mix(in oklch, var(--accent) 92%, transparent);
- --link-visited: color-mix(in oklch, var(--accent) 70%, white 20%);
- --preview-nav-bg: oklch(0.3 0.04 var(--dark-neutral-hue));
- --preview-nav-bg-hover: oklch(0.35 0.04 var(--dark-neutral-hue));
- --preview-nav-fg: var(--fg);
- --text-on-dark: color-mix(in oklch, white 92%, transparent);
- --surface-ink-soft: color-mix(in oklch, white 12%, transparent);
- }
-}
-
-html,
-body {
- height: 100%;
-}
-
-body {
- margin: 0;
- background: var(--bg);
- color: var(--fg);
- overflow: hidden;
-}
-
-body.has-main-content {
- background: var(--surface);
-}
-
-a {
- color: var(--accent);
- text-decoration: underline;
- text-decoration-thickness: 1px;
- text-underline-offset: 0.18em;
- transition: color 0.15s ease, text-decoration-color 0.15s ease;
-}
-
-a:visited {
- color: var(--link-visited);
-}
-
-a:hover,
-a:focus-visible {
- color: var(--accent-hover);
- outline: none;
- text-decoration-color: currentColor;
-}
-
-#app {
- height: 100%;
-}
-
-button {
- font: inherit;
- border-radius: 2px;
- border: 1px solid transparent;
- padding: 0.35rem 0.85rem;
- background: var(--accent);
- color: var(--on-accent);
- cursor: pointer;
- font-weight: 500;
- transition: background 0.15s ease, border-color 0.15s ease;
-}
-
-button[disabled] {
- opacity: 0.55;
- cursor: not-allowed;
-}
-
-a.button-link {
- display: inline-flex;
- align-items: center;
- gap: 0.35rem;
- font: inherit;
- border-radius: 2px;
- border: 1px solid transparent;
- padding: 0.35rem 0.85rem;
- background: var(--accent);
- color: var(--on-accent);
- text-decoration: none;
- font-weight: 500;
- transition: background 0.15s ease, border-color 0.15s ease;
-}
-
-a.button-link[aria-disabled='true'] {
- opacity: 0.55;
-}
-
-a.button-link:hover:not([aria-disabled='true']) {
- background: var(--accent-hover);
-}
-
-button.secondary {
- background: transparent;
- color: var(--fg);
- border-color: var(--border);
-}
-
-button.secondary:hover:not([disabled]) {
- background: var(--surface-subtle);
-}
-
-button.danger {
- background: transparent;
- color: var(--danger);
- border-color: var(--danger-border);
-}
-
-button.danger:hover:not([disabled]) {
- border-color: var(--danger);
- background: var(--danger-subtle);
-}
-
-.icon-button {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- border: none;
- background: transparent;
- color: var(--muted);
- padding: 0.25rem;
- border-radius: 4px;
- cursor: pointer;
- transition: background 0.15s ease, color 0.15s ease;
-}
-
-.icon-button:hover:not([disabled]) {
- background: var(--sidebar-hover-bg);
- color: var(--fg);
-}
-
-.icon-button.danger {
- color: var(--danger);
-}
-
-.icon-button.danger:hover:not([disabled]) {
- background: var(--danger-subtle);
-}
-
-.icon-button.ghost {
- border: none;
- background: transparent;
- color: var(--muted);
- padding: 0.2rem;
-}
-
-.icon-button.ghost:hover:not([disabled]) {
- color: var(--danger);
- background: transparent;
-}
-
-.panel-header .icon-button,
-.panel-header button,
-.panel-header a.icon-button {
- display: inline-flex;
- align-items: center;
- justify-content: flex-start;
- border: none;
- background: transparent;
- color: var(--muted);
- padding: 0.25rem;
- border-radius: 4px;
- cursor: pointer;
- transition: background 0.15s ease, color 0.15s ease;
- text-decoration: none;
- text-align: left;
-}
-
-.panel-header .icon-button:hover:not([disabled]),
-.panel-header button:hover:not([disabled]),
-.panel-header a.icon-button:hover {
- background: var(--sidebar-hover-bg);
- color: var(--fg);
-}
-
-.panel-header .icon-button.active:hover:not([disabled]),
-.panel-header button.active:hover:not([disabled]),
-.panel-header a.icon-button.active:hover {
- background: var(--accent-soft);
- color: var(--fg);
-}
-
-.panel-header .icon-button.ghost,
-.panel-header button.icon-button.ghost {
- color: var(--muted);
- padding: 0.25rem;
-}
-
-.panel-header .icon-button.ghost:hover:not([disabled]),
-.panel-header button.icon-button.ghost:hover:not([disabled]) {
- color: var(--fg);
- background: var(--sidebar-hover-bg);
-}
-
-.preview-zoom-backdrop {
- position: fixed;
- inset: 0;
- background: var(--overlay-backdrop);
- transition: background 0.25s ease, opacity 0.25s ease;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 2rem;
- z-index: 2000000;
- cursor: zoom-out;
- opacity: 0;
- pointer-events: none;
-}
-
-.preview-zoom-backdrop--visible {
- opacity: 1;
- background: var(--overlay-backdrop);
- pointer-events: auto;
-}
-
-.preview-zoom__stage {
- position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
- max-width: 95vw;
- max-height: 95vh;
- z-index: 3000000;
-}
-
-.preview-zoom__image {
- max-width: 95vw;
- max-height: 95vh;
- width: auto;
- height: auto;
-}
-
-.preview-zoom__scroll {
- display: flex;
- align-items: center;
- justify-content: center;
- max-width: 95vw;
- max-height: 95vh;
- box-shadow: 0 32px 120px var(--shadow-deep);
-}
-
-.preview-zoom__scroll:focus {
- outline: none;
-}
-
-.preview-zoom__scroll--native {
- overflow: auto;
- cursor: zoom-out;
- justify-content: flex-start;
- align-items: flex-start;
-}
-
-.preview-zoom__nav {
- position: absolute;
- bottom: 1em;
- left: 50%;
- transform: translateX(-50%);
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 0.9rem;
- opacity: 0;
- transition: opacity 0.18s ease;
- pointer-events: none;
-}
-
-.preview-zoom__stage:hover .preview-zoom__nav,
-.preview-zoom__stage:focus-within .preview-zoom__nav {
- opacity: 1;
- pointer-events: auto;
-}
-
-.preview-zoom__nav-button {
- width: 2.8rem;
- height: 2.8rem;
- border: none;
- border-radius: 999px;
- background: var(--preview-nav-bg);
- color: var(--preview-nav-fg);
- display: inline-flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- z-index: 1;
- transition: background 0.15s ease, transform 0.15s ease;
- box-shadow: 0 12px 28px var(--shadow-strong);
-}
-
-.preview-zoom__nav-button:hover {
- background: var(--preview-nav-bg-hover);
-}
-
-.preview-zoom__nav-button:focus-visible {
- outline: 2px solid var(--accent);
- outline-offset: 3px;
-}
-
-.preview-zoom__nav-button svg {
- width: 1.5rem;
- height: 1.5rem;
-}
-
-.preview-zoom__nav-button[disabled] {
- opacity: 0.35;
- cursor: default;
-}
-
-.with-icon {
- display: inline-flex;
- align-items: center;
- gap: 0.35rem;
-}
-
-.icon-inline {
- width: 1rem;
- height: 1rem;
-}
-
-.icon {
- width: 1rem;
- height: 1rem;
- display: inline-flex;
- align-items: center;
- justify-content: center;
-}
-
-@keyframes icon-spin {
- to {
- transform: rotate(360deg);
- }
-}
-
-.icon--spin {
- animation: icon-spin 0.9s linear infinite;
-}
-
-.icon--flip-y {
- transform: scaleX(-1);
-}
-
-.visually-hidden {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- clip: rect(0, 0, 0, 0);
- border: 0;
-}
-
-.icon path {
- stroke: currentColor;
- stroke-width: 1.6;
- fill: none;
-}
-
-.icon--fill path {
- stroke: none;
- fill: currentColor;
-}
-
-
-.view-toggle {
- display: inline-flex;
- align-items: center;
- gap: 0.25rem;
-}
-
-.view-toggle__button {
- border: 1px solid var(--border);
- background: transparent;
- color: var(--muted);
- padding: 0.3rem 0.6rem;
- border-radius: 4px;
- cursor: pointer;
- transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
-}
-
-.view-toggle__button:hover,
-.view-toggle__button:focus-visible {
- background: var(--surface-subtle);
- color: var(--fg);
- outline: none;
-}
-
-.view-toggle__button.active {
- background: var(--accent);
- color: var(--on-accent);
- border-color: var(--accent);
-}
-
-.view-toggle__icon {
- width: 1.4rem;
- height: 1.4rem;
-}
-
-.documents-actions__sort-group {
- display: inline-flex;
- align-items: center;
- gap: 0.35rem;
-}
-
-.documents-sort {
- display: inline-flex;
- align-items: center;
- position: relative;
-}
-
-.documents-sort__trigger {
- display: inline-flex;
- align-items: center;
- gap: 0.35rem;
- font-size: 0.85rem;
- white-space: nowrap;
- padding: 0.25rem 0.5rem;
- min-height: 2.1rem;
-}
-
-.documents-sort__label {
- display: inline-flex;
- align-items: center;
- line-height: 1.1;
-}
-
-.documents-sort__trigger-content {
- display: inline-flex;
- align-items: center;
- gap: 0.35rem;
-}
-
-.documents-sort__quickmenu .menu__item,
-.documents-sort__quickmenu .menu__item.active {
- font-weight: 400;
-}
-
-.documents-toolbar__toggle {
- border: 1px solid var(--border);
- border-radius: 4px;
- padding: 0.3rem;
- background: transparent;
- color: var(--muted);
- transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
-}
-
-.documents-toolbar__toggle:hover:not([disabled]) {
- color: var(--fg);
- border-color: var(--border);
-}
-
-.documents-toolbar__toggle[aria-pressed='true'] {
- border-color: var(--accent);
- color: var(--accent);
- background: var(--surface-subtle);
-}
-
-.documents-sort__direction {
- padding: 0.3rem 0.45rem;
-}
-
-.documents-sort__direction[aria-pressed='true'] {
- border-color: var(--border);
- color: var(--muted);
- background: transparent;
-}
-
-.documents-sort__direction svg {
- width: 1.1rem;
- height: 1.1rem;
-}
-
-.app-shell {
- height: 100%;
- display: flex;
- flex-direction: column;
- color: var(--fg);
-}
-
-
-.documents-main {
- flex: 1;
- display: flex;
- min-height: 100vh;
- width: 100%;
- position: relative;
-}
-
-.documents-main--sidebar-collapsed {
- position: relative;
-}
-
-.main-content {
- display: flex;
- flex-direction: column;
- min-height: 0;
- min-width: 0;
- margin: 0;
- border-radius: 0;
- background: var(--surface);
- flex-grow: 1;
- box-shadow: -12px 0 24px -12px var(--shadow-faint);
-}
-
-.documents-main:not(.documents-main--sidebar-collapsed) .main-content {
- border-left: 1px solid var(--border);
-}
-
-.main-content__header {
- padding: 0.5rem 1.25rem;
- justify-content: space-between;
- align-items: center;
- gap: 0.75rem;
-}
-
-.main-content__header-wrapper {
- position: relative;
-}
-
-.main-content__body {
- position: relative;
- flex: 1 1 auto;
- display: flex;
- min-height: 0;
-}
-
-.main-content__body > * {
- min-width: 0;
- min-height: 0;
-}
-
-.main-content__body > *:not(.detail-panel) {
- flex: 1 1 auto;
-}
-
-.main-content__body--workspace.main-content__body--has-detail > .desk-shell {
- margin-right: calc(-1 * var(--detail-panel-width));
-}
-
-.main-content--has-detail {
- padding-right: var(--detail-panel-width);
-}
-
-.main-content__title {
- margin: 0;
- font-size: 1rem;
- font-weight: 600;
- color: var(--fg);
-}
-
-
-.documents-main--sidebar-collapsed {
- position: relative;
- grid-template-columns: auto minmax(0, 1fr);
-}
-
-.sidebar .panel-header .icon,
-.main-content__header .icon,
-.detail-panel .panel-header .icon {
- width: 1.35rem;
- height: 1.35rem;
-}
-
-.main-content__actions {
- display: flex;
- align-items: center;
- gap: 0.6rem;
-}
-
-.main-content__actions-divider {
- display: inline-flex;
- align-items: center;
- color: var(--muted);
-}
-
-.main-content__title {
- margin: 0;
- font-size: 1rem;
- font-weight: 600;
- color: var(--fg);
- display: flex;
- align-items: center;
- gap: 0.5rem;
- min-width: 0;
- flex: 1 1 auto;
- overflow: hidden;
-}
-
-.main-content__breadcrumbs {
- flex: 1 1 auto;
- overflow: hidden;
-}
-
-.main-content__subtitle {
- font-size: 0.9rem;
- color: var(--muted);
- font-weight: 400;
- line-height: 1.2;
- flex-shrink: 0;
-}
-
-.panels-main {
- flex: 1;
- display: flex;
- justify-content: center;
- padding: 1.5rem;
- overflow: auto;
-}
-
-.panels-main > * {
- flex: 0 1 720px;
-}
-
-.preview-main {
- flex: 1;
- display: flex;
- flex-direction: column;
- min-height: 0;
-}
-
-.document-viewer {
- flex: 1;
- display: grid;
- grid-template-columns: minmax(0, 30em) minmax(0, 1fr);
- gap: 1rem;
- min-height: 0;
- padding: 1rem 1rem;
-}
-
-.document-drag-preview {
- position: fixed;
- pointer-events: none;
- top: -9999px;
- left: -9999px;
- width: var(--drag-preview-size, 96px);
- height: var(--drag-preview-size, 96px);
- z-index: 9999;
-}
-
-.document-drag-preview__thumb {
- position: absolute;
- top: 50%;
- left: 50%;
- width: 64px;
- height: 64px;
- border-radius: 6px;
- box-shadow: 0 6px 12px var(--shadow-pop);
- overflow: hidden;
- background-color: var(--overlay-dim);
- display: flex;
- align-items: center;
- justify-content: center;
- color: #fff;
- font-weight: 600;
- font-size: 0.8rem;
- text-transform: uppercase;
- transform: translate(-50%, -50%) rotate(var(--rotation-deg, 0deg));
- transform-origin: center;
-}
-
-.document-drag-preview__thumb--image {
- background-color: #000;
- background-repeat: no-repeat;
- background-size: contain;
- background-position: center;
-}
-
-.document-drag-preview__thumb .document-thumbnail,
-.document-drag-preview__thumb img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- pointer-events: none;
-}
-
-.document-drag-preview__thumb .thumb-placeholder,
-.document-drag-preview__thumb .thumb-placeholder * {
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- pointer-events: none;
-}
-
-.document-drag-preview__folder-thumb {
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- pointer-events: none;
-}
-
-.document-drag-preview__folder-thumb svg {
- width: 48px;
- height: 48px;
- color: var(--accent-strong, var(--accent));
-}
-
-.document-drag-preview__folder-placeholder {
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 0.75rem;
- font-weight: 600;
- text-transform: uppercase;
- letter-spacing: 0.04em;
- color: var(--text-on-dark);
-}
-
-.document-drag-preview__count {
- position: absolute;
- bottom: 4px;
- right: 4px;
- background-color: var(--accent);
- color: var(--on-accent);
- border-radius: 999px;
- padding: 0.25rem 0.5rem;
- font-size: 0.75rem;
- font-weight: 600;
- box-shadow: 0 4px 8px var(--shadow-pop);
- pointer-events: none;
-}
-
-.document-viewer__details {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
- min-height: 0;
- flex: 1;
-}
-
-.document-viewer__details-pane {
- display: flex;
- flex-direction: column;
- min-height: 0;
- overflow: auto;
- flex: 1;
-}
-.document-viewer__tabs-wrapper {
- display: flex;
- flex-direction: column;
- flex: 1;
- min-height: 0;
-}
-
-.document-viewer__section {
- display: flex;
- flex-direction: column;
- flex: 1;
- min-height: 0;
- padding-top: 1rem;
-}
-
-.document-viewer__section-title {
- margin: 0;
- font-size: 0.95rem;
- font-weight: 600;
- letter-spacing: 0.01em;
-}
-
-.document-viewer__section-list {
- margin: 0;
- padding: 0;
- list-style: none;
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
- gap: 0.75rem 1.25rem;
-}
-
-.document-viewer__section-item {
- display: flex;
- flex-direction: column;
- gap: 0.25rem;
-}
-
-.document-viewer__section-item dt {
- font-size: 0.75rem;
- color: var(--muted);
-}
-
-.document-viewer__section-item dd {
- margin: 0;
- font-size: 0.9rem;
- font-weight: 500;
- word-break: break-word;
-}
-
-.document-viewer__section-placeholder {
- margin: 0;
- font-size: 0.9rem;
- color: var(--muted);
-}
-
-.document-viewer__section-payload {
- font-size: 0.85rem;
-}
-
-.document-viewer__section-payload summary {
- cursor: pointer;
- font-weight: 500;
- color: var(--accent-strong, var(--accent));
-}
-
-.document-viewer__section-payload pre {
- margin: 0.75rem 0 0;
- padding: 0.75rem;
- background: var(--surface);
- max-height: 280px;
- overflow: auto;
- font-size: 0.8rem;
-}
-
-.document-viewer__section--metadata-json {
- overflow: auto;
-}
-
-.document-viewer__metadata-json {
- margin: 0;
- padding: 0.75rem;
- background: var(--surface);
- border-radius: 0.5rem;
- font-size: 0.85rem;
- line-height: 1.35;
- overflow: auto;
-}
-
-.document-viewer__tabs {
- display: inline-flex;
- align-items: center;
- gap: 0.5rem;
- border-bottom: 1px solid var(--outline-subtle);
-}
-
-.document-viewer__tab {
- appearance: none;
- border: none;
- background: transparent;
- padding: 0.4rem 0.75rem;
- font-size: 0.85rem;
- font-weight: 500;
- color: var(--muted);
- cursor: pointer;
- border-bottom: 2px solid transparent;
- transition:
- color 120ms ease,
- border-color 120ms ease;
-}
-
-.document-viewer__tab:hover,
-.document-viewer__tab:focus-visible {
- color: var(--accent);
-}
-
-.document-viewer__tab.is-active {
- color: var(--accent);
- border-color: var(--accent);
-}
-
-.document-viewer__tabpanes {
- flex: 1;
- min-height: 0;
- display: flex;
-}
-
-.document-viewer__tabpanes--single {
- flex: 1;
- min-height: 0;
-}
-
-.document-viewer__tabpanel {
- flex: 1;
- min-height: 0;
- display: flex;
- position: relative;
-}
-
-.document-viewer__object--ocr {
- width: 100%;
- height: 100%;
-}
-
-.document-viewer__object--ocr-text {
- width: 100%;
- height: 100%;
- margin: 0;
- padding: 1rem 0;
- font-size: 1rem;
- white-space: pre-wrap;
- font-family: inherit;
-}
-
-.document-viewer__message--error {
- color: var(--danger);
-}
-
-.document-viewer__viewport {
- flex: 1;
- min-width: 0;
- display: flex;
- position: relative;
- overflow: hidden;
- align-items: flex-start;
-}
-
-.document-viewer__object {
- width: 100%;
- height: 100%;
- border: none;
-}
-
-.document-viewer__object--image {
- width: auto;
- height: auto;
- max-width: 100%;
- max-height: 100%;
- object-fit: contain;
- align-self: flex-start;
-}
-
-.document-viewer__unsupported {
- height: 100%;
- width: 100%;
- padding: 2rem;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 0.75rem;
- text-align: center;
- background: var(--surface-subtle);
-}
-
-.document-viewer__unsupported-filename {
- font-size: 0.9rem;
- color: var(--muted);
-}
-
-.document-viewer__unsupported-message {
- font-size: 0.95rem;
- color: var(--fg);
-}
-
-.document-viewer__unsupported-download {
- font-weight: 600;
-}
-
-.document-viewer__unsupported-download svg {
- width: 1rem;
- height: 1rem;
-}
-
-.tags-panel__body {
- overflow-y: auto;
-}
-
-.tags-table {
- width: 100%;
- overflow: auto;
-}
-
-.tags-table table {
- width: 100%;
- border-collapse: collapse;
- min-width: 320px;
-}
-
-.tags-table th,
-.tags-table td {
- padding: 0.45rem 0.6rem;
- text-align: left;
- font-size: 0.85rem;
-}
-
-.tags-table th.numeric,
-.tags-table td.numeric {
- text-align: right;
-}
-
-.tags-table th.actions,
-.tags-table td.actions {
- text-align: right;
- width: 0;
-}
-
-.tags-table tbody tr:hover {
- background: var(--surface-subtle);
-}
-
-.tags-table tr.editing {
- background: var(--sidebar-hover-bg);
-}
-
-.tags-table__label {
- max-width: 24rem;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-.tags-table__swatch {
- display: inline-block;
- width: 1rem;
- height: 1rem;
- border-radius: 2px;
- box-shadow: inset 0 0 0 1px var(--shadow-faint);
-}
-
-.tags-panel__error {
- margin: 0.5rem 0;
- color: var(--danger);
- font-size: 0.8rem;
-}
-
-.tags-table__label-input {
- width: 100%;
-}
-
-.tags-table__color-editor {
- display: flex;
- align-items: center;
- gap: 0.4rem;
-}
-
-.tags-table__color-picker {
- width: 2.25rem;
- height: 2.25rem;
- padding: 0;
- background: none;
- cursor: pointer;
-}
-
-.tags-table__edit-controls {
- display: flex;
- justify-content: flex-start;
- padding: 0.25rem;
- gap: 0.4rem;
-}
-
-.tags-table__row-actions {
- display: flex;
- justify-content: flex-start;
- padding: 0.25rem;
- gap: 0.4rem;
-}
-
-.correspondents-panel .header-actions {
- gap: 0.5rem;
-}
-
-.tags-actions {
- display: flex;
- flex-wrap: wrap;
- gap: 0.6rem;
- align-items: center;
-}
-
-.tags-actions__form {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 0.4rem;
-}
-
-.tags-actions__form input[type='text'] {
- min-width: 14rem;
-}
-
-.correspondents-actions__form {
- display: flex;
- gap: 0.4rem;
-}
-
-.correspondents-actions__form input {
- min-width: 14rem;
-}
-
-
-
-.correspondent-pill {
- display: inline-flex;
- align-items: center;
- gap: 0.35rem;
- background: var(--surface-subtle);
- border-radius: 999px;
- padding: 0.15rem 0.5rem;
- font-size: 0.9rem;
- border: none;
-}
-
-.correspondent-pill__label {
- line-height: 1.2;
-}
-
-.correspondent-pill__label strong {
- font-size: 0.8rem;
- text-transform: capitalize;
- color: var(--muted);
-}
-
-.correspondent-pill__remove {
- border: none;
- background: none;
- cursor: pointer;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- color: var(--muted);
- padding: 0;
-}
-
-.correspondent-pill__remove:hover {
- color: var(--danger);
-}
-
-.correspondent-form {
- display: flex;
- gap: 0.4rem;
- align-items: center;
-}
-
-.correspondent-form input,
-.correspondent-form select {
- min-height: 32px;
-}
-
-.document-viewer__message {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- color: var(--on-accent);
- background: var(--overlay-dark);
- padding: 0.5rem 1rem;
- border-radius: 999px;
- font-size: 0.9rem;
-}
-
-.panel-section__header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 0.5rem;
- padding: 0 0 0.5rem;
-}
-
-.panel-section__titles {
- display: flex;
- flex-direction: column;
- gap: 0.3rem;
-}
-
-.panel-section__header h2 {
- margin: 0;
- font-size: 0.9rem;
- font-weight: 600;
-}
-
-.documents-panel__title {
- display: flex;
- align-items: center;
- gap: 0.35rem;
- margin: 0;
- font-size: 0.9rem;
- font-weight: 600;
- min-width: 0;
- flex-wrap: nowrap;
- overflow: hidden;
-}
-
-.panel-section__subtitle {
- margin-top: 0.25rem;
- font-size: 0.78rem;
- color: var(--muted);
-}
-
-.breadcrumb-trail {
- display: flex;
- align-items: center;
- gap: 0.35rem;
- flex-wrap: nowrap;
- min-width: 0;
- flex: 1 1 auto;
- max-width: 100%;
- overflow: hidden;
-}
-
-.breadcrumb-trail--measure {
- position: absolute;
- visibility: hidden;
- pointer-events: none;
- left: -9999px;
- top: -9999px;
- max-width: none;
- overflow: visible;
-}
-
-.breadcrumb-trail__link {
- display: inline-flex;
- align-items: center;
- max-width: 100%;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- border: none;
- background: none;
- padding: 0;
- font: inherit;
- color: inherit;
- cursor: default;
-}
-
-.breadcrumb-trail__link:not(.is-current) {
- cursor: pointer;
- color: var(--accent);
-}
-
-.breadcrumb-trail__link:not(.is-current):hover,
-.breadcrumb-trail__link:not(.is-current):focus-visible {
- text-decoration: underline;
-}
-
-.breadcrumb-trail__separator {
- color: var(--muted);
- margin: 0 0.2rem;
-}
-
-.breadcrumb-trail__ellipsis {
- position: relative;
- display: inline-flex;
-}
-
-.breadcrumb-trail__ellipsis-button {
- cursor: pointer;
-}
-
-.documents-panel__breadcrumbs {
- flex: 1 1 auto;
- min-width: 0;
- max-width: 100%;
- overflow: hidden;
-}
-
-.documents-panel__breadcrumbs {
- max-width: 22rem;
-}
-
-.panel-section__body {
- flex: 1;
- display: flex;
- flex-direction: column;
- min-height: 0;
-}
-
-.panel-section__body--scrollable,
-.panel-section__body.scrollable {
- overflow-y: auto;
-}
-
-.folder-tree {
- list-style: none;
- margin: 0;
- padding: 0;
-}
-
-.folder-node {
- margin: 0;
-}
-
-.folder-row {
- display: flex;
- align-items: center;
- gap: 0.22rem;
- padding: 0.32rem 0.48rem;
- border-radius: 8px;
- cursor: pointer;
- color: inherit;
- transition: background 0.12s ease, color 0.12s ease;
- position: relative;
- overflow: hidden;
- user-select: none;
- -webkit-user-select: none;
-}
-
-.folder-row .name-wrap {
- display: inline-flex;
- align-items: center;
- gap: 0.25rem;
- flex: 1;
- min-width: 0;
-}
-
-.folder-row .name-wrap .name__label {
- display: block;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.folder-row .folder-icon-image {
- flex-shrink: 0;
-}
-
-.folder-row:hover {
- background: var(--sidebar-hover-bg);
- color: var(--fg);
-}
-
-.folder-row.active {
- background: var(--sidebar-active-bg);
- color: var(--fg);
-}
-
-.folder-row .toggle {
- width: 1.05rem;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- user-select: none;
- flex-shrink: 0;
- transition: transform 0.18s ease;
-}
-
-.folder-row .toggle.invisible {
- visibility: hidden;
-}
-
-.toggle-icon {
- width: 0.9rem;
- height: 0.9rem;
- transition: transform 0.18s ease;
-}
-
-.folder-row .toggle.expanded .toggle-icon {
- transform: rotate(90deg);
-}
-
-.folder-row__actions {
- position: absolute;
- right: 0;
- top: 50%;
- transform: translateY(-50%);
- display: flex;
- gap: 0;
- opacity: 0;
- pointer-events: none;
- transition: opacity 0.12s ease;
- background-color: var(--surface-overlay);
- border-radius: 8px;
- padding: 0 0.18rem;
-}
-
-.folder-row__actions .icon-button {
- margin: 0;
-}
-
-.folder-row:hover .folder-row__actions,
-.folder-row:focus-within .folder-row__actions {
- opacity: 1;
- pointer-events: auto;
-}
-
-.folder-children {
- list-style: none;
- margin: 0 0 0 0.45rem;
- padding: 0;
-}
-
-
-
-.sidebar-section {
- margin-top: 1rem;
- display: flex;
- flex-direction: column;
- gap: 0.4rem;
-}
-
-.sidebar {
- gap: 0;
- overflow: hidden;
- color: var(--sidebar-fg);
- display: flex;
- flex-direction: column;
- width: 20em;
- max-width: 20em;
- flex: 0 0 20em;
- background: var(--bg);
-}
-
-.sidebar__body {
- flex: 1;
- display: flex;
- flex-direction: column;
- padding: 0.5rem 1rem;
- gap: 0.75rem;
- overflow-y: auto;
- min-height: 0;
-}
-
-.sidebar__footer {
- margin-top: auto;
- padding: 0.75rem 1rem 1rem;
- border-top: 1px solid var(--border);
- background: var(--surface-subtle);
- display: flex;
- flex-direction: column;
- gap: 0.75rem;
-}
-
-.sidebar__title {
- margin: 0;
- font-size: 1rem;
- font-weight: 600;
- color: var(--fg);
- padding-left: 0.25rem;
-}
-
-.sidebar__tenant {
- color: var(--muted);
- font-weight: 500;
-}
-
-.sidebar__header {
- position: relative;
-}
-
-.sidebar__title-button {
- display: inline-flex;
- align-items: center;
- gap: 0.35rem;
- border: none;
- background: none;
- color: inherit;
- font: inherit;
- cursor: pointer;
- padding: 0;
-}
-
-.sidebar__title-button:focus-visible {
- outline: 2px solid var(--accent);
- outline-offset: 2px;
-}
-
-.sidebar__title-chevron {
- transition: transform 0.2s ease;
-}
-
-.sidebar__title-chevron.is-open {
- transform: rotate(180deg);
-}
-
-.menu {
- position: absolute;
- top: 100%;
- left: 1em;
- right: auto;
- background: var(--surface);
- border: 1px solid var(--border-muted, var(--border));
- border-radius: 0.5rem;
- box-shadow: 0 12px 28px var(--shadow-strong);
- min-width: 220px;
- z-index: 20;
- overflow: hidden;
-}
-
-.menu__list {
- max-height: 260px;
- overflow-y: auto;
- padding: 0.35rem 0;
- display: flex;
- flex-direction: column;
- gap: 0.15rem;
-}
-
-.menu__heading {
- padding: 0.5rem 1rem 0.25rem;
- font-size: 0.82rem;
- font-weight: 600;
- color: var(--muted);
-}
-
-.menu button.menu__item,
-.menu .menu__item {
- display: flex;
- align-items: center;
- justify-content: flex-start;
- gap: 0.6rem;
- width: 100%;
- padding: 0.55rem 1rem;
- border: none;
- background: none;
- color: inherit;
- font: inherit;
- text-align: left;
- cursor: pointer;
- transition: background 0.15s ease, color 0.15s ease;
-}
-
-.menu button.menu__item:hover,
-.menu button.menu__item:focus-visible,
-.menu .menu__item:hover,
-.menu .menu__item:focus-visible {
- background: var(--sidebar-hover-bg);
- color: var(--fg);
-}
-
-.menu button.menu__item.active,
-.menu .menu__item.active {
- font-weight: 600;
- color: var(--accent);
-}
-
-.menu button.menu__item:focus-visible,
-.menu .menu__item:focus-visible {
- outline: 2px solid var(--accent);
- outline-offset: -2px;
-}
-
-.menu__label {
- flex: 1;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-.menu__check-slot {
- width: 1rem;
- display: flex;
- align-items: center;
- justify-content: center;
- color: var(--accent);
-}
-
-.menu__active-indicator {
- font-size: 0.75rem;
- color: var(--muted);
-}
-
-.menu__empty {
- display: block;
- padding: 0.6rem 0.75rem;
- color: var(--muted);
- font-size: 0.85rem;
-}
-
-.menu__footer {
- border-top: 1px solid var(--border-muted, var(--border));
- padding: 0.35rem 0.5rem;
- display: flex;
- flex-direction: column;
- gap: 0.25rem;
-}
-
-.menu--simple .menu__footer {
- border-top: none;
-}
-
-.menu__settings {
- width: 100%;
- display: inline-flex;
- align-items: center;
- gap: 0.4rem;
- border: none;
- background: none;
- color: inherit;
- font: inherit;
- cursor: pointer;
- padding: 0.4rem 0.5rem;
- border-radius: 0.35rem;
-}
-
-.menu__settings:hover,
-.menu__settings:focus-visible {
- background: var(--sidebar-hover-bg);
-}
-
-.menu__logout {
- width: 100%;
- display: inline-flex;
- align-items: center;
- gap: 0.4rem;
- border: none;
- background: none;
- color: var(--danger);
- font: inherit;
- cursor: pointer;
- padding: 0.4rem 0.5rem;
- border-radius: 0.35rem;
-}
-
-.menu__logout:hover,
-.menu__logout:focus-visible {
- background: var(--surface-danger-subtle);
-}
-
-.sidebar__search {
- display: flex;
- align-items: center;
- gap: 0.4rem;
- margin: 0 0 0.75rem;
-}
-
-.sidebar__search input[type='search'] {
- flex: 1;
- padding: 0.4rem 0.6rem;
- border-radius: 4px;
- border: 1px solid var(--border);
- background: var(--surface);
- color: var(--fg);
-}
-
-.sidebar__search button {
- padding: 0.35rem 0.75rem;
- border: 1px solid transparent;
- border-radius: 4px;
- background: transparent;
- color: var(--accent);
- font-size: 0.85rem;
- cursor: pointer;
-}
-
-.sidebar__search button:hover:not([disabled]) {
- background: var(--sidebar-hover-bg);
-}
-
-.sidebar__footer .sidebar-section {
- margin-top: 0;
-}
-
-.sidebar__footer .sidebar-section__actions {
- gap: 0.4rem;
-}
-
-.sidebar-section:first-of-type,
-.sidebar-section--folders {
- margin-top: 0;
-}
-
-.sidebar-section--folders {
- display: flex;
- flex-direction: column;
-}
-
-.sidebar-section__header {
- display: flex;
- align-items: center;
- font-size: 0.75rem;
- justify-content: space-between;
- color: var(--muted);
-}
-
-.sidebar-section__title {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 0.4rem;
- width: 100%;
- background: transparent;
- border: none;
- border-radius: 6px;
- padding: 0.25rem 0.35rem;
- margin: -0.25rem -0.35rem;
- color: inherit;
- font: inherit;
- cursor: pointer;
- text-align: left;
-}
-
-.sidebar-section__title:hover:not([disabled]),
-.sidebar-section__title:focus-visible {
- background: var(--sidebar-hover-bg);
- color: var(--fg);
-}
-
-.sidebar-section__title:hover:not([disabled]) h3,
-.sidebar-section__title:focus-visible h3 {
- color: var(--fg);
-}
-
-.sidebar-section__header h3 {
- margin: 0;
- font-weight: 600;
-}
-
-.sidebar-section__actions {
- display: inline-flex;
- align-items: center;
- gap: 0.35rem;
-}
-
-.sidebar-section__actions .meta {
- font-size: 0.75rem;
- color: var(--muted);
-}
-
-.sidebar-slider {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- font-size: 0.8rem;
- color: var(--muted);
-}
-
-.sidebar-slider input[type='range'] {
- flex: 1;
- accent-color: var(--accent);
-}
-
-.sidebar-slider__value {
- min-width: 3rem;
- text-align: right;
- color: var(--fg);
- font-variant-numeric: tabular-nums;
-}
-
-.sidebar-item {
- background: transparent;
- padding: 0.16rem 0.26rem;
- text-align: left;
- color: var(--sidebar-fg);
- border-radius: 2px;
- cursor: pointer;
- transition: background 0.12s ease, color 0.12s ease;
- display: flex;
- align-items: center;
- gap: 0.38rem;
- width: 100%;
-}
-
-.sidebar-item:hover {
- background: var(--sidebar-hover-bg);
- color: var(--fg);
-}
-
-.sidebar-item.active {
- background: var(--sidebar-active-bg);
- color: var(--fg);
- font-weight: 600;
-}
-
-.sidebar-item:focus-visible {
- outline: 2px solid var(--accent);
- outline-offset: 2px;
-}
-
-.sidebar-item__icon {
- width: 1rem;
- height: 1rem;
- flex-shrink: 0;
- color: var(--accent);
-}
-
-.sidebar-item.active .sidebar-item__icon {
- color: currentColor;
-}
-
-.sidebar-tag-cloud {
- display: flex;
- flex-wrap: wrap;
- gap: 0.35rem;
- padding: 0.25rem 0 0.1rem;
-}
-
-.sidebar-tag-pill {
- border-radius: 999px;
- padding: 0.25rem 0.6rem;
- font-size: 0.75rem;
- font-weight: 600;
- line-height: 1;
- cursor: pointer;
- background: var(--surface-soft);
- color: var(--fg);
- transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease,
- opacity 0.12s ease;
- box-shadow: 0 1px 2px var(--shadow-faint);
-}
-
-.sidebar-tag-pill:hover {
- transform: none;
- box-shadow: 0 2px 6px var(--shadow-pop);
- border-color: var(--accent-soft);
-}
-
-.sidebar-tag-pill:focus-visible {
- outline: 2px solid var(--accent);
- outline-offset: 2px;
-}
-
-.sidebar-tag-pill.active {
- border-color: var(--sidebar-active-pill-border);
- box-shadow: 0 0 0 1.5px var(--sidebar-active-pill-border);
-}
-
-.sidebar-tag-pill--untagged {
- border: 1px dashed var(--border);
- background: var(--surface-subtle);
- color: var(--muted);
-}
-
-.sidebar-tag-pill--untagged.active {
- color: var(--fg);
-}
-
-.sidebar-tag-cloud--has-active .sidebar-tag-pill:not(.active) {
- opacity: 0.45;
-}
-
-.sidebar-correspondent-list {
- list-style: none;
- margin: 0.4rem 0 0;
- padding: 0;
- display: flex;
- flex-direction: column;
- gap: 0.25rem;
-}
-
-.sidebar-correspondent-item {
- display: block;
- background: transparent;
- border-radius: 4px;
- padding: 0.35rem 0.5rem;
- text-align: left;
- color: var(--sidebar-fg);
- cursor: pointer;
- font-size: 0.85rem;
- transition: background 0.15s ease, box-shadow 0.15s ease;
-}
-
-.sidebar-correspondent-item:hover {
- background: var(--sidebar-hover-bg);
- color: var(--fg);
-}
-
-.sidebar-correspondent-item:focus-visible {
- outline: 2px solid var(--accent);
- outline-offset: 2px;
-}
-
-.sidebar-correspondent-item.active {
- background: var(--sidebar-active-bg);
- color: var(--fg);
-}
-
-
-.documents-panel {
- padding: 0 0.75rem 0.75rem 0.75rem;
- display: flex;
- flex-direction: column;
- min-height: 0;
-}
-
-.tags-panel,
-.correspondents-panel {
- padding: 1.25rem;
- display: flex;
- flex-direction: column;
- min-height: 0;
-}
-
-.folder-row.is-drop-target {
- outline: 2px dashed var(--accent);
- outline-offset: 2px;
-}
-
-.documents-panel .panel-section__header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- position: relative;
-}
-
-.documents-panel .panel-section__header .header-actions {
- display: flex;
- gap: 0.5rem;
-}
-
-.documents-panel__title {
- display: flex;
- align-items: center;
- gap: 0.35rem;
- margin: 0;
- font-size: 0.9rem;
- font-weight: 600;
- min-width: 0;
- white-space: nowrap;
- overflow: hidden;
-}
-
-.documents-panel__breadcrumbs {
- flex: 1 1 auto;
- overflow: hidden;
-}
-
-.documents-panel .panel-section__body {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
-}
-
-.panel-floating {
- position: absolute;
- top: calc(50% + 0.25rem);
- left: 50%;
- transform: translate(-50%, -50%);
- background: color-mix(in oklch, var(--surface) 100%, transparent);
- border: 1px solid color-mix(in oklch, var(--border) 95%, transparent);
- padding: 0.45rem 0.85rem;
- border-radius: 1rem;
- font-size: 0.95rem;
- font-weight: 400;
- color: var(--fg);
- box-shadow: 0 2px 6px color-mix(in oklch, var(--shadow-soft) 60%, transparent);
- pointer-events: auto;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-wrap: nowrap;
- gap: 0.75rem;
- z-index: 2000000;
-}
-
-.panel-floating__label {
- white-space: nowrap;
- pointer-events: none;
- font-size: 0.95rem;
- color: var(--fg);
-}
-
-.selection-summary {
- display: inline-flex;
- align-items: center;
- gap: 0.4rem;
-}
-
-.selection-summary__token {
- display: inline-flex;
- align-items: center;
- gap: 0.3rem;
-}
-
-.selection-summary__count {
- font-weight: 600;
- font-variant-numeric: tabular-nums;
-}
-
-.selection-summary__icon {
- width: 1rem;
- height: 1rem;
-}
-
-.selection-summary--text {
- font-weight: 600;
-}
-
-.selection-summary__separator {
- opacity: 0.45;
-}
-
-.panel-floating-actions {
- display: inline-flex;
- align-items: center;
- gap: 0.4rem;
- flex-wrap: nowrap;
- pointer-events: auto;
-}
-
-.panel-floating-actions .quick-add {
- pointer-events: auto;
-}
-
-.panel-floating-actions .quick-add__trigger {
- pointer-events: auto;
-}
-
-.panel-floating-actions .quick-add__trigger[disabled] {
- opacity: 0.45;
- cursor: not-allowed;
-}
-
-.panel-floating-actions__button {
- display: inline-flex;
- align-items: center;
- gap: 0;
- pointer-events: auto;
- font-size: 1.35rem;
-}
-
-.panel-floating-actions__button .icon-inline {
- display: inline-flex;
- width: 1.35rem;
- height: 1.35rem;
-}
-
-.documents-panel .documents-scroll {
- overflow-y: auto;
- background: transparent;
- min-height: 0;
- padding-right: 0.3rem;
- flex-grow: 1;
-}
-
-.documents-panel .documents-scroll:focus-visible {
- outline: 2px solid var(--selection-ring);
- outline-offset: 2px;
- border-radius: 0.25rem;
-}
-
-.documents-panel table {
- max-width: 100%;
- border-collapse: collapse;
- font-size: 0.88rem;
- margin-top: 0.4rem;
-}
-
-.documents-panel thead th {
- background-color: var(--surface);
- position: sticky;
- top: 0;
- z-index: 1;
- padding: 0.45rem 0.6rem;
- color: var(--muted);
-}
-
-
-.documents-panel th,
-.documents-panel td {
- padding: 0.45rem 0.6rem;
- text-align: left;
-}
-
-.documents-panel--view-grid .documents-scroll {
- padding: 0.35rem 0.5rem 1rem;
-}
-
-.documents-panel th.thumb-column,
-.documents-panel td.thumb-cell {
- width: 54px;
- text-align: center;
-}
-
-.documents-panel--view-grid .document-thumbnail-wrapper {
- width: var(--documents-grid-icon-size);
- height: var(--documents-grid-icon-size);
- padding: 8px;
- border-radius: 10px;
- display: flex;
- justify-content: center;
- align-items: center;
-}
-
-.documents-panel--view-grid .folder-card__icon {
- width: var(--documents-grid-icon-size);
- height: var(--documents-grid-icon-size);
- padding: 8px;
- border-radius: 10px;
- display: flex;
- justify-content: center;
- align-items: center;
-}
-
-.document-thumbnail-inner {
- position: relative;
- display: inline-flex;
- align-items: center;
- justify-content: center;
-}
-
-.document-thumbnail-inner--multipage::after {
- content: '';
- position: absolute;
- top: 0;
- right: 0;
- width: 24px;
- height: 24px;
- background-image: url('./assets/papercorner.svg');
- background-repeat: no-repeat;
- background-size: contain;
- pointer-events: none;
-}
-
-.documents-panel--view-grid .document-thumbnail-inner--multipage::after {
- width: 28px;
- height: 28px;
-}
-
-.document-thumbnail {
- max-width: 100%;
- max-height: 100%;
- box-shadow: 0 1px 6px var(--shadow-medium);
-}
-
-.documents-panel--view-grid .document-thumbnail {
- box-shadow: 0 1px 12px var(--shadow-medium);
-}
-
-.thumb-placeholder {
- width: 100%;
- height: 100%;
- border-radius: 1px;
- background: var(--surface-subtle);
- color: var(--muted);
- display: inline-flex;
- align-items: center;
- justify-content: center;
- font-size: 0.68rem;
- font-weight: 600;
- letter-spacing: 0.08em;
- box-shadow: 0 1px 3px var(--shadow-medium);
-}
-
-.documents-panel--view-grid .thumb-placeholder {
- font-size: 1.1rem;
- letter-spacing: 0.12em;
- display: inline-flex;
- width: 100%;
- height: 100%;
-}
-
-.thumb-icon {
- width: 100%;
- height: 100%;
- display: inline-flex;
- align-items: center;
- justify-content: center;
-}
-
-.thumb-icon__image {
- width: 32px;
- height: 32px;
- display: block;
-}
-
-.documents-panel th.actions-column,
-.documents-panel td.actions {
- width: 1%;
- white-space: nowrap;
-}
-
-.documents-panel td.doc-list__name {
- width: 100%;
-}
-
-.doc-title-edit {
- display: inline-flex;
- align-items: center;
- gap: 0.35rem;
- flex-wrap: nowrap;
-}
-
-.doc-title-edit input[type='text'] {
- padding: 0.3rem 0.55rem;
- border-radius: 4px;
- border: 1px solid var(--border);
- background: var(--surface);
- color: var(--fg);
- min-width: 8rem;
-}
-
-.doc-title-edit input[type='text']:focus-visible {
- outline: 2px solid var(--selection-ring);
- outline-offset: 1px;
-}
-
-.doc-title-edit .icon-button {
- flex-shrink: 0;
-}
-
-.documents-panel .doc-name__primary {
- display: inline-flex;
- align-items: center;
- gap: 0.35rem;
- flex-wrap: wrap;
-}
-
-.documents-panel .doc-name__primary-text {
- overflow-wrap: anywhere;
-}
-
-
-.doc-entry {
- display: flex;
- align-items: center;
- gap: 0.6rem;
-}
-
-.doc-entry--with-thumb .doc-entry__thumb {
- flex: 0 0 auto;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.doc-entry__thumb .document-thumbnail-wrapper {
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.doc-entry__main {
- display: flex;
- flex-direction: column;
- gap: 0.35rem;
- min-width: 0;
-}
-
-.doc-entry__name {
- font-weight: 600;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- max-width: 100%;
-}
-
-.doc-entry__tags {
- display: flex;
- flex-wrap: wrap;
- gap: 0.3rem;
-}
-
-.documents-panel td.actions .action-buttons {
- display: inline-flex;
- align-items: center;
- gap: 0.3rem;
-}
-
-.documents-panel tbody tr {
- background: transparent;
- transition: background 0.15s ease;
-}
-
-.documents-panel tbody tr.folder {
- cursor: pointer;
-}
-
-.documents-panel tbody tr.folder.focused {
- box-shadow: inset 2px 0 0 var(--accent-outline);
- background: var(--sidebar-hover-bg);
-}
-
-.documents-panel tbody tr.document {
- cursor: pointer;
-}
-
-.documents-panel tbody tr.document.selected,
-.documents-panel tbody tr.folder.selected {
- background: var(--selection-soft);
- box-shadow: inset 2px 0 0 var(--accent-outline-strong);
-}
-
-.documents-panel tbody tr.document,
-.documents-panel tbody tr.document * {
- user-select: none;
- -webkit-user-select: none;
- -moz-user-select: none;
-}
-
-.documents-panel tbody tr.document.focused:not(.selected) {
- box-shadow: inset 2px 0 0 var(--accent-outline);
-}
-
-.doc-name {
- display: flex;
- flex-direction: column;
- gap: 0.25rem;
-}
-
-.documents-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(var(--documents-grid-icon-size), 1fr));
- gap: 0.3rem 1.3rem;
- padding: 0;
-}
-
-.document-card {
- padding: 0rem;
- display: flex;
- flex-direction: column;
- gap: 0.3rem;
- cursor: pointer;
- align-items: center;
- text-align: center;
-}
-
-.document-card.selected .document-thumbnail-wrapper,
-.folder-card.selected .folder-card__icon {
- background-color: var(--selection-soft);
-}
-
-.document-card:focus-visible .document-thumbnail-wrapper {
- background-color: var(--selection-ring);
-}
-
-.document-card.is-dragging {
- opacity: 0.55;
-}
-
-.document-card__meta {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 0.35rem;
- width: 100%;
-}
-
-.document-card__title {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 0.35rem;
- color: var(--fg);
-}
-
-.document-card__title-row {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 0.35rem;
- flex-wrap: wrap;
-}
-
-.document-card__title-badge {
- padding: 0.2rem 0.7rem;
- border-radius: 1rem;
- max-width: 100%;
- word-break: break-word;
- font-size: var(--documents-grid-title-size);
- color: inherit;
-}
-
-.document-card .doc-correspondent-link {
- font-size: var(--documents-grid-title-size);
-}
-
-.document-card.selected .document-card__title-badge {
- background-color: var(--accent);
- color: var(--on-accent);
-}
-
-.document-card__subtitle {
- font-size: 0.78rem;
- color: var(--muted);
-}
-
-.document-card__tags {
- display: flex;
- flex-wrap: wrap;
- gap: 0.25rem;
- justify-content: center;
-}
-
-.folder-card {
- align-items: center;
- text-align: center;
-}
-
-.folder-card__icon {
- display: flex;
- align-items: center;
- justify-content: center;
- width: var(--documents-grid-icon-size);
- height: var(--documents-grid-icon-size);
-}
-
-.folder-card__icon-svg {
- width: 100%;
- height: 100%;
- object-fit: contain;
-}
-
-.folder-card__meta {
- display: flex;
- flex-direction: column;
- gap: 0.28rem;
- padding-top: 0.35rem;
-}
-
-.folder-card__label-row {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 0.35rem;
- flex-wrap: wrap;
-}
-
-.folder-card__name {
- color: var(--fg);
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: break-word;
- font-size: var(--documents-grid-title-size);
- padding: 0.2rem 0.7rem;
- border-radius: 1rem;
-}
-
-.folder-card__edit {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 0.35rem;
-}
-
-.folder-card.selected .folder-card__name {
- background-color: var(--accent);
- color: var(--on-accent);
-}
-
-
-.doc-name__title {
- max-width: 100%;
- word-break: break-word;
-}
-
-.doc-name__tags {
- display: flex;
- flex-wrap: wrap;
- gap: 0.25rem;
-}
-
-.doc-correspondents {
- display: inline;
- color: inherit;
-}
-
-.doc-correspondent-link {
- background: none;
- background-color: transparent;
- border: none;
- padding: 0;
- margin: 0;
- color: var(--accent);
- text-decoration: none;
- font: inherit;
- cursor: pointer;
- box-shadow: none;
- appearance: none;
-}
-
-.doc-correspondent-link:not(.is-static):hover,
-.doc-correspondent-link:not(.is-static):focus-visible {
- color: var(--accent-strong, var(--accent));
- text-decoration: underline;
- text-decoration-thickness: 1.5px;
- background-color: transparent;
-}
-
-.doc-correspondent-link:not(.is-static):focus-visible {
- outline: 2px solid currentColor;
- outline-offset: 2px;
-}
-
-.documents-panel tbody tr.document.selected .doc-correspondents,
-.documents-panel tbody tr.document.selected .doc-correspondent-link,
-.document-card.selected .doc-correspondent-link {
- color: inherit;
-}
-
-.document-card.selected .doc-correspondents {
- color: inherit;
-}
-
-.doc-correspondent-link.is-active {
- font-weight: 600;
-}
-
-.doc-correspondent-link.is-static {
- color: inherit;
- cursor: default;
- text-decoration: none;
-}
-
-.doc-correspondent-link__separator {
- color: inherit;
-}
-
-.documents-panel tbody tr.document.dragging {
- opacity: 0.4;
-}
-
-.documents-panel tbody tr.document.tag-drop-target {
- background: var(--accent-soft);
- box-shadow: inset 0 0 0 2px var(--accent);
-}
-
-.document-card.tag-drop-target {
- box-shadow: 0 0 0 2px var(--accent);
- border-color: var(--accent);
-}
-
-.document-card.tag-drop-target .document-card__title {
- color: var(--accent);
-}
-
-.filter-bar {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 0.32rem;
- margin-bottom: 0.5rem;
-}
-
-.filter-bar input[type='search'] {
- flex: 1;
- min-width: 180px;
-}
-
-.filter-actions {
- display: flex;
- gap: 0.32rem;
-}
-
-.search-hint {
- margin-top: 0.75rem;
- font-size: 0.85rem;
- color: var(--muted);
-}
-
-.badge {
- display: inline-flex;
- align-items: center;
- padding: 0.15rem 0.35rem;
- border-radius: 2px;
- background: var(--surface-subtle);
- color: var(--fg);
- font-size: 0.74rem;
-}
-
-.tag-chip {
- gap: 0.25rem;
- border-radius: 1rem;
- padding: 0.2rem 0.4rem;
- font-weight: 600;
-}
-
-.tag-chip--more {
- background: transparent;
- border-color: var(--border-strong);
- color: var(--muted);
-}
-
-.tag-chip--removable {
- gap: 0.35rem;
-}
-
-.tag-chip__remove {
- background: none;
- border: none;
- color: inherit;
- padding: 0;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- line-height: 1;
- cursor: pointer;
- opacity: 0.8;
-}
-
-.tag-chip__remove:hover,
-.tag-chip__remove:focus-visible {
- opacity: 1;
-}
-
-.tag-chip__remove:focus-visible {
- outline: 2px solid currentColor;
- outline-offset: 2px;
- border-radius: 50%;
-}
-
-.empty-state {
- border: 1px dashed var(--border);
- border-radius: 0;
- text-align: center;
- padding: 1.1rem 0.9rem;
- color: var(--muted);
- margin-top: 0.75rem;
- background: var(--surface-subtle);
-}
-
-.empty-state--global {
- border-style: solid;
-}
-
-.panel.detail-panel {
- position: fixed;
- top: 0;
- right: 0;
- width: min(100vw, var(--detail-panel-width));
- min-height: 100vh;
- height: 100%;
- height: 100%;
- background: var(--bg);
- box-shadow: 0 0 24px var(--shadow-soft);
- border-left: 1px solid var(--border);
- display: flex;
- flex-direction: column;
- z-index: 1000000;
-}
-
-.panel-header {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- padding: 0.5rem;
- min-width: 0;
-}
-
-.panel-header__leading,
-.panel-header__actions {
- display: flex;
- align-items: center;
- gap: 0.25rem;
-}
-
-.panel-header__title {
- margin: 0;
- font-size: 1rem;
- font-weight: 600;
- line-height: 1.2;
- flex: 1 1 auto;
- min-width: 0;
- display: flex;
- align-items: center;
- gap: 0.5rem;
- overflow: hidden;
- text-wrap: nowrap;
-}
-
-.panel-header > .panel-header__title:first-child {
- padding-left: 0.5rem;
-}
-
-.panel-header__actions {
- margin-left: auto;
-}
-
-.panel-body {
- padding: 0.5rem 1rem;
-}
-
-
-.detail-panel .panel-body {
- flex: 1;
- display: flex;
- flex-direction: column;
- overflow-y: auto;
- min-height: 0;
- padding: 0;
-}
-
-.detail-section__header {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- margin-bottom: 0.5rem;
-}
-
-.detail-section__title {
- margin: 0;
- font-weight: 600;
- flex: 1;
- min-width: 0;
-}
-
-.quick-add {
- display: inline-flex;
- align-items: center;
- position: relative;
-}
-
-.quick-add__trigger {
- white-space: nowrap;
-}
-
-.documents-sort__trigger.quick-add__trigger {
- padding: 0.25rem 0.5rem;
- min-height: 2.1rem;
-}
-
-.quick-add__chip {
- display: inline-flex;
- align-items: center;
- gap: 0.35rem;
- white-space: nowrap;
- border: 1px dashed var(--border);
- border-radius: 999px;
- padding: 0.18rem 0.6rem;
- font-size: 0.85rem;
- background: transparent;
- color: var(--muted);
- cursor: pointer;
- transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
-}
-
-.quick-add__chip:hover,
-.quick-add__chip:focus-visible {
- border-style: solid;
- background: var(--selection-soft);
- color: var(--fg);
- outline: none;
-}
-
-.quick-add__chip .icon-inline {
- width: 1rem;
- height: 1rem;
-}
-
-.quick-add__chip-label {
- display: inline-flex;
- align-items: center;
- gap: 0.25rem;
-}
-
-.quick-add__menu {
- padding: 0.25rem 0;
-}
-
-.quick-add__form {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- padding: 0.5rem 0.75rem;
- border-bottom: 1px solid var(--border);
-}
-
-.quick-add__form input {
- flex: 1;
- min-width: 0;
-}
-
-.quick-add__list {
- max-height: 240px;
- overflow-y: auto;
-}
-
-.quick-add__option {
- display: flex;
- align-items: center;
- gap: 0.5rem;
-}
-
-.quick-add__swatch {
- width: 0.75rem;
- height: 0.75rem;
- border-radius: 9999px;
- border: 1px solid var(--border);
- background: var(--surface-subtle);
-}
-
-.selection-assignment {
- display: inline-flex;
- position: relative;
-}
-
-.selection-assignment__menu {
- font-size: 0.95rem;
- font-weight: 400;
- padding: 0.4rem 0;
- max-width: min(22rem, 90vw);
-}
-
-.selection-assignment__header {
- padding: 0.4rem 0.75rem 0.3rem;
- border-bottom: 1px solid var(--border-subtle);
-}
-
-.selection-assignment__header input {
- width: 100%;
- padding: 0.35rem 0.6rem;
- border: 1px solid var(--border-subtle);
- border-radius: 0.5rem;
- background: var(--surface-subtle);
- color: var(--fg);
- font-size: 0.95rem;
-}
-
-.selection-assignment__header input:focus-visible {
- outline: none;
- border-color: var(--selection-border);
- box-shadow: 0 0 0 2px color-mix(in oklch, var(--selection) 25%, transparent);
-}
-
-.selection-assignment__list {
- max-height: 240px;
- overflow-y: auto;
-}
-
-.selection-assignment__item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 0.5rem;
- font-weight: 400;
-}
-
-.selection-assignment__label {
- flex: 1 1 auto;
- display: inline-flex;
- align-items: center;
- gap: 0.25rem;
-}
-
-.selection-assignment__spinner {
- margin-left: 0.4rem;
-}
-
-.selection-assignment__label--nowrap {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-.selection-assignment__indent {
- display: inline-block;
- flex: 0 0 auto;
-}
-
-.selection-assignment__folder-label {
- display: inline-flex;
- flex-direction: column;
- gap: 0.15rem;
- min-width: 0;
- max-width: 16rem;
-}
-
-.selection-assignment__folder-name {
- font-weight: 500;
- color: var(--fg);
- min-width: 0;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-.selection-assignment__folder-path {
- font-size: 0.8rem;
- color: var(--muted);
- white-space: normal;
- word-break: break-word;
-}
-
-.selection-assignment__slash {
- color: var(--muted);
- margin: 0 0.25rem;
-}
-
-.selection-assignment__segment {
- display: inline-block;
-}
-
-.selection-assignment__item--all .selection-assignment__icon {
- color: var(--success);
-}
-
-.selection-assignment__item--partial .selection-assignment__icon {
- color: var(--warning);
-}
-
-.selection-assignment__icon {
- width: 1rem;
- height: 1rem;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
-}
-
-.selection-assignment__icon--empty {
- border: 1px solid var(--border-subtle);
- border-radius: 999px;
- opacity: 0.6;
-}
-
-.selection-assignment__label {
- flex: 1 1 auto;
- min-width: 0;
- text-align: left;
- font-weight: 400;
-}
-
-.selection-assignment__count {
- font-size: 0.8rem;
- color: var(--muted);
-}
-
-.selection-assignment__empty {
- padding: 0.75rem;
-}
-
-.selection-assignment__create {
- border-top: 1px solid var(--border-subtle);
- display: flex;
- align-items: center;
- gap: 0.5rem;
-}
-
-
-.preview-pane {
- margin-top: 0.4rem;
- background: transparent;
- display: flex;
- flex-direction: column;
- gap: 1.25rem;
- min-height: 0;
- flex: 1;
- overflow: auto;
- position: relative;
- padding: 1.25rem;
-}
-
-.preview-pane__media {
- border-radius: 0;
- background: transparent;
- min-height: 220px;
- display: flex;
- align-items: center;
- justify-content: center;
- overflow: visible;
- position: relative;
-}
-
-.preview-image {
- width: 100%;
- max-width: 360px;
- max-height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- overflow: hidden;
- pointer-events: auto;
- position: relative;
-}
-
-.preview-image__content {
- display: block;
- width: 100%;
- height: auto;
- max-width: 100%;
- max-height: 100%;
- object-fit: contain;
- background: transparent;
- cursor: pointer;
- transition:
- outline-color 120ms ease,
- box-shadow 120ms ease,
- filter 120ms ease,
- background-color 120ms ease;
- outline: 2px solid transparent;
- outline-offset: -2px;
-}
-
-.preview-image__content:hover,
-.preview-image__content:focus-visible {
- outline-color: var(--accent-focus);
- box-shadow:
- inset 0 0 0 999px var(--accent-elevated),
- 0 6px 18px var(--accent-elevated-strong);
-}
-
-.thumbnail-preview {
- display: flex;
- flex-direction: column;
- gap: 0.32rem;
- margin: 0.4rem 0 0.6rem;
-}
-
-.thumbnail-image {
- max-width: 100%;
- border-radius: 0;
- box-shadow: none;
-}
-
-.detail-panel .meta,
-.document-summary .meta {
- font-size: 0.9rem;
- color: var(--muted);
- margin: 0.5rem 0;
-}
-
-.detail-panel .detail-folder-path,
-.document-summary .detail-folder-path {
- display: inline-flex;
- flex-wrap: wrap;
- gap: 0.25rem;
- align-items: center;
-}
-
-.detail-folder-path--block {
- display: flex;
- gap: 0.25rem;
- flex-wrap: wrap;
- align-items: center;
- margin: 0.5rem 0;
-}
-
-.detail-panel .detail-folder-path__link,
-.document-summary .detail-folder-path__link {
- color: var(--accent);
- text-decoration: none;
-}
-
-.detail-panel .detail-folder-path__link:hover,
-.detail-panel .detail-folder-path__link:focus-visible,
-.document-summary .detail-folder-path__link:hover,
-.document-summary .detail-folder-path__link:focus-visible {
- text-decoration: underline;
- outline: none;
-}
-
-.detail-panel .detail-folder-path__separator,
-.document-summary .detail-folder-path__separator {
- color: var(--muted);
-}
-
-.detail-panel .detail-folder-path__segment,
-.document-summary .detail-folder-path__segment {
- color: var(--fg);
-}
-
-.detail-panel .doc-title-row,
-.document-summary .doc-title-row {
- display: flex;
- flex-direction: column;
- gap: 0.35rem;
- margin: 0.25rem 0 1.25rem;
- max-width: 100%;
- word-break: break-word;
-}
-
-.doc-title-row__primary {
- display: flex;
- align-items: center;
- gap: 0.4rem;
- width: 100%;
-}
-
-.doc-title-row__title {
- margin: 0;
-}
-
-.doc-title-row__path {
- font-size: 0.85rem;
- color: var(--muted);
- display: flex;
- flex-wrap: wrap;
- gap: 0.25rem;
-}
-
-.detail-panel .doc-title-edit,
-.document-summary .doc-title-edit {
- display: flex;
- align-items: center;
- gap: 0.4rem;
- width: 100%;
-}
-
-.detail-panel .doc-title-edit input,
-.document-summary .doc-title-edit input {
- flex: 1;
- min-width: 0;
-}
-
-.status-inline {
- font-size: 0.85rem;
- margin-top: 0.2rem;
-}
-
-.detail-meta {
- display: flex;
- flex-direction: column;
- gap: 0.35rem;
- margin: 1rem 0;
-}
-
-.detail-meta__row {
- display: inline-flex;
- align-items: center;
- gap: 0.35rem;
- font-size: 0.9rem;
- color: var(--muted);
-}
-
-.detail-meta__label {
- font-weight: 600;
- color: var(--fg);
-}
-
-.detail-meta__value {
- color: var(--fg);
-}
-
-.detail-meta__row > .icon-button {
- margin: -0.25rem;
-}
-
-.doc-issued-row {
- display: inline-flex;
- align-items: center;
- gap: 0.35rem;
- font-size: 0.9rem;
- color: var(--muted);
- margin: 0.25rem 0 0.5rem;
-}
-
-.doc-issued-row__label {
- font-weight: 600;
- color: var(--fg);
-}
-
-.doc-issued-row__value {
- color: var(--fg);
-}
-
-.doc-issued-edit {
- display: inline-flex;
- align-items: center;
- gap: 0.35rem;
-}
-
-.doc-issued-edit input[type='date'] {
- font: inherit;
- padding: 0.35rem 0.5rem;
-}
-
-.status-inline.error {
- color: var(--danger);
-}
-
-.bulk-detail-actions {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
- margin: 0.6rem 0;
-}
-
-.bulk-detail-actions .inline {
- display: flex;
- gap: 0.4rem;
-}
-
-.bulk-move {
- display: flex;
- flex-direction: column;
- gap: 0.3rem;
- margin: 0.6rem 0;
-}
-
-.bulk-move select {
- max-width: 100%;
-}
-
-.preview-stack {
- position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.preview-stack--stacked {
- width: 100%;
- min-height: 420px;
- flex-shrink: 0;
-}
-
-.preview-stack--empty {
- width: 100%;
- min-height: 420px;
- flex-shrink: 0;
-}
-
-.preview-stack__item {
- position: absolute;
- top: 50%;
- left: 50%;
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: transform 120ms ease;
- filter: drop-shadow(0 2px 6px var(--shadow-soft));
- transform-origin: center;
- border-radius: 6px;
- pointer-events: none;
-}
-
-.preview-stack .preview-stack__item.orientation-portrait {
- width: 80%;
- height: 100%;
-}
-
-.preview-stack .preview-stack__item.orientation-landscape {
- width: 100%;
- height: 80%;
-}
-
-.preview-pane__unsupported {
- width: 100%;
- max-width: 320px;
- display: flex;
- flex-direction: column;
- gap: 0.6rem;
- align-items: center;
- text-align: center;
- padding: 1.5rem;
- border-radius: 8px;
- background: var(--surface-subtle);
- color: var(--fg);
-}
-
-.preview-pane__unsupported-message {
- font-size: 0.95rem;
- font-weight: 500;
-}
-
-.preview-pane__unsupported-filename {
- font-size: 0.85rem;
- color: var(--muted);
- word-break: break-word;
-}
-
-.preview-pane__unsupported-download {
- font-weight: 600;
-}
-
-.preview-pane__unsupported-download svg {
- width: 1rem;
- height: 1rem;
-}
-
-.preview-pane--stack {
- min-height: 460px;
- position: relative;
- --preview-nav-scale: 1;
-}
-
-.preview-pane__nav-button {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 2.2em;
- height: 2.2em;
- padding: 0.45em;
- border-radius: 999px;
- background: var(--preview-nav-bg);
- color: var(--preview-nav-fg);
- cursor: pointer;
- transition: background 0.15s ease, opacity 0.15s ease;
- pointer-events: auto;
-}
-
-.preview-pane__nav-button:hover:not([disabled]) {
- background: var(--preview-nav-bg-hover);
-}
-
-.preview-pane__nav-button:disabled {
- opacity: 0.4;
- cursor: default;
-}
-
-.preview-pane__nav-button:focus-visible {
- outline: 2px solid var(--accent);
- outline-offset: 2px;
-}
-
-.preview-pane__nav {
- margin-top: 0.5rem;
- display: flex;
- justify-content: center;
- gap: 0.5rem;
-}
-
-.preview-pane__nav--overlay {
- position: absolute;
- bottom: 0.75rem;
- left: 50%;
- transform: translateX(-50%) scale(var(--preview-nav-scale, 1));
- margin-top: 0;
- pointer-events: none;
- z-index: 20;
- opacity: 0;
- transition: opacity 0.2s ease;
-}
-
-.preview-pane__nav--overlay .preview-pane__nav-button {
- pointer-events: auto;
- transform: scale(calc(1 / var(--preview-nav-scale, 1)));
-}
-
-.preview-pane__media:hover .preview-pane__nav--overlay,
-.desk-item__card:hover .preview-pane__nav--overlay {
- opacity: 1;
-}
-
-.desk-card__nav {
- bottom: 0.5rem;
-}
-
-.bulk-tags {
- display: flex;
- flex-wrap: wrap;
- gap: 0.35rem;
- margin: 0.4rem 0 0.6rem;
-}
-
-.detail-field {
- margin: 0.9rem 0;
-}
-
-.detail-field__label {
- font-weight: 600;
- margin-bottom: 0.25rem;
-}
-
-.detail-field__value {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- flex-wrap: wrap;
- margin-bottom: 0.5rem;
-}
-
-.detail-field__value .meta {
- color: var(--muted);
-}
-
-.detail-panel dl {
- margin: 0;
-}
-
-.detail-panel dt {
- font-weight: 600;
- margin-top: 0.8rem;
-}
-
-.detail-panel dd {
- margin: 0.2rem 0 0;
-}
-
-.detail-panel .tag-list,
-.document-summary .tag-list,
-.correspondent-list {
- display: flex;
- flex-wrap: wrap;
- gap: 0.5rem;
- margin: 1rem 0;
- align-items: center;
-}
-
-.tag-list__empty {
- color: var(--muted);
-}
-
-.detail-metadata__block {
- margin: 0.35rem 0 0;
- padding: 0.5rem 0.6rem;
- background: var(--surface-soft);
- border-radius: 4px;
- border: 1px solid var(--border-muted, var(--border));
- white-space: pre-wrap;
- word-break: break-word;
- font-family: var(--font-mono);
- font-size: 0.82rem;
- line-height: 1.45;
-}
-
-input,
-textarea,
-select {
- font: inherit;
- border-radius: 2px;
- border: 1px solid var(--border);
- padding: 0.4rem 0.5rem;
- background: var(--surface);
- color: inherit;
- width: 100%;
- box-sizing: border-box;
-}
-
-input:focus,
-textarea:focus,
-select:focus {
- outline: none;
- border-color: var(--accent);
- box-shadow: 0 0 0 2px var(--accent-soft);
- background: var(--surface);
-}
-
-textarea {
- resize: vertical;
-}
-
-form.inline {
- display: flex;
- gap: 0.32rem;
- margin-top: 0.5rem;
-}
-
-.status-banner {
- padding: 0.45rem 0.8rem;
- border-radius: 2px;
- font-size: 0.85rem;
-}
-
-.status-banner.info {
- background: var(--surface-subtle);
- color: var(--muted);
-}
-
-.status-banner.success {
- background: var(--success-subtle);
- color: var(--success);
-}
-
-.status-banner.error {
- background: var(--danger-soft);
- color: var(--danger);
-}
-
-.drop-overlay {
- position: fixed;
- inset: 0;
- background: var(--accent-soft);
- backdrop-filter: blur(4px);
- display: none;
- align-items: center;
- justify-content: center;
- z-index: 9999;
-}
-
-.drop-overlay.active {
- display: flex;
-}
-
-.drop-overlay__content {
- background: var(--surface);
- color: var(--fg);
- padding: 1.25rem 1.75rem;
- border-radius: 2px;
- text-align: center;
- font-size: 0.95rem;
- box-shadow: none;
-}
-.modal-backdrop {
- position: fixed;
- inset: 0;
- background: var(--overlay-backdrop);
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 1.5rem;
- z-index: 3000;
-}
-
-.modal {
- background: var(--surface);
- border-radius: 4px;
- box-shadow: 0 18px 42px var(--overlay-shadow);
- width: min(360px, 100%);
- padding: 1.6rem;
- display: flex;
- flex-direction: column;
- gap: 1rem;
-}
-
-.modal--panel {
- width: 80vw;
- max-width: 960px;
- max-height: 90vh;
- padding: 0;
- border-radius: 12px;
- overflow: hidden;
- gap: 0;
-}
-
-.settings-modal__body {
- display: grid;
- grid-template-columns: 12rem 1fr;
- gap: 1.5rem;
- height: 90vh;
- padding: 1.5rem;
-}
-
-.settings-modal__sidebar {
- border-right: 1px solid var(--border-muted, var(--border));
- padding-right: 1rem;
-}
-
-.settings-modal__sidebar ul {
- list-style: none;
- margin: 0;
- padding: 0;
- display: flex;
- flex-direction: column;
- gap: 0.4rem;
-}
-
-.settings-modal__sidebar button {
- width: 100%;
- display: inline-flex;
- align-items: center;
- justify-content: flex-start;
- gap: 0.4rem;
- border: none;
- background: none;
- color: inherit;
- font: inherit;
- padding: 0.45rem 0.6rem;
- border-radius: 0.35rem;
- cursor: pointer;
-}
-
-.settings-modal__sidebar button:hover,
-.settings-modal__sidebar button:focus-visible {
- background: var(--sidebar-hover-bg);
-}
-
-.settings-modal__sidebar button.active {
- background: var(--accent-soft);
- font-weight: 600;
-}
-
-.settings-modal__content {
- overflow-y: auto;
- padding-bottom: 1rem;
-}
-
-.settings-section h4 {
- margin-top: 0;
-}
-
-.settings-section {
- display: flex;
- flex-direction: column;
-}
-
-.settings-actions {
- display: flex;
- justify-content: flex-end;
- margin-bottom: 1rem;
-}
-
-.settings-actions .secondary {
- min-width: 7rem;
-}
-
-.settings-form {
- display: flex;
- flex-wrap: wrap;
- gap: 1rem;
- align-items: flex-end;
- margin-bottom: 0.75rem;
-}
-
-.settings-form__field {
- display: flex;
- flex-direction: column;
- gap: 0.35rem;
- min-width: 14rem;
-}
-
-.settings-form__field--full {
- flex: 1 1 100%;
- min-width: 100%;
-}
-
-fieldset.settings-form__field {
- border: 1px solid var(--border-muted, var(--border));
- border-radius: 0.5rem;
- padding: 0.75rem 0.9rem 0.85rem;
- background: var(--surface-soft);
-}
-
-fieldset.settings-form__field legend {
- padding: 0 0.35rem;
- font-weight: 600;
- color: var(--muted-strong, inherit);
-}
-
-.settings-form__field input[type='text'],
-.settings-form__field input[type='datetime-local'] {
- padding: 0.45rem 0.6rem;
- border: 1px solid var(--border);
- border-radius: 0.35rem;
- background: var(--surface-soft);
- color: inherit;
-}
-
-.settings-form__choices {
- display: flex;
- flex-direction: column;
- gap: 0.4rem;
- align-items: flex-start;
-}
-
-.settings-capability-picker {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
-}
-
-.capability-dropdown {
- position: relative;
- width: 100%;
-}
-
-.capability-dropdown__trigger {
- width: 100%;
- display: inline-flex;
- align-items: center;
- justify-content: space-between;
- gap: 0.5rem;
- padding: 0.5rem 0.75rem;
- border: 1px solid var(--border);
- border-radius: 0.45rem;
- background: var(--surface-soft);
- color: inherit;
- font: inherit;
- text-align: left;
- cursor: pointer;
- transition: border-color 0.15s ease, background 0.15s ease;
-}
-
-.capability-dropdown__trigger:hover:not(:disabled),
-.capability-dropdown__trigger:focus-visible {
- border-color: var(--accent);
- background: var(--surface);
- outline: none;
-}
-
-.capability-dropdown__trigger:disabled {
- cursor: not-allowed;
- color: var(--muted);
- background: var(--surface-muted, var(--surface-soft));
-}
-
-.capability-dropdown__chevron {
- flex-shrink: 0;
- opacity: 0.8;
-}
-
-.capability-dropdown__menu {
- margin-top: 0.35rem;
- max-height: 18rem;
- overflow-y: auto;
- padding: 0.25rem 0;
- width: 100%;
-}
-
-.capability-dropdown__option {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- padding: 0.45rem 0.75rem;
-}
-
-.capability-dropdown__option-icon {
- width: 1.1rem;
- display: flex;
- align-items: center;
- justify-content: center;
- color: var(--accent);
-}
-
-.capability-dropdown__option:not(.is-selected) .capability-dropdown__option-icon {
- color: transparent;
-}
-
-.capability-dropdown__option-label {
- flex: 1;
- text-align: left;
-}
-
-.capability-dropdown__empty {
- padding: 0.6rem 0.8rem;
- color: var(--muted);
-}
-
-.settings-capability-picker__chips {
- display: flex;
- flex-wrap: wrap;
- gap: 0.5rem;
- margin-top: 0.25rem;
-}
-
-.settings-capability-picker__chips--inline {
- margin-top: 0.35rem;
-}
-
-.settings-capabilities-summary {
- display: inline-block;
- margin-top: 0.4rem;
- color: var(--muted);
- font-size: 0.9rem;
-}
-
-.settings-capability-picker__placeholder {
- color: var(--muted);
- font-size: 0.9rem;
-}
-
-.settings-capability-list {
- display: flex;
- flex-wrap: wrap;
- gap: 0.35rem;
- margin-top: 0.35rem;
-}
-
-.settings-capability-list--compact {
- margin-top: 0.2rem;
- gap: 0.25rem;
-}
-
-.settings-capability-list__item {
- display: inline-flex;
- align-items: center;
-}
-
-.settings-choice {
- display: inline-flex;
- align-items: center;
- gap: 0.4rem;
- font-size: 0.9rem;
- font-weight: 500;
- color: inherit;
-}
-
-.settings-choice input[type='checkbox'] {
- width: 1.05rem;
- height: 1.05rem;
- cursor: pointer;
- accent-color: var(--accent);
-}
-
-.settings-choice input[type='checkbox']:disabled + span {
- color: var(--muted);
-}
-
-.settings-form__actions {
- display: flex;
- gap: 0.5rem;
-}
-
-.settings-form__error {
- color: var(--danger);
- font-size: 0.85rem;
- margin: 0 0 0.75rem;
-}
-
-.settings-empty {
- margin: 1rem 0;
- color: var(--muted);
-}
-
-.settings-table {
- width: 100%;
- border-collapse: collapse;
- margin-top: 0.5rem;
-}
-
-.settings-table th,
-.settings-table td {
- padding: 0.55rem 0.75rem;
- border-bottom: 1px solid var(--border-muted, var(--border));
- text-align: left;
- font-size: 0.9rem;
-}
-
-.settings-table tr:last-child td {
- border-bottom: none;
-}
-
-.settings-table__actions {
- white-space: nowrap;
-}
-
-.settings-status {
- color: var(--muted);
- font-size: 0.9rem;
-}
-
-.settings-table tr.is-revoked {
- opacity: 0.65;
-}
-
-.settings-notice {
- border: 1px solid var(--accent);
- background: var(--accent-soft);
- padding: 0.9rem 1rem;
- border-radius: 0.5rem;
- margin-bottom: 1rem;
-}
-
-.token-display {
- background: var(--surface-ink-soft);
- padding: 0.5rem 0.65rem;
- border-radius: 0.35rem;
- font-family: var(--font-mono);
- font-size: 0.95rem;
- overflow-x: auto;
-}
-
-.settings-notice__actions {
- display: flex;
- gap: 0.5rem;
- margin-top: 0.6rem;
-}
-
-.settings-notice__actions button {
- flex: 0 0 auto;
-}
-
-.panel-modal__header {
- padding: 1rem 1rem 1rem 0.5rem;
- border-bottom: 1px solid var(--border);
-}
-
-.panel-modal__body {
- flex: 1;
- overflow: auto;
- padding: 0;
-}
-
-.modal__body {
- width: 100%;
-}
-
-.modal h3 {
- margin: 0;
- font-size: 1.15rem;
- font-weight: 600;
-}
-
-.modal__form {
- display: flex;
- flex-direction: column;
- gap: 0.9rem;
-}
-
-.modal__form label {
- font-weight: 600;
- font-size: 0.9rem;
-}
-
-.modal__error {
- margin: -0.45rem 0 0;
- font-size: 0.85rem;
- color: var(--danger);
-}
-
-.modal__actions {
- display: flex;
- justify-content: flex-start;
- padding: 0.25rem;
- gap: 0.6rem;
-}
-
-@media (max-width: 768px) {
- .app-bar {
- flex-direction: column;
- align-items: flex-start;
- gap: 0.75rem;
- }
- .app-main {
- grid-template-columns: 1fr;
- }
- .sidebar,
- .documents-panel,
- .detail-panel,
- .tags-panel,
- .correspondents-panel {
- min-height: auto;
- }
-}
-.login-screen {
- min-height: 100vh;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- background: var(--bg);
- padding: 2rem;
-}
-
-.login-card {
- width: min(340px, 100%);
- background: var(--surface);
- border-radius: 2px;
- padding: 1.75rem;
- box-shadow: none;
- display: flex;
- flex-direction: column;
- gap: 1rem;
-}
-
-.login-card h1 {
- margin: 0;
- font-size: 1.6rem;
- font-weight: 600;
-}
-
-.login-card p {
- margin: 0;
- color: var(--muted);
- font-size: 0.9rem;
-}
-
-.login-card form {
- display: flex;
- flex-direction: column;
- gap: 0.75rem;
-}
-
-.login-card label {
- font-size: 0.85rem;
- font-weight: 600;
- color: var(--muted);
-}
-
-.login-card__fields {
- display: flex;
- flex-direction: column;
- gap: 0.75rem;
- width: 100%;
-}
-
-.login-card__passkey-button {
- margin-top: 1rem;
- width: 100%;
- display: inline-flex;
- justify-content: center;
-}
-
-.login-card__signup-button {
- margin-top: 0.75rem;
- width: 100%;
- display: inline-flex;
- justify-content: center;
-}
-
-.login-card .status-banner {
- margin-bottom: 0;
-}
-
-.login-card__selection {
- display: flex;
- flex-direction: column;
- gap: 0.9rem;
-}
-
-.login-card__tenant-list {
- display: flex;
- flex-direction: column;
- gap: 0.6rem;
-}
-
-.login-card__tenant-button {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 100%;
- padding: 0.65rem 0.75rem;
- border-radius: 0.6rem;
- border: 1px solid var(--border);
- background: var(--surface);
- color: inherit;
- font-weight: 600;
- font-size: 0.95rem;
- cursor: pointer;
- transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
-}
-
-.login-card__tenant-button:hover:not([disabled]) {
- background: var(--surface-hover);
- border-color: var(--border-strong);
-}
-
-.login-card__tenant-button:disabled {
- opacity: 0.6;
- cursor: wait;
-}
-
-.login-card__tenant-button.is-loading {
- opacity: 0.6;
-}
-
-.login-card__back-button {
- align-self: flex-start;
- background: none;
- border: none;
- padding: 0;
- color: var(--accent);
- font-size: 0.85rem;
- cursor: pointer;
- text-decoration: underline;
-}
-
-.login-card__back-button:hover {
- text-decoration: none;
-}
-
-.login-card__back-button:disabled {
- opacity: 0.6;
- cursor: default;
-}
-.doc-list__name {
- width: 100%;
-}
-
-.doc-list__name-content {
- display: inline-flex;
- align-items: center;
- gap: 0.22rem;
- max-width: 100%;
-}
-
-.doc-list__name-content span {
- max-width: 100%;
- overflow-wrap: anywhere;
- word-break: break-word;
-}
-
-.preview-pane__nav-button svg {
- width: 100%;
- height: 100%;
-}
-
-.panel-header .icon-button svg {
- width: 1.41rem;
- height: 1.41rem;
-}
diff --git a/frontend/src/styles/auth/login.css b/frontend/src/styles/auth/login.css
new file mode 100644
index 0000000..91cff78
--- /dev/null
+++ b/frontend/src/styles/auth/login.css
@@ -0,0 +1,149 @@
+.login-screen {
+ min-height: 100vh;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--bg);
+ padding: 2rem;
+}
+
+.login-card {
+ width: min(340px, 100%);
+ background: var(--surface);
+ border-radius: 2px;
+ padding: 1.75rem;
+ box-shadow: none;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.login-card h1 {
+ margin: 0;
+ font-size: 1.6rem;
+ font-weight: 600;
+}
+
+.login-card p {
+ margin: 0;
+ color: var(--muted);
+ font-size: 0.9rem;
+}
+
+.login-card form {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+}
+
+.login-card label {
+ font-size: 0.85rem;
+ font-weight: 600;
+ color: var(--muted);
+}
+
+.login-card__fields {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ width: 100%;
+}
+
+.login-card .login-card__fields input {
+ appearance: none;
+ border-radius: 0.45rem;
+ border: 1px solid var(--border);
+ padding: 0.55rem 0.65rem;
+ font-size: 0.95rem;
+ background: var(--surface);
+ color: var(--fg);
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
+}
+
+.login-card .login-card__fields input:focus-visible {
+ outline: none;
+ border-color: var(--accent);
+ box-shadow: 0 0 0 2px var(--accent-soft);
+ background: var(--surface);
+}
+
+.login-card__passkey-button {
+ margin-top: 1rem;
+ width: 100%;
+ display: inline-flex;
+ justify-content: center;
+}
+
+.login-card__signup-button {
+ margin-top: 0.75rem;
+ width: 100%;
+ display: inline-flex;
+ justify-content: center;
+}
+
+.login-card .status-banner {
+ margin-bottom: 0;
+}
+
+.login-card__selection {
+ display: flex;
+ flex-direction: column;
+ gap: 0.9rem;
+}
+
+.login-card__tenant-list {
+ display: flex;
+ flex-direction: column;
+ gap: 0.6rem;
+}
+
+.login-card__tenant-button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ padding: 0.65rem 0.75rem;
+ border-radius: 0.6rem;
+ border: 1px solid var(--border);
+ background: var(--surface);
+ color: inherit;
+ font-weight: 600;
+ font-size: 0.95rem;
+ cursor: pointer;
+ transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
+}
+
+.login-card__tenant-button:hover:not([disabled]) {
+ background: var(--surface-hover);
+ border-color: var(--border-strong);
+}
+
+.login-card__tenant-button:disabled {
+ opacity: 0.6;
+ cursor: wait;
+}
+
+.login-card__tenant-button.is-loading {
+ opacity: 0.6;
+}
+
+.login-card__back-button {
+ align-self: flex-start;
+ background: none;
+ border: none;
+ padding: 0;
+ color: var(--accent);
+ font-size: 0.85rem;
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+.login-card__back-button:hover {
+ text-decoration: none;
+}
+
+.login-card__back-button:disabled {
+ opacity: 0.6;
+ cursor: default;
+}
diff --git a/frontend/src/styles/base/controls.css b/frontend/src/styles/base/controls.css
new file mode 100644
index 0000000..68b8bbe
--- /dev/null
+++ b/frontend/src/styles/base/controls.css
@@ -0,0 +1,99 @@
+button {
+ font: inherit;
+ border-radius: 2px;
+ border: 1px solid transparent;
+ padding: 0.35rem 0.85rem;
+ background: var(--accent);
+ color: var(--on-accent);
+ cursor: pointer;
+ font-weight: 500;
+ transition: background 0.15s ease, border-color 0.15s ease;
+}
+
+button[disabled] {
+ opacity: 0.55;
+ cursor: not-allowed;
+}
+
+a.button-link {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ font: inherit;
+ border-radius: 2px;
+ border: 1px solid transparent;
+ padding: 0.35rem 0.85rem;
+ background: var(--accent);
+ color: var(--on-accent);
+ text-decoration: none;
+ font-weight: 500;
+ transition: background 0.15s ease, border-color 0.15s ease;
+}
+
+a.button-link[aria-disabled='true'] {
+ opacity: 0.55;
+}
+
+a.button-link:hover:not([aria-disabled='true']) {
+ background: var(--accent-hover);
+}
+
+button.secondary {
+ background: transparent;
+ color: var(--fg);
+ border-color: var(--border);
+}
+
+button.secondary:hover:not([disabled]) {
+ background: var(--surface-subtle);
+}
+
+button.danger {
+ background: transparent;
+ color: var(--danger);
+ border-color: var(--danger-border);
+}
+
+button.danger:hover:not([disabled]) {
+ border-color: var(--danger);
+ background: var(--danger-subtle);
+}
+
+.icon-button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: none;
+ background: transparent;
+ color: var(--muted);
+ padding: 0.25rem;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: background 0.15s ease, color 0.15s ease;
+}
+
+.icon-button:hover:not([disabled]) {
+ background: var(--sidebar-hover-bg);
+ color: var(--fg);
+}
+
+.icon-button.danger {
+ color: var(--danger);
+}
+
+.icon-button.danger:hover:not([disabled]) {
+ background: var(--danger-subtle);
+}
+
+.icon-button.ghost {
+ border: none;
+ background: transparent;
+ color: var(--muted);
+ padding: 0.2rem;
+}
+
+.icon-button.ghost:hover:not([disabled]) {
+ color: var(--danger);
+ background: transparent;
+}
+
diff --git a/frontend/src/styles/base/iconography.css b/frontend/src/styles/base/iconography.css
new file mode 100644
index 0000000..fa84f2a
--- /dev/null
+++ b/frontend/src/styles/base/iconography.css
@@ -0,0 +1,56 @@
+.with-icon {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+}
+
+.icon-inline {
+ width: 1rem;
+ height: 1rem;
+}
+
+.icon {
+ width: 1rem;
+ height: 1rem;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+
+@keyframes icon-spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+.icon--spin {
+ animation: icon-spin 0.9s linear infinite;
+}
+
+.icon--flip-y {
+ transform: scaleX(-1);
+}
+
+.visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ border: 0;
+}
+
+.icon path {
+ stroke: currentColor;
+ stroke-width: 1.6;
+ fill: none;
+}
+
+.icon--fill path {
+ stroke: none;
+ fill: currentColor;
+}
+
+
diff --git a/frontend/src/styles/base/text-button.css b/frontend/src/styles/base/text-button.css
new file mode 100644
index 0000000..fceeabc
--- /dev/null
+++ b/frontend/src/styles/base/text-button.css
@@ -0,0 +1,14 @@
+.text-button {
+ background: none;
+ border: none;
+ color: var(--accent);
+ font-weight: 500;
+ font-size: 0.8rem;
+ cursor: pointer;
+ padding: 0.1rem 0.25rem;
+}
+
+.text-button:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+}
diff --git a/frontend/src/styles/base/theme.css b/frontend/src/styles/base/theme.css
new file mode 100644
index 0000000..6b7db9f
--- /dev/null
+++ b/frontend/src/styles/base/theme.css
@@ -0,0 +1,295 @@
+:root {
+ color-scheme: light dark;
+
+ --neutral-hue: 180deg;
+ --foreground-hue-offset: 10deg;
+ --accent-hue-offset: -20deg;
+ --dark-hue-offset: 30deg;
+ --dark-foreground-hue-offset: 10deg;
+ --surface-hue-shift: -4deg;
+
+ /* --- Neutrals --- */
+ --bg: oklch(0.97 0.001 calc(var(--neutral-hue) + var(--surface-hue-shift)));
+ --surface: oklch(0.985 0.001 calc(var(--neutral-hue) + var(--surface-hue-shift)));
+ --surface-subtle: oklch(0.96 0.002 calc(var(--neutral-hue) + var(--surface-hue-shift)));
+ --fg: oklch(0.32 0.005 calc(var(--neutral-hue) + var(--foreground-hue-offset)));
+ --muted: oklch(0.54 0.008 calc(var(--neutral-hue) + var(--foreground-hue-offset)));
+ --sidebar-fg: oklch(0.56 0.007 calc(var(--neutral-hue) + var(--foreground-hue-offset)));
+ --border: oklch(0.92 0.002 calc(var(--neutral-hue) + var(--foreground-hue-offset)));
+
+ /* --- Accent (primary) --- */
+ --accent: oklch(0.61 0.16 calc(var(--neutral-hue) + var(--foreground-hue-offset) + var(--accent-hue-offset)));
+ --accent-hover: oklch(0.70 0.12 calc(var(--neutral-hue) + var(--foreground-hue-offset) + var(--accent-hue-offset)));
+ --on-accent: oklch(1 0 0);
+
+ --folder-icon-back: color-mix(in oklch, var(--accent) 78%, black 8%);
+ --folder-icon-mid: color-mix(in oklch, var(--accent) 60%, white 30%);
+ --folder-icon-front: color-mix(in oklch, var(--accent) 42%, white 58%);
+
+ --success-hue: 140deg;
+ --warning-hue: 85deg;
+ --danger-hue: 25deg;
+
+ --accent-soft: color-mix(in oklch, var(--accent) 12%, transparent);
+ --accent-elevated: color-mix(in oklch, var(--accent) 16%, transparent);
+ --accent-elevated-strong: color-mix(in oklch, var(--accent) 22%, transparent);
+ --accent-outline: color-mix(in oklch, var(--accent) 45%, transparent);
+ --accent-outline-strong: color-mix(in oklch, var(--accent) 80%, transparent);
+ --accent-focus: color-mix(in oklch, var(--accent) 75%, transparent);
+ --surface-overlay: color-mix(in oklch, white 82%, transparent);
+
+ /* -- Selection --- */
+ --selection: oklch(0.61 0.01 var(--neutral-hue));
+
+ /* --- Derived accent states --- */
+ --selection-soft: color-mix(in oklch, var(--selection) 12%, transparent);
+
+ /* --- Shadows & overlays --- */
+ --shadow-faint: color-mix(in oklch, black 6%, transparent);
+ --shadow-soft: color-mix(in oklch, black 12%, transparent);
+ --shadow-medium: color-mix(in oklch, black 18%, transparent);
+ --shadow-strong: color-mix(in oklch, black 28%, transparent);
+ --shadow-deep: color-mix(in oklch, black 55%, transparent);
+ --shadow-pop: color-mix(in oklch, black 25%, transparent);
+ --outline-subtle: color-mix(in oklch, black 6%, transparent);
+ --overlay-dark: color-mix(in oklch, black 55%, transparent);
+ --overlay-dim: color-mix(in oklch, oklch(0.28 0.01 calc(var(--neutral-hue) + var(--foreground-hue-offset) - 5deg)) 20%, transparent);
+ --overlay-darker: color-mix(in oklch, black 75%, transparent);
+ --surface-danger-subtle: color-mix(in oklch, var(--danger) 12%, transparent);
+ --overlay-accent-subtle: color-mix(in oklch, var(--accent) 12%, transparent);
+ --border-strong: color-mix(in oklch, var(--fg) 24%, transparent);
+ --surface-hover: color-mix(in oklch, white 10%, transparent);
+ --overlay-accent-strong: color-mix(in oklch, var(--accent) 24%, transparent);
+ --overlay-backdrop: color-mix(in oklch, oklch(0.22 0.015 calc(var(--neutral-hue) + var(--foreground-hue-offset) - 5deg)) 45%, transparent);
+ --overlay-shadow: color-mix(in oklch, oklch(0.22 0.015 calc(var(--neutral-hue) + var(--foreground-hue-offset) - 5deg)) 18%, transparent);
+
+ /* --- Semantic colors --- */
+ --success: oklch(0.68 0.16 var(--success-hue));
+ --warning: oklch(0.76 0.17 var(--warning-hue));
+ --danger: oklch(0.64 0.2 var(--danger-hue));
+
+ --success-subtle: color-mix(in oklch, var(--success) 12%, transparent);
+ --warning-subtle: color-mix(in oklch, var(--warning) 12%, transparent);
+ --danger-border: color-mix(in oklch, var(--danger) 45%, transparent);
+ --danger-soft: color-mix(in oklch, var(--danger) 12%, transparent);
+ --danger-subtle: color-mix(in oklch, var(--danger) 8%, transparent);
+
+ /* --- Explorer states --- */
+ --row-hover-bg: color-mix(in oklch, var(--selection) 6%, transparent);
+ --row-active-bg: color-mix(in oklch, var(--selection) 12%, transparent);
+ --sidebar-hover-bg: color-mix(in oklch, var(--selection) 8%, transparent);
+ --sidebar-active-bg: color-mix(in oklch, var(--selection) 16%, transparent);
+ --selection-ring: oklch(0.78 0.16 calc(var(--neutral-hue) + var(--foreground-hue-offset)));
+ --sidebar-active-pill-border: color-mix(in oklch, var(--accent) 64%, transparent);
+ --link: var(--accent);
+ --link-visited: color-mix(in oklch, var(--accent) 75%, var(--fg) 25%);
+ --preview-nav-bg: oklch(0.18 0.05 calc(var(--neutral-hue) + var(--foreground-hue-offset)));
+ --preview-nav-bg-hover: oklch(0.14 0.05 calc(var(--neutral-hue) + var(--foreground-hue-offset)));
+ --preview-nav-fg: var(--on-accent);
+ --text-on-dark: color-mix(in oklch, white 90%, transparent);
+ --surface-ink-soft: color-mix(in oklch, var(--fg) 8%, transparent);
+
+ font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ font-size: 15px;
+ --font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
+
+ --detail-panel-width: 30em;
+ --documents-grid-title-size: 0.8rem;
+}
+
+:root[data-theme='light'] {
+ color-scheme: light;
+}
+
+:root[data-theme='dark'] {
+ color-scheme: dark;
+
+ --dark-neutral-hue: calc(var(--neutral-hue) + var(--dark-hue-offset));
+ --dark-foreground-hue: calc(var(--neutral-hue) + var(--dark-foreground-hue-offset));
+
+ --bg: oklch(0.15 0.01 calc(var(--dark-neutral-hue) + var(--surface-hue-shift)));
+ --surface: oklch(0.19 0.012 calc(var(--dark-neutral-hue) + var(--surface-hue-shift)));
+ --surface-subtle: oklch(0.23 0.012 calc(var(--dark-neutral-hue) + var(--surface-hue-shift)));
+ --fg: oklch(0.89 0.015 var(--dark-foreground-hue));
+ --muted: oklch(0.72 0.02 var(--dark-foreground-hue));
+ --sidebar-fg: oklch(0.78 0.02 var(--dark-foreground-hue));
+ --border: oklch(0.33 0.01 var(--dark-foreground-hue));
+
+ --accent: oklch(0.75 0.16 calc(var(--dark-foreground-hue) + var(--accent-hue-offset)));
+ --accent-hover: oklch(0.82 0.13 calc(var(--dark-foreground-hue) + var(--accent-hue-offset)));
+ --on-accent: oklch(0.15 0.015 var(--dark-foreground-hue));
+
+ --folder-icon-back: color-mix(in oklch, var(--accent) 72%, black 12%);
+ --folder-icon-mid: color-mix(in oklch, var(--accent) 50%, white 24%);
+ --folder-icon-front: color-mix(in oklch, var(--accent) 30%, white 50%);
+
+ --accent-soft: color-mix(in oklch, var(--accent) 22%, transparent);
+ --accent-elevated: color-mix(in oklch, var(--accent) 28%, transparent);
+ --accent-elevated-strong: color-mix(in oklch, var(--accent) 38%, transparent);
+ --accent-outline: color-mix(in oklch, var(--accent) 55%, transparent);
+ --accent-outline-strong: color-mix(in oklch, var(--accent) 80%, transparent);
+ --accent-focus: color-mix(in oklch, var(--accent) 72%, transparent);
+ --surface-overlay: color-mix(in oklch, black 60%, transparent);
+
+ --selection: oklch(0.44 0.04 220deg);
+ --selection-soft: color-mix(in oklch, var(--selection) 18%, transparent);
+
+ --shadow-faint: color-mix(in oklch, black 35%, transparent);
+ --shadow-soft: color-mix(in oklch, black 50%, transparent);
+ --shadow-medium: color-mix(in oklch, black 65%, transparent);
+ --shadow-strong: color-mix(in oklch, black 80%, transparent);
+ --shadow-deep: color-mix(in oklch, black 90%, transparent);
+ --shadow-pop: color-mix(in oklch, black 70%, transparent);
+ --outline-subtle: color-mix(in oklch, white 10%, transparent);
+ --overlay-dark: color-mix(in oklch, black 70%, transparent);
+ --overlay-dim: color-mix(in oklch, oklch(0.42 0.04 var(--dark-neutral-hue)) 35%, transparent);
+ --overlay-darker: color-mix(in oklch, black 85%, transparent);
+ --surface-danger-subtle: color-mix(in oklch, var(--danger) 26%, transparent);
+ --overlay-accent-subtle: color-mix(in oklch, var(--accent) 32%, transparent);
+ --border-strong: color-mix(in oklch, var(--fg) 30%, transparent);
+ --surface-hover: color-mix(in oklch, white 6%, transparent);
+ --overlay-accent-strong: color-mix(in oklch, var(--accent) 55%, transparent);
+ --overlay-backdrop: color-mix(in oklch, oklch(0.1 0.015 var(--dark-neutral-hue)) 70%, transparent);
+ --overlay-shadow: color-mix(in oklch, oklch(0.12 0.015 var(--dark-neutral-hue)) 55%, transparent);
+
+ --success: oklch(0.62 0.16 var(--success-hue));
+ --warning: oklch(0.68 0.17 var(--warning-hue));
+ --danger: oklch(0.60 0.2 var(--danger-hue));
+
+ --success-subtle: color-mix(in oklch, var(--success) 20%, transparent);
+ --warning-subtle: color-mix(in oklch, var(--warning) 20%, transparent);
+ --danger-border: color-mix(in oklch, var(--danger) 45%, transparent);
+ --danger-soft: color-mix(in oklch, var(--danger) 24%, transparent);
+ --danger-subtle: color-mix(in oklch, var(--danger) 18%, transparent);
+
+ --row-hover-bg: color-mix(in oklch, white 4%, transparent);
+ --row-active-bg: color-mix(in oklch, var(--selection) 20%, transparent);
+ --sidebar-hover-bg: color-mix(in oklch, white 6%, transparent);
+ --sidebar-active-bg: color-mix(in oklch, var(--selection) 26%, transparent);
+ --selection-ring: oklch(0.68 0.16 var(--dark-foreground-hue));
+ --sidebar-active-pill-border: color-mix(in oklch, var(--accent) 72%, transparent);
+ --link: color-mix(in oklch, var(--accent) 92%, transparent);
+ --link-visited: color-mix(in oklch, var(--accent) 70%, white 20%);
+ --preview-nav-bg: oklch(0.3 0.04 var(--dark-neutral-hue));
+ --preview-nav-bg-hover: oklch(0.35 0.04 var(--dark-neutral-hue));
+ --preview-nav-fg: var(--fg);
+ --text-on-dark: color-mix(in oklch, white 92%, transparent);
+ --surface-ink-soft: color-mix(in oklch, white 12%, transparent);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme='light']) {
+ color-scheme: dark;
+
+ --dark-neutral-hue: calc(var(--neutral-hue) + var(--dark-hue-offset));
+ --dark-foreground-hue: calc(var(--neutral-hue) + var(--dark-foreground-hue-offset));
+
+ --bg: oklch(0.15 0.01 calc(var(--dark-neutral-hue) + var(--surface-hue-shift)));
+ --surface: oklch(0.19 0.012 calc(var(--dark-neutral-hue) + var(--surface-hue-shift)));
+ --surface-subtle: oklch(0.23 0.012 calc(var(--dark-neutral-hue) + var(--surface-hue-shift)));
+ --fg: oklch(0.89 0.015 var(--dark-foreground-hue));
+ --muted: oklch(0.72 0.02 var(--dark-foreground-hue));
+ --sidebar-fg: oklch(0.78 0.02 var(--dark-foreground-hue));
+ --border: oklch(0.33 0.01 var(--dark-foreground-hue));
+
+ --accent: oklch(0.75 0.16 calc(var(--dark-foreground-hue) + var(--accent-hue-offset)));
+ --accent-hover: oklch(0.82 0.13 calc(var(--dark-foreground-hue) + var(--accent-hue-offset)));
+ --on-accent: oklch(0.15 0.015 var(--dark-foreground-hue));
+
+ --folder-icon-back: color-mix(in oklch, var(--accent) 72%, black 12%);
+ --folder-icon-mid: color-mix(in oklch, var(--accent) 50%, white 24%);
+ --folder-icon-front: color-mix(in oklch, var(--accent) 30%, white 50%);
+
+ --accent-soft: color-mix(in oklch, var(--accent) 22%, transparent);
+ --accent-elevated: color-mix(in oklch, var(--accent) 28%, transparent);
+ --accent-elevated-strong: color-mix(in oklch, var(--accent) 38%, transparent);
+ --accent-outline: color-mix(in oklch, var(--accent) 55%, transparent);
+ --accent-outline-strong: color-mix(in oklch, var(--accent) 80%, transparent);
+ --accent-focus: color-mix(in oklch, var(--accent) 72%, transparent);
+ --surface-overlay: color-mix(in oklch, black 60%, transparent);
+
+ --selection: oklch(0.44 0.04 220deg);
+ --selection-soft: color-mix(in oklch, var(--selection) 18%, transparent);
+
+ --shadow-faint: color-mix(in oklch, black 35%, transparent);
+ --shadow-soft: color-mix(in oklch, black 50%, transparent);
+ --shadow-medium: color-mix(in oklch, black 65%, transparent);
+ --shadow-strong: color-mix(in oklch, black 80%, transparent);
+ --shadow-deep: color-mix(in oklch, black 90%, transparent);
+ --shadow-pop: color-mix(in oklch, black 70%, transparent);
+ --outline-subtle: color-mix(in oklch, white 10%, transparent);
+ --overlay-dark: color-mix(in oklch, black 70%, transparent);
+ --overlay-dim: color-mix(in oklch, oklch(0.42 0.04 var(--dark-neutral-hue)) 35%, transparent);
+ --overlay-darker: color-mix(in oklch, black 85%, transparent);
+ --surface-danger-subtle: color-mix(in oklch, var(--danger) 26%, transparent);
+ --overlay-accent-subtle: color-mix(in oklch, var(--accent) 32%, transparent);
+ --border-strong: color-mix(in oklch, var(--fg) 30%, transparent);
+ --surface-hover: color-mix(in oklch, white 6%, transparent);
+ --overlay-accent-strong: color-mix(in oklch, var(--accent) 55%, transparent);
+ --overlay-backdrop: color-mix(in oklch, oklch(0.1 0.015 var(--dark-neutral-hue)) 70%, transparent);
+ --overlay-shadow: color-mix(in oklch, oklch(0.12 0.015 var(--dark-neutral-hue)) 55%, transparent);
+
+ --success: oklch(0.62 0.16 var(--success-hue));
+ --warning: oklch(0.68 0.17 var(--warning-hue));
+ --danger: oklch(0.60 0.2 var(--danger-hue));
+
+ --success-subtle: color-mix(in oklch, var(--success) 20%, transparent);
+ --warning-subtle: color-mix(in oklch, var(--warning) 20%, transparent);
+ --danger-border: color-mix(in oklch, var(--danger) 45%, transparent);
+ --danger-soft: color-mix(in oklch, var(--danger) 24%, transparent);
+ --danger-subtle: color-mix(in oklch, var(--danger) 18%, transparent);
+
+ --row-hover-bg: color-mix(in oklch, white 4%, transparent);
+ --row-active-bg: color-mix(in oklch, var(--selection) 20%, transparent);
+ --sidebar-hover-bg: color-mix(in oklch, white 6%, transparent);
+ --sidebar-active-bg: color-mix(in oklch, var(--selection) 26%, transparent);
+ --selection-ring: oklch(0.68 0.16 var(--dark-foreground-hue));
+ --sidebar-active-pill-border: color-mix(in oklch, var(--accent) 72%, transparent);
+ --link: color-mix(in oklch, var(--accent) 92%, transparent);
+ --link-visited: color-mix(in oklch, var(--accent) 70%, white 20%);
+ --preview-nav-bg: oklch(0.3 0.04 var(--dark-neutral-hue));
+ --preview-nav-bg-hover: oklch(0.35 0.04 var(--dark-neutral-hue));
+ --preview-nav-fg: var(--fg);
+ --text-on-dark: color-mix(in oklch, white 92%, transparent);
+ --surface-ink-soft: color-mix(in oklch, white 12%, transparent);
+ }
+}
+
+html,
+body {
+ height: 100%;
+}
+
+body {
+ margin: 0;
+ background: var(--bg);
+ color: var(--fg);
+ overflow: hidden;
+}
+
+body.has-main-content {
+ background: var(--surface);
+}
+
+a {
+ color: var(--accent);
+ text-decoration: underline;
+ text-decoration-thickness: 1px;
+ text-underline-offset: 0.18em;
+ transition: color 0.15s ease, text-decoration-color 0.15s ease;
+}
+
+a:visited {
+ color: var(--link-visited);
+}
+
+a:hover,
+a:focus-visible {
+ color: var(--accent-hover);
+ outline: none;
+ text-decoration-color: currentColor;
+}
+
+#app {
+ height: 100%;
+}
diff --git a/frontend/src/styles/detail/detail-panels.css b/frontend/src/styles/detail/detail-panels.css
new file mode 100644
index 0000000..d559895
--- /dev/null
+++ b/frontend/src/styles/detail/detail-panels.css
@@ -0,0 +1,778 @@
+.panel.detail-panel {
+ position: fixed;
+ top: 0;
+ right: 0;
+ width: min(100vw, var(--detail-panel-width));
+ min-height: 100vh;
+ height: 100%;
+ height: 100%;
+ background: var(--bg);
+ box-shadow: 0 0 24px var(--shadow-soft);
+ border-left: 1px solid var(--border);
+ display: flex;
+ flex-direction: column;
+ z-index: 1000000;
+}
+
+.panel-header {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.5rem;
+ min-width: 0;
+}
+
+.panel-header__leading,
+.panel-header__actions {
+ display: flex;
+ align-items: center;
+ gap: 0.25rem;
+}
+
+.panel-header__title {
+ margin: 0;
+ font-size: 1rem;
+ font-weight: 600;
+ line-height: 1.2;
+ flex: 1 1 auto;
+ min-width: 0;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ overflow: hidden;
+ text-wrap: nowrap;
+}
+
+.panel-header > .panel-header__title:first-child {
+ padding-left: 0.5rem;
+}
+
+.panel-header__actions {
+ margin-left: auto;
+}
+
+.panel-body {
+ padding: 0.5rem 1rem;
+}
+
+
+.detail-panel .panel-body {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ overflow-y: auto;
+ min-height: 0;
+ padding: 0;
+}
+
+.detail-section__header {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ margin-bottom: 0.5rem;
+}
+
+.detail-section__title {
+ margin: 0;
+ font-weight: 600;
+ flex: 1;
+ min-width: 0;
+}
+
+.quick-add {
+ display: inline-flex;
+ align-items: center;
+ position: relative;
+}
+
+.quick-add__trigger {
+ white-space: nowrap;
+}
+
+.documents-sort__trigger.quick-add__trigger {
+ padding: 0.25rem 0.5rem;
+ min-height: 2.1rem;
+}
+
+.quick-add__chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ white-space: nowrap;
+ border: 1px dashed var(--border);
+ border-radius: 999px;
+ padding: 0.18rem 0.6rem;
+ font-size: 0.85rem;
+ background: transparent;
+ color: var(--muted);
+ cursor: pointer;
+ transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
+}
+
+.quick-add__chip:hover,
+.quick-add__chip:focus-visible {
+ border-style: solid;
+ background: var(--selection-soft);
+ color: var(--fg);
+ outline: none;
+}
+
+.quick-add__chip .icon-inline {
+ width: 1rem;
+ height: 1rem;
+}
+
+.quick-add__chip-label {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+}
+
+.quick-add__menu {
+ padding: 0.25rem 0;
+}
+
+.quick-add__form {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.5rem 0.75rem;
+ border-bottom: 1px solid var(--border);
+}
+
+.quick-add__form input {
+ flex: 1;
+ min-width: 0;
+}
+
+.quick-add__list {
+ max-height: 240px;
+ overflow-y: auto;
+}
+
+.quick-add__option {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.quick-add__swatch {
+ width: 0.75rem;
+ height: 0.75rem;
+ border-radius: 9999px;
+ border: 1px solid var(--border);
+ background: var(--surface-subtle);
+}
+
+.selection-assignment {
+ display: inline-flex;
+ position: relative;
+}
+
+.selection-assignment__menu {
+ font-size: 0.95rem;
+ font-weight: 400;
+ padding: 0.4rem 0;
+ max-width: min(22rem, 90vw);
+}
+
+.selection-assignment__header {
+ padding: 0.4rem 0.75rem 0.3rem;
+ border-bottom: 1px solid var(--border-subtle);
+}
+
+.selection-assignment__header input {
+ width: 100%;
+ padding: 0.35rem 0.6rem;
+ border: 1px solid var(--border-subtle);
+ border-radius: 0.5rem;
+ background: var(--surface-subtle);
+ color: var(--fg);
+ font-size: 0.95rem;
+}
+
+.selection-assignment__header input:focus-visible {
+ outline: none;
+ border-color: var(--selection-border);
+ box-shadow: 0 0 0 2px color-mix(in oklch, var(--selection) 25%, transparent);
+}
+
+.selection-assignment__list {
+ max-height: 240px;
+ overflow-y: auto;
+ padding: 0 0.75rem;
+}
+
+.selection-assignment__item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.5rem;
+ font-weight: 400;
+}
+
+.selection-assignment__label {
+ flex: 1 1 auto;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+}
+
+.selection-assignment__spinner {
+ margin-left: 0.4rem;
+}
+
+.selection-assignment__label--nowrap {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.selection-assignment__indent {
+ display: inline-block;
+ flex: 0 0 auto;
+}
+
+.selection-assignment__folder-label {
+ display: inline-flex;
+ flex-direction: column;
+ gap: 0.15rem;
+ min-width: 0;
+ max-width: 16rem;
+}
+
+.selection-assignment__folder-name {
+ font-weight: 500;
+ color: var(--fg);
+ min-width: 0;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.selection-assignment__folder-path {
+ font-size: 0.8rem;
+ color: var(--muted);
+ white-space: normal;
+ word-break: break-word;
+}
+
+.selection-assignment__slash {
+ color: var(--muted);
+ margin: 0 0.25rem;
+}
+
+.selection-assignment__segment {
+ display: inline-block;
+}
+
+.selection-assignment__item--all .selection-assignment__icon {
+ color: var(--success);
+}
+
+.selection-assignment__item--partial .selection-assignment__icon {
+ color: var(--warning);
+}
+
+.selection-assignment__icon {
+ width: 1rem;
+ height: 1rem;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+}
+
+.selection-assignment__icon--empty {
+ border: 1px solid var(--border-subtle);
+ border-radius: 999px;
+ opacity: 0.6;
+}
+
+.selection-assignment__label {
+ flex: 1 1 auto;
+ min-width: 0;
+ text-align: left;
+ font-weight: 400;
+}
+
+.selection-assignment__count {
+ font-size: 0.8rem;
+ color: var(--muted);
+}
+
+.selection-assignment__empty {
+ padding: 0.75rem;
+}
+
+.selection-assignment__create {
+ border-top: 1px solid var(--border-subtle);
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+
+.preview-pane {
+ margin-top: 0.4rem;
+ background: transparent;
+ display: flex;
+ flex-direction: column;
+ gap: 1.25rem;
+ min-height: 0;
+ flex: 1;
+ overflow: auto;
+ position: relative;
+ padding: 1.25rem;
+}
+
+.preview-pane__media {
+ border-radius: 0;
+ background: transparent;
+ min-height: 220px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: visible;
+ position: relative;
+}
+
+.preview-image {
+ width: 100%;
+ max-width: 360px;
+ max-height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+ pointer-events: auto;
+ position: relative;
+}
+
+.preview-image__content {
+ display: block;
+ width: 100%;
+ height: auto;
+ max-width: 100%;
+ max-height: 100%;
+ object-fit: contain;
+ background: transparent;
+ cursor: pointer;
+ transition:
+ outline-color 120ms ease,
+ box-shadow 120ms ease,
+ filter 120ms ease,
+ background-color 120ms ease;
+ outline: 2px solid transparent;
+ outline-offset: -2px;
+}
+
+.preview-image__content:hover,
+.preview-image__content:focus-visible {
+ outline-color: var(--accent-focus);
+ box-shadow:
+ inset 0 0 0 999px var(--accent-elevated),
+ 0 6px 18px var(--accent-elevated-strong);
+}
+
+.thumbnail-preview {
+ display: flex;
+ flex-direction: column;
+ gap: 0.32rem;
+ margin: 0.4rem 0 0.6rem;
+}
+
+.thumbnail-image {
+ max-width: 100%;
+ border-radius: 0;
+ box-shadow: none;
+}
+
+.detail-panel .meta,
+.document-summary .meta {
+ font-size: 0.9rem;
+ color: var(--muted);
+ margin: 0.5rem 0;
+}
+
+.detail-panel .detail-folder-path,
+.document-summary .detail-folder-path {
+ display: inline-flex;
+ flex-wrap: wrap;
+ gap: 0.25rem;
+ align-items: center;
+}
+
+.detail-folder-path--block {
+ display: flex;
+ gap: 0.25rem;
+ flex-wrap: wrap;
+ align-items: center;
+ margin: 0.5rem 0;
+}
+
+.detail-panel .detail-folder-path__link,
+.document-summary .detail-folder-path__link {
+ color: var(--accent);
+ text-decoration: none;
+}
+
+.detail-panel .detail-folder-path__link:hover,
+.detail-panel .detail-folder-path__link:focus-visible,
+.document-summary .detail-folder-path__link:hover,
+.document-summary .detail-folder-path__link:focus-visible {
+ text-decoration: underline;
+ outline: none;
+}
+
+.detail-panel .detail-folder-path__separator,
+.document-summary .detail-folder-path__separator {
+ color: var(--muted);
+}
+
+.detail-panel .detail-folder-path__segment,
+.document-summary .detail-folder-path__segment {
+ color: var(--fg);
+}
+
+.detail-panel .doc-title-row,
+.document-summary .doc-title-row {
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+ margin: 0.25rem 0 1.25rem;
+ max-width: 100%;
+ word-break: break-word;
+}
+
+.doc-title-row__primary {
+ display: flex;
+ align-items: center;
+ gap: 0.4rem;
+ width: 100%;
+}
+
+.doc-title-row__title {
+ margin: 0;
+}
+
+.doc-title-row__path {
+ font-size: 0.85rem;
+ color: var(--muted);
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.25rem;
+}
+
+.detail-panel .doc-title-edit,
+.document-summary .doc-title-edit {
+ display: flex;
+ align-items: center;
+ gap: 0.4rem;
+ width: 100%;
+}
+
+.detail-panel .doc-title-edit input,
+.document-summary .doc-title-edit input {
+ flex: 1;
+ min-width: 0;
+}
+
+.status-inline {
+ font-size: 0.85rem;
+ margin-top: 0.2rem;
+}
+
+.detail-meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+ margin: 1rem 0;
+}
+
+.detail-meta__row {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ font-size: 0.9rem;
+ color: var(--muted);
+}
+
+.detail-meta__label {
+ font-weight: 600;
+ color: var(--fg);
+}
+
+.detail-meta__value {
+ color: var(--fg);
+}
+
+.detail-meta__row > .icon-button {
+ margin: -0.25rem;
+}
+
+.doc-issued-row {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ font-size: 0.9rem;
+ color: var(--muted);
+ margin: 0.25rem 0 0.5rem;
+}
+
+.doc-issued-row__label {
+ font-weight: 600;
+ color: var(--fg);
+}
+
+.doc-issued-row__value {
+ color: var(--fg);
+}
+
+.doc-issued-edit {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+}
+
+.doc-issued-edit input[type='date'] {
+ font: inherit;
+ padding: 0.35rem 0.5rem;
+}
+
+.status-inline.error {
+ color: var(--danger);
+}
+
+.bulk-detail-actions {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ margin: 0.6rem 0;
+}
+
+.bulk-detail-actions .inline {
+ display: flex;
+ gap: 0.4rem;
+}
+
+.bulk-move {
+ display: flex;
+ flex-direction: column;
+ gap: 0.3rem;
+ margin: 0.6rem 0;
+}
+
+.bulk-move select {
+ max-width: 100%;
+}
+
+.preview-stack {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.preview-stack--stacked {
+ width: 100%;
+ min-height: 420px;
+ flex-shrink: 0;
+}
+
+.preview-stack--empty {
+ width: 100%;
+ min-height: 420px;
+ flex-shrink: 0;
+}
+
+.preview-stack__item {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: transform 120ms ease;
+ filter: drop-shadow(0 2px 6px var(--shadow-soft));
+ transform-origin: center;
+ border-radius: 6px;
+ pointer-events: none;
+}
+
+.preview-stack .preview-stack__item.orientation-portrait {
+ width: 80%;
+ height: 100%;
+}
+
+.preview-stack .preview-stack__item.orientation-landscape {
+ width: 100%;
+ height: 80%;
+}
+
+.preview-pane__unsupported {
+ width: 100%;
+ max-width: 320px;
+ display: flex;
+ flex-direction: column;
+ gap: 0.6rem;
+ align-items: center;
+ text-align: center;
+ padding: 1.5rem;
+ border-radius: 8px;
+ background: var(--surface-subtle);
+ color: var(--fg);
+}
+
+.preview-pane__unsupported-message {
+ font-size: 0.95rem;
+ font-weight: 500;
+}
+
+.preview-pane__unsupported-filename {
+ font-size: 0.85rem;
+ color: var(--muted);
+ word-break: break-word;
+}
+
+.preview-pane__unsupported-download {
+ font-weight: 600;
+}
+
+.preview-pane__unsupported-download svg {
+ width: 1rem;
+ height: 1rem;
+}
+
+.preview-pane--stack {
+ min-height: 460px;
+ position: relative;
+ --preview-nav-scale: 1;
+}
+
+.preview-pane__nav-button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 2.2em;
+ height: 2.2em;
+ padding: 0.45em;
+ border-radius: 999px;
+ background: var(--preview-nav-bg);
+ color: var(--preview-nav-fg);
+ cursor: pointer;
+ transition: background 0.15s ease, opacity 0.15s ease;
+ pointer-events: auto;
+}
+
+.preview-pane__nav-button:hover:not([disabled]) {
+ background: var(--preview-nav-bg-hover);
+}
+
+.preview-pane__nav-button:disabled {
+ opacity: 0.4;
+ cursor: default;
+}
+
+.preview-pane__nav-button:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+
+.preview-pane__nav {
+ margin-top: 0.5rem;
+ display: flex;
+ justify-content: center;
+ gap: 0.5rem;
+}
+
+.preview-pane__nav--overlay {
+ position: absolute;
+ bottom: 0.75rem;
+ left: 50%;
+ transform: translateX(-50%) scale(var(--preview-nav-scale, 1));
+ margin-top: 0;
+ pointer-events: none;
+ z-index: 20;
+ opacity: 0;
+ transition: opacity 0.2s ease;
+}
+
+.preview-pane__nav--overlay .preview-pane__nav-button {
+ pointer-events: auto;
+ transform: scale(calc(1 / var(--preview-nav-scale, 1)));
+}
+
+.preview-pane__media:hover .preview-pane__nav--overlay,
+.desk-item__card:hover .preview-pane__nav--overlay {
+ opacity: 1;
+}
+
+.desk-card__nav {
+ bottom: 0.5rem;
+}
+
+.bulk-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.35rem;
+ margin: 0.4rem 0 0.6rem;
+}
+
+.detail-field {
+ margin: 0.9rem 0;
+}
+
+.detail-field__label {
+ font-weight: 600;
+ margin-bottom: 0.25rem;
+}
+
+.detail-field__value {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+ margin-bottom: 0.5rem;
+}
+
+.detail-field__value .meta {
+ color: var(--muted);
+}
+
+.detail-panel dl {
+ margin: 0;
+}
+
+.detail-panel dt {
+ font-weight: 600;
+ margin-top: 0.8rem;
+}
+
+.detail-panel dd {
+ margin: 0.2rem 0 0;
+}
+
+.detail-panel .tag-list,
+.document-summary .tag-list,
+.correspondent-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ margin: 1rem 0;
+ align-items: center;
+}
+
+.tag-list__empty {
+ color: var(--muted);
+}
+
+.detail-metadata__block {
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+ padding: 0.75rem 0;
+ border-top: 1px solid var(--border);
+}
diff --git a/frontend/src/styles/documents/controls.css b/frontend/src/styles/documents/controls.css
new file mode 100644
index 0000000..8c5c8ac
--- /dev/null
+++ b/frontend/src/styles/documents/controls.css
@@ -0,0 +1,108 @@
+.view-toggle {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+}
+
+.view-toggle__button {
+ border: 1px solid var(--border);
+ background: transparent;
+ color: var(--muted);
+ padding: 0.3rem 0.6rem;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
+}
+
+.view-toggle__button:hover,
+.view-toggle__button:focus-visible {
+ background: var(--surface-subtle);
+ color: var(--fg);
+ outline: none;
+}
+
+.view-toggle__button.active {
+ background: var(--accent);
+ color: var(--on-accent);
+ border-color: var(--accent);
+}
+
+.view-toggle__icon {
+ width: 1.4rem;
+ height: 1.4rem;
+}
+
+.documents-actions__sort-group {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+}
+
+.documents-sort {
+ display: inline-flex;
+ align-items: center;
+ position: relative;
+}
+
+.documents-sort__trigger {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ font-size: 0.85rem;
+ white-space: nowrap;
+ padding: 0.25rem 0.5rem;
+ min-height: 2.1rem;
+}
+
+.documents-sort__label {
+ display: inline-flex;
+ align-items: center;
+ line-height: 1.1;
+}
+
+.documents-sort__trigger-content {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+}
+
+.documents-sort__quickmenu .menu__item,
+.documents-sort__quickmenu .menu__item.active {
+ font-weight: 400;
+}
+
+.documents-toolbar__toggle {
+ border: 1px solid var(--border);
+ border-radius: 4px;
+ padding: 0.3rem;
+ background: transparent;
+ color: var(--muted);
+ transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
+}
+
+.documents-toolbar__toggle:hover:not([disabled]) {
+ color: var(--fg);
+ border-color: var(--border);
+}
+
+.documents-toolbar__toggle[aria-pressed='true'] {
+ border-color: var(--accent);
+ color: var(--accent);
+ background: var(--surface-subtle);
+}
+
+.documents-sort__direction {
+ padding: 0.3rem 0.45rem;
+}
+
+.documents-sort__direction[aria-pressed='true'] {
+ border-color: var(--border);
+ color: var(--muted);
+ background: transparent;
+}
+
+.documents-sort__direction svg {
+ width: 1.1rem;
+ height: 1.1rem;
+}
+
diff --git a/frontend/src/styles/documents/listing.css b/frontend/src/styles/documents/listing.css
new file mode 100644
index 0000000..751336e
--- /dev/null
+++ b/frontend/src/styles/documents/listing.css
@@ -0,0 +1,736 @@
+.documents-panel {
+ padding: 0 0.75rem 0.75rem 0.75rem;
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+}
+
+.tags-panel,
+.correspondents-panel {
+ padding: 1.25rem;
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+}
+
+.folder-row.is-drop-target {
+ outline: 2px dashed var(--accent);
+ outline-offset: 2px;
+}
+
+.documents-panel .panel-section__header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ position: relative;
+}
+
+.documents-panel .panel-section__header .header-actions {
+ display: flex;
+ gap: 0.5rem;
+}
+
+.documents-panel__title {
+ display: flex;
+ align-items: center;
+ gap: 0.35rem;
+ margin: 0;
+ font-size: 0.9rem;
+ font-weight: 600;
+ min-width: 0;
+ white-space: nowrap;
+ overflow: hidden;
+}
+
+.documents-panel__breadcrumbs {
+ flex: 1 1 auto;
+ overflow: hidden;
+}
+
+.documents-panel .panel-section__body {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.panel-floating {
+ position: absolute;
+ top: calc(50% + 0.25rem);
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background: color-mix(in oklch, var(--surface) 100%, transparent);
+ border: 1px solid color-mix(in oklch, var(--border) 95%, transparent);
+ padding: 0.45rem 0.85rem;
+ border-radius: 1rem;
+ font-size: 0.95rem;
+ font-weight: 400;
+ color: var(--fg);
+ box-shadow: 0 2px 6px color-mix(in oklch, var(--shadow-soft) 60%, transparent);
+ pointer-events: auto;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-wrap: nowrap;
+ gap: 0.75rem;
+ z-index: 2000000;
+}
+
+.panel-floating__label {
+ white-space: nowrap;
+ pointer-events: none;
+ font-size: 0.95rem;
+ color: var(--fg);
+}
+
+.selection-summary {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.4rem;
+}
+
+.selection-summary__token {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.3rem;
+}
+
+.selection-summary__count {
+ font-weight: 600;
+ font-variant-numeric: tabular-nums;
+}
+
+.selection-summary__icon {
+ width: 1rem;
+ height: 1rem;
+}
+
+.selection-summary--text {
+ font-weight: 600;
+}
+
+.selection-summary__separator {
+ opacity: 0.45;
+}
+
+.panel-floating-actions {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.4rem;
+ flex-wrap: nowrap;
+ pointer-events: auto;
+}
+
+.panel-floating-actions .quick-add {
+ pointer-events: auto;
+}
+
+.panel-floating-actions .quick-add__trigger {
+ pointer-events: auto;
+}
+
+.panel-floating-actions .quick-add__trigger[disabled] {
+ opacity: 0.45;
+ cursor: not-allowed;
+}
+
+.panel-floating-actions__button {
+ display: inline-flex;
+ align-items: center;
+ gap: 0;
+ pointer-events: auto;
+ font-size: 1.35rem;
+}
+
+.panel-floating-actions__button .icon-inline {
+ display: inline-flex;
+ width: 1.35rem;
+ height: 1.35rem;
+}
+
+.documents-panel .documents-scroll {
+ overflow-y: auto;
+ background: transparent;
+ min-height: 0;
+ padding-right: 0.3rem;
+ flex-grow: 1;
+}
+
+.documents-panel .documents-scroll:focus-visible {
+ outline: 2px solid var(--selection-ring);
+ outline-offset: 2px;
+ border-radius: 0.25rem;
+}
+
+.documents-panel table {
+ max-width: 100%;
+ border-collapse: collapse;
+ font-size: 0.88rem;
+ margin-top: 0.4rem;
+}
+
+.documents-panel thead th {
+ background-color: var(--surface);
+ position: sticky;
+ top: 0;
+ z-index: 1;
+ padding: 0.45rem 0.6rem;
+ color: var(--muted);
+}
+
+
+.documents-panel th,
+.documents-panel td {
+ padding: 0.45rem 0.6rem;
+ text-align: left;
+}
+
+.documents-panel--view-grid .documents-scroll {
+ padding: 0.35rem 0.5rem 1rem;
+}
+
+.documents-panel th.thumb-column,
+.documents-panel td.thumb-cell {
+ width: 54px;
+ text-align: center;
+}
+
+.documents-panel--view-grid .document-thumbnail-wrapper {
+ width: var(--documents-grid-icon-size);
+ height: var(--documents-grid-icon-size);
+ padding: 8px;
+ border-radius: 10px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.documents-panel--view-grid .folder-card__icon {
+ width: var(--documents-grid-icon-size);
+ height: var(--documents-grid-icon-size);
+ padding: 8px;
+ border-radius: 10px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.document-thumbnail-inner {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.document-thumbnail-inner--multipage::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 24px;
+ height: 24px;
+ background-image: url('../../assets/papercorner.svg');
+ background-repeat: no-repeat;
+ background-size: contain;
+ pointer-events: none;
+}
+
+.documents-panel--view-grid .document-thumbnail-inner--multipage::after {
+ width: 28px;
+ height: 28px;
+}
+
+.document-thumbnail {
+ max-width: 100%;
+ max-height: 100%;
+ box-shadow: 0 1px 6px var(--shadow-medium);
+}
+
+.documents-panel--view-grid .document-thumbnail {
+ box-shadow: 0 1px 12px var(--shadow-medium);
+}
+
+.thumb-placeholder {
+ width: 100%;
+ height: 100%;
+ border-radius: 1px;
+ background: var(--surface-subtle);
+ color: var(--muted);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.68rem;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ box-shadow: 0 1px 3px var(--shadow-medium);
+}
+
+.documents-panel--view-grid .thumb-placeholder {
+ font-size: 1.1rem;
+ letter-spacing: 0.12em;
+ display: inline-flex;
+ width: 100%;
+ height: 100%;
+}
+
+.thumb-icon {
+ width: 100%;
+ height: 100%;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.thumb-icon__image {
+ width: 32px;
+ height: 32px;
+ display: block;
+}
+
+.documents-panel th.actions-column,
+.documents-panel td.actions {
+ width: 1%;
+ white-space: nowrap;
+}
+
+.documents-panel td.doc-list__name {
+ width: 100%;
+}
+
+.doc-title-edit {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ flex-wrap: nowrap;
+}
+
+.doc-title-edit input[type='text'] {
+ padding: 0.3rem 0.55rem;
+ border-radius: 4px;
+ border: 1px solid var(--border);
+ background: var(--surface);
+ color: var(--fg);
+ min-width: 8rem;
+}
+
+.doc-title-edit input[type='text']:focus-visible {
+ outline: 2px solid var(--selection-ring);
+ outline-offset: 1px;
+}
+
+.doc-title-edit .icon-button {
+ flex-shrink: 0;
+}
+
+.documents-panel .doc-name__primary {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ flex-wrap: wrap;
+}
+
+.documents-panel .doc-name__primary-text {
+ overflow-wrap: anywhere;
+}
+
+
+.doc-entry {
+ display: flex;
+ align-items: center;
+ gap: 0.6rem;
+}
+
+.doc-entry--with-thumb .doc-entry__thumb {
+ flex: 0 0 auto;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.doc-entry__thumb .document-thumbnail-wrapper {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.doc-entry__main {
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+ min-width: 0;
+}
+
+.doc-entry__name {
+ font-weight: 600;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ max-width: 100%;
+}
+
+.doc-entry__tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.3rem;
+}
+
+.documents-panel td.actions .action-buttons {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.3rem;
+}
+
+.documents-panel tbody tr {
+ background: transparent;
+ transition: background 0.15s ease;
+}
+
+.documents-panel tbody tr.folder {
+ cursor: pointer;
+}
+
+.documents-panel tbody tr.folder.focused {
+ box-shadow: inset 2px 0 0 var(--accent-outline);
+ background: var(--sidebar-hover-bg);
+}
+
+.documents-panel tbody tr.document {
+ cursor: pointer;
+}
+
+.documents-panel tbody tr.document.selected,
+.documents-panel tbody tr.folder.selected {
+ background: var(--selection-soft);
+ box-shadow: inset 2px 0 0 var(--accent-outline-strong);
+}
+
+.documents-panel tbody tr.document,
+.documents-panel tbody tr.document * {
+ user-select: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+}
+
+.documents-panel tbody tr.document.focused:not(.selected) {
+ box-shadow: inset 2px 0 0 var(--accent-outline);
+}
+
+.doc-name {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+.documents-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(var(--documents-grid-icon-size), 1fr));
+ gap: 0.3rem 1.3rem;
+ padding: 0;
+}
+
+.document-card {
+ padding: 0rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.3rem;
+ cursor: pointer;
+ align-items: center;
+ text-align: center;
+}
+
+.document-card.selected .document-thumbnail-wrapper,
+.folder-card.selected .folder-card__icon {
+ background-color: var(--selection-soft);
+}
+
+.document-card:focus-visible .document-thumbnail-wrapper {
+ background-color: var(--selection-ring);
+}
+
+.document-card.is-dragging {
+ opacity: 0.55;
+}
+
+.document-card__meta {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.35rem;
+ width: 100%;
+}
+
+.document-card__title {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.35rem;
+ color: var(--fg);
+}
+
+.document-card__title-row {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.35rem;
+ flex-wrap: wrap;
+}
+
+.document-card__title-badge {
+ padding: 0.2rem 0.7rem;
+ border-radius: 1rem;
+ max-width: 100%;
+ word-break: break-word;
+ font-size: var(--documents-grid-title-size);
+ color: inherit;
+}
+
+.document-card .doc-correspondent-link {
+ font-size: var(--documents-grid-title-size);
+}
+
+.document-card.selected .document-card__title-badge {
+ background-color: var(--accent);
+ color: var(--on-accent);
+}
+
+.document-card__subtitle {
+ font-size: 0.78rem;
+ color: var(--muted);
+}
+
+.document-card__tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.25rem;
+ justify-content: center;
+}
+
+.folder-card {
+ align-items: center;
+ text-align: center;
+}
+
+.folder-card__icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--documents-grid-icon-size);
+ height: var(--documents-grid-icon-size);
+}
+
+.folder-card__icon-svg {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+}
+
+.folder-card__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.28rem;
+ padding-top: 0.35rem;
+}
+
+.folder-card__label-row {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.35rem;
+ flex-wrap: wrap;
+}
+
+.folder-card__name {
+ color: var(--fg);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: break-word;
+ font-size: var(--documents-grid-title-size);
+ padding: 0.2rem 0.7rem;
+ border-radius: 1rem;
+}
+
+.folder-card__edit {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.35rem;
+}
+
+.folder-card.selected .folder-card__name {
+ background-color: var(--accent);
+ color: var(--on-accent);
+}
+
+
+.doc-name__title {
+ max-width: 100%;
+ word-break: break-word;
+}
+
+.doc-name__tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.25rem;
+}
+
+.doc-correspondents {
+ display: inline;
+ color: inherit;
+}
+
+.doc-correspondent-link {
+ background: none;
+ background-color: transparent;
+ border: none;
+ padding: 0;
+ margin: 0;
+ color: var(--accent);
+ text-decoration: none;
+ font: inherit;
+ cursor: pointer;
+ box-shadow: none;
+ appearance: none;
+}
+
+.doc-correspondent-link:not(.is-static):hover,
+.doc-correspondent-link:not(.is-static):focus-visible {
+ color: var(--accent-strong, var(--accent));
+ text-decoration: underline;
+ text-decoration-thickness: 1.5px;
+ background-color: transparent;
+}
+
+.doc-correspondent-link:not(.is-static):focus-visible {
+ outline: 2px solid currentColor;
+ outline-offset: 2px;
+}
+
+.documents-panel tbody tr.document.selected .doc-correspondents,
+.documents-panel tbody tr.document.selected .doc-correspondent-link,
+.document-card.selected .doc-correspondent-link {
+ color: inherit;
+}
+
+.document-card.selected .doc-correspondents {
+ color: inherit;
+}
+
+.doc-correspondent-link.is-active {
+ font-weight: 600;
+}
+
+.doc-correspondent-link.is-static {
+ color: inherit;
+ cursor: default;
+ text-decoration: none;
+}
+
+.doc-correspondent-link__separator {
+ color: inherit;
+}
+
+.documents-panel tbody tr.document.dragging {
+ opacity: 0.4;
+}
+
+.documents-panel tbody tr.document.tag-drop-target {
+ background: var(--accent-soft);
+ box-shadow: inset 0 0 0 2px var(--accent);
+}
+
+.document-card.tag-drop-target {
+ box-shadow: 0 0 0 2px var(--accent);
+ border-color: var(--accent);
+}
+
+.document-card.tag-drop-target .document-card__title {
+ color: var(--accent);
+}
+
+.filter-bar {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.32rem;
+ margin-bottom: 0.5rem;
+}
+
+.filter-bar input[type='search'] {
+ flex: 1;
+ min-width: 180px;
+}
+
+.filter-actions {
+ display: flex;
+ gap: 0.32rem;
+}
+
+.search-hint {
+ margin-top: 0.75rem;
+ font-size: 0.85rem;
+ color: var(--muted);
+}
+
+.badge {
+ display: inline-flex;
+ align-items: center;
+ padding: 0.15rem 0.35rem;
+ border-radius: 2px;
+ background: var(--surface-subtle);
+ color: var(--fg);
+ font-size: 0.74rem;
+}
+
+.tag-chip {
+ gap: 0.25rem;
+ border-radius: 1rem;
+ padding: 0.2rem 0.4rem;
+ font-weight: 600;
+}
+
+.tag-chip--more {
+ background: transparent;
+ border-color: var(--border-strong);
+ color: var(--muted);
+}
+
+.tag-chip--removable {
+ gap: 0.35rem;
+}
+
+.tag-chip__remove {
+ background: none;
+ border: none;
+ color: inherit;
+ padding: 0;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ line-height: 1;
+ cursor: pointer;
+ opacity: 0.8;
+}
+
+.tag-chip__remove:hover,
+.tag-chip__remove:focus-visible {
+ opacity: 1;
+}
+
+.tag-chip__remove:focus-visible {
+ outline: 2px solid currentColor;
+ outline-offset: 2px;
+ border-radius: 50%;
+}
+
+.empty-state {
+ border: 1px dashed var(--border);
+ border-radius: 0;
+ text-align: center;
+ padding: 1.1rem 0.9rem;
+ color: var(--muted);
+ margin-top: 0.75rem;
+ background: var(--surface-subtle);
+}
+
+.empty-state--global {
+ border-style: solid;
+}
diff --git a/frontend/src/styles/documents/panel-sections.css b/frontend/src/styles/documents/panel-sections.css
new file mode 100644
index 0000000..de07a4f
--- /dev/null
+++ b/frontend/src/styles/documents/panel-sections.css
@@ -0,0 +1,133 @@
+.document-viewer__message {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ color: var(--on-accent);
+ background: var(--overlay-dark);
+ padding: 0.5rem 1rem;
+ border-radius: 999px;
+ font-size: 0.9rem;
+}
+
+.panel-section__header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.5rem;
+ padding: 0 0 0.5rem;
+}
+
+.panel-section__titles {
+ display: flex;
+ flex-direction: column;
+ gap: 0.3rem;
+}
+
+.panel-section__header h2 {
+ margin: 0;
+ font-size: 0.9rem;
+ font-weight: 600;
+}
+
+.documents-panel__title {
+ display: flex;
+ align-items: center;
+ gap: 0.35rem;
+ margin: 0;
+ font-size: 0.9rem;
+ font-weight: 600;
+ min-width: 0;
+ flex-wrap: nowrap;
+ overflow: hidden;
+}
+
+.panel-section__subtitle {
+ margin-top: 0.25rem;
+ font-size: 0.78rem;
+ color: var(--muted);
+}
+
+.breadcrumb-trail {
+ display: flex;
+ align-items: center;
+ gap: 0.35rem;
+ flex-wrap: nowrap;
+ min-width: 0;
+ flex: 1 1 auto;
+ max-width: 100%;
+ overflow: hidden;
+}
+
+.breadcrumb-trail--measure {
+ position: absolute;
+ visibility: hidden;
+ pointer-events: none;
+ left: -9999px;
+ top: -9999px;
+ max-width: none;
+ overflow: visible;
+}
+
+.breadcrumb-trail__link {
+ display: inline-flex;
+ align-items: center;
+ max-width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ border: none;
+ background: none;
+ padding: 0;
+ font: inherit;
+ color: inherit;
+ cursor: default;
+}
+
+.breadcrumb-trail__link:not(.is-current) {
+ cursor: pointer;
+ color: var(--accent);
+}
+
+.breadcrumb-trail__link:not(.is-current):hover,
+.breadcrumb-trail__link:not(.is-current):focus-visible {
+ text-decoration: underline;
+}
+
+.breadcrumb-trail__separator {
+ color: var(--muted);
+ margin: 0 0.2rem;
+}
+
+.breadcrumb-trail__ellipsis {
+ position: relative;
+ display: inline-flex;
+}
+
+.breadcrumb-trail__ellipsis-button {
+ cursor: pointer;
+}
+
+.documents-panel__breadcrumbs {
+ flex: 1 1 auto;
+ min-width: 0;
+ max-width: 100%;
+ overflow: hidden;
+}
+
+.documents-panel__breadcrumbs {
+ max-width: 22rem;
+}
+
+.panel-section__body {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+}
+
+.panel-section__body--scrollable,
+.panel-section__body.scrollable {
+ overflow-y: auto;
+}
+
diff --git a/frontend/src/styles/documents/shared-snippets.css b/frontend/src/styles/documents/shared-snippets.css
new file mode 100644
index 0000000..e03be4e
--- /dev/null
+++ b/frontend/src/styles/documents/shared-snippets.css
@@ -0,0 +1,26 @@
+.doc-list__name {
+ width: 100%;
+}
+
+.doc-list__name-content {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.22rem;
+ max-width: 100%;
+}
+
+.doc-list__name-content span {
+ max-width: 100%;
+ overflow-wrap: anywhere;
+ word-break: break-word;
+}
+
+.preview-pane__nav-button svg {
+ width: 100%;
+ height: 100%;
+}
+
+.panel-header .icon-button svg {
+ width: 1.41rem;
+ height: 1.41rem;
+}
diff --git a/frontend/src/styles/documents/tags-correspondents.css b/frontend/src/styles/documents/tags-correspondents.css
new file mode 100644
index 0000000..9fd9b87
--- /dev/null
+++ b/frontend/src/styles/documents/tags-correspondents.css
@@ -0,0 +1,173 @@
+.tags-panel__body {
+ overflow-y: auto;
+}
+
+.tags-table {
+ width: 100%;
+ overflow: auto;
+}
+
+.tags-table table {
+ width: 100%;
+ border-collapse: collapse;
+ min-width: 320px;
+}
+
+.tags-table th,
+.tags-table td {
+ padding: 0.45rem 0.6rem;
+ text-align: left;
+ font-size: 0.85rem;
+}
+
+.tags-table th.numeric,
+.tags-table td.numeric {
+ text-align: right;
+}
+
+.tags-table th.actions,
+.tags-table td.actions {
+ text-align: right;
+ width: 0;
+}
+
+.tags-table tbody tr:hover {
+ background: var(--surface-subtle);
+}
+
+.tags-table tr.editing {
+ background: var(--sidebar-hover-bg);
+}
+
+.tags-table__label {
+ max-width: 24rem;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.tags-table__swatch {
+ display: inline-block;
+ width: 1rem;
+ height: 1rem;
+ border-radius: 2px;
+ box-shadow: inset 0 0 0 1px var(--shadow-faint);
+}
+
+.tags-panel__error {
+ margin: 0.5rem 0;
+ color: var(--danger);
+ font-size: 0.8rem;
+}
+
+.tags-table__label-input {
+ width: 100%;
+}
+
+.tags-table__color-editor {
+ display: flex;
+ align-items: center;
+ gap: 0.4rem;
+}
+
+.tags-table__color-picker {
+ width: 2.25rem;
+ height: 2.25rem;
+ padding: 0;
+ background: none;
+ cursor: pointer;
+}
+
+.tags-table__edit-controls {
+ display: flex;
+ justify-content: flex-start;
+ padding: 0.25rem;
+ gap: 0.4rem;
+}
+
+.tags-table__row-actions {
+ display: flex;
+ justify-content: flex-start;
+ padding: 0.25rem;
+ gap: 0.4rem;
+}
+
+.correspondents-panel .header-actions {
+ gap: 0.5rem;
+}
+
+.tags-actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.6rem;
+ align-items: center;
+}
+
+.tags-actions__form {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.4rem;
+}
+
+.tags-actions__form input[type='text'] {
+ min-width: 14rem;
+}
+
+.correspondents-actions__form {
+ display: flex;
+ gap: 0.4rem;
+}
+
+.correspondents-actions__form input {
+ min-width: 14rem;
+}
+
+
+
+.correspondent-pill {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ background: var(--surface-subtle);
+ border-radius: 999px;
+ padding: 0.15rem 0.5rem;
+ font-size: 0.9rem;
+ border: none;
+}
+
+.correspondent-pill__label {
+ line-height: 1.2;
+}
+
+.correspondent-pill__label strong {
+ font-size: 0.8rem;
+ text-transform: capitalize;
+ color: var(--muted);
+}
+
+.correspondent-pill__remove {
+ border: none;
+ background: none;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--muted);
+ padding: 0;
+}
+
+.correspondent-pill__remove:hover {
+ color: var(--danger);
+}
+
+.correspondent-form {
+ display: flex;
+ gap: 0.4rem;
+ align-items: center;
+}
+
+.correspondent-form input,
+.correspondent-form select {
+ min-height: 32px;
+}
diff --git a/frontend/src/styles/documents/viewer.css b/frontend/src/styles/documents/viewer.css
new file mode 100644
index 0000000..74af8df
--- /dev/null
+++ b/frontend/src/styles/documents/viewer.css
@@ -0,0 +1,335 @@
+.document-viewer {
+ flex: 1;
+ display: grid;
+ grid-template-columns: minmax(0, 30em) minmax(0, 1fr);
+ gap: 1rem;
+ min-height: 0;
+ padding: 1rem 1rem;
+}
+
+.document-drag-preview {
+ position: fixed;
+ pointer-events: none;
+ top: -9999px;
+ left: -9999px;
+ width: var(--drag-preview-size, 96px);
+ height: var(--drag-preview-size, 96px);
+ z-index: 9999;
+}
+
+.document-drag-preview__thumb {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 64px;
+ height: 64px;
+ border-radius: 6px;
+ box-shadow: 0 6px 12px var(--shadow-pop);
+ overflow: hidden;
+ background-color: var(--overlay-dim);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
+ font-weight: 600;
+ font-size: 0.8rem;
+ text-transform: uppercase;
+ transform: translate(-50%, -50%) rotate(var(--rotation-deg, 0deg));
+ transform-origin: center;
+}
+
+.document-drag-preview__thumb--image {
+ background-color: #000;
+ background-repeat: no-repeat;
+ background-size: contain;
+ background-position: center;
+}
+
+.document-drag-preview__thumb .document-thumbnail,
+.document-drag-preview__thumb img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ pointer-events: none;
+}
+
+.document-drag-preview__thumb .thumb-placeholder,
+.document-drag-preview__thumb .thumb-placeholder * {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ pointer-events: none;
+}
+
+.document-drag-preview__folder-thumb {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ pointer-events: none;
+}
+
+.document-drag-preview__folder-thumb svg {
+ width: 48px;
+ height: 48px;
+ color: var(--accent-strong, var(--accent));
+}
+
+.document-drag-preview__folder-placeholder {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.75rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+ color: var(--text-on-dark);
+}
+
+.document-drag-preview__count {
+ position: absolute;
+ bottom: 4px;
+ right: 4px;
+ background-color: var(--accent);
+ color: var(--on-accent);
+ border-radius: 999px;
+ padding: 0.25rem 0.5rem;
+ font-size: 0.75rem;
+ font-weight: 600;
+ box-shadow: 0 4px 8px var(--shadow-pop);
+ pointer-events: none;
+}
+
+.document-viewer__details {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ min-height: 0;
+ flex: 1;
+}
+
+.document-viewer__details-pane {
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+ overflow: auto;
+ flex: 1;
+}
+.document-viewer__tabs-wrapper {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ min-height: 0;
+}
+
+.document-viewer__section {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ min-height: 0;
+ padding-top: 1rem;
+}
+
+.document-viewer__section-title {
+ margin: 0;
+ font-size: 0.95rem;
+ font-weight: 600;
+ letter-spacing: 0.01em;
+}
+
+.document-viewer__section-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+ gap: 0.75rem 1.25rem;
+}
+
+.document-viewer__section-item {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+.document-viewer__section-item dt {
+ font-size: 0.75rem;
+ color: var(--muted);
+}
+
+.document-viewer__section-item dd {
+ margin: 0;
+ font-size: 0.9rem;
+ font-weight: 500;
+ word-break: break-word;
+}
+
+.document-viewer__section-placeholder {
+ margin: 0;
+ font-size: 0.9rem;
+ color: var(--muted);
+}
+
+.document-viewer__section-payload {
+ font-size: 0.85rem;
+}
+
+.document-viewer__section-payload summary {
+ cursor: pointer;
+ font-weight: 500;
+ color: var(--accent-strong, var(--accent));
+}
+
+.document-viewer__section-payload pre {
+ margin: 0.75rem 0 0;
+ padding: 0.75rem;
+ background: var(--surface);
+ max-height: 280px;
+ overflow: auto;
+ font-size: 0.8rem;
+}
+
+.document-viewer__section--metadata-json {
+ overflow: auto;
+}
+
+.document-viewer__metadata-json {
+ margin: 0;
+ padding: 0.75rem;
+ background: var(--surface);
+ border-radius: 0.5rem;
+ font-size: 0.85rem;
+ line-height: 1.35;
+ overflow: auto;
+}
+
+.document-viewer__tabs {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ border-bottom: 1px solid var(--outline-subtle);
+}
+
+.document-viewer__tab {
+ appearance: none;
+ border: none;
+ background: transparent;
+ padding: 0.4rem 0.75rem;
+ font-size: 0.85rem;
+ font-weight: 500;
+ color: var(--muted);
+ cursor: pointer;
+ border-bottom: 2px solid transparent;
+ transition:
+ color 120ms ease,
+ border-color 120ms ease;
+}
+
+.document-viewer__tab:hover,
+.document-viewer__tab:focus-visible {
+ color: var(--accent);
+}
+
+.document-viewer__tab.is-active {
+ color: var(--accent);
+ border-color: var(--accent);
+}
+
+.document-viewer__tabpanes {
+ flex: 1;
+ min-height: 0;
+ display: flex;
+}
+
+.document-viewer__tabpanes--single {
+ flex: 1;
+ min-height: 0;
+}
+
+.document-viewer__tabpanel {
+ flex: 1;
+ min-height: 0;
+ display: flex;
+ position: relative;
+}
+
+.document-viewer__object--ocr {
+ width: 100%;
+ height: 100%;
+}
+
+.document-viewer__object--ocr-text {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 1rem 0;
+ font-size: 1rem;
+ white-space: pre-wrap;
+ font-family: inherit;
+}
+
+.document-viewer__message--error {
+ color: var(--danger);
+}
+
+.document-viewer__viewport {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ position: relative;
+ overflow: hidden;
+ align-items: flex-start;
+}
+
+.document-viewer__object {
+ width: 100%;
+ height: 100%;
+ border: none;
+}
+
+.document-viewer__object--image {
+ width: auto;
+ height: auto;
+ max-width: 100%;
+ max-height: 100%;
+ object-fit: contain;
+ align-self: flex-start;
+}
+
+.document-viewer__unsupported {
+ height: 100%;
+ width: 100%;
+ padding: 2rem;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 0.75rem;
+ text-align: center;
+ background: var(--surface-subtle);
+}
+
+.document-viewer__unsupported-filename {
+ font-size: 0.9rem;
+ color: var(--muted);
+}
+
+.document-viewer__unsupported-message {
+ font-size: 0.95rem;
+ color: var(--fg);
+}
+
+.document-viewer__unsupported-download {
+ font-weight: 600;
+}
+
+.document-viewer__unsupported-download svg {
+ width: 1rem;
+ height: 1rem;
+}
+
diff --git a/frontend/src/styles/forms/elements.css b/frontend/src/styles/forms/elements.css
new file mode 100644
index 0000000..d1c8ad1
--- /dev/null
+++ b/frontend/src/styles/forms/elements.css
@@ -0,0 +1,46 @@
+select {
+ font: inherit;
+ border-radius: 2px;
+ border: 1px solid var(--border);
+ padding: 0.4rem 0.5rem;
+ background: var(--surface);
+ color: inherit;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+input[type='text'],
+input[type='email'],
+input[type='password'],
+input[type='search'],
+input[type='number'],
+input[type='url'],
+input[type='tel'] {
+ font: inherit;
+ border-radius: 2px;
+ border: 1px solid var(--border);
+ padding: 0.4rem 0.5rem;
+ background: var(--surface);
+ color: inherit;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+input:focus,
+textarea:focus,
+select:focus {
+ outline: none;
+ border-color: var(--accent);
+ box-shadow: 0 0 0 2px var(--accent-soft);
+ background: var(--surface);
+}
+
+textarea {
+ resize: vertical;
+}
+
+form.inline {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+}
diff --git a/frontend/src/styles/index.css b/frontend/src/styles/index.css
new file mode 100644
index 0000000..884e2fd
--- /dev/null
+++ b/frontend/src/styles/index.css
@@ -0,0 +1,22 @@
+@import './base/theme.css';
+@import './base/controls.css';
+@import './layout/panel-header-controls.css';
+@import './preview/preview-zoom.css';
+@import './base/iconography.css';
+@import './documents/controls.css';
+@import './layout/structure.css';
+@import './documents/viewer.css';
+@import './documents/tags-correspondents.css';
+@import './documents/panel-sections.css';
+@import './sidebar/sidebar.css';
+@import './documents/listing.css';
+@import './detail/detail-panels.css';
+@import './forms/elements.css';
+@import './modals/status-drop.css';
+@import './settings/settings.css';
+@import './modals/panel-modals.css';
+@import './layout/responsive.css';
+@import './auth/login.css';
+@import './documents/shared-snippets.css';
+@import './uploads/overlay.css';
+@import './base/text-button.css';
diff --git a/frontend/src/styles/layout/panel-header-controls.css b/frontend/src/styles/layout/panel-header-controls.css
new file mode 100644
index 0000000..7846e7c
--- /dev/null
+++ b/frontend/src/styles/layout/panel-header-controls.css
@@ -0,0 +1,43 @@
+.panel-header .icon-button,
+.panel-header button,
+.panel-header a.icon-button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: flex-start;
+ border: none;
+ background: transparent;
+ color: var(--muted);
+ padding: 0.25rem;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: background 0.15s ease, color 0.15s ease;
+ text-decoration: none;
+ text-align: left;
+}
+
+.panel-header .icon-button:hover:not([disabled]),
+.panel-header button:hover:not([disabled]),
+.panel-header a.icon-button:hover {
+ background: var(--sidebar-hover-bg);
+ color: var(--fg);
+}
+
+.panel-header .icon-button.active:hover:not([disabled]),
+.panel-header button.active:hover:not([disabled]),
+.panel-header a.icon-button.active:hover {
+ background: var(--accent-soft);
+ color: var(--fg);
+}
+
+.panel-header .icon-button.ghost,
+.panel-header button.icon-button.ghost {
+ color: var(--muted);
+ padding: 0.25rem;
+}
+
+.panel-header .icon-button.ghost:hover:not([disabled]),
+.panel-header button.icon-button.ghost:hover:not([disabled]) {
+ color: var(--fg);
+ background: var(--sidebar-hover-bg);
+}
+
diff --git a/frontend/src/styles/layout/responsive.css b/frontend/src/styles/layout/responsive.css
new file mode 100644
index 0000000..a3195d5
--- /dev/null
+++ b/frontend/src/styles/layout/responsive.css
@@ -0,0 +1,17 @@
+@media (max-width: 768px) {
+ .app-bar {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 0.75rem;
+ }
+ .app-main {
+ grid-template-columns: 1fr;
+ }
+ .sidebar,
+ .documents-panel,
+ .detail-panel,
+ .tags-panel,
+ .correspondents-panel {
+ min-height: auto;
+ }
+}
diff --git a/frontend/src/styles/layout/structure.css b/frontend/src/styles/layout/structure.css
new file mode 100644
index 0000000..4f07e1e
--- /dev/null
+++ b/frontend/src/styles/layout/structure.css
@@ -0,0 +1,147 @@
+.app-shell {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ color: var(--fg);
+}
+
+
+.documents-main {
+ flex: 1;
+ display: flex;
+ min-height: 100vh;
+ width: 100%;
+ position: relative;
+}
+
+.documents-main--sidebar-collapsed {
+ position: relative;
+}
+
+.main-content {
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+ min-width: 0;
+ margin: 0;
+ border-radius: 0;
+ background: var(--surface);
+ flex-grow: 1;
+ box-shadow: -12px 0 24px -12px var(--shadow-faint);
+}
+
+.documents-main:not(.documents-main--sidebar-collapsed) .main-content {
+ border-left: 1px solid var(--border);
+}
+
+.main-content__header {
+ padding: 0.5rem 1.25rem;
+ justify-content: space-between;
+ align-items: center;
+ gap: 0.75rem;
+}
+
+.main-content__header-wrapper {
+ position: relative;
+}
+
+.main-content__body {
+ position: relative;
+ flex: 1 1 auto;
+ display: flex;
+ min-height: 0;
+}
+
+.main-content__body > * {
+ min-width: 0;
+ min-height: 0;
+}
+
+.main-content__body > *:not(.detail-panel) {
+ flex: 1 1 auto;
+}
+
+.main-content__body--workspace.main-content__body--has-detail > .desk-shell {
+ margin-right: calc(-1 * var(--detail-panel-width));
+}
+
+.main-content--has-detail {
+ padding-right: var(--detail-panel-width);
+}
+
+.main-content__title {
+ margin: 0;
+ font-size: 1rem;
+ font-weight: 600;
+ color: var(--fg);
+}
+
+
+.documents-main--sidebar-collapsed {
+ position: relative;
+ grid-template-columns: auto minmax(0, 1fr);
+}
+
+.sidebar .panel-header .icon,
+.main-content__header .icon,
+.detail-panel .panel-header .icon {
+ width: 1.35rem;
+ height: 1.35rem;
+}
+
+.main-content__actions {
+ display: flex;
+ align-items: center;
+ gap: 0.6rem;
+}
+
+.main-content__actions-divider {
+ display: inline-flex;
+ align-items: center;
+ color: var(--muted);
+}
+
+.main-content__title {
+ margin: 0;
+ font-size: 1rem;
+ font-weight: 600;
+ color: var(--fg);
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ min-width: 0;
+ flex: 1 1 auto;
+ overflow: hidden;
+}
+
+.main-content__breadcrumbs {
+ flex: 1 1 auto;
+ overflow: hidden;
+}
+
+.main-content__subtitle {
+ font-size: 0.9rem;
+ color: var(--muted);
+ font-weight: 400;
+ line-height: 1.2;
+ flex-shrink: 0;
+}
+
+.panels-main {
+ flex: 1;
+ display: flex;
+ justify-content: center;
+ padding: 1.5rem;
+ overflow: auto;
+}
+
+.panels-main > * {
+ flex: 0 1 720px;
+}
+
+.preview-main {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+}
diff --git a/frontend/src/styles/modals/panel-modals.css b/frontend/src/styles/modals/panel-modals.css
new file mode 100644
index 0000000..67f6791
--- /dev/null
+++ b/frontend/src/styles/modals/panel-modals.css
@@ -0,0 +1,44 @@
+.panel-modal__header {
+ padding: 1rem 1rem 1rem 0.5rem;
+ border-bottom: 1px solid var(--border);
+}
+
+.panel-modal__body {
+ flex: 1;
+ overflow: auto;
+ padding: 0;
+}
+
+.modal__body {
+ width: 100%;
+}
+
+.modal h3 {
+ margin: 0;
+ font-size: 1.15rem;
+ font-weight: 600;
+}
+
+.modal__form {
+ display: flex;
+ flex-direction: column;
+ gap: 0.9rem;
+}
+
+.modal__form label {
+ font-weight: 600;
+ font-size: 0.9rem;
+}
+
+.modal__error {
+ margin: -0.45rem 0 0;
+ font-size: 0.85rem;
+ color: var(--danger);
+}
+
+.modal__actions {
+ display: flex;
+ justify-content: flex-start;
+ padding: 0.25rem;
+ gap: 0.6rem;
+}
diff --git a/frontend/src/styles/modals/status-drop.css b/frontend/src/styles/modals/status-drop.css
new file mode 100644
index 0000000..483213d
--- /dev/null
+++ b/frontend/src/styles/modals/status-drop.css
@@ -0,0 +1,77 @@
+.status-banner {
+ padding: 0.45rem 0.8rem;
+ border-radius: 2px;
+ font-size: 0.85rem;
+}
+
+.status-banner.info {
+ background: var(--surface-subtle);
+ color: var(--muted);
+}
+
+.status-banner.success {
+ background: var(--success-subtle);
+ color: var(--success);
+}
+
+.status-banner.error {
+ background: var(--danger-soft);
+ color: var(--danger);
+}
+
+.drop-overlay {
+ position: fixed;
+ inset: 0;
+ background: var(--accent-soft);
+ backdrop-filter: blur(4px);
+ display: none;
+ align-items: center;
+ justify-content: center;
+ z-index: 9999;
+}
+
+.drop-overlay.active {
+ display: flex;
+}
+
+.drop-overlay__content {
+ background: var(--surface);
+ color: var(--fg);
+ padding: 1.25rem 1.75rem;
+ border-radius: 2px;
+ text-align: center;
+ font-size: 0.95rem;
+ box-shadow: none;
+}
+.modal-backdrop {
+ position: fixed;
+ inset: 0;
+ background: var(--overlay-backdrop);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1.5rem;
+ z-index: 2000000;
+}
+
+.modal {
+ background: var(--surface);
+ border-radius: 4px;
+ box-shadow: 0 18px 42px var(--overlay-shadow);
+ width: min(360px, 100%);
+ padding: 1.6rem;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.modal--panel {
+ width: 80vw;
+ max-width: 960px;
+ max-height: 90vh;
+ padding: 0;
+ border-radius: 12px;
+ overflow: hidden;
+ gap: 0;
+}
+
diff --git a/frontend/src/styles/preview/preview-zoom.css b/frontend/src/styles/preview/preview-zoom.css
new file mode 100644
index 0000000..fc4e861
--- /dev/null
+++ b/frontend/src/styles/preview/preview-zoom.css
@@ -0,0 +1,113 @@
+.preview-zoom-backdrop {
+ position: fixed;
+ inset: 0;
+ background: var(--overlay-backdrop);
+ transition: background 0.25s ease, opacity 0.25s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 2rem;
+ z-index: 2000000;
+ cursor: zoom-out;
+ opacity: 0;
+ pointer-events: none;
+}
+
+.preview-zoom-backdrop--visible {
+ opacity: 1;
+ background: var(--overlay-backdrop);
+ pointer-events: auto;
+}
+
+.preview-zoom__stage {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ max-width: 95vw;
+ max-height: 95vh;
+ z-index: 3000000;
+}
+
+.preview-zoom__image {
+ max-width: 95vw;
+ max-height: 95vh;
+ width: auto;
+ height: auto;
+}
+
+.preview-zoom__scroll {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ max-width: 95vw;
+ max-height: 95vh;
+ box-shadow: 0 32px 120px var(--shadow-deep);
+}
+
+.preview-zoom__scroll:focus {
+ outline: none;
+}
+
+.preview-zoom__scroll--native {
+ overflow: auto;
+ cursor: zoom-out;
+ justify-content: flex-start;
+ align-items: flex-start;
+}
+
+.preview-zoom__nav {
+ position: absolute;
+ bottom: 1em;
+ left: 50%;
+ transform: translateX(-50%);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.9rem;
+ opacity: 0;
+ transition: opacity 0.18s ease;
+ pointer-events: none;
+}
+
+.preview-zoom__stage:hover .preview-zoom__nav,
+.preview-zoom__stage:focus-within .preview-zoom__nav {
+ opacity: 1;
+ pointer-events: auto;
+}
+
+.preview-zoom__nav-button {
+ width: 2.8rem;
+ height: 2.8rem;
+ border: none;
+ border-radius: 999px;
+ background: var(--preview-nav-bg);
+ color: var(--preview-nav-fg);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ z-index: 1;
+ transition: background 0.15s ease, transform 0.15s ease;
+ box-shadow: 0 12px 28px var(--shadow-strong);
+}
+
+.preview-zoom__nav-button:hover {
+ background: var(--preview-nav-bg-hover);
+}
+
+.preview-zoom__nav-button:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 3px;
+}
+
+.preview-zoom__nav-button svg {
+ width: 1.5rem;
+ height: 1.5rem;
+}
+
+.preview-zoom__nav-button[disabled] {
+ opacity: 0.35;
+ cursor: default;
+}
+
diff --git a/frontend/src/styles/settings/settings.css b/frontend/src/styles/settings/settings.css
new file mode 100644
index 0000000..1714e2c
--- /dev/null
+++ b/frontend/src/styles/settings/settings.css
@@ -0,0 +1,337 @@
+.settings-modal__body {
+ display: grid;
+ grid-template-columns: 12rem 1fr;
+ gap: 1.5rem;
+ height: 90vh;
+ padding: 1.5rem;
+}
+
+.settings-modal__sidebar {
+ border-right: 1px solid var(--border-muted, var(--border));
+ padding-right: 1rem;
+}
+
+.settings-modal__sidebar ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 0.4rem;
+}
+
+.settings-modal__sidebar button {
+ width: 100%;
+ display: inline-flex;
+ align-items: center;
+ justify-content: flex-start;
+ gap: 0.4rem;
+ border: none;
+ background: none;
+ color: inherit;
+ font: inherit;
+ padding: 0.45rem 0.6rem;
+ border-radius: 0.35rem;
+ cursor: pointer;
+}
+
+.settings-modal__sidebar button:hover,
+.settings-modal__sidebar button:focus-visible {
+ background: var(--sidebar-hover-bg);
+}
+
+.settings-modal__sidebar button.active {
+ background: var(--accent-soft);
+ font-weight: 600;
+}
+
+.settings-modal__content {
+ overflow-y: auto;
+ padding-bottom: 1rem;
+}
+
+.settings-section h4 {
+ margin-top: 0;
+}
+
+.settings-section {
+ display: flex;
+ flex-direction: column;
+}
+
+.settings-actions {
+ display: flex;
+ justify-content: flex-end;
+ margin-bottom: 1rem;
+}
+
+.settings-actions .secondary {
+ min-width: 7rem;
+}
+
+.settings-form {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+ align-items: flex-end;
+ margin-bottom: 0.75rem;
+}
+
+.settings-form__field {
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+ min-width: 14rem;
+}
+
+.settings-form__field--full {
+ flex: 1 1 100%;
+ min-width: 100%;
+}
+
+fieldset.settings-form__field {
+ border: 1px solid var(--border-muted, var(--border));
+ border-radius: 0.5rem;
+ padding: 0.75rem 0.9rem 0.85rem;
+ background: var(--surface-soft);
+}
+
+fieldset.settings-form__field legend {
+ padding: 0 0.35rem;
+ font-weight: 600;
+ color: var(--muted-strong, inherit);
+}
+
+.settings-form__field input[type='text'],
+.settings-form__field input[type='datetime-local'] {
+ padding: 0.45rem 0.6rem;
+ border: 1px solid var(--border);
+ border-radius: 0.35rem;
+ background: var(--surface-soft);
+ color: inherit;
+}
+
+.settings-form__choices {
+ display: flex;
+ flex-direction: column;
+ gap: 0.4rem;
+ align-items: flex-start;
+}
+
+.settings-capability-picker {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.capability-dropdown {
+ position: relative;
+ width: 100%;
+}
+
+.capability-dropdown__trigger {
+ width: 100%;
+ display: inline-flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.5rem;
+ padding: 0.5rem 0.75rem;
+ border: 1px solid var(--border);
+ border-radius: 0.45rem;
+ background: var(--surface-soft);
+ color: inherit;
+ font: inherit;
+ text-align: left;
+ cursor: pointer;
+ transition: border-color 0.15s ease, background 0.15s ease;
+}
+
+.capability-dropdown__trigger:hover:not(:disabled),
+.capability-dropdown__trigger:focus-visible {
+ border-color: var(--accent);
+ background: var(--surface);
+ outline: none;
+}
+
+.capability-dropdown__trigger:disabled {
+ cursor: not-allowed;
+ color: var(--muted);
+ background: var(--surface-muted, var(--surface-soft));
+}
+
+.capability-dropdown__chevron {
+ flex-shrink: 0;
+ opacity: 0.8;
+}
+
+.capability-dropdown__menu {
+ margin-top: 0.35rem;
+ max-height: 18rem;
+ overflow-y: auto;
+ padding: 0.25rem 0;
+ width: 100%;
+}
+
+.capability-dropdown__option {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.45rem 0.75rem;
+}
+
+.capability-dropdown__option-icon {
+ width: 1.1rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--accent);
+}
+
+.capability-dropdown__option:not(.is-selected) .capability-dropdown__option-icon {
+ color: transparent;
+}
+
+.capability-dropdown__option-label {
+ flex: 1;
+ text-align: left;
+}
+
+.capability-dropdown__empty {
+ padding: 0.6rem 0.8rem;
+ color: var(--muted);
+}
+
+.settings-capability-picker__chips {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ margin-top: 0.25rem;
+}
+
+.settings-capability-picker__chips--inline {
+ margin-top: 0.35rem;
+}
+
+.settings-capabilities-summary {
+ display: inline-block;
+ margin-top: 0.4rem;
+ color: var(--muted);
+ font-size: 0.9rem;
+}
+
+.settings-capability-picker__placeholder {
+ color: var(--muted);
+ font-size: 0.9rem;
+}
+
+.settings-capability-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.35rem;
+ margin-top: 0.35rem;
+}
+
+.settings-capability-list--compact {
+ margin-top: 0.2rem;
+ gap: 0.25rem;
+}
+
+.settings-capability-list__item {
+ display: inline-flex;
+ align-items: center;
+}
+
+.settings-choice {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.4rem;
+ font-size: 0.9rem;
+ font-weight: 500;
+ color: inherit;
+}
+
+.settings-choice input[type='checkbox'] {
+ width: 1.05rem;
+ height: 1.05rem;
+ cursor: pointer;
+ accent-color: var(--accent);
+}
+
+.settings-choice input[type='checkbox']:disabled + span {
+ color: var(--muted);
+}
+
+.settings-form__actions {
+ display: flex;
+ gap: 0.5rem;
+}
+
+.settings-form__error {
+ color: var(--danger);
+ font-size: 0.85rem;
+ margin: 0 0 0.75rem;
+}
+
+.settings-empty {
+ margin: 1rem 0;
+ color: var(--muted);
+}
+
+.settings-table {
+ width: 100%;
+ border-collapse: collapse;
+ margin-top: 0.5rem;
+}
+
+.settings-table th,
+.settings-table td {
+ padding: 0.55rem 0.75rem;
+ border-bottom: 1px solid var(--border-muted, var(--border));
+ text-align: left;
+ font-size: 0.9rem;
+}
+
+.settings-table tr:last-child td {
+ border-bottom: none;
+}
+
+.settings-table__actions {
+ white-space: nowrap;
+}
+
+.settings-status {
+ color: var(--muted);
+ font-size: 0.9rem;
+}
+
+.settings-table tr.is-revoked {
+ opacity: 0.65;
+}
+
+.settings-notice {
+ border: 1px solid var(--accent);
+ background: var(--accent-soft);
+ padding: 0.9rem 1rem;
+ border-radius: 0.5rem;
+ margin-bottom: 1rem;
+}
+
+.token-display {
+ background: var(--surface-ink-soft);
+ padding: 0.5rem 0.65rem;
+ border-radius: 0.35rem;
+ font-family: var(--font-mono);
+ font-size: 0.95rem;
+ overflow-x: auto;
+}
+
+.settings-notice__actions {
+ display: flex;
+ gap: 0.5rem;
+ margin-top: 0.6rem;
+}
+
+.settings-notice__actions button {
+ flex: 0 0 auto;
+}
+
diff --git a/frontend/src/styles/sidebar/sidebar.css b/frontend/src/styles/sidebar/sidebar.css
new file mode 100644
index 0000000..2adf0bd
--- /dev/null
+++ b/frontend/src/styles/sidebar/sidebar.css
@@ -0,0 +1,655 @@
+.folder-tree {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.folder-node {
+ margin: 0;
+}
+
+.folder-row {
+ display: flex;
+ align-items: center;
+ gap: 0.22rem;
+ padding: 0.32rem 0.48rem;
+ border-radius: 8px;
+ cursor: pointer;
+ color: inherit;
+ transition: background 0.12s ease, color 0.12s ease;
+ position: relative;
+ overflow: hidden;
+ user-select: none;
+ -webkit-user-select: none;
+}
+
+.folder-row .name-wrap {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ flex: 1;
+ min-width: 0;
+}
+
+.folder-row .name-wrap .name__label {
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.folder-row .folder-icon-image {
+ flex-shrink: 0;
+}
+
+.folder-row:hover {
+ background: var(--sidebar-hover-bg);
+ color: var(--fg);
+}
+
+.folder-row.active {
+ background: var(--sidebar-active-bg);
+ color: var(--fg);
+}
+
+.folder-row .toggle {
+ width: 1.05rem;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ user-select: none;
+ flex-shrink: 0;
+ transition: transform 0.18s ease;
+}
+
+.folder-row .toggle.invisible {
+ visibility: hidden;
+}
+
+.toggle-icon {
+ width: 0.9rem;
+ height: 0.9rem;
+ transition: transform 0.18s ease;
+}
+
+.folder-row .toggle.expanded .toggle-icon {
+ transform: rotate(90deg);
+}
+
+.folder-row__actions {
+ position: absolute;
+ right: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ display: flex;
+ gap: 0;
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 0.12s ease;
+ background-color: var(--surface-overlay);
+ border-radius: 8px;
+ padding: 0 0.18rem;
+}
+
+.folder-row__actions .icon-button {
+ margin: 0;
+}
+
+.folder-row:hover .folder-row__actions,
+.folder-row:focus-within .folder-row__actions {
+ opacity: 1;
+ pointer-events: auto;
+}
+
+.folder-children {
+ list-style: none;
+ margin: 0 0 0 0.45rem;
+ padding: 0;
+}
+
+
+
+.sidebar-section {
+ margin-top: 1rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.4rem;
+}
+
+.sidebar {
+ gap: 0;
+ overflow: hidden;
+ color: var(--sidebar-fg);
+ display: flex;
+ flex-direction: column;
+ width: 20em;
+ max-width: 20em;
+ flex: 0 0 20em;
+ background: var(--bg);
+}
+
+.sidebar__body {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ padding: 0.5rem 1rem;
+ gap: 0.75rem;
+ overflow-y: auto;
+ min-height: 0;
+}
+
+.sidebar__footer {
+ margin-top: auto;
+ padding: 0.75rem 1rem 1rem;
+ border-top: 1px solid var(--border);
+ background: var(--surface-subtle);
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+}
+
+.sidebar__title {
+ margin: 0;
+ font-size: 1rem;
+ font-weight: 600;
+ color: var(--fg);
+ padding-left: 0.25rem;
+}
+
+.sidebar__tenant {
+ color: var(--muted);
+ font-weight: 500;
+}
+
+.sidebar__header {
+ position: relative;
+}
+
+.sidebar__title-button {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ border: none;
+ background: none;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ padding: 0;
+}
+
+.sidebar__title-button:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+
+.sidebar__collapse-button {
+ color: color-mix(in oklch, var(--muted) 85%, transparent);
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 0.18s ease;
+}
+
+.sidebar:hover .sidebar__collapse-button,
+.sidebar__collapse-button:focus-visible {
+ opacity: 0.85;
+ pointer-events: auto;
+}
+
+.sidebar__collapse-button:hover:not([disabled]),
+.sidebar__collapse-button:focus-visible {
+ color: var(--muted);
+}
+
+
+.sidebar__title-button .sidebar__title-chevron {
+ opacity: 0;
+ transform: translateY(-1px);
+ transition:
+ opacity 0.12s ease,
+ transform 0.2s ease;
+}
+
+.sidebar__title-button:hover .sidebar__title-chevron,
+.sidebar__title-button:focus-visible .sidebar__title-chevron,
+.sidebar__title-chevron.is-open {
+ opacity: 1;
+}
+
+.sidebar__title-chevron.is-open {
+ transform: rotate(180deg) translateY(-1px);
+}
+
+.menu {
+ position: absolute;
+ top: 100%;
+ left: 1em;
+ right: auto;
+ background: var(--surface);
+ border: 1px solid var(--border-muted, var(--border));
+ border-radius: 0.5rem;
+ box-shadow: 0 12px 28px var(--shadow-strong);
+ min-width: 220px;
+ z-index: 20;
+ overflow: hidden;
+}
+
+.menu__list {
+ max-height: 260px;
+ overflow-y: auto;
+ padding: 0.35rem 0.5rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+.menu__heading {
+ padding: 0.5rem 1rem 0.25rem;
+ font-size: 0.82rem;
+ font-weight: 600;
+ color: var(--muted);
+}
+
+.menu__heading--with-actions {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.75rem;
+}
+
+.menu button.menu__item,
+.menu .menu__item {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ gap: 0.5rem;
+ width: 100%;
+ padding: 0.45rem 0.75rem;
+ border: none;
+ border-radius: 0.4rem;
+ background: none;
+ color: var(--sidebar-fg);
+ font: inherit;
+ text-align: left;
+ cursor: pointer;
+ transition: background 0.15s ease, color 0.15s ease;
+}
+
+.menu button.menu__item:hover,
+.menu button.menu__item:focus-visible,
+.menu .menu__item:hover,
+.menu .menu__item:focus-visible {
+ background: var(--sidebar-hover-bg);
+ color: var(--fg);
+}
+
+.menu button.menu__item.active,
+.menu .menu__item.active {
+ font-weight: 600;
+ color: var(--accent);
+ background: var(--accent-soft);
+}
+
+.menu button.menu__item:focus-visible,
+.menu .menu__item:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: -2px;
+}
+
+.menu__label {
+ flex: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.menu__check-slot {
+ width: 1rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--accent);
+}
+
+.menu__active-indicator {
+ font-size: 0.75rem;
+ color: var(--muted);
+}
+
+.menu__empty {
+ display: block;
+ padding: 0.6rem 0.75rem;
+ color: var(--muted);
+ font-size: 0.85rem;
+}
+
+.menu__footer {
+ border-top: 1px solid var(--border-muted, var(--border));
+ padding: 0.35rem 0.5rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+.menu--simple .menu__footer {
+ border-top: none;
+}
+
+
+.menu__section {
+ border-top: 1px solid var(--border);
+ padding: 0.35rem 0;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.menu__heading-actions {
+ display: inline-flex;
+ gap: 0.35rem;
+}
+
+.menu__slider {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 0.8rem;
+ color: var(--muted);
+ padding: 0 1rem 0.25rem;
+}
+
+.menu__section > .menu__slider {
+ margin-top: -0.25rem;
+}
+
+.menu__slider input[type='range'] {
+ flex: 1;
+ accent-color: var(--accent);
+}
+
+.menu__slider-label {
+ min-width: 2.5rem;
+}
+
+.menu__slider-value {
+ font-variant-numeric: tabular-nums;
+ color: var(--fg);
+ min-width: 3rem;
+ text-align: right;
+}
+
+.menu__button {
+ width: 100%;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.4rem;
+ border: none;
+ background: none;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ padding: 0.4rem 0.5rem;
+ border-radius: 0.35rem;
+ transition: background 0.15s ease, color 0.15s ease;
+}
+
+.menu__button:hover,
+.menu__button:focus-visible {
+ background: var(--sidebar-hover-bg);
+}
+
+.menu__button--danger {
+ color: var(--danger);
+}
+
+.menu__button--danger:hover,
+.menu__button--danger:focus-visible {
+ background: var(--surface-danger-subtle);
+ color: var(--danger);
+}
+
+.sidebar__search {
+ display: flex;
+ align-items: center;
+ gap: 0.4rem;
+ margin: 0 0 0.75rem;
+}
+
+.sidebar__search input[type='search'] {
+ flex: 1;
+ padding: 0.4rem 0.6rem;
+ border-radius: 4px;
+ border: 1px solid var(--border);
+ background: var(--surface);
+ color: var(--fg);
+}
+
+.sidebar__search button {
+ padding: 0.35rem 0.75rem;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ background: transparent;
+ color: var(--accent);
+ font-size: 0.85rem;
+ cursor: pointer;
+}
+
+.sidebar__search button:hover:not([disabled]) {
+ background: var(--sidebar-hover-bg);
+}
+
+.sidebar__footer .sidebar-section {
+ margin-top: 0;
+}
+
+.sidebar__footer .sidebar-section__actions {
+ gap: 0.4rem;
+}
+
+.sidebar-section:first-of-type,
+.sidebar-section--folders {
+ margin-top: 0;
+}
+
+.sidebar-section--folders {
+ display: flex;
+ flex-direction: column;
+}
+
+.sidebar-section__header {
+ display: flex;
+ align-items: center;
+ font-size: 0.75rem;
+ justify-content: space-between;
+ color: var(--muted);
+}
+
+.sidebar-section__title {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.4rem;
+ width: 100%;
+ background: transparent;
+ border: none;
+ border-radius: 6px;
+ padding: 0.25rem 0.35rem;
+ margin: -0.25rem -0.35rem;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ text-align: left;
+}
+
+.sidebar-section__title:hover:not([disabled]),
+.sidebar-section__title:focus-visible {
+ background: var(--sidebar-hover-bg);
+ color: var(--fg);
+}
+
+.sidebar-section__title:hover:not([disabled]) h3,
+.sidebar-section__title:focus-visible h3 {
+ color: var(--fg);
+}
+
+.sidebar-section__header h3 {
+ margin: 0;
+ font-weight: 600;
+}
+
+.sidebar-section__actions {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+}
+
+.sidebar-section__actions .meta {
+ font-size: 0.75rem;
+ color: var(--muted);
+}
+
+.sidebar-slider {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 0.8rem;
+ color: var(--muted);
+}
+
+.sidebar-slider input[type='range'] {
+ flex: 1;
+ accent-color: var(--accent);
+}
+
+.sidebar-slider__value {
+ min-width: 3rem;
+ text-align: right;
+ color: var(--fg);
+ font-variant-numeric: tabular-nums;
+}
+
+.sidebar-item {
+ background: transparent;
+ padding: 0.16rem 0.26rem;
+ text-align: left;
+ color: var(--sidebar-fg);
+ border-radius: 2px;
+ cursor: pointer;
+ transition: background 0.12s ease, color 0.12s ease;
+ display: flex;
+ align-items: center;
+ gap: 0.38rem;
+ width: 100%;
+}
+
+.sidebar-item:hover {
+ background: var(--sidebar-hover-bg);
+ color: var(--fg);
+}
+
+.sidebar-item.active {
+ background: var(--sidebar-active-bg);
+ color: var(--fg);
+ font-weight: 600;
+}
+
+.sidebar-item:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+
+.sidebar-item__icon {
+ width: 1rem;
+ height: 1rem;
+ flex-shrink: 0;
+ color: var(--accent);
+}
+
+.sidebar-item.active .sidebar-item__icon {
+ color: currentColor;
+}
+
+.sidebar-tag-cloud {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.35rem;
+ padding: 0.25rem 0 0.1rem;
+}
+
+.sidebar-tag-pill {
+ border-radius: 999px;
+ padding: 0.25rem 0.6rem;
+ font-size: 0.75rem;
+ font-weight: 600;
+ line-height: 1;
+ cursor: pointer;
+ background: var(--surface-soft);
+ color: var(--fg);
+ transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease,
+ opacity 0.12s ease;
+ box-shadow: 0 1px 2px var(--shadow-faint);
+}
+
+.sidebar-tag-pill:hover {
+ transform: none;
+ box-shadow: 0 2px 6px var(--shadow-pop);
+ border-color: var(--accent-soft);
+}
+
+.sidebar-tag-pill:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+
+.sidebar-tag-pill.active {
+ border-color: var(--sidebar-active-pill-border);
+ box-shadow: 0 0 0 1.5px var(--sidebar-active-pill-border);
+}
+
+.sidebar-tag-pill--untagged {
+ border: 1px dashed var(--border);
+ background: var(--surface-subtle);
+ color: var(--muted);
+}
+
+.sidebar-tag-pill--untagged.active {
+ color: var(--fg);
+}
+
+.sidebar-tag-cloud--has-active .sidebar-tag-pill:not(.active) {
+ opacity: 0.45;
+}
+
+.sidebar-correspondent-list {
+ list-style: none;
+ margin: 0.4rem 0 0;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+.sidebar-correspondent-item {
+ display: block;
+ background: transparent;
+ border-radius: 4px;
+ padding: 0.35rem 0.5rem;
+ text-align: left;
+ color: var(--sidebar-fg);
+ cursor: pointer;
+ font-size: 0.85rem;
+ transition: background 0.15s ease, box-shadow 0.15s ease;
+}
+
+.sidebar-correspondent-item:hover {
+ background: var(--sidebar-hover-bg);
+ color: var(--fg);
+}
+
+.sidebar-correspondent-item:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+
+.sidebar-correspondent-item.active {
+ background: var(--sidebar-active-bg);
+ color: var(--fg);
+}
diff --git a/frontend/src/styles/uploads/overlay.css b/frontend/src/styles/uploads/overlay.css
new file mode 100644
index 0000000..640432c
--- /dev/null
+++ b/frontend/src/styles/uploads/overlay.css
@@ -0,0 +1,176 @@
+.upload-queue-overlay {
+ position: fixed;
+ bottom: 24px;
+ right: 24px;
+ width: min(360px, calc(100vw - 32px));
+ background: var(--surface);
+ border: 1px solid var(--border);
+ border-radius: 14px;
+ box-shadow: 0 18px 45px var(--shadow-pop);
+ z-index: 400;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ padding: 0.9rem;
+ color: var(--fg);
+}
+
+
+.upload-queue-overlay__header {
+ padding: 0;
+ border: none;
+}
+
+.upload-queue-overlay__title {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.4rem;
+ font-weight: 600;
+ font-size: 0.95rem;
+ min-width: 0;
+}
+
+.upload-queue-overlay__summary {
+ font-size: 0.8rem;
+ color: var(--muted);
+ white-space: nowrap;
+}
+
+.upload-queue-overlay__controls {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+}
+
+.upload-queue-overlay__list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ max-height: 260px;
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+ gap: 0.6rem;
+}
+
+.upload-queue-overlay--collapsed .upload-queue-overlay__list {
+ display: none;
+}
+
+.upload-queue-overlay__item {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ gap: 0.6rem;
+ padding: 0.15rem 0;
+}
+
+.upload-queue-overlay__status {
+ width: 28px;
+ height: 28px;
+ border-radius: 999px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--surface-subtle);
+ color: var(--muted);
+}
+
+.upload-queue-overlay__status--muted {
+ background: var(--surface-subtle);
+ color: var(--muted);
+}
+
+.upload-queue-overlay__status--accent {
+ background: var(--accent-soft);
+ color: var(--accent);
+}
+
+.upload-queue-overlay__status--success {
+ background: var(--success-subtle);
+ color: var(--success);
+}
+
+.upload-queue-overlay__status--info {
+ background: var(--selection-soft);
+ color: var(--accent);
+}
+
+.upload-queue-overlay__status--danger {
+ background: var(--danger-subtle);
+ color: var(--danger);
+}
+
+.upload-queue-overlay__details {
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 0.15rem;
+}
+
+.upload-queue-overlay__name {
+ font-size: 0.88rem;
+ font-weight: 500;
+ color: var(--fg);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.upload-queue-overlay__name-link {
+ font-size: 0.88rem;
+ font-weight: 500;
+ color: inherit;
+ background: none;
+ border: none;
+ padding: 0;
+ text-align: left;
+ cursor: pointer;
+ text-decoration: none;
+}
+
+.upload-queue-overlay__name-link:hover {
+ color: var(--accent);
+ text-decoration: underline;
+}
+
+.upload-queue-overlay__meta-line {
+ font-size: 0.72rem;
+ color: var(--muted);
+ display: flex;
+ align-items: center;
+ gap: 0.35rem;
+ flex-wrap: wrap;
+}
+
+.upload-queue-overlay__meta-id,
+.upload-queue-overlay__meta-secondary {
+ color: inherit;
+}
+
+.upload-queue-overlay__meta-link {
+ border: none;
+ background: none;
+ font: inherit;
+ cursor: pointer;
+ padding: 0;
+ color: inherit;
+ text-decoration: none;
+}
+
+.upload-queue-overlay__meta-link:hover {
+ color: var(--accent);
+ text-decoration: underline;
+}
+
+.upload-queue-overlay__meta-link:disabled {
+ cursor: default;
+ opacity: 0.5;
+}
+
+.upload-queue-overlay__meta-error {
+ color: var(--danger);
+}
+
+.upload-queue-overlay__item--error .upload-queue-overlay__name {
+ color: var(--danger);
+}
diff --git a/frontend/src/styles/workspace/workspace-cards.css b/frontend/src/styles/workspace/workspace-cards.css
new file mode 100644
index 0000000..4079627
--- /dev/null
+++ b/frontend/src/styles/workspace/workspace-cards.css
@@ -0,0 +1,122 @@
+/* Workspace cards, thumbnails, and hover controls */
+.desk-item__shadow {
+ display: none;
+}
+
+.desk-item__card {
+ position: relative;
+ border-radius: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ box-shadow: 0 12px 32px var(--shadow-medium);
+ overflow: hidden;
+}
+
+.desk-item__card img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ display: block;
+ pointer-events: none;
+ user-select: none;
+ -webkit-user-drag: none;
+}
+
+.desk-item__card--empty {
+ box-shadow: 0 12px 32px var(--shadow-medium);
+ background:
+ radial-gradient(circle at 42% 38%, color-mix(in oklch, var(--surface-subtle) 75%, var(--selection) 25%), color-mix(in oklch, var(--surface-subtle) 85%, var(--selection) 15%) 70%),
+ linear-gradient(135deg, color-mix(in oklch, var(--surface-subtle) 88%, var(--selection-soft) 12%) 0%, color-mix(in oklch, var(--surface-subtle) 65%, var(--shadow-faint) 35%) 100%);
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+}
+
+.desk-item__placeholder {
+ font-size: 0.95rem;
+ font-weight: 500;
+ letter-spacing: normal;
+ color: var(--muted);
+}
+
+.desk-item__empty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ gap: 0.5rem;
+ padding: 1rem;
+ text-align: center;
+}
+
+.desk-item__title {
+ font-size: 0.95rem;
+ font-weight: 500;
+ color: var(--fg);
+ max-width: 90%;
+ overflow: hidden;
+ overflow-wrap: anywhere;
+ word-break: break-word;
+ white-space: normal;
+}
+
+.desk-card__nav {
+ position: absolute;
+ bottom: 1.8rem;
+ left: 50%;
+ transform: translateX(-50%);
+ transform-origin: center;
+ display: flex;
+ gap: 1.5rem;
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 0.2s ease;
+}
+
+.desk-item__card:hover .desk-card__nav {
+ opacity: 1;
+ pointer-events: auto;
+}
+
+.desk-card__nav-button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 2.4em;
+ height: 2.4em;
+ padding: 0.25em;
+ border-radius: 50%;
+ border: none;
+ background: var(--preview-nav-bg);
+ color: var(--preview-nav-fg);
+ cursor: pointer;
+ transition: background 0.15s ease, opacity 0.15s ease;
+}
+
+.desk-card__nav-button:hover:not([disabled]) {
+ background: var(--preview-nav-bg-hover);
+}
+
+.desk-card__nav-button:disabled {
+ opacity: 0.4;
+ cursor: default;
+}
+
+.desk-card__nav-button:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+
+.desk-card__nav-button svg {
+ width: 100%;
+ height: 100%;
+}
diff --git a/frontend/src/styles/workspace/workspace-items.css b/frontend/src/styles/workspace/workspace-items.css
new file mode 100644
index 0000000..962d1d8
--- /dev/null
+++ b/frontend/src/styles/workspace/workspace-items.css
@@ -0,0 +1,135 @@
+/* Workspace items, states, and inline badges */
+.desk-item {
+ position: absolute;
+ display: block;
+ width: auto;
+ cursor: grab;
+ touch-action: none;
+ transform-origin: center center;
+ transition: box-shadow 0.16s ease;
+ outline: none;
+ will-change: transform;
+ -webkit-user-select: none;
+ user-select: none;
+ -webkit-touch-callout: none;
+}
+
+.desk-item__body {
+ flex-grow: 1;
+ width: 100%;
+ height: 100%;
+}
+
+.desk-item:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 4px;
+}
+
+.desk-item.is-dragging {
+ cursor: grabbing;
+ transition: none;
+}
+
+.desk-item.is-tag-target .desk-item__card {
+ outline: 0.35rem dashed var(--accent);
+ outline-offset: 0.35rem;
+}
+
+.desk-item.is-tag-pending .desk-item__card {
+ outline: 0.25rem solid var(--accent-outline);
+ outline-offset: 0.25rem;
+}
+
+.desk-item.is-filtered-out {
+ opacity: 0.12;
+ pointer-events: none;
+ filter: blur(15px) grayscale(100%);
+ transition: opacity 0.6s ease, filter 0.28s ease;
+ z-index: 0;
+}
+
+.desk-item.is-selected {
+ z-index: 5;
+}
+
+.desk-item.is-selected .desk-item__card {
+ box-shadow:
+ 0 0 0 0.18rem color-mix(in oklch, var(--accent) 45%, transparent),
+ 0 0 0.35rem 0 color-mix(in oklch, var(--accent) 28%, transparent),
+ 0 12px 28px -14px color-mix(in oklch, var(--accent) 20%, transparent),
+ 0 10px 24px var(--shadow-medium);
+}
+
+.desk-item.is-selected .desk-item__title {
+ color: var(--accent);
+}
+
+.desk-item__tags {
+ position: absolute;
+ top: 0;
+ right: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+ align-items: flex-end;
+ transform-origin: top right;
+ transform: translate(-0.5em, 0.5em);
+ transition: transform 0.28s ease;
+}
+
+.desk-item__correspondents {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+ align-items: flex-start;
+ transform-origin: bottom left;
+ transform: translate(0.5em, -0.5em);
+ pointer-events: none;
+}
+
+.desk-correspondent-chip {
+ pointer-events: none;
+ font-size: 0.82rem;
+ padding: 0.18rem 0.55rem;
+ max-width: min(16rem, 80%);
+ display: inline-flex;
+ align-items: center;
+ overflow: hidden;
+ background: color-mix(in oklch, var(--surface-subtle) 90%, transparent);
+ color: var(--muted);
+}
+
+.desk-correspondent-chip__label {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.tag-chip--draggable {
+ user-select: none;
+ pointer-events: auto;
+ cursor: grab;
+ transition: transform 0.16s ease, opacity 0.2s ease, box-shadow 0.2s ease;
+}
+
+.tag-chip--draggable:active {
+ cursor: grabbing;
+}
+
+.tag-chip--draggable.is-drag-hidden {
+ opacity: 0.4;
+}
+
+.desk-item__tags .tag-chip {
+ font-size: 0.85rem;
+ padding: 0.18rem 0.55rem;
+ gap: 0.3rem;
+}
+
+.desk-item__tags .tag-chip--tear-pending {
+ opacity: 0.35;
+}
diff --git a/frontend/src/styles/workspace/workspace-layout.css b/frontend/src/styles/workspace/workspace-layout.css
new file mode 100644
index 0000000..223e693
--- /dev/null
+++ b/frontend/src/styles/workspace/workspace-layout.css
@@ -0,0 +1,45 @@
+/* Workspace layout & canvas scaffolding */
+.desk-main {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+}
+
+.desk-shell {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ grid-column: 2 / -1;
+ min-height: 0;
+ position: relative;
+}
+
+.desk-canvas {
+ flex: 1;
+ position: relative;
+ overflow: hidden;
+ margin: 0;
+ outline: none;
+}
+
+.desk-canvas:focus,
+.desk-canvas:focus-visible {
+ outline: none;
+}
+
+.desk-empty {
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 3rem;
+ text-align: center;
+ color: var(--muted);
+ font-size: 0.95rem;
+}
+
+body.desk-cursor-remove,
+body.desk-cursor-remove * {
+ cursor: not-allowed !important;
+}
diff --git a/frontend/src/ui/PanelHeader.jsx b/frontend/src/ui/PanelHeader.jsx
index e7bad5f..5aa1e79 100644
--- a/frontend/src/ui/PanelHeader.jsx
+++ b/frontend/src/ui/PanelHeader.jsx
@@ -1,6 +1,5 @@
import React from 'react';
-
-const composeClassName = (base, extra) => (extra ? `${base} ${extra}` : base);
+import composeClassName from './classNames';
const PanelHeader = ({
className = '',
diff --git a/frontend/src/ui/classNames.js b/frontend/src/ui/classNames.js
new file mode 100644
index 0000000..a9bd605
--- /dev/null
+++ b/frontend/src/ui/classNames.js
@@ -0,0 +1,3 @@
+export const composeClassName = (base, extra) => (extra ? `${base} ${extra}` : base);
+
+export default composeClassName;
diff --git a/frontend/src/ui/icons.js b/frontend/src/ui/icons.js
index 6b4cb25..d8a8dfc 100644
--- a/frontend/src/ui/icons.js
+++ b/frontend/src/ui/icons.js
@@ -11,6 +11,7 @@ import {
IconArrowRight,
IconArrowUp,
IconAnalyze,
+ IconUpload,
IconWindowMaximize,
IconTextScan2,
IconFolderPlus,
@@ -32,6 +33,9 @@ import {
IconLayoutSidebarLeftCollapse,
IconLayoutSidebarLeftExpand,
IconLayoutSidebarRightCollapse,
+ IconLayoutBottombarCollapse,
+ IconLayoutBottombarExpand,
+ IconClearAll,
IconInfoCircle,
IconCircleDashedCheck,
IconFile,
@@ -39,10 +43,10 @@ import {
IconSortAscendingLetters,
IconSortDescendingLetters,
IconFileInfo,
+ IconAlertTriangle,
} from '@tabler/icons-react';
import FolderSvg from '../assets/folder.svg';
-
-const composeClassName = (base, extra) => (extra ? `${base} ${extra}` : base);
+import composeClassName from './classNames';
export const ChevronIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
);
+export const UploadIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
+
+);
+
export const ArrowLeftIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
);
+export const BottombarCollapseIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
+
+);
+
+export const BottombarExpandIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
+
+);
+
+export const ClearAllIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
+
+);
+
export const FolderPlusIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
);
+export const WarningIcon = ({ className, size = '1em', stroke = 1.8, ...rest }) => (
+
+);
+
export const WindowMaximizeIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
Math.min(Math.max(value, min), max);
-
const resolveViewportWidth = () => {
if (typeof window !== 'undefined' && typeof window.innerWidth === 'number') {
return window.innerWidth;
@@ -198,12 +197,22 @@ const useFloatingMenu = ({
return undefined;
}
+ let ignoreFocusEvents = true;
+ const rafId = typeof window !== 'undefined'
+ ? window.requestAnimationFrame(() => {
+ ignoreFocusEvents = false;
+ })
+ : null;
+
if (!anchorRef?.current) {
close();
return undefined;
}
const handlePointer = (event) => {
+ if (event.type === 'focusin' && ignoreFocusEvents) {
+ return;
+ }
const menu = menuRef.current;
const anchor = anchorRef?.current;
if ((anchor && anchor.contains(event.target)) || (menu && menu.contains(event.target))) {
@@ -223,6 +232,9 @@ const useFloatingMenu = ({
document.addEventListener('focusin', handlePointer);
document.addEventListener('keydown', handleKeyDown);
return () => {
+ if (rafId != null) {
+ window.cancelAnimationFrame(rafId);
+ }
document.removeEventListener('mousedown', handlePointer);
document.removeEventListener('touchstart', handlePointer);
document.removeEventListener('focusin', handlePointer);
diff --git a/frontend/src/utils/date.js b/frontend/src/utils/date.js
new file mode 100644
index 0000000..955158a
--- /dev/null
+++ b/frontend/src/utils/date.js
@@ -0,0 +1,57 @@
+const ensureDate = (value) => {
+ if (!value) {
+ return null;
+ }
+ const date = value instanceof Date ? new Date(value.getTime()) : new Date(value);
+ return Number.isNaN(date.getTime()) ? null : date;
+};
+
+export const formatDate = (value, { fallback = '—', locale, options } = {}) => {
+ const date = ensureDate(value);
+ if (!date) {
+ return fallback;
+ }
+ return date.toLocaleDateString(locale, options);
+};
+
+export const formatDateTime = (value, { fallback = '—', locale, options } = {}) => {
+ const date = ensureDate(value);
+ if (!date) {
+ return fallback;
+ }
+ return date.toLocaleString(locale, options);
+};
+
+export const toDateInputValue = (value) => {
+ const date = ensureDate(value);
+ if (!date) {
+ return '';
+ }
+ const timezoneOffset = date.getTimezoneOffset();
+ const localDate = new Date(date.getTime() - timezoneOffset * 60000);
+ return localDate.toISOString().slice(0, 10);
+};
+
+export const toIssuedTimestamp = (dateString, fallback) => {
+ if (!dateString) {
+ return null;
+ }
+ const base = ensureDate(fallback) || new Date();
+ const [year, month, day] = dateString.split('-').map((part) => Number.parseInt(part, 10));
+ if (!year || !month || !day) {
+ return null;
+ }
+ const candidate = new Date(base);
+ candidate.setUTCFullYear(year, month - 1, day);
+ return Number.isNaN(candidate.getTime()) ? null : candidate.toISOString();
+};
+
+export const parseDateValue = (value) => ensureDate(value);
+
+export default {
+ formatDate,
+ formatDateTime,
+ toDateInputValue,
+ toIssuedTimestamp,
+ parseDateValue,
+};
diff --git a/frontend/src/utils/math.js b/frontend/src/utils/math.js
new file mode 100644
index 0000000..f7ab48f
--- /dev/null
+++ b/frontend/src/utils/math.js
@@ -0,0 +1,13 @@
+export const clamp = (value, min, max) => {
+ if (value < min) {
+ return min;
+ }
+ if (value > max) {
+ return max;
+ }
+ return value;
+};
+
+export default {
+ clamp,
+};
|