@buildcores/render-client 1.0.5 → 1.0.7

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/api.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { RenderBuildRequest, AvailablePartsResponse } from "./types";
2
- declare const API_BASE_URL = "https://squid-app-7aeyk.ondigitalocean.app";
2
+ declare const API_BASE_URL = "https://www.renderapi.buildcores.com";
3
3
  export declare const API_ENDPOINTS: {
4
4
  readonly RENDER_BUILD_EXPERIMENTAL: "/render-build-experimental";
5
5
  readonly AVAILABLE_PARTS: "/available-parts";
@@ -38,17 +38,24 @@ export interface RenderAPIService {
38
38
  /**
39
39
  * Submit a render build request
40
40
  * @param parts - The parts configuration for the build
41
+ * @param config - API configuration (environment, auth token) - required
41
42
  * @returns Promise with the rendered MP4 video
42
43
  */
43
- renderBuildExperimental(parts: RenderBuildRequest): Promise<RenderBuildResponse>;
44
+ renderBuildExperimental(parts: RenderBuildRequest, config: ApiConfig): Promise<RenderBuildResponse>;
44
45
  /**
45
46
  * Get available parts for building
47
+ * @param config - API configuration (environment, auth token) - required
46
48
  * @returns Promise with available parts by category
47
49
  */
48
- getAvailableParts(): Promise<AvailablePartsResponse>;
50
+ getAvailableParts(config: ApiConfig): Promise<AvailablePartsResponse>;
49
51
  }
50
- export declare const buildApiUrl: (endpoint: string) => string;
51
- export declare const renderBuildExperimental: (request: RenderBuildRequest) => Promise<RenderBuildResponse>;
52
- export declare const renderSpriteExperimental: (request: RenderBuildRequest) => Promise<RenderSpriteResponse>;
53
- export declare const getAvailableParts: () => Promise<AvailablePartsResponse>;
52
+ export interface ApiConfig {
53
+ environment?: "staging" | "prod";
54
+ authToken?: string;
55
+ }
56
+ export declare const buildApiUrl: (endpoint: string, config: ApiConfig) => string;
57
+ export declare const buildHeaders: (config: ApiConfig) => Record<string, string>;
58
+ export declare const renderBuildExperimental: (request: RenderBuildRequest, config: ApiConfig) => Promise<RenderBuildResponse>;
59
+ export declare const renderSpriteExperimental: (request: RenderBuildRequest, config: ApiConfig) => Promise<RenderSpriteResponse>;
60
+ export declare const getAvailableParts: (config: ApiConfig) => Promise<AvailablePartsResponse>;
54
61
  export { API_BASE_URL };
@@ -1,4 +1,4 @@
1
- import { RenderBuildRequest } from "../types";
1
+ import { RenderBuildRequest, ApiConfig } from "../types";
2
2
  /**
3
3
  * Compares two RenderBuildRequest objects for equality by checking if the same IDs
4
4
  * are present in each category array, regardless of order.
@@ -9,4 +9,4 @@ export interface UseBuildRenderReturn {
9
9
  isRenderingBuild: boolean;
10
10
  renderError: string | null;
11
11
  }
12
- export declare const useBuildRender: (parts: RenderBuildRequest, onLoadStart?: () => void) => UseBuildRenderReturn;
12
+ export declare const useBuildRender: (parts: RenderBuildRequest, apiConfig: ApiConfig, onLoadStart?: () => void) => UseBuildRenderReturn;
@@ -1,4 +1,4 @@
1
- import { RenderBuildRequest } from "../types";
1
+ import { RenderBuildRequest, ApiConfig } from "../types";
2
2
  export interface UseSpriteRenderReturn {
3
3
  spriteSrc: string | null;
4
4
  isRenderingSprite: boolean;
@@ -9,4 +9,4 @@ export interface UseSpriteRenderReturn {
9
9
  totalFrames: number;
10
10
  } | null;
11
11
  }
12
- export declare const useSpriteRender: (parts: RenderBuildRequest, onLoadStart?: () => void) => UseSpriteRenderReturn;
12
+ export declare const useSpriteRender: (parts: RenderBuildRequest, apiConfig: ApiConfig, onLoadStart?: () => void) => UseSpriteRenderReturn;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,134 @@
1
1
  import * as React$1 from 'react';
2
2
  import React__default, { RefObject } from 'react';
3
3
 
4
+ interface BuildRenderVideoProps {
5
+ /**
6
+ * Parts configuration for the build render.
7
+ *
8
+ * This object defines which PC components should be included in the 3D render.
9
+ * Each part category contains an array with a single part ID that will be rendered.
10
+ *
11
+ * **Current Limitation**: Only 1 part per category is supported. Arrays must contain
12
+ * exactly one part ID per category. Future versions will support multiple parts per category.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * const parts = {
17
+ * parts: {
18
+ * CPU: ["7xjqsomhr"], // AMD Ryzen 7 9800X3D
19
+ * GPU: ["z7pyphm9k"], // ASUS GeForce RTX 5080 ASTRAL
20
+ * RAM: ["dpl1iyvb5"], // PNY DDR5
21
+ * Motherboard: ["iwin2u9vx"], // Asus ROG STRIX X870E-E GAMING WIFI
22
+ * PSU: ["m4kilv190"], // LIAN LI 1300W
23
+ * Storage: ["0bkvs17po"], // SAMSUNG 990 EVO
24
+ * PCCase: ["qq9jamk7c"], // MONTECH KING 95 PRO
25
+ * CPUCooler: ["62d8zelr5"], // ARCTIC LIQUID FREEZER 360
26
+ * }
27
+ * };
28
+ *
29
+ * <BuildRender parts={parts} size={300} />
30
+ * ```
31
+ *
32
+ * @example Minimal build (only required components)
33
+ * ```tsx
34
+ * const parts = {
35
+ * parts: {
36
+ * CPU: ["7xjqsomhr"], // Single CPU required
37
+ * Motherboard: ["iwin2u9vx"], // Single motherboard required
38
+ * PCCase: ["qq9jamk7c"], // Single case required
39
+ * }
40
+ * };
41
+ * ```
42
+ *
43
+ * Note: Part IDs must correspond to valid components in the BuildCores database.
44
+ * Use the available parts API to get valid part IDs for each category.
45
+ */
46
+ parts: RenderBuildRequest;
47
+ /**
48
+ * Video size in pixels (width and height will be the same).
49
+ *
50
+ * This determines the resolution of the rendered 3D video. Higher values
51
+ * provide better quality but may impact performance.
52
+ *
53
+ * @example
54
+ * ```tsx
55
+ * <BuildRender parts={parts} size={300} /> // 300x300px
56
+ * <BuildRender parts={parts} size={500} /> // 500x500px
57
+ * <BuildRender parts={parts} size={800} /> // 800x800px - high quality
58
+ * ```
59
+ *
60
+ * Recommended sizes:
61
+ * - 300px: Good for thumbnails or small previews
62
+ * - 500px: Standard size for most use cases
63
+ * - 800px+: High quality for detailed viewing
64
+ */
65
+ size: number;
66
+ /**
67
+ * API configuration for environment and authentication.
68
+ * This is required to make API calls to the BuildCores rendering service.
69
+ *
70
+ * @example
71
+ * ```tsx
72
+ * <BuildRender
73
+ * parts={parts}
74
+ * size={300}
75
+ * apiConfig={{
76
+ * environment: 'staging',
77
+ * authToken: 'your-auth-token'
78
+ * }}
79
+ * />
80
+ * ```
81
+ */
82
+ apiConfig: ApiConfig$1;
83
+ /**
84
+ * Optional mouse sensitivity for dragging (default: 0.005).
85
+ *
86
+ * Controls how responsive the 3D model rotation is to mouse movements.
87
+ * Lower values make rotation slower and more precise, higher values make it faster.
88
+ *
89
+ * @example
90
+ * ```tsx
91
+ * <BuildRender
92
+ * parts={parts}
93
+ * size={300}
94
+ * mouseSensitivity={0.003} // Slower, more precise
95
+ * />
96
+ *
97
+ * <BuildRender
98
+ * parts={parts}
99
+ * size={300}
100
+ * mouseSensitivity={0.01} // Faster rotation
101
+ * />
102
+ * ```
103
+ *
104
+ * @default 0.005
105
+ */
106
+ mouseSensitivity?: number;
107
+ /**
108
+ * Optional touch sensitivity for dragging (default: 0.01).
109
+ *
110
+ * Controls how responsive the 3D model rotation is to touch gestures on mobile devices.
111
+ * Generally set higher than mouseSensitivity for better touch experience.
112
+ *
113
+ * @example
114
+ * ```tsx
115
+ * <BuildRender
116
+ * parts={parts}
117
+ * size={300}
118
+ * touchSensitivity={0.008} // Slower touch rotation
119
+ * />
120
+ *
121
+ * <BuildRender
122
+ * parts={parts}
123
+ * size={300}
124
+ * touchSensitivity={0.015} // Faster touch rotation
125
+ * />
126
+ * ```
127
+ *
128
+ * @default 0.01
129
+ */
130
+ touchSensitivity?: number;
131
+ }
4
132
  interface BuildRenderProps {
5
133
  /**
6
134
  * Parts configuration for the sprite render.
@@ -44,6 +172,23 @@ interface BuildRenderProps {
44
172
  * ```
45
173
  */
46
174
  size: number;
175
+ /**
176
+ * API configuration for environment and authentication.
177
+ * This is required to make API calls to the BuildCores rendering service.
178
+ *
179
+ * @example
180
+ * ```tsx
181
+ * <SpriteRender
182
+ * parts={parts}
183
+ * size={300}
184
+ * apiConfig={{
185
+ * environment: 'staging',
186
+ * authToken: 'your-auth-token'
187
+ * }}
188
+ * />
189
+ * ```
190
+ */
191
+ apiConfig: ApiConfig$1;
47
192
  /**
48
193
  * Optional mouse sensitivity for dragging (default: 0.05).
49
194
  *
@@ -63,6 +208,37 @@ interface BuildRenderProps {
63
208
  */
64
209
  touchSensitivity?: number;
65
210
  }
211
+ /**
212
+ * API configuration for environment and authentication
213
+ */
214
+ interface ApiConfig$1 {
215
+ /**
216
+ * Environment to use for API requests
217
+ * - 'staging': Development/testing environment
218
+ * - 'prod': Production environment
219
+ *
220
+ * @example
221
+ * ```tsx
222
+ * const config: ApiConfig = {
223
+ * environment: 'staging',
224
+ * authToken: 'your-bearer-token'
225
+ * };
226
+ * ```
227
+ */
228
+ environment?: "staging" | "prod";
229
+ /**
230
+ * Bearer token for API authentication
231
+ *
232
+ * @example
233
+ * ```tsx
234
+ * const config: ApiConfig = {
235
+ * environment: 'prod',
236
+ * authToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
237
+ * };
238
+ * ```
239
+ */
240
+ authToken?: string;
241
+ }
66
242
  /**
67
243
  * Enum defining all available PC part categories that can be rendered.
68
244
  *
@@ -233,6 +409,8 @@ type AvailablePartsResponse = {
233
409
 
234
410
  declare const BuildRender: React.FC<BuildRenderProps>;
235
411
 
412
+ declare const BuildRenderVideo: React.FC<BuildRenderVideoProps>;
413
+
236
414
  declare const calculateCircularTime: (startTime: number, deltaX: number, sensitivity: number, duration: number) => number;
237
415
  interface UseVideoScrubbingOptions {
238
416
  mouseSensitivity?: number;
@@ -277,7 +455,7 @@ interface UseBuildRenderReturn {
277
455
  isRenderingBuild: boolean;
278
456
  renderError: string | null;
279
457
  }
280
- declare const useBuildRender: (parts: RenderBuildRequest, onLoadStart?: () => void) => UseBuildRenderReturn;
458
+ declare const useBuildRender: (parts: RenderBuildRequest, apiConfig: ApiConfig$1, onLoadStart?: () => void) => UseBuildRenderReturn;
281
459
 
282
460
  interface UseSpriteRenderReturn {
283
461
  spriteSrc: string | null;
@@ -289,7 +467,7 @@ interface UseSpriteRenderReturn {
289
467
  totalFrames: number;
290
468
  } | null;
291
469
  }
292
- declare const useSpriteRender: (parts: RenderBuildRequest, onLoadStart?: () => void) => UseSpriteRenderReturn;
470
+ declare const useSpriteRender: (parts: RenderBuildRequest, apiConfig: ApiConfig$1, onLoadStart?: () => void) => UseSpriteRenderReturn;
293
471
 
294
472
  interface DragIconProps {
295
473
  width?: number;
@@ -313,7 +491,7 @@ interface InstructionTooltipProps {
313
491
  }
314
492
  declare const InstructionTooltip: React__default.FC<InstructionTooltipProps>;
315
493
 
316
- declare const API_BASE_URL = "https://squid-app-7aeyk.ondigitalocean.app";
494
+ declare const API_BASE_URL = "https://www.renderapi.buildcores.com";
317
495
  declare const API_ENDPOINTS: {
318
496
  readonly RENDER_BUILD_EXPERIMENTAL: "/render-build-experimental";
319
497
  readonly AVAILABLE_PARTS: "/available-parts";
@@ -352,19 +530,26 @@ interface RenderAPIService {
352
530
  /**
353
531
  * Submit a render build request
354
532
  * @param parts - The parts configuration for the build
533
+ * @param config - API configuration (environment, auth token) - required
355
534
  * @returns Promise with the rendered MP4 video
356
535
  */
357
- renderBuildExperimental(parts: RenderBuildRequest): Promise<RenderBuildResponse>;
536
+ renderBuildExperimental(parts: RenderBuildRequest, config: ApiConfig): Promise<RenderBuildResponse>;
358
537
  /**
359
538
  * Get available parts for building
539
+ * @param config - API configuration (environment, auth token) - required
360
540
  * @returns Promise with available parts by category
361
541
  */
362
- getAvailableParts(): Promise<AvailablePartsResponse>;
542
+ getAvailableParts(config: ApiConfig): Promise<AvailablePartsResponse>;
543
+ }
544
+ interface ApiConfig {
545
+ environment?: "staging" | "prod";
546
+ authToken?: string;
363
547
  }
364
- declare const buildApiUrl: (endpoint: string) => string;
365
- declare const renderBuildExperimental: (request: RenderBuildRequest) => Promise<RenderBuildResponse>;
366
- declare const renderSpriteExperimental: (request: RenderBuildRequest) => Promise<RenderSpriteResponse>;
367
- declare const getAvailableParts: () => Promise<AvailablePartsResponse>;
548
+ declare const buildApiUrl: (endpoint: string, config: ApiConfig) => string;
549
+ declare const buildHeaders: (config: ApiConfig) => Record<string, string>;
550
+ declare const renderBuildExperimental: (request: RenderBuildRequest, config: ApiConfig) => Promise<RenderBuildResponse>;
551
+ declare const renderSpriteExperimental: (request: RenderBuildRequest, config: ApiConfig) => Promise<RenderSpriteResponse>;
552
+ declare const getAvailableParts: (config: ApiConfig) => Promise<AvailablePartsResponse>;
368
553
 
369
- export { API_BASE_URL, API_ENDPOINTS, BuildRender, DragIcon, InstructionTooltip, LoadingErrorOverlay, PartCategory, arePartsEqual, buildApiUrl, calculateCircularFrame, calculateCircularTime, getAvailableParts, renderBuildExperimental, renderSpriteExperimental, useBouncePatternProgress, useBuildRender, useSpriteRender, useSpriteScrubbing, useVideoScrubbing };
370
- export type { AvailablePartsResponse, BuildRenderProps, PartDetails, RenderAPIService, RenderBuildRequest, RenderBuildResponse, RenderSpriteResponse, UseBuildRenderReturn, UseSpriteRenderReturn };
554
+ export { API_BASE_URL, API_ENDPOINTS, BuildRender, BuildRenderVideo, DragIcon, InstructionTooltip, LoadingErrorOverlay, PartCategory, arePartsEqual, buildApiUrl, buildHeaders, calculateCircularFrame, calculateCircularTime, getAvailableParts, renderBuildExperimental, renderSpriteExperimental, useBouncePatternProgress, useBuildRender, useSpriteRender, useSpriteScrubbing, useVideoScrubbing };
555
+ export type { ApiConfig$1 as ApiConfig, AvailablePartsResponse, BuildRenderProps, BuildRenderVideoProps, PartDetails, RenderAPIService, RenderBuildRequest, RenderBuildResponse, RenderSpriteResponse, UseBuildRenderReturn, UseSpriteRenderReturn };
package/dist/index.esm.js CHANGED
@@ -320,27 +320,41 @@ function useBouncePatternProgress(enabled = true) {
320
320
  }
321
321
 
322
322
  // API Configuration
323
- const API_BASE_URL = "https://squid-app-7aeyk.ondigitalocean.app";
323
+ const API_BASE_URL = "https://www.renderapi.buildcores.com";
324
324
  // API Endpoints
325
325
  const API_ENDPOINTS = {
326
326
  RENDER_BUILD_EXPERIMENTAL: "/render-build-experimental",
327
327
  AVAILABLE_PARTS: "/available-parts",
328
328
  };
329
329
  // API URL helpers
330
- const buildApiUrl = (endpoint) => {
331
- return `${API_BASE_URL}${endpoint}`;
330
+ const buildApiUrl = (endpoint, config) => {
331
+ const baseUrl = `${API_BASE_URL}${endpoint}`;
332
+ if (config.environment) {
333
+ const separator = endpoint.includes("?") ? "&" : "?";
334
+ return `${baseUrl}${separator}environment=${config.environment}`;
335
+ }
336
+ return baseUrl;
337
+ };
338
+ // Helper to build request headers with auth token
339
+ const buildHeaders = (config) => {
340
+ const headers = {
341
+ "Content-Type": "application/json",
342
+ accept: "application/json",
343
+ };
344
+ if (config.authToken) {
345
+ headers["Authorization"] = `Bearer ${config.authToken}`;
346
+ }
347
+ return headers;
332
348
  };
333
349
  // API Implementation
334
- const renderBuildExperimental = async (request) => {
350
+ const renderBuildExperimental = async (request, config) => {
335
351
  const requestWithFormat = {
336
352
  ...request,
337
353
  format: request.format || "video", // Default to video format
338
354
  };
339
- const response = await fetch(buildApiUrl(API_ENDPOINTS.RENDER_BUILD_EXPERIMENTAL), {
355
+ const response = await fetch(buildApiUrl(API_ENDPOINTS.RENDER_BUILD_EXPERIMENTAL, config), {
340
356
  method: "POST",
341
- headers: {
342
- "Content-Type": "application/json",
343
- },
357
+ headers: buildHeaders(config),
344
358
  body: JSON.stringify(requestWithFormat),
345
359
  });
346
360
  if (!response.ok) {
@@ -355,16 +369,14 @@ const renderBuildExperimental = async (request) => {
355
369
  },
356
370
  };
357
371
  };
358
- const renderSpriteExperimental = async (request) => {
372
+ const renderSpriteExperimental = async (request, config) => {
359
373
  const requestWithFormat = {
360
374
  ...request,
361
375
  format: "sprite",
362
376
  };
363
- const response = await fetch(buildApiUrl(API_ENDPOINTS.RENDER_BUILD_EXPERIMENTAL), {
377
+ const response = await fetch(buildApiUrl(API_ENDPOINTS.RENDER_BUILD_EXPERIMENTAL, config), {
364
378
  method: "POST",
365
- headers: {
366
- "Content-Type": "application/json",
367
- },
379
+ headers: buildHeaders(config),
368
380
  body: JSON.stringify(requestWithFormat),
369
381
  });
370
382
  if (!response.ok) {
@@ -382,12 +394,10 @@ const renderSpriteExperimental = async (request) => {
382
394
  },
383
395
  };
384
396
  };
385
- const getAvailableParts = async () => {
386
- const response = await fetch(buildApiUrl(API_ENDPOINTS.AVAILABLE_PARTS), {
397
+ const getAvailableParts = async (config) => {
398
+ const response = await fetch(buildApiUrl(API_ENDPOINTS.AVAILABLE_PARTS, config), {
387
399
  method: "GET",
388
- headers: {
389
- "Content-Type": "application/json",
390
- },
400
+ headers: buildHeaders(config),
391
401
  });
392
402
  if (!response.ok) {
393
403
  throw new Error(`Get available parts failed: ${response.status} ${response.statusText}`);
@@ -395,7 +405,6 @@ const getAvailableParts = async () => {
395
405
  return response.json();
396
406
  };
397
407
 
398
- // API Types
399
408
  /**
400
409
  * Enum defining all available PC part categories that can be rendered.
401
410
  *
@@ -464,7 +473,7 @@ const arePartsEqual = (parts1, parts2) => {
464
473
  }
465
474
  return true;
466
475
  };
467
- const useBuildRender = (parts, onLoadStart) => {
476
+ const useBuildRender = (parts, apiConfig, onLoadStart) => {
468
477
  const [videoSrc, setVideoSrc] = useState(null);
469
478
  const [isRenderingBuild, setIsRenderingBuild] = useState(false);
470
479
  const [renderError, setRenderError] = useState(null);
@@ -474,7 +483,7 @@ const useBuildRender = (parts, onLoadStart) => {
474
483
  setIsRenderingBuild(true);
475
484
  setRenderError(null);
476
485
  onLoadStart?.();
477
- const response = await renderBuildExperimental(currentParts);
486
+ const response = await renderBuildExperimental(currentParts, apiConfig);
478
487
  const objectUrl = URL.createObjectURL(response.video);
479
488
  // Clean up previous video URL before setting new one
480
489
  setVideoSrc((prevSrc) => {
@@ -490,7 +499,7 @@ const useBuildRender = (parts, onLoadStart) => {
490
499
  finally {
491
500
  setIsRenderingBuild(false);
492
501
  }
493
- }, [onLoadStart]);
502
+ }, [apiConfig, onLoadStart]);
494
503
  // Effect to call API when parts content changes (using custom equality check)
495
504
  useEffect(() => {
496
505
  const shouldFetch = previousPartsRef.current === null ||
@@ -515,7 +524,7 @@ const useBuildRender = (parts, onLoadStart) => {
515
524
  };
516
525
  };
517
526
 
518
- const useSpriteRender = (parts, onLoadStart) => {
527
+ const useSpriteRender = (parts, apiConfig, onLoadStart) => {
519
528
  const [spriteSrc, setSpriteSrc] = useState(null);
520
529
  const [isRenderingSprite, setIsRenderingSprite] = useState(false);
521
530
  const [renderError, setRenderError] = useState(null);
@@ -526,7 +535,7 @@ const useSpriteRender = (parts, onLoadStart) => {
526
535
  setIsRenderingSprite(true);
527
536
  setRenderError(null);
528
537
  onLoadStart?.();
529
- const response = await renderSpriteExperimental(currentParts);
538
+ const response = await renderSpriteExperimental(currentParts, apiConfig);
530
539
  const objectUrl = URL.createObjectURL(response.sprite);
531
540
  // Clean up previous sprite URL before setting new one
532
541
  setSpriteSrc((prevSrc) => {
@@ -548,7 +557,7 @@ const useSpriteRender = (parts, onLoadStart) => {
548
557
  finally {
549
558
  setIsRenderingSprite(false);
550
559
  }
551
- }, [onLoadStart]);
560
+ }, [apiConfig, onLoadStart]);
552
561
  // Effect to call API when parts content changes (using custom equality check)
553
562
  useEffect(() => {
554
563
  const shouldFetch = previousPartsRef.current === null ||
@@ -629,13 +638,13 @@ const InstructionTooltip = ({ isVisible, progressValue, instructionIcon, }) => {
629
638
  } })) }));
630
639
  };
631
640
 
632
- const BuildRender = ({ parts, size, mouseSensitivity = 0.2, touchSensitivity = 0.2, }) => {
641
+ const BuildRender = ({ parts, size, apiConfig, mouseSensitivity = 0.2, touchSensitivity = 0.2, }) => {
633
642
  const canvasRef = useRef(null);
634
643
  const [img, setImg] = useState(null);
635
644
  const [isLoading, setIsLoading] = useState(true);
636
645
  const [bouncingAllowed, setBouncingAllowed] = useState(false);
637
646
  // Use custom hook for sprite rendering
638
- const { spriteSrc, isRenderingSprite, renderError, spriteMetadata } = useSpriteRender(parts);
647
+ const { spriteSrc, isRenderingSprite, renderError, spriteMetadata } = useSpriteRender(parts, apiConfig);
639
648
  const { value: progressValue, isBouncing } = useBouncePatternProgress(bouncingAllowed);
640
649
  const total = spriteMetadata ? spriteMetadata.totalFrames : 72;
641
650
  const cols = spriteMetadata ? spriteMetadata.cols : 12;
@@ -842,5 +851,63 @@ const useVideoScrubbing = (videoRef, options = {}) => {
842
851
  };
843
852
  };
844
853
 
845
- export { API_BASE_URL, API_ENDPOINTS, BuildRender, DragIcon, InstructionTooltip, LoadingErrorOverlay, PartCategory, arePartsEqual, buildApiUrl, calculateCircularFrame, calculateCircularTime, getAvailableParts, renderBuildExperimental, renderSpriteExperimental, useBouncePatternProgress, useBuildRender, useSpriteRender, useSpriteScrubbing, useVideoScrubbing };
854
+ const BuildRenderVideo = ({ parts, size, apiConfig, mouseSensitivity = 0.01, touchSensitivity = 0.01, }) => {
855
+ const videoRef = useRef(null);
856
+ const [isLoading, setIsLoading] = useState(true);
857
+ const [bouncingAllowed, setBouncingAllowed] = useState(false);
858
+ // Use custom hook for build rendering
859
+ const { videoSrc, isRenderingBuild, renderError } = useBuildRender(parts, apiConfig);
860
+ const { value: progressValue, isBouncing } = useBouncePatternProgress(bouncingAllowed);
861
+ const { isDragging, handleMouseDown, handleTouchStart, hasDragged } = useVideoScrubbing(videoRef, {
862
+ mouseSensitivity,
863
+ touchSensitivity,
864
+ });
865
+ const handleLoadStartInternal = useCallback(() => {
866
+ setIsLoading(true);
867
+ setBouncingAllowed(false);
868
+ }, []);
869
+ const handleCanPlayInternal = useCallback(() => {
870
+ setIsLoading(false);
871
+ // Start bouncing animation after delay
872
+ setTimeout(() => {
873
+ setBouncingAllowed(true);
874
+ }, 2000);
875
+ }, []);
876
+ useEffect(() => {
877
+ if (hasDragged.current || !videoRef.current)
878
+ return;
879
+ const duration = videoRef.current.duration;
880
+ if (!isFinite(duration))
881
+ return;
882
+ const time = calculateCircularTime(0, progressValue, 0.5, duration);
883
+ if (isFinite(time)) {
884
+ videoRef.current.currentTime = time;
885
+ }
886
+ }, [progressValue, hasDragged]);
887
+ return (jsxs("div", { style: { position: "relative", width: size, height: size }, children: [videoSrc && (jsx("video", { ref: videoRef, src: videoSrc, width: size, height: size, autoPlay: true, preload: "metadata", muted: true, playsInline: true, controls: false, disablePictureInPicture: true, controlsList: "nodownload nofullscreen noremoteplayback", ...{ "x-webkit-airplay": "deny" }, onMouseDown: handleMouseDown, onTouchStart: handleTouchStart, onLoadStart: handleLoadStartInternal, onCanPlay: handleCanPlayInternal, onLoadedData: () => {
888
+ if (videoRef.current) {
889
+ videoRef.current.pause();
890
+ }
891
+ }, style: {
892
+ cursor: isDragging ? "grabbing" : "grab",
893
+ touchAction: "none", // Prevents default touch behaviors like scrolling
894
+ display: "block",
895
+ // Completely hide video controls on all browsers including mobile
896
+ WebkitMediaControls: "none",
897
+ MozMediaControls: "none",
898
+ OMediaControls: "none",
899
+ msMediaControls: "none",
900
+ mediaControls: "none",
901
+ // Additional iOS-specific properties
902
+ WebkitTouchCallout: "none",
903
+ WebkitUserSelect: "none",
904
+ userSelect: "none",
905
+ }, children: "Your browser does not support the video tag." }, videoSrc)), jsx(LoadingErrorOverlay, { isVisible: isLoading || isRenderingBuild || !!renderError, renderError: renderError || undefined, size: size }), jsx(InstructionTooltip, { isVisible: !isLoading &&
906
+ !isRenderingBuild &&
907
+ !renderError &&
908
+ isBouncing &&
909
+ !hasDragged.current, progressValue: progressValue })] }));
910
+ };
911
+
912
+ export { API_BASE_URL, API_ENDPOINTS, BuildRender, BuildRenderVideo, DragIcon, InstructionTooltip, LoadingErrorOverlay, PartCategory, arePartsEqual, buildApiUrl, buildHeaders, calculateCircularFrame, calculateCircularTime, getAvailableParts, renderBuildExperimental, renderSpriteExperimental, useBouncePatternProgress, useBuildRender, useSpriteRender, useSpriteScrubbing, useVideoScrubbing };
846
913
  //# sourceMappingURL=index.esm.js.map