@gallop.software/studio 0.1.72 → 0.1.74

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.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'\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)} isVisible={isOpen} />\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"],"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)} isVisible={isOpen} />\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"]}
package/dist/index.mjs CHANGED
@@ -1,4 +1,8 @@
1
1
  "use client";
2
+ import {
3
+ getAllThumbnailPaths,
4
+ getThumbnailPath
5
+ } from "./chunk-3RI33B7A.mjs";
2
6
  import {
3
7
  baseReset,
4
8
  colors,
@@ -10,7 +14,7 @@ import {
10
14
  import { useState, useEffect, lazy, Suspense } from "react";
11
15
  import { css, keyframes } from "@emotion/react";
12
16
  import { Fragment, jsx, jsxs } from "@emotion/react/jsx-runtime";
13
- var StudioUI = lazy(() => import("./StudioUI-YHQ2KQV6.mjs"));
17
+ var StudioUI = lazy(() => import("./StudioUI-N5Q5PMSR.mjs"));
14
18
  var spin = keyframes`
15
19
  to {
16
20
  transform: rotate(360deg);
@@ -174,43 +178,9 @@ function LoadingState() {
174
178
  /* @__PURE__ */ jsx("p", { css: styles.loadingText, children: "Loading Studio..." })
175
179
  ] }) });
176
180
  }
177
-
178
- // src/lib/meta.ts
179
- var _meta = {
180
- $schema: "https://gallop.software/schemas/studio-meta.json",
181
- version: 1,
182
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
183
- images: {}
184
- };
185
- var meta = _meta;
186
- function initializeMeta(data) {
187
- _meta = data;
188
- Object.assign(meta, data);
189
- }
190
- function getImageUrl(imageKey, size = "medium") {
191
- const image = meta.images[imageKey];
192
- if (!image) return void 0;
193
- const sizeData = image.sizes[size] || image.sizes.full;
194
- if (!sizeData) return void 0;
195
- if (image.cdn?.synced && image.cdn.baseUrl) {
196
- return `${image.cdn.baseUrl}${sizeData.path}`;
197
- }
198
- return sizeData.path;
199
- }
200
- function getStudioMeta(imageKey) {
201
- return meta.images[imageKey];
202
- }
203
- function getImageSize(imageKey, size = "medium") {
204
- const image = meta.images[imageKey];
205
- if (!image) return void 0;
206
- return image.sizes[size] || image.sizes.full;
207
- }
208
181
  export {
209
182
  StudioButton,
210
- getImageSize,
211
- getImageUrl,
212
- getStudioMeta,
213
- initializeMeta,
214
- meta
183
+ getAllThumbnailPaths,
184
+ getThumbnailPath
215
185
  };
216
186
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/StudioButton.tsx","../src/lib/meta.ts"],"sourcesContent":["/** @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)} isVisible={isOpen} />\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"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/components/StudioButton.tsx"],"sourcesContent":["/** @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)} isVisible={isOpen} />\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"],"mappings":"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","names":[]}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Lean meta entry - minimal data per image
3
+ */
4
+ interface LeanImageEntry {
5
+ w: number;
6
+ h: number;
7
+ blur: string;
8
+ s?: 1;
9
+ }
10
+ /**
11
+ * Lean meta schema - keyed by path from public folder
12
+ * Example: { "/portfolio/photo.jpg": { w: 2400, h: 1600, blur: "..." } }
13
+ */
14
+ type LeanMeta = Record<string, LeanImageEntry>;
15
+ /**
16
+ * File/folder item for browser
17
+ */
18
+ interface FileItem {
19
+ name: string;
20
+ path: string;
21
+ type: 'file' | 'folder';
22
+ size?: number;
23
+ dimensions?: {
24
+ width: number;
25
+ height: number;
26
+ };
27
+ cdnSynced?: boolean;
28
+ fileCount?: number;
29
+ totalSize?: number;
30
+ thumbnail?: string;
31
+ hasThumbnail?: boolean;
32
+ }
33
+ /**
34
+ * Studio configuration
35
+ */
36
+ interface StudioConfig {
37
+ r2AccountId?: string;
38
+ r2AccessKeyId?: string;
39
+ r2SecretAccessKey?: string;
40
+ r2BucketName?: string;
41
+ r2PublicUrl?: string;
42
+ thumbnailSizes?: {
43
+ small: number;
44
+ medium: number;
45
+ large: number;
46
+ };
47
+ }
48
+ /**
49
+ * Get thumbnail path from original image path
50
+ */
51
+ declare function getThumbnailPath(originalPath: string, size: 'sm' | 'md' | 'lg' | 'full'): string;
52
+ /**
53
+ * Get all thumbnail paths for an image
54
+ */
55
+ declare function getAllThumbnailPaths(originalPath: string): string[];
56
+
57
+ export { type FileItem as F, type LeanImageEntry as L, type StudioConfig as S, type LeanMeta as a, getThumbnailPath as b, getAllThumbnailPaths as g };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Lean meta entry - minimal data per image
3
+ */
4
+ interface LeanImageEntry {
5
+ w: number;
6
+ h: number;
7
+ blur: string;
8
+ s?: 1;
9
+ }
10
+ /**
11
+ * Lean meta schema - keyed by path from public folder
12
+ * Example: { "/portfolio/photo.jpg": { w: 2400, h: 1600, blur: "..." } }
13
+ */
14
+ type LeanMeta = Record<string, LeanImageEntry>;
15
+ /**
16
+ * File/folder item for browser
17
+ */
18
+ interface FileItem {
19
+ name: string;
20
+ path: string;
21
+ type: 'file' | 'folder';
22
+ size?: number;
23
+ dimensions?: {
24
+ width: number;
25
+ height: number;
26
+ };
27
+ cdnSynced?: boolean;
28
+ fileCount?: number;
29
+ totalSize?: number;
30
+ thumbnail?: string;
31
+ hasThumbnail?: boolean;
32
+ }
33
+ /**
34
+ * Studio configuration
35
+ */
36
+ interface StudioConfig {
37
+ r2AccountId?: string;
38
+ r2AccessKeyId?: string;
39
+ r2SecretAccessKey?: string;
40
+ r2BucketName?: string;
41
+ r2PublicUrl?: string;
42
+ thumbnailSizes?: {
43
+ small: number;
44
+ medium: number;
45
+ large: number;
46
+ };
47
+ }
48
+ /**
49
+ * Get thumbnail path from original image path
50
+ */
51
+ declare function getThumbnailPath(originalPath: string, size: 'sm' | 'md' | 'lg' | 'full'): string;
52
+ /**
53
+ * Get all thumbnail paths for an image
54
+ */
55
+ declare function getAllThumbnailPaths(originalPath: string): string[];
56
+
57
+ export { type FileItem as F, type LeanImageEntry as L, type StudioConfig as S, type LeanMeta as a, getThumbnailPath as b, getAllThumbnailPaths as g };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gallop.software/studio",
3
- "version": "0.1.72",
3
+ "version": "0.1.74",
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",