@gallop.software/studio 0.1.15 → 0.1.16

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.
@@ -741,6 +741,11 @@ var styles4 = {
741
741
  height: 64px;
742
742
  color: #facc15;
743
743
  `,
744
+ fileIcon: _react3.css`
745
+ width: 48px;
746
+ height: 48px;
747
+ color: #9ca3af;
748
+ `,
744
749
  image: _react3.css`
745
750
  max-width: 100%;
746
751
  max-height: 100%;
@@ -825,16 +830,6 @@ function StudioFileGrid() {
825
830
  if (a.type !== "folder" && b.type === "folder") return 1;
826
831
  return a.name.localeCompare(b.name);
827
832
  });
828
- const files = sortedItems.filter((item) => item.type !== "folder");
829
- const allFilesSelected = files.length > 0 && files.every((item) => selectedItems.has(item.path));
830
- const someFilesSelected = files.some((item) => selectedItems.has(item.path));
831
- const handleSelectAll = () => {
832
- if (allFilesSelected) {
833
- clearSelection();
834
- } else {
835
- selectAll(files);
836
- }
837
- };
838
833
  const handleItemClick = (item, e) => {
839
834
  if (item.type === "folder") {
840
835
  setCurrentPath(item.path);
@@ -846,22 +841,38 @@ function StudioFileGrid() {
846
841
  toggleSelection(item.path);
847
842
  }
848
843
  };
844
+ const handleCheckboxClick = (item, e) => {
845
+ if (e.shiftKey && lastSelectedPath) {
846
+ selectRange(lastSelectedPath, item.path, sortedItems);
847
+ } else {
848
+ toggleSelection(item.path);
849
+ }
850
+ };
851
+ const allItemsSelected = sortedItems.length > 0 && sortedItems.every((item) => selectedItems.has(item.path));
852
+ const someItemsSelected = sortedItems.some((item) => selectedItems.has(item.path));
853
+ const handleSelectAll = () => {
854
+ if (allItemsSelected) {
855
+ clearSelection();
856
+ } else {
857
+ selectAll(sortedItems);
858
+ }
859
+ };
849
860
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { children: [
850
- files.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.selectAllRow, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "label", { css: styles4.selectAllLabel, children: [
861
+ sortedItems.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.selectAllRow, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "label", { css: styles4.selectAllLabel, children: [
851
862
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
852
863
  "input",
853
864
  {
854
865
  type: "checkbox",
855
866
  css: styles4.selectAllCheckbox,
856
- checked: allFilesSelected,
867
+ checked: allItemsSelected,
857
868
  ref: (el) => {
858
- if (el) el.indeterminate = someFilesSelected && !allFilesSelected;
869
+ if (el) el.indeterminate = someItemsSelected && !allItemsSelected;
859
870
  },
860
871
  onChange: handleSelectAll
861
872
  }
862
873
  ),
863
874
  "Select all (",
864
- files.length,
875
+ sortedItems.length,
865
876
  ")"
866
877
  ] }) }),
867
878
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.grid, children: sortedItems.map((item) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
@@ -869,16 +880,17 @@ function StudioFileGrid() {
869
880
  {
870
881
  item,
871
882
  isSelected: selectedItems.has(item.path),
872
- onClick: (e) => handleItemClick(item, e)
883
+ onClick: (e) => handleItemClick(item, e),
884
+ onCheckboxClick: (e) => handleCheckboxClick(item, e)
873
885
  },
874
886
  item.path
875
887
  )) })
876
888
  ] });
877
889
  }
878
- function GridItem({ item, isSelected, onClick }) {
890
+ function GridItem({ item, isSelected, onClick, onCheckboxClick }) {
879
891
  const isFolder = item.type === "folder";
880
892
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: [styles4.item, isSelected && styles4.itemSelected], onClick, children: [
881
- !isFolder && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
893
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
882
894
  "input",
883
895
  {
884
896
  type: "checkbox",
@@ -888,23 +900,27 @@ function GridItem({ item, isSelected, onClick }) {
888
900
  },
889
901
  onClick: (e) => {
890
902
  e.stopPropagation();
891
- onClick(e);
903
+ onCheckboxClick(e);
892
904
  }
893
905
  }
894
906
  ),
895
907
  item.cdnSynced && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles4.cdnBadge, children: "CDN" }),
896
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.content, children: isFolder ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.folderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "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" }) }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
908
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.content, children: isFolder ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.folderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "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" }) }) : item.thumbnail ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
897
909
  "img",
898
910
  {
899
911
  css: styles4.image,
900
- src: item.path.replace("public", ""),
912
+ src: item.thumbnail,
901
913
  alt: item.name,
902
914
  loading: "lazy"
903
915
  }
904
- ) }),
916
+ ) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.fileIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "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" }) }) }),
905
917
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles4.label, children: [
906
918
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles4.name, title: item.name, children: item.name }),
907
- item.size && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles4.size, children: formatFileSize(item.size) })
919
+ isFolder ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "p", { css: styles4.size, children: [
920
+ item.fileCount !== void 0 ? `${item.fileCount} files` : "",
921
+ item.fileCount !== void 0 && item.totalSize !== void 0 ? " \xB7 " : "",
922
+ item.totalSize !== void 0 ? formatFileSize(item.totalSize) : ""
923
+ ] }) : item.size !== void 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles4.size, children: formatFileSize(item.size) })
908
924
  ] })
909
925
  ] });
910
926
  }
@@ -1012,6 +1028,13 @@ var styles5 = {
1012
1028
  height: 20px;
1013
1029
  color: #9ca3af;
1014
1030
  `,
1031
+ thumbnail: _react3.css`
1032
+ width: 32px;
1033
+ height: 32px;
1034
+ object-fit: cover;
1035
+ border-radius: 4px;
1036
+ flex-shrink: 0;
1037
+ `,
1015
1038
  name: _react3.css`
1016
1039
  font-size: 14px;
1017
1040
  color: #111827;
@@ -1067,16 +1090,6 @@ function StudioFileList() {
1067
1090
  if (a.type !== "folder" && b.type === "folder") return 1;
1068
1091
  return a.name.localeCompare(b.name);
1069
1092
  });
1070
- const files = sortedItems.filter((item) => item.type !== "folder");
1071
- const allFilesSelected = files.length > 0 && files.every((item) => selectedItems.has(item.path));
1072
- const someFilesSelected = files.some((item) => selectedItems.has(item.path));
1073
- const handleSelectAll = () => {
1074
- if (allFilesSelected) {
1075
- clearSelection();
1076
- } else {
1077
- selectAll(files);
1078
- }
1079
- };
1080
1093
  const handleItemClick = (item, e) => {
1081
1094
  if (item.type === "folder") {
1082
1095
  setCurrentPath(item.path);
@@ -1088,16 +1101,32 @@ function StudioFileList() {
1088
1101
  toggleSelection(item.path);
1089
1102
  }
1090
1103
  };
1104
+ const handleCheckboxClick = (item, e) => {
1105
+ if (e.shiftKey && lastSelectedPath) {
1106
+ selectRange(lastSelectedPath, item.path, sortedItems);
1107
+ } else {
1108
+ toggleSelection(item.path);
1109
+ }
1110
+ };
1111
+ const allItemsSelected = sortedItems.length > 0 && sortedItems.every((item) => selectedItems.has(item.path));
1112
+ const someItemsSelected = sortedItems.some((item) => selectedItems.has(item.path));
1113
+ const handleSelectAll = () => {
1114
+ if (allItemsSelected) {
1115
+ clearSelection();
1116
+ } else {
1117
+ selectAll(sortedItems);
1118
+ }
1119
+ };
1091
1120
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "table", { css: styles5.table, children: [
1092
1121
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "thead", { children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tr", { children: [
1093
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles5.th, styles5.thCheckbox], children: files.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1122
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles5.th, styles5.thCheckbox], children: sortedItems.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1094
1123
  "input",
1095
1124
  {
1096
1125
  type: "checkbox",
1097
1126
  css: styles5.checkbox,
1098
- checked: allFilesSelected,
1127
+ checked: allItemsSelected,
1099
1128
  ref: (el) => {
1100
- if (el) el.indeterminate = someFilesSelected && !allFilesSelected;
1129
+ if (el) el.indeterminate = someItemsSelected && !allItemsSelected;
1101
1130
  },
1102
1131
  onChange: handleSelectAll
1103
1132
  }
@@ -1112,16 +1141,17 @@ function StudioFileList() {
1112
1141
  {
1113
1142
  item,
1114
1143
  isSelected: selectedItems.has(item.path),
1115
- onClick: (e) => handleItemClick(item, e)
1144
+ onClick: (e) => handleItemClick(item, e),
1145
+ onCheckboxClick: (e) => handleCheckboxClick(item, e)
1116
1146
  },
1117
1147
  item.path
1118
1148
  )) })
1119
1149
  ] });
1120
1150
  }
1121
- function ListRow({ item, isSelected, onClick }) {
1151
+ function ListRow({ item, isSelected, onClick, onCheckboxClick }) {
1122
1152
  const isFolder = item.type === "folder";
1123
1153
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tr", { css: [styles5.row, isSelected && styles5.rowSelected], onClick, children: [
1124
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles5.td, children: !isFolder && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1154
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles5.td, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1125
1155
  "input",
1126
1156
  {
1127
1157
  type: "checkbox",
@@ -1131,16 +1161,16 @@ function ListRow({ item, isSelected, onClick }) {
1131
1161
  },
1132
1162
  onClick: (e) => {
1133
1163
  e.stopPropagation();
1134
- onClick(e);
1164
+ onCheckboxClick(e);
1135
1165
  }
1136
1166
  }
1137
1167
  ) }),
1138
1168
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles5.td, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.nameCell, children: [
1139
- isFolder ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.folderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "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" }) }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.fileIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "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" }) }),
1169
+ isFolder ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.folderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "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" }) }) : item.thumbnail ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "img", { css: styles5.thumbnail, src: item.thumbnail, alt: item.name, loading: "lazy" }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.fileIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "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" }) }),
1140
1170
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles5.name, children: item.name })
1141
1171
  ] }) }),
1142
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles5.td, styles5.meta], children: item.size ? formatFileSize2(item.size) : "--" }),
1143
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles5.td, styles5.meta], children: item.dimensions ? `${item.dimensions.width}x${item.dimensions.height}` : "--" }),
1172
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles5.td, styles5.meta], children: isFolder ? item.fileCount !== void 0 ? `${item.fileCount} files` : "--" : item.size !== void 0 ? formatFileSize2(item.size) : "--" }),
1173
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles5.td, styles5.meta], children: isFolder ? item.totalSize !== void 0 ? formatFileSize2(item.totalSize) : "--" : item.dimensions ? `${item.dimensions.width}x${item.dimensions.height}` : "--" }),
1144
1174
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles5.td, children: item.cdnSynced ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles5.cdnBadge, children: [
1145
1175
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.cdnIcon, fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "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" }) }),
1146
1176
  "Synced"
@@ -1884,4 +1914,4 @@ var StudioUI_default = StudioUI;
1884
1914
 
1885
1915
 
1886
1916
  exports.StudioUI = StudioUI; exports.default = StudioUI_default;
1887
- //# sourceMappingURL=StudioUI-U6GFAPCY.js.map
1917
+ //# sourceMappingURL=StudioUI-EOAO24O6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/chrisb/Sites/studio/dist/StudioUI-EOAO24O6.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-EOAO24O6.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\nconst styles = {\n container: css`\n display: flex;\n flex-direction: column;\n height: 100%;\n font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n `,\n header: css`\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 24px;\n border-bottom: 1px solid #e5e7eb;\n `,\n title: css`\n font-size: 20px;\n font-weight: 600;\n color: #111827;\n margin: 0;\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: 8px;\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n closeIcon: css`\n width: 20px;\n height: 20px;\n color: #6b7280;\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: 16px;\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 // Get only files (not folders)\n const files = allItems.filter(item => item.type !== 'folder')\n const fromIndex = files.findIndex(item => item.path === fromPath)\n const toIndex = files.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(files[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\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: #f9fafb;\n border-bottom: 1px solid #e5e7eb;\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: 16px;\n `,\n btn: css`\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n background: none;\n border: none;\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n }\n `,\n btnDefault: css`\n color: #374151;\n \n &:hover:not(:disabled) {\n background-color: white;\n }\n `,\n btnDanger: css`\n color: #dc2626;\n \n &:hover:not(:disabled) {\n background-color: #fef2f2;\n }\n `,\n icon: css`\n width: 16px;\n height: 16px;\n `,\n iconSpin: css`\n animation: ${spin} 1s linear infinite;\n `,\n selectionCount: css`\n font-size: 14px;\n color: #4b5563;\n `,\n clearBtn: css`\n margin-left: 8px;\n color: #9333ea;\n background: none;\n border: none;\n cursor: pointer;\n font-size: 14px;\n \n &:hover {\n text-decoration: underline;\n }\n `,\n viewToggle: css`\n display: flex;\n align-items: center;\n background-color: white;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n overflow: hidden;\n `,\n viewBtn: css`\n padding: 8px;\n background: none;\n border: none;\n cursor: pointer;\n color: #6b7280;\n transition: all 0.15s;\n \n &:hover {\n background-color: #f9fafb;\n }\n `,\n viewBtnActive: css`\n background-color: #f3e8ff;\n color: #7c3aed;\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 // Stop spinning after a short delay (the actual refresh is instant)\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 // Only log server errors (500s), not validation messages (400s)\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 // Validation message - not an error, just guidance\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 {/* Hidden file input for upload */}\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n accept=\"image/*\"\n onChange={handleFileChange}\n style={{ display: 'none' }}\n />\n \n <div css={styles.left}>\n <ToolbarButton \n onClick={handleUpload} \n icon=\"upload\" \n label={uploading ? 'Uploading...' : 'Upload'} \n disabled={uploading || isInImagesFolder}\n />\n <ToolbarButton\n onClick={handleReprocess}\n icon=\"refresh\"\n label=\"Reprocess\"\n disabled={!hasSelection}\n />\n <ToolbarButton\n onClick={handleDeleteClick}\n icon=\"trash\"\n label=\"Delete\"\n disabled={!hasSelection}\n variant=\"danger\"\n />\n <ToolbarButton\n onClick={handleSyncCdn}\n icon=\"cloud\"\n label=\"Sync CDN\"\n disabled={!hasSelection}\n />\n <ToolbarButton onClick={handleScan} icon=\"scan\" label=\"Scan\" />\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 <ToolbarButton\n onClick={handleRefresh}\n icon=\"reload\"\n label=\"Refresh\"\n spinning={refreshing}\n />\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\ninterface ToolbarButtonProps {\n onClick: () => void\n icon: 'upload' | 'refresh' | 'trash' | 'cloud' | 'scan' | 'reload'\n label: string\n disabled?: boolean\n variant?: 'default' | 'danger'\n spinning?: boolean\n}\n\nfunction ToolbarButton({\n onClick,\n icon,\n label,\n disabled,\n variant = 'default',\n spinning,\n}: ToolbarButtonProps) {\n return (\n <button\n css={[styles.btn, variant === 'danger' ? styles.btnDanger : styles.btnDefault]}\n onClick={onClick}\n disabled={disabled}\n >\n <IconComponent icon={icon} spinning={spinning} />\n {label}\n </button>\n )\n}\n\nfunction IconComponent({ icon, spinning }: { icon: string; spinning?: boolean }) {\n switch (icon) {\n case 'upload':\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 case 'refresh':\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 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 case 'trash':\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 case 'cloud':\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 case 'scan':\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 case 'reload':\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 default:\n return null\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\nconst fadeIn = keyframes`\n from { opacity: 0; }\n to { opacity: 1; }\n`\n\nconst slideIn = keyframes`\n from { \n opacity: 0;\n transform: scale(0.95);\n }\n to { \n opacity: 1;\n transform: scale(1);\n }\n`\n\nconst styles = {\n overlay: css`\n position: fixed;\n inset: 0;\n background-color: rgba(0, 0, 0, 0.5);\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: white;\n border-radius: 12px;\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);\n max-width: 400px;\n width: 90%;\n animation: ${slideIn} 0.15s ease-out;\n `,\n header: css`\n padding: 20px 24px 0;\n `,\n title: css`\n font-size: 18px;\n font-weight: 600;\n color: #111827;\n margin: 0;\n `,\n body: css`\n padding: 12px 24px 24px;\n `,\n message: css`\n font-size: 14px;\n color: #6b7280;\n margin: 0;\n line-height: 1.5;\n `,\n footer: css`\n display: flex;\n justify-content: flex-end;\n gap: 12px;\n padding: 16px 24px;\n border-top: 1px solid #e5e7eb;\n background-color: #f9fafb;\n border-radius: 0 0 12px 12px;\n `,\n btn: css`\n padding: 8px 16px;\n font-size: 14px;\n font-weight: 500;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.15s;\n `,\n btnCancel: css`\n background-color: white;\n border: 1px solid #d1d5db;\n color: #374151;\n \n &:hover {\n background-color: #f9fafb;\n }\n `,\n btnConfirm: css`\n background-color: #9333ea;\n border: 1px solid #9333ea;\n color: white;\n \n &:hover {\n background-color: #7c3aed;\n }\n `,\n btnDanger: css`\n background-color: #dc2626;\n border: 1px solid #dc2626;\n color: white;\n \n &:hover {\n background-color: #b91c1c;\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\nconst styles = {\n container: css`\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 24px;\n background-color: white;\n border-bottom: 1px solid #f3f4f6;\n `,\n backBtn: css`\n padding: 4px;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n backIcon: css`\n width: 16px;\n height: 16px;\n color: #6b7280;\n `,\n nav: css`\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 14px;\n `,\n item: css`\n display: flex;\n align-items: center;\n gap: 4px;\n `,\n separator: css`\n color: #d1d5db;\n `,\n btn: css`\n padding: 2px 4px;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n transition: all 0.15s;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n btnActive: css`\n color: #111827;\n font-weight: 500;\n `,\n btnInactive: css`\n color: #6b7280;\n \n &:hover {\n color: #374151;\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\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: 2px solid transparent;\n border-bottom-color: #9333ea;\n animation: ${spin} 1s 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: #6b7280;\n `,\n emptyIcon: css`\n width: 48px;\n height: 48px;\n margin-bottom: 16px;\n `,\n emptyText: css`\n font-size: 14px;\n margin: 0;\n `,\n grid: css`\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 16px;\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: 2px solid transparent;\n overflow: hidden;\n cursor: pointer;\n transition: all 0.15s;\n background-color: #f9fafb;\n user-select: none;\n \n &:hover {\n border-color: #e5e7eb;\n }\n `,\n itemSelected: css`\n border-color: #a855f7;\n background-color: #faf5ff;\n \n &:hover {\n border-color: #a855f7;\n }\n `,\n checkbox: css`\n position: absolute;\n top: 8px;\n left: 8px;\n z-index: 10;\n width: 16px;\n height: 16px;\n accent-color: #9333ea;\n `,\n cdnBadge: css`\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 10;\n background-color: #dcfce7;\n color: #15803d;\n font-size: 12px;\n padding: 2px 6px;\n border-radius: 9999px;\n `,\n content: css`\n aspect-ratio: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 16px;\n `,\n folderIcon: css`\n width: 64px;\n height: 64px;\n color: #facc15;\n `,\n fileIcon: css`\n width: 48px;\n height: 48px;\n color: #9ca3af;\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: 6px 8px;\n background-color: white;\n border-top: 1px solid #e5e7eb;\n `,\n name: css`\n font-size: 12px;\n color: #374151;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n margin: 0;\n `,\n size: css`\n font-size: 12px;\n color: #9ca3af;\n margin: 0;\n `,\n selectAllRow: css`\n display: flex;\n align-items: center;\n margin-bottom: 12px;\n padding-bottom: 12px;\n border-bottom: 1px solid #e5e7eb;\n `,\n selectAllLabel: css`\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n color: #6b7280;\n cursor: pointer;\n \n &:hover {\n color: #374151;\n }\n `,\n selectAllCheckbox: css`\n width: 16px;\n height: 16px;\n accent-color: #9333ea;\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 if (item.type === 'folder') {\n // Clicking on folder box navigates into it\n setCurrentPath(item.path)\n return\n }\n\n // For files, toggle selection\n if (e.shiftKey && lastSelectedPath) {\n selectRange(lastSelectedPath, item.path, sortedItems)\n } else {\n toggleSelection(item.path)\n }\n }\n\n const handleCheckboxClick = (item: FileItem, e: React.MouseEvent) => {\n // Checkbox click always toggles selection (for both files and folders)\n if (e.shiftKey && lastSelectedPath) {\n selectRange(lastSelectedPath, item.path, sortedItems)\n } else {\n toggleSelection(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 onCheckboxClick={(e) => handleCheckboxClick(item, e)}\n />\n ))}\n </div>\n </div>\n )\n}\n\ninterface GridItemProps {\n item: FileItem\n isSelected: boolean\n onClick: (e: React.MouseEvent) => void\n onCheckboxClick: (e: React.MouseEvent) => void\n}\n\nfunction GridItem({ item, isSelected, onClick, onCheckboxClick }: GridItemProps) {\n const isFolder = item.type === 'folder'\n\n return (\n <div css={[styles.item, isSelected && styles.itemSelected]} onClick={onClick}>\n {/* Show checkbox for both files and folders */}\n <input\n type=\"checkbox\"\n css={styles.checkbox}\n checked={isSelected}\n onChange={() => {}}\n onClick={(e) => {\n e.stopPropagation()\n onCheckboxClick(e)\n }}\n />\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 <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 </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\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: 2px solid transparent;\n border-bottom-color: #9333ea;\n animation: ${spin} 1s 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: #6b7280;\n `,\n table: css`\n width: 100%;\n border-collapse: collapse;\n `,\n th: css`\n text-align: left;\n font-size: 12px;\n color: #6b7280;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n padding-bottom: 8px;\n font-weight: normal;\n `,\n thCheckbox: css`\n width: 32px;\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 border-top: 1px solid #f3f4f6;\n `,\n row: css`\n cursor: pointer;\n transition: background-color 0.15s;\n user-select: none;\n \n &:hover {\n background-color: #f9fafb;\n }\n `,\n rowSelected: css`\n background-color: #faf5ff;\n \n &:hover {\n background-color: #faf5ff;\n }\n `,\n td: css`\n padding: 8px 0;\n border-bottom: 1px solid #f3f4f6;\n `,\n checkbox: css`\n width: 16px;\n height: 16px;\n accent-color: #9333ea;\n `,\n nameCell: css`\n display: flex;\n align-items: center;\n gap: 8px;\n `,\n folderIcon: css`\n width: 20px;\n height: 20px;\n color: #facc15;\n `,\n fileIcon: css`\n width: 20px;\n height: 20px;\n color: #9ca3af;\n `,\n thumbnail: css`\n width: 32px;\n height: 32px;\n object-fit: cover;\n border-radius: 4px;\n flex-shrink: 0;\n `,\n name: css`\n font-size: 14px;\n color: #111827;\n `,\n meta: css`\n font-size: 14px;\n color: #6b7280;\n `,\n cdnBadge: css`\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n color: #15803d;\n `,\n cdnIcon: css`\n width: 12px;\n height: 12px;\n `,\n cdnEmpty: css`\n font-size: 12px;\n color: #9ca3af;\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 if (item.type === 'folder') {\n // Clicking on folder row navigates into it\n setCurrentPath(item.path)\n return\n }\n\n // For files, toggle selection\n if (e.shiftKey && lastSelectedPath) {\n selectRange(lastSelectedPath, item.path, sortedItems)\n } else {\n toggleSelection(item.path)\n }\n }\n\n const handleCheckboxClick = (item: FileItem, e: React.MouseEvent) => {\n // Checkbox click always toggles selection (for both files and folders)\n if (e.shiftKey && lastSelectedPath) {\n selectRange(lastSelectedPath, item.path, sortedItems)\n } else {\n toggleSelection(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 onCheckboxClick={(e) => handleCheckboxClick(item, e)}\n />\n ))}\n </tbody>\n </table>\n )\n}\n\ninterface ListRowProps {\n item: FileItem\n isSelected: boolean\n onClick: (e: React.MouseEvent) => void\n onCheckboxClick: (e: React.MouseEvent) => void\n}\n\nfunction ListRow({ item, isSelected, onClick, onCheckboxClick }: ListRowProps) {\n const isFolder = item.type === 'folder'\n\n return (\n <tr css={[styles.row, isSelected && styles.rowSelected]} onClick={onClick}>\n <td css={styles.td}>\n {/* Show checkbox for both files and folders */}\n <input\n type=\"checkbox\"\n css={styles.checkbox}\n checked={isSelected}\n onChange={() => {}}\n onClick={(e) => {\n e.stopPropagation()\n onCheckboxClick(e)\n }}\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 </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 styles = {\n panel: css`\n width: 320px;\n border-left: 1px solid #e5e7eb;\n background-color: #f9fafb;\n padding: 16px;\n overflow: auto;\n `,\n title: css`\n font-size: 14px;\n font-weight: 500;\n color: #111827;\n margin: 0 0 16px 0;\n `,\n imageContainer: css`\n background-color: white;\n border-radius: 8px;\n border: 1px solid #e5e7eb;\n padding: 8px;\n margin-bottom: 16px;\n `,\n image: css`\n width: 100%;\n height: auto;\n border-radius: 4px;\n `,\n info: css`\n display: flex;\n flex-direction: column;\n gap: 12px;\n `,\n row: css`\n display: flex;\n justify-content: space-between;\n font-size: 12px;\n `,\n label: css`\n color: #6b7280;\n `,\n value: css`\n color: #111827;\n `,\n valueTruncate: css`\n max-width: 128px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n `,\n section: css`\n padding-top: 8px;\n border-top: 1px solid #e5e7eb;\n `,\n sectionTitle: css`\n font-size: 12px;\n font-weight: 500;\n color: #6b7280;\n margin: 0 0 8px 0;\n `,\n cdnStatus: css`\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: #16a34a;\n `,\n cdnIcon: css`\n width: 16px;\n height: 16px;\n `,\n copyBtn: css`\n margin-top: 8px;\n font-size: 12px;\n color: #9333ea;\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: 4px;\n `,\n emptyState: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 200px;\n `,\n emptyText: css`\n font-size: 14px;\n color: #9ca3af;\n margin: 0;\n `,\n actions: css`\n margin-top: 16px;\n padding-top: 16px;\n border-top: 1px solid #e5e7eb;\n display: flex;\n flex-direction: column;\n gap: 8px;\n `,\n actionBtn: css`\n width: 100%;\n padding: 8px 12px;\n font-size: 14px;\n background-color: white;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.15s;\n color: #374151;\n \n &:hover {\n background-color: #f9fafb;\n }\n `,\n actionBtnDanger: css`\n color: #dc2626;\n \n &:hover {\n background-color: #fef2f2;\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 imageKey = selectedPath\n .replace(/^public\\/images\\//, '')\n .replace(/^public\\/originals\\//, '')\n\n const imageData = meta?.images?.[imageKey]\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 <img\n css={styles.image}\n src={selectedPath.replace('public', '')}\n alt=\"Preview\"\n />\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"]}