@gallop.software/studio 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -8,9 +8,12 @@ import * as _emotion_react_jsx_runtime from '@emotion/react/jsx-runtime';
8
8
  declare function StudioButton(): _emotion_react_jsx_runtime.JSX.Element | null;
9
9
 
10
10
  /**
11
- * Dimensions tuple [width, height]
11
+ * Dimensions object {w, h}
12
12
  */
13
- type Dimensions = [number, number];
13
+ interface Dimensions {
14
+ w: number;
15
+ h: number;
16
+ }
14
17
  /**
15
18
  * Meta entry - works for images and non-images
16
19
  * o: original dimensions, b: blurhash, c: CDN index
@@ -27,7 +30,7 @@ interface MetaEntry {
27
30
  }
28
31
  /**
29
32
  * Meta schema - keyed by path from public folder
30
- * Example: { "/portfolio/photo.jpg": { o: [2400, 1600], b: "...", sm: [300, 200], ... } }
33
+ * Example: { "/portfolio/photo.jpg": { o: {w:2400,h:1600}, b: "...", sm: {w:300,h:200}, ... } }
31
34
  */
32
35
  type LeanMeta = Record<string, MetaEntry>;
33
36
  type LeanImageEntry = MetaEntry;
package/dist/index.d.ts CHANGED
@@ -8,9 +8,12 @@ import * as _emotion_react_jsx_runtime from '@emotion/react/jsx-runtime';
8
8
  declare function StudioButton(): _emotion_react_jsx_runtime.JSX.Element | null;
9
9
 
10
10
  /**
11
- * Dimensions tuple [width, height]
11
+ * Dimensions object {w, h}
12
12
  */
13
- type Dimensions = [number, number];
13
+ interface Dimensions {
14
+ w: number;
15
+ h: number;
16
+ }
14
17
  /**
15
18
  * Meta entry - works for images and non-images
16
19
  * o: original dimensions, b: blurhash, c: CDN index
@@ -27,7 +30,7 @@ interface MetaEntry {
27
30
  }
28
31
  /**
29
32
  * Meta schema - keyed by path from public folder
30
- * Example: { "/portfolio/photo.jpg": { o: [2400, 1600], b: "...", sm: [300, 200], ... } }
33
+ * Example: { "/portfolio/photo.jpg": { o: {w:2400,h:1600}, b: "...", sm: {w:300,h:200}, ... } }
31
34
  */
32
35
  type LeanMeta = Record<string, MetaEntry>;
33
36
  type LeanImageEntry = MetaEntry;
package/dist/index.js CHANGED
@@ -2,19 +2,19 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkVSOTEQ7Xjs = require('./chunk-VSOTEQ7X.js');
5
+ var _chunkWOHZ4LYGjs = require('./chunk-WOHZ4LYG.js');
6
6
 
7
7
 
8
8
 
9
9
 
10
10
 
11
- var _chunkUFCWGUAGjs = require('./chunk-UFCWGUAG.js');
11
+ var _chunkN6JYTJCBjs = require('./chunk-N6JYTJCB.js');
12
12
 
13
13
  // src/components/StudioButton.tsx
14
14
  var _react = require('react');
15
15
  var _react3 = require('@emotion/react');
16
16
  var _jsxruntime = require('@emotion/react/jsx-runtime');
17
- var StudioUI = _react.lazy.call(void 0, () => Promise.resolve().then(() => _interopRequireWildcard(require("./StudioUI-FCQJK23M.js"))));
17
+ var StudioUI = _react.lazy.call(void 0, () => Promise.resolve().then(() => _interopRequireWildcard(require("./StudioUI-7GX4ZLF3.js"))));
18
18
  var spin = _react3.keyframes`
19
19
  to {
20
20
  transform: rotate(360deg);
@@ -29,21 +29,21 @@ var styles = {
29
29
  width: 52px;
30
30
  height: 52px;
31
31
  border-radius: 50%;
32
- background: ${_chunkUFCWGUAGjs.colors.primary};
32
+ background: ${_chunkN6JYTJCBjs.colors.primary};
33
33
  color: white;
34
- box-shadow: 0 4px 12px ${_chunkUFCWGUAGjs.colors.shadowDark}, 0 1px 3px ${_chunkUFCWGUAGjs.colors.shadow};
34
+ box-shadow: 0 4px 12px ${_chunkN6JYTJCBjs.colors.shadowDark}, 0 1px 3px ${_chunkN6JYTJCBjs.colors.shadow};
35
35
  display: flex;
36
36
  align-items: center;
37
37
  justify-content: center;
38
38
  border: none;
39
39
  cursor: pointer;
40
40
  transition: all 0.15s ease;
41
- font-family: ${_chunkUFCWGUAGjs.fontStack};
41
+ font-family: ${_chunkN6JYTJCBjs.fontStack};
42
42
 
43
43
  &:hover {
44
44
  transform: translateY(-2px);
45
- box-shadow: 0 8px 20px ${_chunkUFCWGUAGjs.colors.shadowDark}, 0 2px 6px ${_chunkUFCWGUAGjs.colors.shadow};
46
- background: ${_chunkUFCWGUAGjs.colors.primaryHover};
45
+ box-shadow: 0 8px 20px ${_chunkN6JYTJCBjs.colors.shadowDark}, 0 2px 6px ${_chunkN6JYTJCBjs.colors.shadow};
46
+ background: ${_chunkN6JYTJCBjs.colors.primaryHover};
47
47
  }
48
48
 
49
49
  &:active {
@@ -78,13 +78,13 @@ var styles = {
78
78
  backdrop-filter: blur(4px);
79
79
  `,
80
80
  modal: _react3.css`
81
- ${_chunkUFCWGUAGjs.baseReset}
81
+ ${_chunkN6JYTJCBjs.baseReset}
82
82
  position: absolute;
83
83
  top: 24px;
84
84
  right: 24px;
85
85
  bottom: 24px;
86
86
  left: 24px;
87
- background-color: ${_chunkUFCWGUAGjs.colors.surface};
87
+ background-color: ${_chunkN6JYTJCBjs.colors.surface};
88
88
  border-radius: 12px;
89
89
  box-shadow: 0 30px 60px -12px rgba(50, 50, 93, 0.25), 0 18px 36px -18px rgba(0, 0, 0, 0.3);
90
90
  display: flex;
@@ -96,8 +96,8 @@ var styles = {
96
96
  align-items: center;
97
97
  justify-content: center;
98
98
  height: 100%;
99
- background: ${_chunkUFCWGUAGjs.colors.background};
100
- font-family: ${_chunkUFCWGUAGjs.fontStack};
99
+ background: ${_chunkN6JYTJCBjs.colors.background};
100
+ font-family: ${_chunkN6JYTJCBjs.fontStack};
101
101
  `,
102
102
  loadingContent: _react3.css`
103
103
  display: flex;
@@ -109,13 +109,13 @@ var styles = {
109
109
  width: 36px;
110
110
  height: 36px;
111
111
  border-radius: 50%;
112
- border: 3px solid ${_chunkUFCWGUAGjs.colors.border};
113
- border-top-color: ${_chunkUFCWGUAGjs.colors.primary};
112
+ border: 3px solid ${_chunkN6JYTJCBjs.colors.border};
113
+ border-top-color: ${_chunkN6JYTJCBjs.colors.primary};
114
114
  animation: ${spin} 0.8s linear infinite;
115
115
  `,
116
116
  loadingText: _react3.css`
117
- color: ${_chunkUFCWGUAGjs.colors.textSecondary};
118
- font-size: ${_chunkUFCWGUAGjs.fontSize.base};
117
+ color: ${_chunkN6JYTJCBjs.colors.textSecondary};
118
+ font-size: ${_chunkN6JYTJCBjs.fontSize.base};
119
119
  font-weight: 500;
120
120
  margin: 0;
121
121
  letter-spacing: -0.01em;
@@ -182,5 +182,5 @@ function LoadingState() {
182
182
 
183
183
 
184
184
 
185
- exports.StudioButton = StudioButton; exports.getAllThumbnailPaths = _chunkVSOTEQ7Xjs.getAllThumbnailPaths; exports.getThumbnailPath = _chunkVSOTEQ7Xjs.getThumbnailPath;
185
+ exports.StudioButton = StudioButton; exports.getAllThumbnailPaths = _chunkWOHZ4LYGjs.getAllThumbnailPaths; exports.getThumbnailPath = _chunkWOHZ4LYGjs.getThumbnailPath;
186
186
  //# sourceMappingURL=index.js.map
package/dist/index.mjs CHANGED
@@ -2,19 +2,19 @@
2
2
  import {
3
3
  getAllThumbnailPaths,
4
4
  getThumbnailPath
5
- } from "./chunk-CIS6B4SP.mjs";
5
+ } from "./chunk-VQJAJVAQ.mjs";
6
6
  import {
7
7
  baseReset,
8
8
  colors,
9
9
  fontSize,
10
10
  fontStack
11
- } from "./chunk-HXE6XCG2.mjs";
11
+ } from "./chunk-RHI3UROE.mjs";
12
12
 
13
13
  // src/components/StudioButton.tsx
14
14
  import { useState, useEffect, lazy, Suspense } from "react";
15
15
  import { css, keyframes } from "@emotion/react";
16
16
  import { Fragment, jsx, jsxs } from "@emotion/react/jsx-runtime";
17
- var StudioUI = lazy(() => import("./StudioUI-ADLDMRJQ.mjs"));
17
+ var StudioUI = lazy(() => import("./StudioUI-GLI2IYCZ.mjs"));
18
18
  var spin = keyframes`
19
19
  to {
20
20
  transform: rotate(360deg);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gallop.software/studio",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "Media manager for Gallop templates - upload, process, and sync images to CDN",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/types.ts"],"sourcesContent":["/**\n * Dimensions tuple [width, height]\n */\nexport type Dimensions = [number, number]\n\n/**\n * Meta entry - works for images and non-images\n * o: original dimensions, b: blurhash, c: CDN index\n * sm/md/lg/f: thumbnail dimensions (presence implies processed)\n */\nexport interface MetaEntry {\n o?: Dimensions // original dimensions [width, height]\n b?: string // blurhash\n sm?: Dimensions // small thumbnail (300px width)\n md?: Dimensions // medium thumbnail (700px width)\n lg?: Dimensions // large thumbnail (1400px width)\n f?: Dimensions // full size (capped at 2560px width)\n c?: number // CDN index - index into _cdns array\n}\n\n/**\n * Full meta schema including special keys\n * _cdns: Array of CDN base URLs\n * Other keys: file paths from public folder\n */\nexport interface FullMeta {\n _cdns?: string[] // Array of CDN base URLs\n [key: string]: MetaEntry | string[] | undefined\n}\n\n/**\n * Meta schema - keyed by path from public folder\n * Example: { \"/portfolio/photo.jpg\": { o: [2400, 1600], b: \"...\", sm: [300, 200], ... } }\n */\nexport type LeanMeta = Record<string, MetaEntry>\n\n// Alias for compatibility\nexport type LeanImageEntry = MetaEntry\n\n/**\n * File/folder item for browser\n */\nexport interface FileItem {\n name: string\n path: string\n type: 'file' | 'folder'\n size?: number\n dimensions?: { width: number; height: number }\n isProcessed?: boolean\n cdnPushed?: boolean\n cdnBaseUrl?: string // CDN base URL when pushed to cloud\n isRemote?: boolean // true if CDN URL doesn't match R2 (external import)\n isProtected?: boolean // true for images folder and its contents (cannot select/modify)\n // Folder-specific properties\n fileCount?: number\n totalSize?: number\n // For showing thumbnails - path to -sm version if exists\n thumbnail?: string\n // Whether a processed thumbnail exists\n hasThumbnail?: boolean\n}\n\n/**\n * Studio configuration\n */\nexport interface StudioConfig {\n r2AccountId?: string\n r2AccessKeyId?: string\n r2SecretAccessKey?: string\n r2BucketName?: string\n r2PublicUrl?: string\n thumbnailSizes?: {\n small: number\n medium: number\n large: number\n }\n}\n\n/**\n * Get thumbnail path from original image path\n */\nexport function getThumbnailPath(originalPath: string, size: 'sm' | 'md' | 'lg' | 'full'): string {\n if (size === 'full') {\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}${outputExt}`\n }\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}-${size}${outputExt}`\n}\n\n/**\n * Get all thumbnail paths for an image\n */\nexport function getAllThumbnailPaths(originalPath: string): string[] {\n return [\n getThumbnailPath(originalPath, 'full'),\n getThumbnailPath(originalPath, 'lg'),\n getThumbnailPath(originalPath, 'md'),\n getThumbnailPath(originalPath, 'sm'),\n ]\n}\n\n/**\n * Check if an image entry is processed (has any thumbnail dimensions)\n */\nexport function isProcessed(entry: MetaEntry | undefined): boolean {\n if (!entry) return false\n return !!(entry.f || entry.lg || entry.md || entry.sm)\n}\n"],"mappings":";AAiFO,SAAS,iBAAiB,cAAsB,MAA2C;AAChG,MAAI,SAAS,QAAQ;AACnB,UAAMA,OAAM,aAAa,MAAM,QAAQ,IAAI,CAAC,KAAK;AACjD,UAAMC,QAAO,aAAa,QAAQ,UAAU,EAAE;AAC9C,UAAMC,aAAYF,KAAI,YAAY,MAAM,SAAS,SAAS;AAC1D,WAAO,UAAUC,KAAI,GAAGC,UAAS;AAAA,EACnC;AACA,QAAM,MAAM,aAAa,MAAM,QAAQ,IAAI,CAAC,KAAK;AACjD,QAAM,OAAO,aAAa,QAAQ,UAAU,EAAE;AAC9C,QAAM,YAAY,IAAI,YAAY,MAAM,SAAS,SAAS;AAC1D,SAAO,UAAU,IAAI,IAAI,IAAI,GAAG,SAAS;AAC3C;AAKO,SAAS,qBAAqB,cAAgC;AACnE,SAAO;AAAA,IACL,iBAAiB,cAAc,MAAM;AAAA,IACrC,iBAAiB,cAAc,IAAI;AAAA,IACnC,iBAAiB,cAAc,IAAI;AAAA,IACnC,iBAAiB,cAAc,IAAI;AAAA,EACrC;AACF;AAKO,SAAS,YAAY,OAAuC;AACjE,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,CAAC,EAAE,MAAM,KAAK,MAAM,MAAM,MAAM,MAAM,MAAM;AACrD;","names":["ext","base","outputExt"]}
@@ -1 +0,0 @@
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 // Special folders\n imagesFolder: '#8b5cf6',\n imagesFolderLight: '#f3f0ff',\n}\n\n// Font sizes - slightly larger for better readability\nexport const fontSize = {\n xs: '13px',\n sm: '14px',\n base: '16px',\n md: '17px',\n lg: '19px',\n xl: '22px',\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;AAAA;AAAA,EAGZ,cAAc;AAAA,EACd,mBAAmB;AACrB;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":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/chrisb/Sites/studio/dist/chunk-UFCWGUAG.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,uBAAA;AAAA;AAAA,EAGZ,YAAA,EAAc,SAAA;AAAA,EACd,iBAAA,EAAmB;AACrB,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;ADJtB;AACA;AACE;AACA;AACA;AACA;AACF,mHAAC","file":"/Users/chrisb/Sites/studio/dist/chunk-UFCWGUAG.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 // Special folders\n imagesFolder: '#8b5cf6',\n imagesFolderLight: '#f3f0ff',\n}\n\n// Font sizes - slightly larger for better readability\nexport const fontSize = {\n xs: '13px',\n sm: '14px',\n base: '16px',\n md: '17px',\n lg: '19px',\n xl: '22px',\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"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/chrisb/Sites/studio/dist/chunk-VSOTEQ7X.js","../src/types.ts"],"names":["ext","base","outputExt"],"mappings":"AAAA;ACiFO,SAAS,gBAAA,CAAiB,YAAA,EAAsB,IAAA,EAA2C;AAChG,EAAA,GAAA,CAAI,KAAA,IAAS,MAAA,EAAQ;AACnB,IAAA,MAAMA,KAAAA,kBAAM,YAAA,mBAAa,KAAA,mBAAM,QAAQ,CAAA,4BAAA,CAAI,CAAC,IAAA,GAAK,MAAA;AACjD,IAAA,MAAMC,MAAAA,EAAO,YAAA,CAAa,OAAA,CAAQ,QAAA,EAAU,EAAE,CAAA;AAC9C,IAAA,MAAMC,WAAAA,EAAYF,IAAAA,CAAI,WAAA,CAAY,EAAA,IAAM,OAAA,EAAS,OAAA,EAAS,MAAA;AAC1D,IAAA,OAAO,CAAA,OAAA,EAAUC,KAAI,CAAA,EAAA;AACvB,EAAA;AACyB,EAAA;AACZ,EAAA;AACS,EAAA;AACG,EAAA;AAC3B;AAKgB;AACP,EAAA;AACY,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACnB,EAAA;AACF;AAK4B;AACP,EAAA;AACE,EAAA;AACvB;ADvF2B;AACA;AACA;AACA;AACA;AACA","file":"/Users/chrisb/Sites/studio/dist/chunk-VSOTEQ7X.js","sourcesContent":[null,"/**\n * Dimensions tuple [width, height]\n */\nexport type Dimensions = [number, number]\n\n/**\n * Meta entry - works for images and non-images\n * o: original dimensions, b: blurhash, c: CDN index\n * sm/md/lg/f: thumbnail dimensions (presence implies processed)\n */\nexport interface MetaEntry {\n o?: Dimensions // original dimensions [width, height]\n b?: string // blurhash\n sm?: Dimensions // small thumbnail (300px width)\n md?: Dimensions // medium thumbnail (700px width)\n lg?: Dimensions // large thumbnail (1400px width)\n f?: Dimensions // full size (capped at 2560px width)\n c?: number // CDN index - index into _cdns array\n}\n\n/**\n * Full meta schema including special keys\n * _cdns: Array of CDN base URLs\n * Other keys: file paths from public folder\n */\nexport interface FullMeta {\n _cdns?: string[] // Array of CDN base URLs\n [key: string]: MetaEntry | string[] | undefined\n}\n\n/**\n * Meta schema - keyed by path from public folder\n * Example: { \"/portfolio/photo.jpg\": { o: [2400, 1600], b: \"...\", sm: [300, 200], ... } }\n */\nexport type LeanMeta = Record<string, MetaEntry>\n\n// Alias for compatibility\nexport type LeanImageEntry = MetaEntry\n\n/**\n * File/folder item for browser\n */\nexport interface FileItem {\n name: string\n path: string\n type: 'file' | 'folder'\n size?: number\n dimensions?: { width: number; height: number }\n isProcessed?: boolean\n cdnPushed?: boolean\n cdnBaseUrl?: string // CDN base URL when pushed to cloud\n isRemote?: boolean // true if CDN URL doesn't match R2 (external import)\n isProtected?: boolean // true for images folder and its contents (cannot select/modify)\n // Folder-specific properties\n fileCount?: number\n totalSize?: number\n // For showing thumbnails - path to -sm version if exists\n thumbnail?: string\n // Whether a processed thumbnail exists\n hasThumbnail?: boolean\n}\n\n/**\n * Studio configuration\n */\nexport interface StudioConfig {\n r2AccountId?: string\n r2AccessKeyId?: string\n r2SecretAccessKey?: string\n r2BucketName?: string\n r2PublicUrl?: string\n thumbnailSizes?: {\n small: number\n medium: number\n large: number\n }\n}\n\n/**\n * Get thumbnail path from original image path\n */\nexport function getThumbnailPath(originalPath: string, size: 'sm' | 'md' | 'lg' | 'full'): string {\n if (size === 'full') {\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}${outputExt}`\n }\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}-${size}${outputExt}`\n}\n\n/**\n * Get all thumbnail paths for an image\n */\nexport function getAllThumbnailPaths(originalPath: string): string[] {\n return [\n getThumbnailPath(originalPath, 'full'),\n getThumbnailPath(originalPath, 'lg'),\n getThumbnailPath(originalPath, 'md'),\n getThumbnailPath(originalPath, 'sm'),\n ]\n}\n\n/**\n * Check if an image entry is processed (has any thumbnail dimensions)\n */\nexport function isProcessed(entry: MetaEntry | undefined): boolean {\n if (!entry) return false\n return !!(entry.f || entry.lg || entry.md || entry.sm)\n}\n"]}