@gallop.software/studio 0.1.20 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{StudioUI-XC5QYL7B.js → StudioUI-QMBOCTJD.js} +473 -286
- package/dist/StudioUI-QMBOCTJD.js.map +1 -0
- package/dist/{StudioUI-T4M7A6BP.mjs → StudioUI-WRFD73YR.mjs} +473 -286
- package/dist/StudioUI-WRFD73YR.mjs.map +1 -0
- package/dist/index.js +43 -23
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +43 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/StudioUI-T4M7A6BP.mjs.map +0 -1
- package/dist/StudioUI-XC5QYL7B.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/chrisb/Sites/studio/dist/StudioUI-QMBOCTJD.js","../src/components/StudioUI.tsx","../src/components/StudioContext.tsx","../src/components/StudioToolbar.tsx","../src/components/StudioModal.tsx","../src/components/StudioBreadcrumb.tsx","../src/components/StudioFileGrid.tsx","../src/components/StudioFileList.tsx","../src/components/StudioPreview.tsx","../src/components/StudioSettings.tsx"],"names":["colors","keyframes","styles","css"],"mappings":"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","file":"/Users/chrisb/Sites/studio/dist/StudioUI-QMBOCTJD.js","sourcesContent":[null,"/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useEffect, useCallback, useState } from 'react'\nimport { css } from '@emotion/react'\nimport { StudioContext } from './StudioContext'\nimport { StudioToolbar } from './StudioToolbar'\nimport { StudioBreadcrumb } from './StudioBreadcrumb'\nimport { StudioFileGrid } from './StudioFileGrid'\nimport { StudioFileList } from './StudioFileList'\nimport { StudioPreview } from './StudioPreview'\nimport { StudioSettings } from './StudioSettings'\nimport type { FileItem, StudioMeta } from '../types'\n\ninterface StudioUIProps {\n onClose: () => void\n}\n\n// Stripe-inspired design tokens\nconst colors = {\n primary: '#635bff',\n primaryHover: '#5851e5',\n background: '#f6f9fc',\n surface: '#ffffff',\n surfaceHover: '#f6f9fc',\n border: '#e3e8ee',\n borderLight: '#eef1f6',\n text: '#1a1f36',\n textSecondary: '#697386',\n textMuted: '#8792a2',\n}\n\nconst styles = {\n container: css`\n display: flex;\n flex-direction: column;\n height: 100%;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif;\n background: ${colors.background};\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n `,\n header: css`\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 24px;\n background: ${colors.surface};\n border-bottom: 1px solid ${colors.border};\n `,\n title: css`\n font-size: 17px;\n font-weight: 600;\n color: ${colors.text};\n margin: 0;\n letter-spacing: -0.02em;\n `,\n headerActions: css`\n display: flex;\n align-items: center;\n gap: 8px;\n `,\n closeBtn: css`\n padding: 8px;\n background: none;\n border: none;\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.15s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n \n &:hover {\n background-color: ${colors.surfaceHover};\n }\n `,\n closeIcon: css`\n width: 18px;\n height: 18px;\n color: ${colors.textSecondary};\n `,\n content: css`\n flex: 1;\n display: flex;\n overflow: hidden;\n `,\n fileBrowser: css`\n flex: 1;\n min-width: 0;\n overflow: auto;\n padding: 20px 24px;\n `,\n}\n\n/**\n * Main Studio UI - contains all panels and manages internal state\n * Rendered inside the modal via lazy loading\n */\nexport function StudioUI({ onClose }: StudioUIProps) {\n const [currentPath, setCurrentPathInternal] = useState('public')\n const [selectedItems, setSelectedItems] = useState<Set<string>>(new Set())\n const [lastSelectedPath, setLastSelectedPath] = useState<string | null>(null)\n const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')\n const [meta, setMeta] = useState<StudioMeta | null>(null)\n const [isLoading, setIsLoading] = useState(false)\n const [refreshKey, setRefreshKey] = useState(0)\n\n const triggerRefresh = useCallback(() => {\n setRefreshKey((k) => k + 1)\n }, [])\n\n const navigateUp = useCallback(() => {\n if (currentPath === 'public') return\n const parts = currentPath.split('/')\n parts.pop()\n setCurrentPathInternal(parts.join('/') || 'public')\n setSelectedItems(new Set())\n }, [currentPath])\n\n const setCurrentPath = useCallback((path: string) => {\n setCurrentPathInternal(path)\n setSelectedItems(new Set())\n }, [])\n\n const toggleSelection = useCallback((path: string) => {\n setSelectedItems((prev) => {\n const next = new Set(prev)\n if (next.has(path)) {\n next.delete(path)\n } else {\n next.add(path)\n }\n return next\n })\n setLastSelectedPath(path)\n }, [])\n\n const selectRange = useCallback((fromPath: string, toPath: string, allItems: FileItem[]) => {\n // Include all items (files and folders)\n const fromIndex = allItems.findIndex(item => item.path === fromPath)\n const toIndex = allItems.findIndex(item => item.path === toPath)\n \n if (fromIndex === -1 || toIndex === -1) return\n \n const start = Math.min(fromIndex, toIndex)\n const end = Math.max(fromIndex, toIndex)\n \n setSelectedItems((prev) => {\n const next = new Set(prev)\n for (let i = start; i <= end; i++) {\n next.add(allItems[i].path)\n }\n return next\n })\n setLastSelectedPath(toPath)\n }, [])\n\n const selectAll = useCallback((items: FileItem[]) => {\n setSelectedItems(new Set(items.map((item) => item.path)))\n }, [])\n\n const clearSelection = useCallback(() => {\n setSelectedItems(new Set())\n }, [])\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n onClose()\n }\n },\n [onClose]\n )\n\n useEffect(() => {\n document.addEventListener('keydown', handleKeyDown)\n document.body.style.overflow = 'hidden'\n return () => {\n document.removeEventListener('keydown', handleKeyDown)\n document.body.style.overflow = ''\n }\n }, [handleKeyDown])\n\n const contextValue = {\n isOpen: true,\n openStudio: () => {},\n closeStudio: onClose,\n toggleStudio: onClose,\n currentPath,\n setCurrentPath,\n navigateUp,\n selectedItems,\n toggleSelection,\n selectRange,\n selectAll,\n clearSelection,\n lastSelectedPath,\n viewMode,\n setViewMode,\n meta,\n setMeta,\n isLoading,\n setIsLoading,\n refreshKey,\n triggerRefresh,\n }\n\n return (\n <StudioContext.Provider value={contextValue}>\n <div css={styles.container}>\n <div css={styles.header}>\n <h1 css={styles.title}>Studio</h1>\n <div css={styles.headerActions}>\n <StudioSettings />\n <button\n css={styles.closeBtn}\n onClick={onClose}\n aria-label=\"Close Studio\"\n >\n <CloseIcon />\n </button>\n </div>\n </div>\n\n <StudioToolbar />\n <StudioBreadcrumb />\n\n <div css={styles.content}>\n <div css={styles.fileBrowser}>\n {viewMode === 'grid' ? <StudioFileGrid /> : <StudioFileList />}\n </div>\n <StudioPreview />\n </div>\n </div>\n </StudioContext.Provider>\n )\n}\n\nfunction CloseIcon() {\n return (\n <svg\n css={styles.closeIcon}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n )\n}\n\nexport default StudioUI\n","'use client'\n\nimport { createContext, useContext } from 'react'\nimport type { FileItem, StudioMeta } from '../types'\n\n/**\n * Studio state interface\n * State is managed by StudioUI and provided to all child components\n */\nexport interface StudioState {\n isOpen: boolean\n openStudio: () => void\n closeStudio: () => void\n toggleStudio: () => void\n\n // Navigation\n currentPath: string\n setCurrentPath: (path: string) => void\n navigateUp: () => void\n\n // Selection\n selectedItems: Set<string>\n toggleSelection: (path: string) => void\n selectRange: (fromPath: string, toPath: string, allItems: FileItem[]) => void\n selectAll: (items: FileItem[]) => void\n clearSelection: () => void\n lastSelectedPath: string | null\n\n // View\n viewMode: 'grid' | 'list'\n setViewMode: (mode: 'grid' | 'list') => void\n\n // Meta\n meta: StudioMeta | null\n setMeta: (meta: StudioMeta) => void\n\n // Loading\n isLoading: boolean\n setIsLoading: (loading: boolean) => void\n\n // Refresh trigger\n refreshKey: number\n triggerRefresh: () => void\n}\n\nconst defaultState: StudioState = {\n isOpen: false,\n openStudio: () => {},\n closeStudio: () => {},\n toggleStudio: () => {},\n currentPath: 'public',\n setCurrentPath: () => {},\n navigateUp: () => {},\n selectedItems: new Set(),\n toggleSelection: () => {},\n selectRange: () => {},\n selectAll: () => {},\n clearSelection: () => {},\n lastSelectedPath: null,\n viewMode: 'grid',\n setViewMode: () => {},\n meta: null,\n setMeta: () => {},\n isLoading: false,\n setIsLoading: () => {},\n refreshKey: 0,\n triggerRefresh: () => {},\n}\n\nexport const StudioContext = createContext<StudioState>(defaultState)\n\n/**\n * Hook to access Studio state from child components\n */\nexport function useStudio() {\n return useContext(StudioContext)\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useCallback, useRef, useState } from 'react'\nimport { css, keyframes } from '@emotion/react'\nimport { useStudio } from './StudioContext'\nimport { ConfirmModal, AlertModal } from './StudioModal'\n\n// Stripe-inspired design tokens\nconst colors = {\n primary: '#635bff',\n primaryHover: '#5851e5',\n primaryLight: '#f0f0ff',\n background: '#ffffff',\n surface: '#ffffff',\n surfaceHover: '#f6f9fc',\n border: '#e3e8ee',\n text: '#1a1f36',\n textSecondary: '#697386',\n danger: '#df1b41',\n dangerLight: '#fff5f7',\n}\n\nconst spin = keyframes`\n to { transform: rotate(360deg); }\n`\n\nconst styles = {\n toolbar: css`\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 24px;\n background-color: ${colors.background};\n border-bottom: 1px solid ${colors.border};\n `,\n left: css`\n display: flex;\n align-items: center;\n gap: 6px;\n `,\n right: css`\n display: flex;\n align-items: center;\n gap: 12px;\n `,\n btn: css`\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n background: ${colors.surface};\n border: 1px solid ${colors.border};\n cursor: pointer;\n transition: all 0.15s ease;\n color: ${colors.text};\n letter-spacing: -0.01em;\n \n &:hover:not(:disabled) {\n background-color: ${colors.surfaceHover};\n border-color: #d0d5dd;\n }\n \n &:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n }\n `,\n btnPrimary: css`\n background: ${colors.primary};\n border-color: ${colors.primary};\n color: white;\n \n &:hover:not(:disabled) {\n background: ${colors.primaryHover};\n border-color: ${colors.primaryHover};\n }\n `,\n btnDanger: css`\n color: ${colors.danger};\n \n &:hover:not(:disabled) {\n background-color: ${colors.dangerLight};\n border-color: ${colors.danger};\n }\n `,\n btnGhost: css`\n border-color: transparent;\n background: transparent;\n \n &:hover:not(:disabled) {\n background-color: ${colors.surfaceHover};\n border-color: transparent;\n }\n `,\n icon: css`\n width: 14px;\n height: 14px;\n `,\n iconSpin: css`\n animation: ${spin} 1s linear infinite;\n `,\n selectionCount: css`\n font-size: 13px;\n color: ${colors.textSecondary};\n display: flex;\n align-items: center;\n gap: 8px;\n `,\n clearBtn: css`\n color: ${colors.primary};\n background: none;\n border: none;\n cursor: pointer;\n font-size: 13px;\n font-weight: 500;\n padding: 0;\n \n &:hover {\n text-decoration: underline;\n }\n `,\n divider: css`\n width: 1px;\n height: 20px;\n background: ${colors.border};\n margin: 0 4px;\n `,\n viewToggle: css`\n display: flex;\n align-items: center;\n background-color: ${colors.surfaceHover};\n border-radius: 6px;\n padding: 2px;\n `,\n viewBtn: css`\n padding: 6px 8px;\n background: transparent;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n color: ${colors.textSecondary};\n transition: all 0.15s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n \n &:hover {\n color: ${colors.text};\n }\n `,\n viewBtnActive: css`\n background-color: ${colors.surface};\n color: ${colors.text};\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n `,\n}\n\nexport function StudioToolbar() {\n const { selectedItems, viewMode, setViewMode, clearSelection, currentPath, triggerRefresh } = useStudio()\n const fileInputRef = useRef<HTMLInputElement>(null)\n const [uploading, setUploading] = useState(false)\n const [refreshing, setRefreshing] = useState(false)\n const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)\n const [alertMessage, setAlertMessage] = useState<{ title: string; message: string } | null>(null)\n\n // Check if we're in the images folder (uploads not allowed there)\n const isInImagesFolder = currentPath === 'public/images' || currentPath.startsWith('public/images/')\n\n const handleUpload = useCallback(() => {\n fileInputRef.current?.click()\n }, [])\n\n const handleRefresh = useCallback(() => {\n setRefreshing(true)\n triggerRefresh()\n setTimeout(() => setRefreshing(false), 600)\n }, [triggerRefresh])\n\n const handleFileChange = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {\n const files = e.target.files\n if (!files || files.length === 0) return\n\n setUploading(true)\n try {\n for (const file of Array.from(files)) {\n const formData = new FormData()\n formData.append('file', file)\n formData.append('path', currentPath)\n\n const response = await fetch('/api/studio/upload', {\n method: 'POST',\n body: formData,\n })\n\n if (!response.ok) {\n const error = await response.json()\n if (response.status >= 500) {\n console.error('Upload error:', error)\n setAlertMessage({\n title: 'Upload Failed',\n message: `Failed to upload ${file.name}: ${error.error || 'Unknown error'}`,\n })\n } else {\n setAlertMessage({\n title: 'Cannot Upload Here',\n message: error.error || 'Upload not allowed in this location.',\n })\n }\n }\n }\n triggerRefresh()\n } catch (error) {\n console.error('Upload error:', error)\n setAlertMessage({\n title: 'Upload Failed',\n message: 'Upload failed. Check console for details.',\n })\n } finally {\n setUploading(false)\n if (fileInputRef.current) {\n fileInputRef.current.value = ''\n }\n }\n }, [currentPath, triggerRefresh])\n\n const handleReprocess = useCallback(() => {\n console.log('Reprocess clicked', selectedItems)\n }, [selectedItems])\n\n const handleDeleteClick = useCallback(() => {\n if (selectedItems.size === 0) return\n setShowDeleteConfirm(true)\n }, [selectedItems])\n\n const handleDeleteConfirm = useCallback(async () => {\n setShowDeleteConfirm(false)\n \n try {\n const response = await fetch('/api/studio/delete', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ paths: Array.from(selectedItems) }),\n })\n\n if (response.ok) {\n clearSelection()\n triggerRefresh()\n } else {\n const error = await response.json()\n setAlertMessage({\n title: 'Delete Failed',\n message: error.error || 'Unknown error',\n })\n }\n } catch (error) {\n console.error('Delete error:', error)\n setAlertMessage({\n title: 'Delete Failed',\n message: 'Delete failed. Check console for details.',\n })\n }\n }, [selectedItems, clearSelection, triggerRefresh])\n\n const handleSyncCdn = useCallback(() => {\n console.log('Sync CDN clicked', selectedItems)\n }, [selectedItems])\n\n const handleScan = useCallback(() => {\n console.log('Scan clicked')\n }, [])\n\n const hasSelection = selectedItems.size > 0\n\n return (\n <>\n {showDeleteConfirm && (\n <ConfirmModal\n title=\"Delete Items\"\n message={`Are you sure you want to delete ${selectedItems.size} item(s)? This action cannot be undone.`}\n confirmLabel=\"Delete\"\n variant=\"danger\"\n onConfirm={handleDeleteConfirm}\n onCancel={() => setShowDeleteConfirm(false)}\n />\n )}\n\n {alertMessage && (\n <AlertModal\n title={alertMessage.title}\n message={alertMessage.message}\n onClose={() => setAlertMessage(null)}\n />\n )}\n\n <div css={styles.toolbar}>\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n accept=\"image/*,video/*,audio/*,.pdf\"\n onChange={handleFileChange}\n style={{ display: 'none' }}\n />\n \n <div css={styles.left}>\n <button\n css={[styles.btn, styles.btnPrimary]}\n onClick={handleUpload}\n disabled={uploading || isInImagesFolder}\n >\n <UploadIcon />\n {uploading ? 'Uploading...' : 'Upload'}\n </button>\n \n <div css={styles.divider} />\n \n <button\n css={[styles.btn, styles.btnGhost]}\n onClick={handleReprocess}\n disabled={!hasSelection}\n >\n <RefreshIcon />\n Reprocess\n </button>\n <button\n css={[styles.btn, styles.btnGhost, styles.btnDanger]}\n onClick={handleDeleteClick}\n disabled={!hasSelection}\n >\n <TrashIcon />\n Delete\n </button>\n <button\n css={[styles.btn, styles.btnGhost]}\n onClick={handleSyncCdn}\n disabled={!hasSelection}\n >\n <CloudIcon />\n Sync CDN\n </button>\n <button css={[styles.btn, styles.btnGhost]} onClick={handleScan}>\n <ScanIcon />\n Scan\n </button>\n </div>\n\n <div css={styles.right}>\n {hasSelection && (\n <span css={styles.selectionCount}>\n {selectedItems.size} selected\n <button css={styles.clearBtn} onClick={clearSelection}>\n Clear\n </button>\n </span>\n )}\n\n <button\n css={[styles.btn, styles.btnGhost]}\n onClick={handleRefresh}\n >\n <RefreshIcon spinning={refreshing} />\n </button>\n\n <div css={styles.viewToggle}>\n <button\n css={[styles.viewBtn, viewMode === 'grid' && styles.viewBtnActive]}\n onClick={() => setViewMode('grid')}\n aria-label=\"Grid view\"\n >\n <GridIcon />\n </button>\n <button\n css={[styles.viewBtn, viewMode === 'list' && styles.viewBtnActive]}\n onClick={() => setViewMode('list')}\n aria-label=\"List view\"\n >\n <ListIcon />\n </button>\n </div>\n </div>\n </div>\n </>\n )\n}\n\nfunction UploadIcon() {\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12\" />\n </svg>\n )\n}\n\nfunction RefreshIcon({ spinning }: { spinning?: boolean }) {\n return (\n <svg css={[styles.icon, spinning && styles.iconSpin]} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15\" />\n </svg>\n )\n}\n\nfunction TrashIcon() {\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n )\n}\n\nfunction CloudIcon() {\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12\" />\n </svg>\n )\n}\n\nfunction ScanIcon() {\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\" />\n </svg>\n )\n}\n\nfunction GridIcon() {\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z\" />\n </svg>\n )\n}\n\nfunction ListIcon() {\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 6h16M4 10h16M4 14h16M4 18h16\" />\n </svg>\n )\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { css, keyframes } from '@emotion/react'\n\n// Stripe-inspired design tokens\nconst colors = {\n primary: '#635bff',\n primaryHover: '#5851e5',\n background: '#f6f9fc',\n surface: '#ffffff',\n surfaceHover: '#f6f9fc',\n border: '#e3e8ee',\n text: '#1a1f36',\n textSecondary: '#697386',\n danger: '#df1b41',\n dangerHover: '#c41535',\n}\n\nconst fadeIn = keyframes`\n from { opacity: 0; }\n to { opacity: 1; }\n`\n\nconst slideIn = keyframes`\n from { \n opacity: 0;\n transform: translateY(-8px) scale(0.98);\n }\n to { \n opacity: 1;\n transform: translateY(0) scale(1);\n }\n`\n\nconst styles = {\n overlay: css`\n position: fixed;\n inset: 0;\n background-color: rgba(26, 31, 54, 0.4);\n backdrop-filter: blur(4px);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10000;\n animation: ${fadeIn} 0.15s ease-out;\n `,\n modal: css`\n background-color: ${colors.surface};\n border-radius: 12px;\n box-shadow: 0 30px 60px -12px rgba(50, 50, 93, 0.25), 0 18px 36px -18px rgba(0, 0, 0, 0.3);\n max-width: 420px;\n width: 90%;\n animation: ${slideIn} 0.2s ease-out;\n overflow: hidden;\n `,\n header: css`\n padding: 24px 24px 0;\n `,\n title: css`\n font-size: 17px;\n font-weight: 600;\n color: ${colors.text};\n margin: 0;\n letter-spacing: -0.02em;\n `,\n body: css`\n padding: 12px 24px 24px;\n `,\n message: css`\n font-size: 14px;\n color: ${colors.textSecondary};\n margin: 0;\n line-height: 1.6;\n `,\n footer: css`\n display: flex;\n justify-content: flex-end;\n gap: 12px;\n padding: 16px 24px;\n border-top: 1px solid ${colors.border};\n background-color: ${colors.background};\n `,\n btn: css`\n padding: 10px 18px;\n font-size: 14px;\n font-weight: 500;\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.15s ease;\n letter-spacing: -0.01em;\n `,\n btnCancel: css`\n background-color: ${colors.surface};\n border: 1px solid ${colors.border};\n color: ${colors.text};\n \n &:hover {\n background-color: ${colors.surfaceHover};\n border-color: #d0d5dd;\n }\n `,\n btnConfirm: css`\n background-color: ${colors.primary};\n border: 1px solid ${colors.primary};\n color: white;\n \n &:hover {\n background-color: ${colors.primaryHover};\n border-color: ${colors.primaryHover};\n }\n `,\n btnDanger: css`\n background-color: ${colors.danger};\n border: 1px solid ${colors.danger};\n color: white;\n \n &:hover {\n background-color: ${colors.dangerHover};\n border-color: ${colors.dangerHover};\n }\n `,\n}\n\ninterface ConfirmModalProps {\n title: string\n message: string\n confirmLabel?: string\n cancelLabel?: string\n variant?: 'default' | 'danger'\n onConfirm: () => void\n onCancel: () => void\n}\n\nexport function ConfirmModal({\n title,\n message,\n confirmLabel = 'Confirm',\n cancelLabel = 'Cancel',\n variant = 'default',\n onConfirm,\n onCancel,\n}: ConfirmModalProps) {\n return (\n <div css={styles.overlay} onClick={onCancel}>\n <div css={styles.modal} onClick={(e) => e.stopPropagation()}>\n <div css={styles.header}>\n <h3 css={styles.title}>{title}</h3>\n </div>\n <div css={styles.body}>\n <p css={styles.message}>{message}</p>\n </div>\n <div css={styles.footer}>\n <button css={[styles.btn, styles.btnCancel]} onClick={onCancel}>\n {cancelLabel}\n </button>\n <button\n css={[styles.btn, variant === 'danger' ? styles.btnDanger : styles.btnConfirm]}\n onClick={onConfirm}\n >\n {confirmLabel}\n </button>\n </div>\n </div>\n </div>\n )\n}\n\ninterface AlertModalProps {\n title: string\n message: string\n buttonLabel?: string\n onClose: () => void\n}\n\nexport function AlertModal({\n title,\n message,\n buttonLabel = 'OK',\n onClose,\n}: AlertModalProps) {\n return (\n <div css={styles.overlay} onClick={onClose}>\n <div css={styles.modal} onClick={(e) => e.stopPropagation()}>\n <div css={styles.header}>\n <h3 css={styles.title}>{title}</h3>\n </div>\n <div css={styles.body}>\n <p css={styles.message}>{message}</p>\n </div>\n <div css={styles.footer}>\n <button css={[styles.btn, styles.btnConfirm]} onClick={onClose}>\n {buttonLabel}\n </button>\n </div>\n </div>\n </div>\n )\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { css } from '@emotion/react'\nimport { useStudio } from './StudioContext'\n\n// Stripe-inspired design tokens\nconst colors = {\n primary: '#635bff',\n background: '#ffffff',\n surface: '#ffffff',\n surfaceHover: '#f6f9fc',\n border: '#e3e8ee',\n borderLight: '#eef1f6',\n text: '#1a1f36',\n textSecondary: '#697386',\n textMuted: '#8792a2',\n}\n\nconst styles = {\n container: css`\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 24px;\n background-color: ${colors.surface};\n border-bottom: 1px solid ${colors.borderLight};\n `,\n backBtn: css`\n padding: 6px;\n background: none;\n border: none;\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.15s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n \n &:hover {\n background-color: ${colors.surfaceHover};\n }\n `,\n backIcon: css`\n width: 16px;\n height: 16px;\n color: ${colors.textSecondary};\n `,\n nav: css`\n display: flex;\n align-items: center;\n gap: 2px;\n font-size: 13px;\n `,\n item: css`\n display: flex;\n align-items: center;\n gap: 2px;\n `,\n separator: css`\n color: ${colors.textMuted};\n margin: 0 2px;\n `,\n btn: css`\n padding: 4px 8px;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n transition: all 0.15s ease;\n font-size: 13px;\n letter-spacing: -0.01em;\n \n &:hover {\n background-color: ${colors.surfaceHover};\n }\n `,\n btnActive: css`\n color: ${colors.text};\n font-weight: 600;\n `,\n btnInactive: css`\n color: ${colors.textSecondary};\n \n &:hover {\n color: ${colors.text};\n }\n `,\n}\n\nexport function StudioBreadcrumb() {\n const { currentPath, setCurrentPath, navigateUp } = useStudio()\n\n const parts = currentPath.split('/').filter(Boolean)\n\n const handleClick = (index: number) => {\n const newPath = parts.slice(0, index + 1).join('/')\n setCurrentPath(newPath)\n }\n\n return (\n <div css={styles.container}>\n {currentPath !== 'public' && (\n <button css={styles.backBtn} onClick={navigateUp} aria-label=\"Go back\">\n <svg css={styles.backIcon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 19l-7-7 7-7\" />\n </svg>\n </button>\n )}\n\n <nav css={styles.nav}>\n {parts.map((part, index) => (\n <span key={index} css={styles.item}>\n {index > 0 && <span css={styles.separator}>/</span>}\n <button\n css={[styles.btn, index === parts.length - 1 ? styles.btnActive : styles.btnInactive]}\n onClick={() => handleClick(index)}\n >\n {part}\n </button>\n </span>\n ))}\n </nav>\n </div>\n )\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useEffect, useState } from 'react'\nimport { css, keyframes } from '@emotion/react'\nimport { useStudio } from './StudioContext'\nimport type { FileItem } from '../types'\n\n// Stripe-inspired design tokens\nconst colors = {\n primary: '#635bff',\n primaryHover: '#5851e5',\n primaryLight: '#f0f0ff',\n background: '#f6f9fc',\n surface: '#ffffff',\n surfaceHover: '#f6f9fc',\n border: '#e3e8ee',\n borderLight: '#eef1f6',\n text: '#1a1f36',\n textSecondary: '#697386',\n textMuted: '#8792a2',\n success: '#0d7d4d',\n successLight: '#e6f7ef',\n}\n\nconst spin = keyframes`\n to { transform: rotate(360deg); }\n`\n\nconst styles = {\n loading: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 256px;\n `,\n spinner: css`\n width: 32px;\n height: 32px;\n border-radius: 50%;\n border: 3px solid ${colors.border};\n border-top-color: ${colors.primary};\n animation: ${spin} 0.8s linear infinite;\n `,\n empty: css`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 256px;\n color: ${colors.textSecondary};\n `,\n emptyIcon: css`\n width: 48px;\n height: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n `,\n emptyText: css`\n font-size: 14px;\n margin: 0 0 4px 0;\n \n &:last-child {\n color: ${colors.textMuted};\n font-size: 13px;\n }\n `,\n grid: css`\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 12px;\n \n @media (min-width: 640px) { grid-template-columns: repeat(3, 1fr); }\n @media (min-width: 768px) { grid-template-columns: repeat(4, 1fr); }\n @media (min-width: 1024px) { grid-template-columns: repeat(5, 1fr); }\n @media (min-width: 1280px) { grid-template-columns: repeat(6, 1fr); }\n `,\n item: css`\n position: relative;\n border-radius: 8px;\n border: 1px solid ${colors.border};\n overflow: hidden;\n cursor: pointer;\n transition: all 0.15s ease;\n background-color: ${colors.surface};\n user-select: none;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);\n \n &:hover {\n border-color: #d0d5dd;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06);\n }\n `,\n itemSelected: css`\n border-color: ${colors.primary};\n box-shadow: 0 0 0 1px ${colors.primary};\n \n &:hover {\n border-color: ${colors.primary};\n }\n `,\n checkboxWrapper: css`\n position: absolute;\n top: 0;\n left: 0;\n z-index: 10;\n padding: 8px;\n cursor: pointer;\n `,\n checkbox: css`\n width: 16px;\n height: 16px;\n accent-color: ${colors.primary};\n cursor: pointer;\n `,\n cdnBadge: css`\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 10;\n background-color: ${colors.successLight};\n color: ${colors.success};\n font-size: 11px;\n font-weight: 500;\n padding: 2px 8px;\n border-radius: 4px;\n `,\n content: css`\n aspect-ratio: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 16px;\n background: ${colors.background};\n `,\n folderIcon: css`\n width: 56px;\n height: 56px;\n color: #f5a623;\n `,\n fileIcon: css`\n width: 40px;\n height: 40px;\n color: ${colors.textMuted};\n `,\n image: css`\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n border-radius: 4px;\n `,\n label: css`\n padding: 10px 12px;\n background-color: ${colors.surface};\n border-top: 1px solid ${colors.borderLight};\n `,\n labelRow: css`\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n `,\n labelText: css`\n flex: 1;\n min-width: 0;\n `,\n name: css`\n font-size: 13px;\n font-weight: 500;\n color: ${colors.text};\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n margin: 0;\n letter-spacing: -0.01em;\n `,\n size: css`\n font-size: 12px;\n color: ${colors.textMuted};\n margin: 2px 0 0 0;\n `,\n openBtn: css`\n flex-shrink: 0;\n font-size: 12px;\n font-weight: 500;\n color: ${colors.primary};\n background: none;\n border: none;\n padding: 4px 8px;\n cursor: pointer;\n border-radius: 4px;\n transition: background-color 0.15s ease;\n \n &:hover {\n background-color: ${colors.primaryLight};\n }\n `,\n selectAllRow: css`\n display: flex;\n align-items: center;\n margin-bottom: 16px;\n padding: 12px 16px;\n background: ${colors.surface};\n border-radius: 8px;\n border: 1px solid ${colors.border};\n `,\n selectAllLabel: css`\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 13px;\n font-weight: 500;\n color: ${colors.textSecondary};\n cursor: pointer;\n \n &:hover {\n color: ${colors.text};\n }\n `,\n selectAllCheckbox: css`\n width: 16px;\n height: 16px;\n accent-color: ${colors.primary};\n `,\n}\n\nexport function StudioFileGrid() {\n const { currentPath, setCurrentPath, selectedItems, toggleSelection, selectRange, lastSelectedPath, selectAll, clearSelection, refreshKey } = useStudio()\n const [items, setItems] = useState<FileItem[]>([])\n const [loading, setLoading] = useState(true)\n\n useEffect(() => {\n async function loadItems() {\n setLoading(true)\n try {\n const response = await fetch(`/api/studio/list?path=${encodeURIComponent(currentPath)}`)\n if (response.ok) {\n const data = await response.json()\n setItems(data.items || [])\n }\n } catch (error) {\n console.error('Failed to load items:', error)\n }\n setLoading(false)\n }\n loadItems()\n }, [currentPath, refreshKey])\n\n if (loading) {\n return (\n <div css={styles.loading}>\n <div css={styles.spinner} />\n </div>\n )\n }\n\n if (items.length === 0) {\n return (\n <div css={styles.empty}>\n <svg css={styles.emptyIcon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={1.5} d=\"M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z\" />\n </svg>\n <p css={styles.emptyText}>No files in this folder</p>\n <p css={styles.emptyText}>Upload images to get started</p>\n </div>\n )\n }\n\n const sortedItems = [...items].sort((a, b) => {\n if (a.type === 'folder' && b.type !== 'folder') return -1\n if (a.type !== 'folder' && b.type === 'folder') return 1\n return a.name.localeCompare(b.name)\n })\n\n const handleItemClick = (item: FileItem, e: React.MouseEvent) => {\n // For both files and folders, clicking toggles selection\n if (e.shiftKey && lastSelectedPath) {\n selectRange(lastSelectedPath, item.path, sortedItems)\n } else {\n toggleSelection(item.path)\n }\n }\n\n const handleOpenFolder = (item: FileItem) => {\n if (item.type === 'folder') {\n setCurrentPath(item.path)\n }\n }\n\n // Count all items for select all (now includes folders)\n const allItemsSelected = sortedItems.length > 0 && sortedItems.every(item => selectedItems.has(item.path))\n const someItemsSelected = sortedItems.some(item => selectedItems.has(item.path))\n\n const handleSelectAll = () => {\n if (allItemsSelected) {\n clearSelection()\n } else {\n selectAll(sortedItems)\n }\n }\n\n return (\n <div>\n {sortedItems.length > 0 && (\n <div css={styles.selectAllRow}>\n <label css={styles.selectAllLabel}>\n <input\n type=\"checkbox\"\n css={styles.selectAllCheckbox}\n checked={allItemsSelected}\n ref={(el) => {\n if (el) el.indeterminate = someItemsSelected && !allItemsSelected\n }}\n onChange={handleSelectAll}\n />\n Select all ({sortedItems.length})\n </label>\n </div>\n )}\n <div css={styles.grid}>\n {sortedItems.map((item) => (\n <GridItem\n key={item.path}\n item={item}\n isSelected={selectedItems.has(item.path)}\n onClick={(e) => handleItemClick(item, e)}\n onOpen={() => handleOpenFolder(item)}\n />\n ))}\n </div>\n </div>\n )\n}\n\ninterface GridItemProps {\n item: FileItem\n isSelected: boolean\n onClick: (e: React.MouseEvent) => void\n onOpen: () => void\n}\n\nfunction GridItem({ item, isSelected, onClick, onOpen }: GridItemProps) {\n const isFolder = item.type === 'folder'\n\n return (\n <div \n css={[styles.item, isSelected && styles.itemSelected]} \n onClick={onClick}\n >\n {/* Show checkbox for both files and folders */}\n <div\n css={styles.checkboxWrapper}\n onClick={(e) => e.stopPropagation()}\n >\n <input\n type=\"checkbox\"\n css={styles.checkbox}\n checked={isSelected}\n onChange={() => onClick({} as React.MouseEvent)}\n />\n </div>\n\n {item.cdnSynced && <span css={styles.cdnBadge}>CDN</span>}\n\n <div css={styles.content}>\n {isFolder ? (\n <svg css={styles.folderIcon} fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M10 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z\" />\n </svg>\n ) : item.thumbnail ? (\n <img\n css={styles.image}\n src={item.thumbnail}\n alt={item.name}\n loading=\"lazy\"\n />\n ) : (\n <svg css={styles.fileIcon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={1.5} d=\"M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z\" />\n </svg>\n )}\n </div>\n\n <div css={styles.label}>\n <div css={styles.labelRow}>\n <div css={styles.labelText}>\n <p css={styles.name} title={item.name}>{item.name}</p>\n {isFolder ? (\n <p css={styles.size}>\n {item.fileCount !== undefined ? `${item.fileCount} files` : ''}\n {item.fileCount !== undefined && item.totalSize !== undefined ? ' · ' : ''}\n {item.totalSize !== undefined ? formatFileSize(item.totalSize) : ''}\n </p>\n ) : (\n item.size !== undefined && <p css={styles.size}>{formatFileSize(item.size)}</p>\n )}\n </div>\n {isFolder && (\n <button\n css={styles.openBtn}\n onClick={(e) => {\n e.stopPropagation()\n onOpen()\n }}\n >\n Open\n </button>\n )}\n </div>\n </div>\n </div>\n )\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useEffect, useState } from 'react'\nimport { css, keyframes } from '@emotion/react'\nimport { useStudio } from './StudioContext'\nimport type { FileItem } from '../types'\n\n// Stripe-inspired design tokens\nconst colors = {\n primary: '#635bff',\n primaryLight: '#f0f0ff',\n background: '#f6f9fc',\n surface: '#ffffff',\n surfaceHover: '#f6f9fc',\n border: '#e3e8ee',\n borderLight: '#eef1f6',\n text: '#1a1f36',\n textSecondary: '#697386',\n textMuted: '#8792a2',\n success: '#0d7d4d',\n}\n\nconst spin = keyframes`\n to { transform: rotate(360deg); }\n`\n\nconst styles = {\n loading: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 256px;\n `,\n spinner: css`\n width: 32px;\n height: 32px;\n border-radius: 50%;\n border: 3px solid ${colors.border};\n border-top-color: ${colors.primary};\n animation: ${spin} 0.8s linear infinite;\n `,\n empty: css`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 256px;\n color: ${colors.textSecondary};\n `,\n tableWrapper: css`\n background: ${colors.surface};\n border-radius: 8px;\n border: 1px solid ${colors.border};\n overflow: hidden;\n `,\n table: css`\n width: 100%;\n border-collapse: collapse;\n `,\n th: css`\n text-align: left;\n font-size: 11px;\n color: ${colors.textMuted};\n text-transform: uppercase;\n letter-spacing: 0.05em;\n padding: 12px 16px;\n font-weight: 600;\n background: ${colors.background};\n border-bottom: 1px solid ${colors.border};\n `,\n thCheckbox: css`\n width: 48px;\n `,\n thSize: css`\n width: 96px;\n `,\n thDimensions: css`\n width: 128px;\n `,\n thCdn: css`\n width: 96px;\n `,\n tbody: css``,\n row: css`\n cursor: pointer;\n transition: background-color 0.15s ease;\n user-select: none;\n \n &:hover {\n background-color: ${colors.surfaceHover};\n }\n \n &:not(:last-child) td {\n border-bottom: 1px solid ${colors.borderLight};\n }\n `,\n rowSelected: css`\n background-color: ${colors.primaryLight};\n \n &:hover {\n background-color: ${colors.primaryLight};\n }\n `,\n td: css`\n padding: 12px 16px;\n `,\n checkboxCell: css`\n padding: 12px 16px;\n cursor: pointer;\n `,\n checkbox: css`\n width: 16px;\n height: 16px;\n accent-color: ${colors.primary};\n cursor: pointer;\n `,\n nameCell: css`\n display: flex;\n align-items: center;\n gap: 12px;\n `,\n folderIcon: css`\n width: 20px;\n height: 20px;\n color: #f5a623;\n flex-shrink: 0;\n `,\n fileIcon: css`\n width: 20px;\n height: 20px;\n color: ${colors.textMuted};\n flex-shrink: 0;\n `,\n thumbnail: css`\n width: 36px;\n height: 36px;\n object-fit: cover;\n border-radius: 6px;\n flex-shrink: 0;\n border: 1px solid ${colors.borderLight};\n `,\n name: css`\n font-size: 14px;\n font-weight: 500;\n color: ${colors.text};\n letter-spacing: -0.01em;\n `,\n meta: css`\n font-size: 13px;\n color: ${colors.textSecondary};\n `,\n cdnBadge: css`\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n font-weight: 500;\n color: ${colors.success};\n `,\n cdnIcon: css`\n width: 12px;\n height: 12px;\n `,\n cdnEmpty: css`\n font-size: 13px;\n color: ${colors.textMuted};\n `,\n openBtn: css`\n font-size: 12px;\n font-weight: 500;\n color: ${colors.primary};\n background: none;\n border: none;\n padding: 4px 10px;\n cursor: pointer;\n border-radius: 4px;\n transition: background-color 0.15s ease;\n \n &:hover {\n background-color: ${colors.primaryLight};\n }\n `,\n}\n\nexport function StudioFileList() {\n const { currentPath, setCurrentPath, selectedItems, toggleSelection, selectRange, lastSelectedPath, selectAll, clearSelection, refreshKey } = useStudio()\n const [items, setItems] = useState<FileItem[]>([])\n const [loading, setLoading] = useState(true)\n\n useEffect(() => {\n async function loadItems() {\n setLoading(true)\n try {\n const response = await fetch(`/api/studio/list?path=${encodeURIComponent(currentPath)}`)\n if (response.ok) {\n const data = await response.json()\n setItems(data.items || [])\n }\n } catch (error) {\n console.error('Failed to load items:', error)\n }\n setLoading(false)\n }\n loadItems()\n }, [currentPath, refreshKey])\n\n if (loading) {\n return (\n <div css={styles.loading}>\n <div css={styles.spinner} />\n </div>\n )\n }\n\n if (items.length === 0) {\n return (\n <div css={styles.empty}>\n <p>No files in this folder</p>\n </div>\n )\n }\n\n const sortedItems = [...items].sort((a, b) => {\n if (a.type === 'folder' && b.type !== 'folder') return -1\n if (a.type !== 'folder' && b.type === 'folder') return 1\n return a.name.localeCompare(b.name)\n })\n\n const handleItemClick = (item: FileItem, e: React.MouseEvent) => {\n // For both files and folders, clicking toggles selection\n if (e.shiftKey && lastSelectedPath) {\n selectRange(lastSelectedPath, item.path, sortedItems)\n } else {\n toggleSelection(item.path)\n }\n }\n\n const handleOpenFolder = (item: FileItem) => {\n if (item.type === 'folder') {\n setCurrentPath(item.path)\n }\n }\n\n // Count all items for select all (now includes folders)\n const allItemsSelected = sortedItems.length > 0 && sortedItems.every(item => selectedItems.has(item.path))\n const someItemsSelected = sortedItems.some(item => selectedItems.has(item.path))\n\n const handleSelectAll = () => {\n if (allItemsSelected) {\n clearSelection()\n } else {\n selectAll(sortedItems)\n }\n }\n\n return (\n <table css={styles.table}>\n <thead>\n <tr>\n <th css={[styles.th, styles.thCheckbox]}>\n {sortedItems.length > 0 && (\n <input\n type=\"checkbox\"\n css={styles.checkbox}\n checked={allItemsSelected}\n ref={(el) => {\n if (el) el.indeterminate = someItemsSelected && !allItemsSelected\n }}\n onChange={handleSelectAll}\n />\n )}\n </th>\n <th css={styles.th}>Name</th>\n <th css={[styles.th, styles.thSize]}>Size</th>\n <th css={[styles.th, styles.thDimensions]}>Dimensions</th>\n <th css={[styles.th, styles.thCdn]}>CDN</th>\n </tr>\n </thead>\n <tbody css={styles.tbody}>\n {sortedItems.map((item) => (\n <ListRow\n key={item.path}\n item={item}\n isSelected={selectedItems.has(item.path)}\n onClick={(e) => handleItemClick(item, e)}\n onOpen={() => handleOpenFolder(item)}\n />\n ))}\n </tbody>\n </table>\n )\n}\n\ninterface ListRowProps {\n item: FileItem\n isSelected: boolean\n onClick: (e: React.MouseEvent) => void\n onOpen: () => void\n}\n\nfunction ListRow({ item, isSelected, onClick, onOpen }: ListRowProps) {\n const isFolder = item.type === 'folder'\n\n return (\n <tr \n css={[styles.row, isSelected && styles.rowSelected]} \n onClick={onClick}\n >\n <td\n css={[styles.td, styles.checkboxCell]}\n onClick={(e) => e.stopPropagation()}\n >\n {/* Show checkbox for both files and folders */}\n <input\n type=\"checkbox\"\n css={styles.checkbox}\n checked={isSelected}\n onChange={() => onClick({} as React.MouseEvent)}\n />\n </td>\n <td css={styles.td}>\n <div css={styles.nameCell}>\n {isFolder ? (\n <svg css={styles.folderIcon} fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M10 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z\" />\n </svg>\n ) : item.thumbnail ? (\n <img css={styles.thumbnail} src={item.thumbnail} alt={item.name} loading=\"lazy\" />\n ) : (\n <svg css={styles.fileIcon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={1.5} d=\"M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z\" />\n </svg>\n )}\n <span css={styles.name}>{item.name}</span>\n {isFolder && (\n <button\n css={styles.openBtn}\n onClick={(e) => {\n e.stopPropagation()\n onOpen()\n }}\n >\n Open\n </button>\n )}\n </div>\n </td>\n <td css={[styles.td, styles.meta]}>\n {isFolder \n ? (item.fileCount !== undefined ? `${item.fileCount} files` : '--')\n : (item.size !== undefined ? formatFileSize(item.size) : '--')\n }\n </td>\n <td css={[styles.td, styles.meta]}>\n {isFolder \n ? (item.totalSize !== undefined ? formatFileSize(item.totalSize) : '--')\n : (item.dimensions ? `${item.dimensions.width}x${item.dimensions.height}` : '--')\n }\n </td>\n <td css={styles.td}>\n {item.cdnSynced ? (\n <span css={styles.cdnBadge}>\n <svg css={styles.cdnIcon} fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path fillRule=\"evenodd\" d=\"M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z\" clipRule=\"evenodd\" />\n </svg>\n Synced\n </span>\n ) : (\n <span css={styles.cdnEmpty}>--</span>\n )}\n </td>\n </tr>\n )\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useState } from 'react'\nimport { css } from '@emotion/react'\nimport { useStudio } from './StudioContext'\nimport { ConfirmModal, AlertModal } from './StudioModal'\n\nconst IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.ico', '.bmp', '.tiff', '.tif']\nconst VIDEO_EXTENSIONS = ['.mp4', '.webm', '.mov', '.avi', '.mkv', '.m4v']\n\nfunction isImageFile(filename: string): boolean {\n const ext = filename.toLowerCase().substring(filename.lastIndexOf('.'))\n return IMAGE_EXTENSIONS.includes(ext)\n}\n\nfunction isVideoFile(filename: string): boolean {\n const ext = filename.toLowerCase().substring(filename.lastIndexOf('.'))\n return VIDEO_EXTENSIONS.includes(ext)\n}\n\n// Stripe-inspired design tokens\nconst colors = {\n primary: '#635bff',\n primaryHover: '#5851e5',\n primaryLight: '#f0f0ff',\n background: '#f6f9fc',\n surface: '#ffffff',\n surfaceHover: '#f6f9fc',\n border: '#e3e8ee',\n borderLight: '#eef1f6',\n text: '#1a1f36',\n textSecondary: '#697386',\n textMuted: '#8792a2',\n success: '#0d7d4d',\n successLight: '#e6f7ef',\n danger: '#df1b41',\n dangerLight: '#fff5f7',\n}\n\nconst styles = {\n panel: css`\n width: 320px;\n border-left: 1px solid ${colors.border};\n background-color: ${colors.surface};\n padding: 20px;\n overflow: auto;\n `,\n title: css`\n font-size: 13px;\n font-weight: 600;\n color: ${colors.textSecondary};\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin: 0 0 16px 0;\n `,\n imageContainer: css`\n background-color: ${colors.background};\n border-radius: 8px;\n border: 1px solid ${colors.border};\n padding: 12px;\n margin-bottom: 20px;\n `,\n image: css`\n width: 100%;\n height: auto;\n border-radius: 6px;\n `,\n info: css`\n display: flex;\n flex-direction: column;\n gap: 10px;\n `,\n row: css`\n display: flex;\n justify-content: space-between;\n font-size: 13px;\n `,\n label: css`\n color: ${colors.textSecondary};\n `,\n value: css`\n color: ${colors.text};\n font-weight: 500;\n `,\n valueTruncate: css`\n max-width: 140px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n `,\n section: css`\n padding-top: 12px;\n margin-top: 4px;\n border-top: 1px solid ${colors.borderLight};\n `,\n sectionTitle: css`\n font-size: 11px;\n font-weight: 600;\n color: ${colors.textMuted};\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin: 0 0 10px 0;\n `,\n cdnStatus: css`\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: ${colors.success};\n font-weight: 500;\n `,\n cdnIcon: css`\n width: 16px;\n height: 16px;\n `,\n copyBtn: css`\n margin-top: 8px;\n font-size: 13px;\n font-weight: 500;\n color: ${colors.primary};\n background: none;\n border: none;\n cursor: pointer;\n padding: 0;\n \n &:hover {\n text-decoration: underline;\n }\n `,\n colorSwatch: css`\n margin-top: 8px;\n height: 32px;\n border-radius: 6px;\n border: 1px solid ${colors.border};\n `,\n emptyState: css`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 200px;\n text-align: center;\n `,\n emptyText: css`\n font-size: 13px;\n color: ${colors.textMuted};\n margin: 0;\n `,\n filePlaceholder: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 120px;\n background: ${colors.background};\n border-radius: 6px;\n `,\n fileIcon: css`\n width: 56px;\n height: 56px;\n color: ${colors.textMuted};\n `,\n folderIcon: css`\n width: 56px;\n height: 56px;\n color: #f5a623;\n `,\n video: css`\n width: 100%;\n height: auto;\n border-radius: 6px;\n `,\n actions: css`\n margin-top: 20px;\n padding-top: 20px;\n border-top: 1px solid ${colors.border};\n display: flex;\n flex-direction: column;\n gap: 8px;\n `,\n actionBtn: css`\n width: 100%;\n padding: 10px 14px;\n font-size: 13px;\n font-weight: 500;\n background-color: ${colors.surface};\n border: 1px solid ${colors.border};\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.15s ease;\n color: ${colors.text};\n \n &:hover {\n background-color: ${colors.surfaceHover};\n border-color: #d0d5dd;\n }\n `,\n actionBtnDanger: css`\n color: ${colors.danger};\n \n &:hover {\n background-color: ${colors.dangerLight};\n border-color: ${colors.danger};\n }\n `,\n}\n\nexport function StudioPreview() {\n const { selectedItems, meta, triggerRefresh, clearSelection } = useStudio()\n const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)\n const [alertMessage, setAlertMessage] = useState<{ title: string; message: string } | null>(null)\n\n const handleDeleteClick = () => {\n if (selectedItems.size === 0) return\n setShowDeleteConfirm(true)\n }\n\n const handleDeleteConfirm = async () => {\n setShowDeleteConfirm(false)\n\n try {\n const response = await fetch('/api/studio/delete', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ paths: Array.from(selectedItems) }),\n })\n\n if (response.ok) {\n clearSelection()\n triggerRefresh()\n } else {\n const error = await response.json()\n setAlertMessage({\n title: 'Delete Failed',\n message: error.error || 'Unknown error',\n })\n }\n } catch (error) {\n console.error('Delete error:', error)\n setAlertMessage({\n title: 'Delete Failed',\n message: 'Delete failed. Check console for details.',\n })\n }\n }\n\n const modals = (\n <>\n {showDeleteConfirm && (\n <ConfirmModal\n title=\"Delete Items\"\n message={`Are you sure you want to delete ${selectedItems.size} item(s)? This action cannot be undone.`}\n confirmLabel=\"Delete\"\n variant=\"danger\"\n onConfirm={handleDeleteConfirm}\n onCancel={() => setShowDeleteConfirm(false)}\n />\n )}\n\n {alertMessage && (\n <AlertModal\n title={alertMessage.title}\n message={alertMessage.message}\n onClose={() => setAlertMessage(null)}\n />\n )}\n </>\n )\n\n // Always show the sidebar\n if (selectedItems.size === 0) {\n return (\n <>\n {modals}\n <div css={styles.panel}>\n <h3 css={styles.title}>Preview</h3>\n <div css={styles.emptyState}>\n <p css={styles.emptyText}>Select an image to preview</p>\n </div>\n </div>\n </>\n )\n }\n\n if (selectedItems.size > 1) {\n return (\n <>\n {modals}\n <div css={styles.panel}>\n <h3 css={styles.title}>{selectedItems.size} items selected</h3>\n <div css={styles.actions}>\n <button css={[styles.actionBtn, styles.actionBtnDanger]} onClick={handleDeleteClick}>\n Delete {selectedItems.size} items\n </button>\n </div>\n </div>\n </>\n )\n }\n\n const selectedPath = Array.from(selectedItems)[0]\n const isFolder = !selectedPath.includes('.') || selectedPath.endsWith('/')\n const filename = selectedPath.split('/').pop() || ''\n const isImage = isImageFile(filename)\n const isVideo = isVideoFile(filename)\n \n const imageKey = selectedPath\n .replace(/^public\\/images\\//, '')\n .replace(/^public\\/originals\\//, '')\n .replace(/^public\\//, '')\n\n const imageData = meta?.images?.[imageKey]\n\n const renderPreview = () => {\n if (isFolder) {\n return (\n <div css={styles.filePlaceholder}>\n <svg css={styles.folderIcon} fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M10 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z\" />\n </svg>\n </div>\n )\n }\n \n if (isImage) {\n return (\n <img\n css={styles.image}\n src={selectedPath.replace('public', '')}\n alt=\"Preview\"\n />\n )\n }\n \n if (isVideo) {\n return (\n <video\n css={styles.video}\n src={selectedPath.replace('public', '')}\n controls\n muted\n />\n )\n }\n \n // Non-image/video file - show file icon\n return (\n <div css={styles.filePlaceholder}>\n <svg css={styles.fileIcon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={1.5} d=\"M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z\" />\n </svg>\n </div>\n )\n }\n\n return (\n <>\n {modals}\n <div css={styles.panel}>\n <h3 css={styles.title}>Preview</h3>\n\n <div css={styles.imageContainer}>\n {renderPreview()}\n </div>\n\n <div css={styles.info}>\n <InfoRow label=\"Filename\" value={selectedPath.split('/').pop() || ''} />\n\n {imageData && (\n <>\n <InfoRow\n label=\"Original\"\n value={`${imageData.original.width}x${imageData.original.height}`}\n />\n <InfoRow\n label=\"File size\"\n value={formatFileSize(imageData.original.fileSize)}\n />\n\n <div css={styles.section}>\n <p css={styles.sectionTitle}>Generated sizes</p>\n {Object.entries(imageData.sizes).map(([size, data]) => (\n <InfoRow key={size} label={size} value={`${data.width}x${data.height}`} />\n ))}\n </div>\n\n {imageData.cdn?.synced && (\n <div css={styles.section}>\n <p css={styles.sectionTitle}>CDN</p>\n <div css={styles.cdnStatus}>\n <svg css={styles.cdnIcon} fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path fillRule=\"evenodd\" d=\"M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z\" clipRule=\"evenodd\" />\n </svg>\n Synced to CDN\n </div>\n <button\n css={styles.copyBtn}\n onClick={() => {\n navigator.clipboard.writeText(`${imageData.cdn?.baseUrl}${imageData.sizes.full.path}`)\n }}\n >\n Copy CDN URL\n </button>\n </div>\n )}\n\n {imageData.blurhash && (\n <div css={styles.section}>\n <InfoRow label=\"Blurhash\" value={imageData.blurhash} truncate />\n <div\n css={styles.colorSwatch}\n style={{ backgroundColor: imageData.dominantColor }}\n title={`Dominant color: ${imageData.dominantColor}`}\n />\n </div>\n )}\n </>\n )}\n </div>\n\n <div css={styles.actions}>\n <button css={styles.actionBtn}>Rename</button>\n <button css={[styles.actionBtn, styles.actionBtnDanger]} onClick={handleDeleteClick}>Delete</button>\n </div>\n </div>\n </>\n )\n}\n\nfunction InfoRow({ label, value, truncate }: { label: string; value: string; truncate?: boolean }) {\n return (\n <div css={styles.row}>\n <span css={styles.label}>{label}</span>\n <span css={[styles.value, truncate && styles.valueTruncate]} title={truncate ? value : undefined}>\n {value}\n </span>\n </div>\n )\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useState } from 'react'\nimport { css } from '@emotion/react'\n\nconst styles = {\n btn: css`\n padding: 8px;\n background: none;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n icon: css`\n width: 20px;\n height: 20px;\n color: #6b7280;\n `,\n overlay: css`\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 10000;\n display: flex;\n align-items: center;\n justify-content: center;\n `,\n backdrop: css`\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n background-color: rgba(0, 0, 0, 0.3);\n `,\n panel: css`\n position: relative;\n background-color: white;\n border-radius: 12px;\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);\n width: 100%;\n max-width: 512px;\n padding: 24px;\n `,\n header: css`\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 24px;\n `,\n title: css`\n font-size: 18px;\n font-weight: 600;\n margin: 0;\n `,\n closeBtn: css`\n padding: 4px;\n background: none;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n sections: css`\n display: flex;\n flex-direction: column;\n gap: 24px;\n `,\n sectionTitle: css`\n font-size: 14px;\n font-weight: 500;\n color: #111827;\n margin: 0 0 12px 0;\n `,\n description: css`\n font-size: 12px;\n color: #6b7280;\n margin: 0 0 12px 0;\n `,\n code: css`\n background-color: #f9fafb;\n border-radius: 8px;\n padding: 12px;\n font-family: monospace;\n font-size: 12px;\n color: #4b5563;\n `,\n codeLine: css`\n margin: 0 0 4px 0;\n \n &:last-child {\n margin: 0;\n }\n `,\n input: css`\n width: 100%;\n padding: 8px 12px;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n font-size: 14px;\n \n &:focus {\n outline: none;\n box-shadow: 0 0 0 2px #a855f7;\n }\n `,\n grid: css`\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 12px;\n `,\n label: css`\n font-size: 12px;\n color: #6b7280;\n display: block;\n margin-bottom: 4px;\n `,\n footer: css`\n margin-top: 24px;\n display: flex;\n justify-content: flex-end;\n gap: 12px;\n `,\n cancelBtn: css`\n padding: 8px 16px;\n font-size: 14px;\n color: #4b5563;\n background: none;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n saveBtn: css`\n padding: 8px 16px;\n font-size: 14px;\n color: white;\n background-color: #9333ea;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n \n &:hover {\n background-color: #7c3aed;\n }\n `,\n}\n\nexport function StudioSettings() {\n const [isOpen, setIsOpen] = useState(false)\n\n return (\n <>\n <button css={styles.btn} onClick={() => setIsOpen(true)} aria-label=\"Settings\">\n <svg\n css={styles.icon}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <path d=\"M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z\" />\n </svg>\n </button>\n\n {isOpen && <SettingsPanel onClose={() => setIsOpen(false)} />}\n </>\n )\n}\n\nfunction SettingsPanel({ onClose }: { onClose: () => void }) {\n return (\n <div css={styles.overlay}>\n <div css={styles.backdrop} onClick={onClose} />\n\n <div css={styles.panel}>\n <div css={styles.header}>\n <h2 css={styles.title}>Settings</h2>\n <button css={styles.closeBtn} onClick={onClose}>\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div css={styles.sections}>\n <section>\n <h3 css={styles.sectionTitle}>Cloudflare R2</h3>\n <p css={styles.description}>Configure in .env.local file:</p>\n <div css={styles.code}>\n <p css={styles.codeLine}>CLOUDFLARE_R2_ACCOUNT_ID</p>\n <p css={styles.codeLine}>CLOUDFLARE_R2_ACCESS_KEY_ID</p>\n <p css={styles.codeLine}>CLOUDFLARE_R2_SECRET_ACCESS_KEY</p>\n <p css={styles.codeLine}>CLOUDFLARE_R2_BUCKET_NAME</p>\n <p css={styles.codeLine}>CLOUDFLARE_R2_PUBLIC_URL</p>\n </div>\n </section>\n\n <section>\n <h3 css={styles.sectionTitle}>Custom CDN URL</h3>\n <p css={styles.description}>Override the default R2 URL with a custom domain:</p>\n <input css={styles.input} type=\"text\" placeholder=\"https://cdn.yourdomain.com\" />\n </section>\n\n <section>\n <h3 css={styles.sectionTitle}>Thumbnail Sizes</h3>\n <div css={styles.grid}>\n <div>\n <label css={styles.label}>Small</label>\n <input css={styles.input} type=\"number\" defaultValue={300} />\n </div>\n <div>\n <label css={styles.label}>Medium</label>\n <input css={styles.input} type=\"number\" defaultValue={700} />\n </div>\n <div>\n <label css={styles.label}>Large</label>\n <input css={styles.input} type=\"number\" defaultValue={1400} />\n </div>\n </div>\n </section>\n </div>\n\n <div css={styles.footer}>\n <button css={styles.cancelBtn} onClick={onClose}>Cancel</button>\n <button css={styles.saveBtn}>Save Changes</button>\n </div>\n </div>\n </div>\n )\n}\n"]}
|