@gallop.software/studio 0.1.21 → 0.1.22

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/chrisb/Sites/studio/dist/StudioUI-TPVIV5T7.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":["keyframes","styles","css"],"mappings":"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","file":"/Users/chrisb/Sites/studio/dist/StudioUI-TPVIV5T7.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 { colors, fontStack, fontSize, baseReset } from './tokens'\nimport type { FileItem, StudioMeta } from '../types'\n\ninterface StudioUIProps {\n onClose: () => void\n}\n\nconst styles = {\n container: css`\n ${baseReset}\n display: flex;\n flex-direction: column;\n height: 100%;\n background: ${colors.background};\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: ${fontSize.xl};\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: ${colors.surface};\n border: 1px solid ${colors.border};\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 border-color: ${colors.borderHover};\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'\nimport { colors, fontSize } from './tokens'\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.surface};\n border-bottom: 1px solid ${colors.border};\n `,\n left: css`\n display: flex;\n align-items: center;\n gap: 8px;\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: 8px 14px;\n border-radius: 6px;\n font-size: ${fontSize.base};\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: ${colors.borderHover};\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 icon: css`\n width: 15px;\n height: 15px;\n `,\n iconSpin: css`\n animation: ${spin} 1s linear infinite;\n `,\n selectionCount: css`\n font-size: ${fontSize.base};\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: ${fontSize.base};\n font-weight: 500;\n padding: 0;\n \n &:hover {\n text-decoration: underline;\n }\n `,\n divider: css`\n width: 1px;\n height: 24px;\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: 1px solid ${colors.border};\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.06);\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}\n onClick={handleReprocess}\n disabled={!hasSelection}\n >\n <RefreshIcon />\n Reprocess\n </button>\n <button\n css={[styles.btn, styles.btnDanger]}\n onClick={handleDeleteClick}\n disabled={!hasSelection}\n >\n <TrashIcon />\n Delete\n </button>\n <button\n css={styles.btn}\n onClick={handleSyncCdn}\n disabled={!hasSelection}\n >\n <CloudIcon />\n Sync CDN\n </button>\n <button css={styles.btn} 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}\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'\nimport { colors, fontSize, fontStack, baseReset } from './tokens'\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 font-family: ${fontStack};\n `,\n modal: css`\n ${baseReset}\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: ${fontSize.lg};\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: ${fontSize.base};\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: ${fontSize.base};\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: ${colors.borderHover};\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'\nimport { colors, fontSize } from './tokens'\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: ${colors.surface};\n border: 1px solid ${colors.border};\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 border-color: ${colors.borderHover};\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: ${fontSize.base};\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: ${fontSize.base};\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 { colors, fontSize } from './tokens'\nimport type { FileItem } from '../types'\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: ${fontSize.base};\n margin: 0 0 4px 0;\n \n &:last-child {\n color: ${colors.textMuted};\n font-size: ${fontSize.sm};\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: ${fontSize.sm};\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: ${fontSize.xs};\n color: ${colors.textMuted};\n margin: 2px 0 0 0;\n `,\n openBtn: css`\n flex-shrink: 0;\n font-size: ${fontSize.xs};\n font-weight: 500;\n color: ${colors.primary};\n background: ${colors.surface};\n border: 1px solid ${colors.border};\n padding: 4px 10px;\n cursor: pointer;\n border-radius: 4px;\n transition: all 0.15s ease;\n \n &:hover {\n background-color: ${colors.primaryLight};\n border-color: ${colors.primary};\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: ${fontSize.base};\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 { colors, fontSize } from './tokens'\nimport type { FileItem } from '../types'\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: ${fontSize.base};\n font-weight: 500;\n color: ${colors.text};\n letter-spacing: -0.01em;\n `,\n meta: css`\n font-size: ${fontSize.sm};\n color: ${colors.textSecondary};\n `,\n cdnBadge: css`\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: ${fontSize.xs};\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: ${fontSize.sm};\n color: ${colors.textMuted};\n `,\n openBtn: css`\n font-size: ${fontSize.xs};\n font-weight: 500;\n color: ${colors.primary};\n background: ${colors.surface};\n border: 1px solid ${colors.border};\n padding: 4px 12px;\n cursor: pointer;\n border-radius: 4px;\n transition: all 0.15s ease;\n \n &:hover {\n background-color: ${colors.primaryLight};\n border-color: ${colors.primary};\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'\nimport { colors, fontSize } from './tokens'\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\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: ${fontSize.sm};\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: ${fontSize.sm};\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: ${fontSize.xs};\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: ${fontSize.sm};\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: ${fontSize.sm};\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: ${fontSize.sm};\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: ${fontSize.base};\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'\nimport { colors, fontSize, baseReset } from './tokens'\n\nconst styles = {\n btn: css`\n padding: 8px;\n background: ${colors.surface};\n border: 1px solid ${colors.border};\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 border-color: ${colors.borderHover};\n }\n `,\n icon: css`\n width: 18px;\n height: 18px;\n color: ${colors.textSecondary};\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 background-color: rgba(26, 31, 54, 0.4);\n backdrop-filter: blur(4px);\n `,\n panel: css`\n ${baseReset}\n position: relative;\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 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: ${fontSize.xl};\n font-weight: 600;\n color: ${colors.text};\n margin: 0;\n letter-spacing: -0.02em;\n `,\n closeBtn: css`\n padding: 6px;\n background: ${colors.surface};\n border: 1px solid ${colors.border};\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 border-color: ${colors.borderHover};\n }\n `,\n sections: css`\n display: flex;\n flex-direction: column;\n gap: 24px;\n `,\n sectionTitle: css`\n font-size: ${fontSize.base};\n font-weight: 600;\n color: ${colors.text};\n margin: 0 0 12px 0;\n `,\n description: css`\n font-size: ${fontSize.sm};\n color: ${colors.textSecondary};\n margin: 0 0 12px 0;\n `,\n code: css`\n background-color: ${colors.background};\n border-radius: 8px;\n padding: 12px;\n font-family: 'SF Mono', Monaco, Consolas, monospace;\n font-size: ${fontSize.xs};\n color: ${colors.textSecondary};\n border: 1px solid ${colors.border};\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: 10px 14px;\n border: 1px solid ${colors.border};\n border-radius: 6px;\n font-size: ${fontSize.base};\n color: ${colors.text};\n background: ${colors.surface};\n transition: all 0.15s ease;\n \n &:focus {\n outline: none;\n border-color: ${colors.primary};\n box-shadow: 0 0 0 3px ${colors.primaryLight};\n }\n \n &::placeholder {\n color: ${colors.textMuted};\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: ${fontSize.xs};\n font-weight: 500;\n color: ${colors.textSecondary};\n display: block;\n margin-bottom: 6px;\n `,\n footer: css`\n margin-top: 24px;\n padding-top: 20px;\n border-top: 1px solid ${colors.border};\n display: flex;\n justify-content: flex-end;\n gap: 12px;\n `,\n cancelBtn: css`\n padding: 10px 18px;\n font-size: ${fontSize.base};\n font-weight: 500;\n color: ${colors.text};\n background: ${colors.surface};\n border: 1px solid ${colors.border};\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.15s ease;\n \n &:hover {\n background-color: ${colors.surfaceHover};\n border-color: ${colors.borderHover};\n }\n `,\n saveBtn: css`\n padding: 10px 18px;\n font-size: ${fontSize.base};\n font-weight: 500;\n color: white;\n background-color: ${colors.primary};\n border: 1px solid ${colors.primary};\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.15s ease;\n \n &:hover {\n background-color: ${colors.primaryHover};\n border-color: ${colors.primaryHover};\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} onClick={onClose}>\n <div css={styles.panel} onClick={(e) => e.stopPropagation()}>\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"]}
@@ -0,0 +1,64 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});// src/components/tokens.ts
2
+ var _react = require('@emotion/react');
3
+ var fontStack = `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif`;
4
+ var colors = {
5
+ // Primary brand
6
+ primary: "#635bff",
7
+ primaryHover: "#5851e5",
8
+ primaryLight: "#f0f0ff",
9
+ // Backgrounds
10
+ background: "#f6f9fc",
11
+ surface: "#ffffff",
12
+ surfaceHover: "#f6f9fc",
13
+ // Borders
14
+ border: "#d8dee4",
15
+ borderLight: "#e3e8ee",
16
+ borderHover: "#c1c9d2",
17
+ // Text
18
+ text: "#1a1f36",
19
+ textSecondary: "#697386",
20
+ textMuted: "#8792a2",
21
+ // Status
22
+ success: "#0d7d4d",
23
+ successLight: "#e6f7ef",
24
+ danger: "#df1b41",
25
+ dangerHover: "#c41535",
26
+ dangerLight: "#fff5f7",
27
+ // Shadows
28
+ shadow: "rgba(50, 50, 93, 0.1)",
29
+ shadowDark: "rgba(50, 50, 93, 0.2)"
30
+ };
31
+ var fontSize = {
32
+ xs: "12px",
33
+ sm: "13px",
34
+ base: "14px",
35
+ md: "15px",
36
+ lg: "16px",
37
+ xl: "18px"
38
+ };
39
+ var baseReset = _react.css`
40
+ font-family: ${fontStack};
41
+ font-size: ${fontSize.base};
42
+ line-height: 1.5;
43
+ color: ${colors.text};
44
+ -webkit-font-smoothing: antialiased;
45
+ -moz-osx-font-smoothing: grayscale;
46
+ box-sizing: border-box;
47
+
48
+ *, *::before, *::after {
49
+ box-sizing: border-box;
50
+ }
51
+
52
+ button, input, select, textarea {
53
+ font-family: inherit;
54
+ font-size: inherit;
55
+ }
56
+ `;
57
+
58
+
59
+
60
+
61
+
62
+
63
+ exports.fontStack = fontStack; exports.colors = colors; exports.fontSize = fontSize; exports.baseReset = baseReset;
64
+ //# sourceMappingURL=chunk-AY2DAS6W.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/chrisb/Sites/studio/dist/chunk-AY2DAS6W.js","../src/components/tokens.ts"],"names":[],"mappings":"AAAA;ACAA,uCAAoB;AAQb,IAAM,UAAA,EAAY,CAAA,2FAAA,CAAA;AAGlB,IAAM,OAAA,EAAS;AAAA;AAAA,EAEpB,OAAA,EAAS,SAAA;AAAA,EACT,YAAA,EAAc,SAAA;AAAA,EACd,YAAA,EAAc,SAAA;AAAA;AAAA,EAGd,UAAA,EAAY,SAAA;AAAA,EACZ,OAAA,EAAS,SAAA;AAAA,EACT,YAAA,EAAc,SAAA;AAAA;AAAA,EAGd,MAAA,EAAQ,SAAA;AAAA,EACR,WAAA,EAAa,SAAA;AAAA,EACb,WAAA,EAAa,SAAA;AAAA;AAAA,EAGb,IAAA,EAAM,SAAA;AAAA,EACN,aAAA,EAAe,SAAA;AAAA,EACf,SAAA,EAAW,SAAA;AAAA;AAAA,EAGX,OAAA,EAAS,SAAA;AAAA,EACT,YAAA,EAAc,SAAA;AAAA,EACd,MAAA,EAAQ,SAAA;AAAA,EACR,WAAA,EAAa,SAAA;AAAA,EACb,WAAA,EAAa,SAAA;AAAA;AAAA,EAGb,MAAA,EAAQ,uBAAA;AAAA,EACR,UAAA,EAAY;AACd,CAAA;AAGO,IAAM,SAAA,EAAW;AAAA,EACtB,EAAA,EAAI,MAAA;AAAA,EACJ,EAAA,EAAI,MAAA;AAAA,EACJ,IAAA,EAAM,MAAA;AAAA,EACN,EAAA,EAAI,MAAA;AAAA,EACJ,EAAA,EAAI,MAAA;AAAA,EACJ,EAAA,EAAI;AACN,CAAA;AAGO,IAAM,UAAA,EAAY,UAAA,CAAA;AAAA,eAAA,EACR,SAAS,CAAA;AAAA,aAAA,EACX,QAAA,CAAS,IAAI,CAAA;AAAA;AAAA,SAAA,EAEjB,MAAA,CAAO,IAAI,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;ADHtB;AACA;AACE;AACA;AACA;AACA;AACF,mHAAC","file":"/Users/chrisb/Sites/studio/dist/chunk-AY2DAS6W.js","sourcesContent":[null,"import { css } from '@emotion/react'\n\n/**\n * Stripe-inspired design tokens for Studio\n * These are self-contained and agnostic of any parent template styling\n */\n\n// Base font stack - system fonts that work everywhere\nexport const fontStack = `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif`\n\n// Color palette\nexport const colors = {\n // Primary brand\n primary: '#635bff',\n primaryHover: '#5851e5',\n primaryLight: '#f0f0ff',\n \n // Backgrounds\n background: '#f6f9fc',\n surface: '#ffffff',\n surfaceHover: '#f6f9fc',\n \n // Borders\n border: '#d8dee4',\n borderLight: '#e3e8ee',\n borderHover: '#c1c9d2',\n \n // Text\n text: '#1a1f36',\n textSecondary: '#697386',\n textMuted: '#8792a2',\n \n // Status\n success: '#0d7d4d',\n successLight: '#e6f7ef',\n danger: '#df1b41',\n dangerHover: '#c41535',\n dangerLight: '#fff5f7',\n \n // Shadows\n shadow: 'rgba(50, 50, 93, 0.1)',\n shadowDark: 'rgba(50, 50, 93, 0.2)',\n}\n\n// Font sizes - slightly larger for better readability\nexport const fontSize = {\n xs: '12px',\n sm: '13px',\n base: '14px',\n md: '15px',\n lg: '16px',\n xl: '18px',\n}\n\n// Base reset styles for Studio container - isolates from parent template\nexport const baseReset = css`\n font-family: ${fontStack};\n font-size: ${fontSize.base};\n line-height: 1.5;\n color: ${colors.text};\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n box-sizing: border-box;\n \n *, *::before, *::after {\n box-sizing: border-box;\n }\n \n button, input, select, textarea {\n font-family: inherit;\n font-size: inherit;\n }\n`\n"]}
@@ -0,0 +1,64 @@
1
+ // src/components/tokens.ts
2
+ import { css } from "@emotion/react";
3
+ var fontStack = `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif`;
4
+ var colors = {
5
+ // Primary brand
6
+ primary: "#635bff",
7
+ primaryHover: "#5851e5",
8
+ primaryLight: "#f0f0ff",
9
+ // Backgrounds
10
+ background: "#f6f9fc",
11
+ surface: "#ffffff",
12
+ surfaceHover: "#f6f9fc",
13
+ // Borders
14
+ border: "#d8dee4",
15
+ borderLight: "#e3e8ee",
16
+ borderHover: "#c1c9d2",
17
+ // Text
18
+ text: "#1a1f36",
19
+ textSecondary: "#697386",
20
+ textMuted: "#8792a2",
21
+ // Status
22
+ success: "#0d7d4d",
23
+ successLight: "#e6f7ef",
24
+ danger: "#df1b41",
25
+ dangerHover: "#c41535",
26
+ dangerLight: "#fff5f7",
27
+ // Shadows
28
+ shadow: "rgba(50, 50, 93, 0.1)",
29
+ shadowDark: "rgba(50, 50, 93, 0.2)"
30
+ };
31
+ var fontSize = {
32
+ xs: "12px",
33
+ sm: "13px",
34
+ base: "14px",
35
+ md: "15px",
36
+ lg: "16px",
37
+ xl: "18px"
38
+ };
39
+ var baseReset = css`
40
+ font-family: ${fontStack};
41
+ font-size: ${fontSize.base};
42
+ line-height: 1.5;
43
+ color: ${colors.text};
44
+ -webkit-font-smoothing: antialiased;
45
+ -moz-osx-font-smoothing: grayscale;
46
+ box-sizing: border-box;
47
+
48
+ *, *::before, *::after {
49
+ box-sizing: border-box;
50
+ }
51
+
52
+ button, input, select, textarea {
53
+ font-family: inherit;
54
+ font-size: inherit;
55
+ }
56
+ `;
57
+
58
+ export {
59
+ fontStack,
60
+ colors,
61
+ fontSize,
62
+ baseReset
63
+ };
64
+ //# sourceMappingURL=chunk-R5WKNVEV.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/tokens.ts"],"sourcesContent":["import { css } from '@emotion/react'\n\n/**\n * Stripe-inspired design tokens for Studio\n * These are self-contained and agnostic of any parent template styling\n */\n\n// Base font stack - system fonts that work everywhere\nexport const fontStack = `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif`\n\n// Color palette\nexport const colors = {\n // Primary brand\n primary: '#635bff',\n primaryHover: '#5851e5',\n primaryLight: '#f0f0ff',\n \n // Backgrounds\n background: '#f6f9fc',\n surface: '#ffffff',\n surfaceHover: '#f6f9fc',\n \n // Borders\n border: '#d8dee4',\n borderLight: '#e3e8ee',\n borderHover: '#c1c9d2',\n \n // Text\n text: '#1a1f36',\n textSecondary: '#697386',\n textMuted: '#8792a2',\n \n // Status\n success: '#0d7d4d',\n successLight: '#e6f7ef',\n danger: '#df1b41',\n dangerHover: '#c41535',\n dangerLight: '#fff5f7',\n \n // Shadows\n shadow: 'rgba(50, 50, 93, 0.1)',\n shadowDark: 'rgba(50, 50, 93, 0.2)',\n}\n\n// Font sizes - slightly larger for better readability\nexport const fontSize = {\n xs: '12px',\n sm: '13px',\n base: '14px',\n md: '15px',\n lg: '16px',\n xl: '18px',\n}\n\n// Base reset styles for Studio container - isolates from parent template\nexport const baseReset = css`\n font-family: ${fontStack};\n font-size: ${fontSize.base};\n line-height: 1.5;\n color: ${colors.text};\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n box-sizing: border-box;\n \n *, *::before, *::after {\n box-sizing: border-box;\n }\n \n button, input, select, textarea {\n font-family: inherit;\n font-size: inherit;\n }\n`\n"],"mappings":";AAAA,SAAS,WAAW;AAQb,IAAM,YAAY;AAGlB,IAAM,SAAS;AAAA;AAAA,EAEpB,SAAS;AAAA,EACT,cAAc;AAAA,EACd,cAAc;AAAA;AAAA,EAGd,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,cAAc;AAAA;AAAA,EAGd,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,aAAa;AAAA;AAAA,EAGb,MAAM;AAAA,EACN,eAAe;AAAA,EACf,WAAW;AAAA;AAAA,EAGX,SAAS;AAAA,EACT,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,aAAa;AAAA;AAAA,EAGb,QAAQ;AAAA,EACR,YAAY;AACd;AAGO,IAAM,WAAW;AAAA,EACtB,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAGO,IAAM,YAAY;AAAA,iBACR,SAAS;AAAA,eACX,SAAS,IAAI;AAAA;AAAA,WAEjB,OAAO,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":[]}
package/dist/index.js CHANGED
@@ -1,27 +1,21 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }"use client";
2
2
 
3
+
4
+
5
+
6
+
7
+ var _chunkAY2DAS6Wjs = require('./chunk-AY2DAS6W.js');
8
+
3
9
  // src/components/StudioButton.tsx
4
10
  var _react = require('react');
5
11
  var _react3 = require('@emotion/react');
6
12
  var _jsxruntime = require('@emotion/react/jsx-runtime');
7
- var StudioUI = _react.lazy.call(void 0, () => Promise.resolve().then(() => _interopRequireWildcard(require("./StudioUI-QMBOCTJD.js"))));
13
+ var StudioUI = _react.lazy.call(void 0, () => Promise.resolve().then(() => _interopRequireWildcard(require("./StudioUI-TPVIV5T7.js"))));
8
14
  var spin = _react3.keyframes`
9
15
  to {
10
16
  transform: rotate(360deg);
11
17
  }
12
18
  `;
13
- var colors = {
14
- primary: "#635bff",
15
- primaryHover: "#5851e5",
16
- primaryLight: "#e8e6ff",
17
- background: "#f6f9fc",
18
- surface: "#ffffff",
19
- border: "#e3e8ee",
20
- text: "#1a1f36",
21
- textSecondary: "#697386",
22
- shadow: "rgba(50, 50, 93, 0.1)",
23
- shadowDark: "rgba(50, 50, 93, 0.2)"
24
- };
25
19
  var styles = {
26
20
  button: _react3.css`
27
21
  position: fixed;
@@ -31,20 +25,21 @@ var styles = {
31
25
  width: 52px;
32
26
  height: 52px;
33
27
  border-radius: 50%;
34
- background: ${colors.primary};
28
+ background: ${_chunkAY2DAS6Wjs.colors.primary};
35
29
  color: white;
36
- box-shadow: 0 4px 12px ${colors.shadowDark}, 0 1px 3px ${colors.shadow};
30
+ box-shadow: 0 4px 12px ${_chunkAY2DAS6Wjs.colors.shadowDark}, 0 1px 3px ${_chunkAY2DAS6Wjs.colors.shadow};
37
31
  display: flex;
38
32
  align-items: center;
39
33
  justify-content: center;
40
34
  border: none;
41
35
  cursor: pointer;
42
36
  transition: all 0.15s ease;
37
+ font-family: ${_chunkAY2DAS6Wjs.fontStack};
43
38
 
44
39
  &:hover {
45
40
  transform: translateY(-2px);
46
- box-shadow: 0 8px 20px ${colors.shadowDark}, 0 2px 6px ${colors.shadow};
47
- background: ${colors.primaryHover};
41
+ box-shadow: 0 8px 20px ${_chunkAY2DAS6Wjs.colors.shadowDark}, 0 2px 6px ${_chunkAY2DAS6Wjs.colors.shadow};
42
+ background: ${_chunkAY2DAS6Wjs.colors.primaryHover};
48
43
  }
49
44
 
50
45
  &:active {
@@ -79,12 +74,13 @@ var styles = {
79
74
  backdrop-filter: blur(4px);
80
75
  `,
81
76
  modal: _react3.css`
77
+ ${_chunkAY2DAS6Wjs.baseReset}
82
78
  position: absolute;
83
79
  top: 24px;
84
80
  right: 24px;
85
81
  bottom: 24px;
86
82
  left: 24px;
87
- background-color: ${colors.surface};
83
+ background-color: ${_chunkAY2DAS6Wjs.colors.surface};
88
84
  border-radius: 12px;
89
85
  box-shadow: 0 30px 60px -12px rgba(50, 50, 93, 0.25), 0 18px 36px -18px rgba(0, 0, 0, 0.3);
90
86
  display: flex;
@@ -96,7 +92,8 @@ var styles = {
96
92
  align-items: center;
97
93
  justify-content: center;
98
94
  height: 100%;
99
- background: ${colors.background};
95
+ background: ${_chunkAY2DAS6Wjs.colors.background};
96
+ font-family: ${_chunkAY2DAS6Wjs.fontStack};
100
97
  `,
101
98
  loadingContent: _react3.css`
102
99
  display: flex;
@@ -108,13 +105,13 @@ var styles = {
108
105
  width: 36px;
109
106
  height: 36px;
110
107
  border-radius: 50%;
111
- border: 3px solid ${colors.border};
112
- border-top-color: ${colors.primary};
108
+ border: 3px solid ${_chunkAY2DAS6Wjs.colors.border};
109
+ border-top-color: ${_chunkAY2DAS6Wjs.colors.primary};
113
110
  animation: ${spin} 0.8s linear infinite;
114
111
  `,
115
112
  loadingText: _react3.css`
116
- color: ${colors.textSecondary};
117
- font-size: 14px;
113
+ color: ${_chunkAY2DAS6Wjs.colors.textSecondary};
114
+ font-size: ${_chunkAY2DAS6Wjs.fontSize.base};
118
115
  font-weight: 500;
119
116
  margin: 0;
120
117
  letter-spacing: -0.01em;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/chrisb/Sites/studio/dist/index.js","../src/components/StudioButton.tsx","../src/lib/meta.ts"],"names":[],"mappings":"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","file":"/Users/chrisb/Sites/studio/dist/index.js","sourcesContent":[null,"/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useState, useEffect, lazy, Suspense } from 'react'\nimport { css, keyframes } from '@emotion/react'\n\n// Lazy load the full Studio UI to avoid bundling in production\nconst StudioUI = lazy(() => import('./StudioUI'))\n\nconst spin = keyframes`\n to {\n transform: rotate(360deg);\n }\n`\n\n// Stripe-inspired design tokens\nconst colors = {\n primary: '#635bff',\n primaryHover: '#5851e5',\n primaryLight: '#e8e6ff',\n background: '#f6f9fc',\n surface: '#ffffff',\n border: '#e3e8ee',\n text: '#1a1f36',\n textSecondary: '#697386',\n shadow: 'rgba(50, 50, 93, 0.1)',\n shadowDark: 'rgba(50, 50, 93, 0.2)',\n}\n\nconst styles = {\n button: css`\n position: fixed;\n bottom: 24px;\n right: 24px;\n z-index: 9998;\n width: 52px;\n height: 52px;\n border-radius: 50%;\n background: ${colors.primary};\n color: white;\n box-shadow: 0 4px 12px ${colors.shadowDark}, 0 1px 3px ${colors.shadow};\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n cursor: pointer;\n transition: all 0.15s ease;\n \n &:hover {\n transform: translateY(-2px);\n box-shadow: 0 8px 20px ${colors.shadowDark}, 0 2px 6px ${colors.shadow};\n background: ${colors.primaryHover};\n }\n \n &:active {\n transform: translateY(0);\n }\n `,\n buttonIcon: css`\n width: 24px;\n height: 24px;\n `,\n overlay: css`\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 9999;\n transition: opacity 0.2s ease, visibility 0.2s ease;\n `,\n overlayHidden: css`\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n `,\n backdrop: css`\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n background-color: rgba(26, 31, 54, 0.4);\n backdrop-filter: blur(4px);\n `,\n modal: css`\n position: absolute;\n top: 24px;\n right: 24px;\n bottom: 24px;\n left: 24px;\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 display: flex;\n flex-direction: column;\n overflow: hidden;\n `,\n loading: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n background: ${colors.background};\n `,\n loadingContent: css`\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n `,\n spinner: css`\n width: 36px;\n height: 36px;\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 loadingText: css`\n color: ${colors.textSecondary};\n font-size: 14px;\n font-weight: 500;\n margin: 0;\n letter-spacing: -0.01em;\n `,\n}\n\n/**\n * Floating button that opens the Studio modal.\n * Fixed position in bottom-right corner.\n * Only renders in development mode.\n */\nexport function StudioButton() {\n const [mounted, setMounted] = useState(false)\n const [isOpen, setIsOpen] = useState(false)\n const [hasBeenOpened, setHasBeenOpened] = useState(false)\n\n // Only render on client to avoid hydration mismatch\n useEffect(() => {\n setMounted(true)\n }, [])\n\n const handleOpen = () => {\n setIsOpen(true)\n setHasBeenOpened(true)\n }\n\n // Only render in development and on client\n if (!mounted || process.env.NODE_ENV !== 'development') {\n return null\n }\n\n return (\n <>\n {!isOpen && (\n <button\n css={styles.button}\n onClick={handleOpen}\n title=\"Open Studio\"\n aria-label=\"Open Studio media manager\"\n >\n <ImageIcon />\n </button>\n )}\n\n {/* Keep mounted once opened to preserve state */}\n {hasBeenOpened && (\n <div css={[styles.overlay, !isOpen && styles.overlayHidden]}>\n <div css={styles.backdrop} onClick={() => setIsOpen(false)} />\n <div css={styles.modal}>\n <Suspense fallback={<LoadingState />}>\n <StudioUI onClose={() => setIsOpen(false)} />\n </Suspense>\n </div>\n </div>\n )}\n </>\n )\n}\n\nfunction ImageIcon() {\n return (\n <svg\n css={styles.buttonIcon}\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 <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\" />\n <circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\" />\n <polyline points=\"21 15 16 10 5 21\" />\n </svg>\n )\n}\n\nfunction LoadingState() {\n return (\n <div css={styles.loading}>\n <div css={styles.loadingContent}>\n <div css={styles.spinner} />\n <p css={styles.loadingText}>Loading Studio...</p>\n </div>\n </div>\n )\n}\n","import type { StudioMeta, ImageEntry, ImageSize, SizeEntry } from '../types'\n\n// Default empty meta - will be populated when reading from project\nlet _meta: StudioMeta = {\n $schema: 'https://gallop.software/schemas/studio-meta.json',\n version: 1,\n generatedAt: new Date().toISOString(),\n images: {},\n}\n\n/**\n * The meta object containing all image metadata.\n * This is read from _data/_meta.json in the consuming project.\n */\nexport const meta: StudioMeta = _meta\n\n/**\n * Initialize meta from a JSON object (called during build/runtime)\n */\nexport function initializeMeta(data: StudioMeta): void {\n _meta = data\n Object.assign(meta, data)\n}\n\n/**\n * Get the resolved URL for an image, handling CDN vs local paths\n */\nexport function getImageUrl(\n imageKey: string,\n size: ImageSize = 'medium'\n): string | undefined {\n const image = meta.images[imageKey]\n if (!image) return undefined\n\n const sizeData = image.sizes[size] || image.sizes.full\n if (!sizeData) return undefined\n\n // If synced to CDN, use CDN URL\n if (image.cdn?.synced && image.cdn.baseUrl) {\n return `${image.cdn.baseUrl}${sizeData.path}`\n }\n\n // Otherwise use local path\n return sizeData.path\n}\n\n/**\n * Get the full image entry for a key\n */\nexport function getStudioMeta(imageKey: string): ImageEntry | undefined {\n return meta.images[imageKey]\n}\n\n/**\n * Get size data for an image\n */\nexport function getImageSize(\n imageKey: string,\n size: ImageSize = 'medium'\n): SizeEntry | undefined {\n const image = meta.images[imageKey]\n if (!image) return undefined\n return image.sizes[size] || image.sizes.full\n}\n"]}
1
+ {"version":3,"sources":["/Users/chrisb/Sites/studio/dist/index.js","../src/components/StudioButton.tsx","../src/lib/meta.ts"],"names":[],"mappings":"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","file":"/Users/chrisb/Sites/studio/dist/index.js","sourcesContent":[null,"/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useState, useEffect, lazy, Suspense } from 'react'\nimport { css, keyframes } from '@emotion/react'\nimport { colors, fontStack, fontSize, baseReset } from './tokens'\n\n// Lazy load the full Studio UI to avoid bundling in production\nconst StudioUI = lazy(() => import('./StudioUI'))\n\nconst spin = keyframes`\n to {\n transform: rotate(360deg);\n }\n`\n\nconst styles = {\n button: css`\n position: fixed;\n bottom: 24px;\n right: 24px;\n z-index: 9998;\n width: 52px;\n height: 52px;\n border-radius: 50%;\n background: ${colors.primary};\n color: white;\n box-shadow: 0 4px 12px ${colors.shadowDark}, 0 1px 3px ${colors.shadow};\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n cursor: pointer;\n transition: all 0.15s ease;\n font-family: ${fontStack};\n \n &:hover {\n transform: translateY(-2px);\n box-shadow: 0 8px 20px ${colors.shadowDark}, 0 2px 6px ${colors.shadow};\n background: ${colors.primaryHover};\n }\n \n &:active {\n transform: translateY(0);\n }\n `,\n buttonIcon: css`\n width: 24px;\n height: 24px;\n `,\n overlay: css`\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 9999;\n transition: opacity 0.2s ease, visibility 0.2s ease;\n `,\n overlayHidden: css`\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n `,\n backdrop: css`\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n background-color: rgba(26, 31, 54, 0.4);\n backdrop-filter: blur(4px);\n `,\n modal: css`\n ${baseReset}\n position: absolute;\n top: 24px;\n right: 24px;\n bottom: 24px;\n left: 24px;\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 display: flex;\n flex-direction: column;\n overflow: hidden;\n `,\n loading: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n background: ${colors.background};\n font-family: ${fontStack};\n `,\n loadingContent: css`\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n `,\n spinner: css`\n width: 36px;\n height: 36px;\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 loadingText: css`\n color: ${colors.textSecondary};\n font-size: ${fontSize.base};\n font-weight: 500;\n margin: 0;\n letter-spacing: -0.01em;\n `,\n}\n\n/**\n * Floating button that opens the Studio modal.\n * Fixed position in bottom-right corner.\n * Only renders in development mode.\n */\nexport function StudioButton() {\n const [mounted, setMounted] = useState(false)\n const [isOpen, setIsOpen] = useState(false)\n const [hasBeenOpened, setHasBeenOpened] = useState(false)\n\n // Only render on client to avoid hydration mismatch\n useEffect(() => {\n setMounted(true)\n }, [])\n\n const handleOpen = () => {\n setIsOpen(true)\n setHasBeenOpened(true)\n }\n\n // Only render in development and on client\n if (!mounted || process.env.NODE_ENV !== 'development') {\n return null\n }\n\n return (\n <>\n {!isOpen && (\n <button\n css={styles.button}\n onClick={handleOpen}\n title=\"Open Studio\"\n aria-label=\"Open Studio media manager\"\n >\n <ImageIcon />\n </button>\n )}\n\n {/* Keep mounted once opened to preserve state */}\n {hasBeenOpened && (\n <div css={[styles.overlay, !isOpen && styles.overlayHidden]}>\n <div css={styles.backdrop} onClick={() => setIsOpen(false)} />\n <div css={styles.modal}>\n <Suspense fallback={<LoadingState />}>\n <StudioUI onClose={() => setIsOpen(false)} />\n </Suspense>\n </div>\n </div>\n )}\n </>\n )\n}\n\nfunction ImageIcon() {\n return (\n <svg\n css={styles.buttonIcon}\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 <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\" />\n <circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\" />\n <polyline points=\"21 15 16 10 5 21\" />\n </svg>\n )\n}\n\nfunction LoadingState() {\n return (\n <div css={styles.loading}>\n <div css={styles.loadingContent}>\n <div css={styles.spinner} />\n <p css={styles.loadingText}>Loading Studio...</p>\n </div>\n </div>\n )\n}\n","import type { StudioMeta, ImageEntry, ImageSize, SizeEntry } from '../types'\n\n// Default empty meta - will be populated when reading from project\nlet _meta: StudioMeta = {\n $schema: 'https://gallop.software/schemas/studio-meta.json',\n version: 1,\n generatedAt: new Date().toISOString(),\n images: {},\n}\n\n/**\n * The meta object containing all image metadata.\n * This is read from _data/_meta.json in the consuming project.\n */\nexport const meta: StudioMeta = _meta\n\n/**\n * Initialize meta from a JSON object (called during build/runtime)\n */\nexport function initializeMeta(data: StudioMeta): void {\n _meta = data\n Object.assign(meta, data)\n}\n\n/**\n * Get the resolved URL for an image, handling CDN vs local paths\n */\nexport function getImageUrl(\n imageKey: string,\n size: ImageSize = 'medium'\n): string | undefined {\n const image = meta.images[imageKey]\n if (!image) return undefined\n\n const sizeData = image.sizes[size] || image.sizes.full\n if (!sizeData) return undefined\n\n // If synced to CDN, use CDN URL\n if (image.cdn?.synced && image.cdn.baseUrl) {\n return `${image.cdn.baseUrl}${sizeData.path}`\n }\n\n // Otherwise use local path\n return sizeData.path\n}\n\n/**\n * Get the full image entry for a key\n */\nexport function getStudioMeta(imageKey: string): ImageEntry | undefined {\n return meta.images[imageKey]\n}\n\n/**\n * Get size data for an image\n */\nexport function getImageSize(\n imageKey: string,\n size: ImageSize = 'medium'\n): SizeEntry | undefined {\n const image = meta.images[imageKey]\n if (!image) return undefined\n return image.sizes[size] || image.sizes.full\n}\n"]}
package/dist/index.mjs CHANGED
@@ -1,27 +1,21 @@
1
1
  "use client";
2
+ import {
3
+ baseReset,
4
+ colors,
5
+ fontSize,
6
+ fontStack
7
+ } from "./chunk-R5WKNVEV.mjs";
2
8
 
3
9
  // src/components/StudioButton.tsx
4
10
  import { useState, useEffect, lazy, Suspense } from "react";
5
11
  import { css, keyframes } from "@emotion/react";
6
12
  import { Fragment, jsx, jsxs } from "@emotion/react/jsx-runtime";
7
- var StudioUI = lazy(() => import("./StudioUI-WRFD73YR.mjs"));
13
+ var StudioUI = lazy(() => import("./StudioUI-3VFEM3VE.mjs"));
8
14
  var spin = keyframes`
9
15
  to {
10
16
  transform: rotate(360deg);
11
17
  }
12
18
  `;
13
- var colors = {
14
- primary: "#635bff",
15
- primaryHover: "#5851e5",
16
- primaryLight: "#e8e6ff",
17
- background: "#f6f9fc",
18
- surface: "#ffffff",
19
- border: "#e3e8ee",
20
- text: "#1a1f36",
21
- textSecondary: "#697386",
22
- shadow: "rgba(50, 50, 93, 0.1)",
23
- shadowDark: "rgba(50, 50, 93, 0.2)"
24
- };
25
19
  var styles = {
26
20
  button: css`
27
21
  position: fixed;
@@ -40,6 +34,7 @@ var styles = {
40
34
  border: none;
41
35
  cursor: pointer;
42
36
  transition: all 0.15s ease;
37
+ font-family: ${fontStack};
43
38
 
44
39
  &:hover {
45
40
  transform: translateY(-2px);
@@ -79,6 +74,7 @@ var styles = {
79
74
  backdrop-filter: blur(4px);
80
75
  `,
81
76
  modal: css`
77
+ ${baseReset}
82
78
  position: absolute;
83
79
  top: 24px;
84
80
  right: 24px;
@@ -97,6 +93,7 @@ var styles = {
97
93
  justify-content: center;
98
94
  height: 100%;
99
95
  background: ${colors.background};
96
+ font-family: ${fontStack};
100
97
  `,
101
98
  loadingContent: css`
102
99
  display: flex;
@@ -114,7 +111,7 @@ var styles = {
114
111
  `,
115
112
  loadingText: css`
116
113
  color: ${colors.textSecondary};
117
- font-size: 14px;
114
+ font-size: ${fontSize.base};
118
115
  font-weight: 500;
119
116
  margin: 0;
120
117
  letter-spacing: -0.01em;