@thewhateverapp/tile-sdk 0.17.10 → 0.17.12

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.
@@ -1 +1 @@
1
- {"version":3,"file":"ExcaliburGame.d.ts","sourceRoot":"","sources":["../../src/react/ExcaliburGame.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ExcaliburGame.d.ts","sourceRoot":"","sources":["../../src/react/ExcaliburGame.tsx"],"names":[],"mappings":"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"}
@@ -195,6 +195,13 @@ export function ExcaliburGame({ children, width = GAME_WIDTH, height = GAME_HEIG
195
195
  });
196
196
  // Start the engine
197
197
  engine.start().then(() => {
198
+ // CRITICAL: Initialize camera position to center of game area
199
+ // If camera.pos.x is null/undefined, ALL actors will be marked as off-screen!
200
+ const camera = engine.currentScene.camera;
201
+ if (camera && (camera.pos.x === null || camera.pos.x === undefined || isNaN(camera.pos.x))) {
202
+ camera.pos.x = width / 2;
203
+ camera.pos.y = height / 2;
204
+ }
198
205
  setIsReady(true);
199
206
  onMount?.(engine);
200
207
  });
@@ -212,6 +219,12 @@ export function ExcaliburGame({ children, width = GAME_WIDTH, height = GAME_HEIG
212
219
  useEffect(() => {
213
220
  if (engineRef.current && engineRef.current.screen) {
214
221
  engineRef.current.screen.resolution = { width, height };
222
+ // Re-center camera when resolution changes to prevent off-screen issues
223
+ const camera = engineRef.current.currentScene?.camera;
224
+ if (camera && (camera.pos.x === null || camera.pos.x === undefined || isNaN(camera.pos.x))) {
225
+ camera.pos.x = width / 2;
226
+ camera.pos.y = height / 2;
227
+ }
215
228
  }
216
229
  }, [width, height]);
217
230
  // Handle background color changes
@@ -26,7 +26,7 @@ export declare const videoLayoutTemplate = "'use client';\n\nimport { VideoPlaye
26
26
  *
27
27
  * File path: src/app/(video)/tile/page.tsx
28
28
  */
29
- export declare const videoTileOverlayTemplate = "'use client';\n\nimport { useState, useEffect, useRef, useCallback } from 'react';\nimport { useVideoState, useTile } from '@thewhateverapp/tile-sdk';\n\nfunction formatTime(seconds: number): string {\n if (!seconds || !isFinite(seconds)) return '0:00';\n const mins = Math.floor(seconds / 60);\n const secs = Math.floor(seconds % 60);\n return `${mins}:${secs.toString().padStart(2, '0')}`;\n}\n\nconst CONTROLS_HIDE_DELAY = 3000;\n\nexport function TileOverlay() {\n const tile = useTile();\n const { state, controls } = useVideoState();\n \n const [hasInteracted, setHasInteracted] = useState(false);\n const [controlsVisible, setControlsVisible] = useState(false);\n const hideTimeoutRef = useRef<NodeJS.Timeout | null>(null);\n\n const showControls = useCallback(() => {\n setHasInteracted(true);\n setControlsVisible(true);\n\n if (hideTimeoutRef.current) {\n clearTimeout(hideTimeoutRef.current);\n }\n\n if (state.isPlaying) {\n hideTimeoutRef.current = setTimeout(() => {\n setControlsVisible(false);\n }, CONTROLS_HIDE_DELAY);\n }\n }, [state.isPlaying]);\n\n useEffect(() => {\n if (!state.isPlaying && hasInteracted) {\n setControlsVisible(true);\n if (hideTimeoutRef.current) {\n clearTimeout(hideTimeoutRef.current);\n }\n }\n }, [state.isPlaying, hasInteracted]);\n\n useEffect(() => {\n return () => {\n if (hideTimeoutRef.current) {\n clearTimeout(hideTimeoutRef.current);\n }\n };\n }, []);\n\n const handleInteraction = useCallback(() => {\n if (!hasInteracted) {\n showControls();\n controls.toggle();\n } else if (controlsVisible) {\n controls.toggle();\n showControls();\n } else {\n showControls();\n }\n }, [hasInteracted, controlsVisible, showControls, controls]);\n\n const showCenterPlayButton = hasInteracted && !state.isPlaying && !state.isLoading && controlsVisible;\n const showBottomControls = hasInteracted && controlsVisible;\n\n return (\n <>\n <div\n className=\"absolute inset-0 z-5 pointer-events-auto cursor-pointer\"\n onClick={handleInteraction}\n onMouseMove={() => hasInteracted && showControls()}\n />\n\n <div className=\"absolute top-3 right-3 z-20 pointer-events-auto\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n tile.navigateToPage();\n }}\n className=\"bg-black/40 backdrop-blur-sm p-2 rounded-full text-white hover:bg-black/60 transition-colors\"\n aria-label=\"Expand to full view\"\n >\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4\" />\n </svg>\n </button>\n </div>\n\n <div\n className={`absolute inset-0 flex items-center justify-center z-10 pointer-events-none transition-opacity duration-300 ${\n showCenterPlayButton ? 'opacity-100' : 'opacity-0'\n }`}\n >\n <button\n onClick={(e) => {\n e.stopPropagation();\n controls.play();\n showControls();\n }}\n className=\"bg-black/50 backdrop-blur-sm p-4 rounded-full text-white hover:bg-black/70 transition-colors pointer-events-auto\"\n aria-label=\"Play video\"\n style={{ pointerEvents: showCenterPlayButton ? 'auto' : 'none' }}\n >\n <svg className=\"w-12 h-12\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n </button>\n </div>\n\n <div\n className={`absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-t from-black/80 to-transparent pointer-events-none transition-opacity duration-300 ${\n showBottomControls ? 'opacity-100' : 'opacity-0'\n }`}\n />\n\n <div\n className={`absolute bottom-3 left-3 right-3 z-20 transition-opacity duration-300 ${\n showBottomControls ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'\n }`}\n >\n <div\n className=\"h-1 bg-white/30 rounded-full overflow-hidden mb-2 cursor-pointer\"\n onClick={(e) => {\n e.stopPropagation();\n const rect = e.currentTarget.getBoundingClientRect();\n const percent = (e.clientX - rect.left) / rect.width;\n controls.seek(percent * state.duration);\n showControls();\n }}\n >\n <div\n className=\"h-full bg-white rounded-full transition-all duration-100\"\n style={{ width: `${(state.currentTime / state.duration) * 100 || 0}%` }}\n />\n </div>\n\n <div className=\"flex items-center justify-between text-white text-xs\">\n <div className=\"flex items-center gap-2\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n controls.toggle();\n showControls();\n }}\n className=\"p-1 hover:bg-white/20 rounded transition-colors\"\n aria-label={state.isPlaying ? 'Pause' : 'Play'}\n >\n {state.isPlaying ? (\n <svg className=\"w-4 h-4\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M6 4h4v16H6V4zm8 0h4v16h-4V4z\" />\n </svg>\n ) : (\n <svg className=\"w-4 h-4\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n )}\n </button>\n <span className=\"font-mono\">\n {formatTime(state.currentTime)} / {formatTime(state.duration)}\n </span>\n </div>\n\n <button\n onClick={(e) => {\n e.stopPropagation();\n controls.setMuted(!state.muted);\n showControls();\n }}\n className=\"p-1 hover:bg-white/20 rounded transition-colors\"\n aria-label={state.muted ? 'Unmute' : 'Mute'}\n >\n {state.muted ? (\n <svg className=\"w-4 h-4\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z\" />\n </svg>\n ) : (\n <svg className=\"w-4 h-4\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z\" />\n </svg>\n )}\n </button>\n </div>\n </div>\n\n </>\n );\n}\n";
29
+ export declare const videoTileOverlayTemplate = "'use client';\n\nimport { useState, useEffect, useRef, useCallback } from 'react';\nimport { useVideoState, useTile } from '@thewhateverapp/tile-sdk';\n\nfunction formatTime(seconds: number): string {\n if (!seconds || !isFinite(seconds)) return '0:00';\n const mins = Math.floor(seconds / 60);\n const secs = Math.floor(seconds % 60);\n return `${mins}:${secs.toString().padStart(2, '0')}`;\n}\n\nconst CONTROLS_HIDE_DELAY = 3000;\n\nexport function TileOverlay() {\n const tile = useTile();\n const { state, controls } = useVideoState();\n \n const [hasInteracted, setHasInteracted] = useState(false);\n const [controlsVisible, setControlsVisible] = useState(false);\n const hideTimeoutRef = useRef<NodeJS.Timeout | null>(null);\n\n const showControls = useCallback(() => {\n setHasInteracted(true);\n setControlsVisible(true);\n\n if (hideTimeoutRef.current) {\n clearTimeout(hideTimeoutRef.current);\n }\n\n if (state.isPlaying) {\n hideTimeoutRef.current = setTimeout(() => {\n setControlsVisible(false);\n }, CONTROLS_HIDE_DELAY);\n }\n }, [state.isPlaying]);\n\n useEffect(() => {\n if (!state.isPlaying && hasInteracted) {\n setControlsVisible(true);\n if (hideTimeoutRef.current) {\n clearTimeout(hideTimeoutRef.current);\n }\n }\n }, [state.isPlaying, hasInteracted]);\n\n useEffect(() => {\n return () => {\n if (hideTimeoutRef.current) {\n clearTimeout(hideTimeoutRef.current);\n }\n };\n }, []);\n\n const handleInteraction = useCallback(() => {\n if (!hasInteracted) {\n showControls();\n controls.toggle();\n } else if (controlsVisible) {\n controls.toggle();\n showControls();\n } else {\n showControls();\n }\n }, [hasInteracted, controlsVisible, showControls, controls]);\n\n const showCenterPlayButton = hasInteracted && !state.isPlaying && !state.isLoading && controlsVisible;\n const showBottomControls = hasInteracted && controlsVisible;\n\n return (\n <>\n <div\n className=\"absolute inset-0 z-5 pointer-events-auto cursor-pointer\"\n onClick={handleInteraction}\n onMouseMove={() => hasInteracted && showControls()}\n />\n\n <div className=\"absolute top-3 right-3 z-20 pointer-events-auto\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n tile.navigateToPage();\n }}\n className=\"bg-black/40 backdrop-blur-sm p-2 rounded-full text-white hover:bg-black/60 transition-colors\"\n aria-label=\"Expand to full view\"\n >\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4\" />\n </svg>\n </button>\n </div>\n\n <div\n className={`absolute inset-0 flex items-center justify-center z-10 pointer-events-none transition-opacity duration-300 ${\n showCenterPlayButton ? 'opacity-100' : 'opacity-0'\n }`}\n >\n <button\n onClick={(e) => {\n e.stopPropagation();\n controls.play();\n showControls();\n }}\n className=\"bg-black/50 backdrop-blur-sm p-4 rounded-full text-white hover:bg-black/70 transition-colors pointer-events-auto\"\n aria-label=\"Play video\"\n style={{ pointerEvents: showCenterPlayButton ? 'auto' : 'none' }}\n >\n <svg className=\"w-12 h-12\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n </button>\n </div>\n\n <div\n className={`absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-t from-black/80 to-transparent pointer-events-none transition-opacity duration-300 ${\n showBottomControls ? 'opacity-100' : 'opacity-0'\n }`}\n />\n\n <div\n className={`absolute bottom-3 left-3 right-3 z-20 transition-opacity duration-300 ${\n showBottomControls ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'\n }`}\n >\n <div\n className=\"h-1 bg-white/30 rounded-full overflow-hidden mb-2 cursor-pointer\"\n onClick={(e) => {\n e.stopPropagation();\n const rect = e.currentTarget.getBoundingClientRect();\n const percent = (e.clientX - rect.left) / rect.width;\n controls.seek(percent * state.duration);\n showControls();\n }}\n >\n <div\n className=\"h-full bg-white rounded-full transition-all duration-100\"\n style={{ width: `${(state.currentTime / state.duration) * 100 || 0}%` }}\n />\n </div>\n\n <div className=\"flex items-center justify-between text-white text-xs\">\n <div className=\"flex items-center gap-2\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n controls.toggle();\n showControls();\n }}\n className=\"p-1 hover:bg-white/20 rounded transition-colors\"\n aria-label={state.isPlaying ? 'Pause' : 'Play'}\n >\n {state.isPlaying ? (\n <svg className=\"w-4 h-4\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M6 4h4v16H6V4zm8 0h4v16h-4V4z\" />\n </svg>\n ) : (\n <svg className=\"w-4 h-4\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n )}\n </button>\n <span className=\"font-mono\">\n {formatTime(state.currentTime)} / {formatTime(state.duration)}\n </span>\n </div>\n\n <button\n onClick={(e) => {\n e.stopPropagation();\n controls.setMuted(!state.muted);\n showControls();\n }}\n className=\"p-1 hover:bg-white/20 rounded transition-colors\"\n aria-label={state.muted ? 'Unmute' : 'Mute'}\n >\n {state.muted ? (\n <svg className=\"w-4 h-4\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z\" />\n </svg>\n ) : (\n <svg className=\"w-4 h-4\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z\" />\n </svg>\n )}\n </button>\n </div>\n </div>\n\n </>\n );\n}\n\n\nexport default TileOverlay;\n";
30
30
  /**
31
31
  * Video Page Overlay Template
32
32
  *
@@ -35,7 +35,7 @@ export declare const videoTileOverlayTemplate = "'use client';\n\nimport { useSt
35
35
  *
36
36
  * File path: src/app/(video)/page/page.tsx
37
37
  */
38
- export declare const videoPageOverlayTemplate = "'use client';\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { useVideoState, useTile } from '@thewhateverapp/tile-sdk';\n\nfunction formatTime(seconds: number): string {\n if (!seconds || !isFinite(seconds)) return '0:00';\n const mins = Math.floor(seconds / 60);\n const secs = Math.floor(seconds % 60);\n return `${mins}:${secs.toString().padStart(2, '0')}`;\n}\n\nexport function PageOverlay() {\n const tile = useTile();\n const { state, controls } = useVideoState();\n const [showControls, setShowControls] = useState(true);\n const [hideTimeout, setHideTimeout] = useState<NodeJS.Timeout | null>(null);\n\n // Auto-hide controls after 3 seconds of inactivity when playing\n const resetHideTimer = useCallback(() => {\n if (hideTimeout) {\n clearTimeout(hideTimeout);\n }\n setShowControls(true);\n\n if (state.isPlaying) {\n const timeout = setTimeout(() => {\n setShowControls(false);\n }, 3000);\n setHideTimeout(timeout);\n }\n }, [state.isPlaying, hideTimeout]);\n\n // Show controls when video is paused\n useEffect(() => {\n if (!state.isPlaying) {\n setShowControls(true);\n if (hideTimeout) {\n clearTimeout(hideTimeout);\n setHideTimeout(null);\n }\n } else {\n resetHideTimer();\n }\n }, [state.isPlaying]);\n\n // Cleanup timeout on unmount\n useEffect(() => {\n return () => {\n if (hideTimeout) {\n clearTimeout(hideTimeout);\n }\n };\n }, [hideTimeout]);\n\n // Toggle controls on tap/click\n const handleToggleControls = useCallback((e: React.MouseEvent | React.TouchEvent) => {\n // Don't toggle if clicking on a button or control\n const target = e.target as HTMLElement;\n if (target.closest('button, .controls-area')) {\n return;\n }\n\n // Prevent default to avoid double-firing on touch devices\n e.preventDefault();\n\n if (showControls) {\n setShowControls(false);\n } else {\n resetHideTimer();\n }\n }, [showControls, resetHideTimer]);\n\n // Handle mouse movement to show controls\n const handleMouseMove = useCallback(() => {\n if (state.isPlaying) {\n resetHideTimer();\n }\n }, [state.isPlaying, resetHideTimer]);\n\n return (\n <div\n className=\"absolute inset-0 z-20 pointer-events-auto\"\n onClick={handleToggleControls}\n onTouchEnd={handleToggleControls}\n onMouseMove={handleMouseMove}\n >\n {/* Center play button when paused */}\n {!state.isPlaying && !state.isLoading && (\n <div className=\"absolute inset-0 flex items-center justify-center pointer-events-auto\">\n <button\n onClick={controls.play}\n className=\"bg-black/50 backdrop-blur-sm p-6 rounded-full text-white hover:bg-black/70 transition-colors\"\n aria-label=\"Play video\"\n >\n <svg className=\"w-16 h-16\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n </button>\n </div>\n )}\n\n {/* Controls - toggleable */}\n <div\n className={`controls-area absolute bottom-0 left-0 right-0 transition-opacity duration-300 ${\n showControls ? 'opacity-100' : 'opacity-0 pointer-events-none'\n }`}\n >\n {/* Gradient background */}\n <div className=\"absolute bottom-0 left-0 right-0 h-32 bg-gradient-to-t from-black/80 to-transparent pointer-events-none\" />\n\n <div className=\"absolute bottom-4 left-4 right-4 z-20 pointer-events-auto\">\n {/* Progress bar / Timeline */}\n <div\n className=\"h-1.5 bg-white/30 rounded-full overflow-hidden mb-3 cursor-pointer group hover:h-2 transition-all\"\n onClick={(e) => {\n e.stopPropagation();\n const rect = e.currentTarget.getBoundingClientRect();\n const percent = (e.clientX - rect.left) / rect.width;\n controls.seek(percent * state.duration);\n resetHideTimer();\n }}\n >\n <div\n className=\"h-full bg-white rounded-full transition-all duration-100 group-hover:bg-blue-400\"\n style={{ width: `${(state.currentTime / state.duration) * 100 || 0}%` }}\n />\n </div>\n\n {/* Controls row */}\n <div className=\"flex items-center justify-between text-white\">\n <div className=\"flex items-center gap-3\">\n {/* Play/Pause */}\n <button\n onClick={(e) => {\n e.stopPropagation();\n controls.toggle();\n }}\n className=\"p-2 hover:bg-white/20 rounded-full transition-colors\"\n aria-label={state.isPlaying ? 'Pause' : 'Play'}\n >\n {state.isPlaying ? (\n <svg className=\"w-6 h-6\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M6 4h4v16H6V4zm8 0h4v16h-4V4z\" />\n </svg>\n ) : (\n <svg className=\"w-6 h-6\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n )}\n </button>\n\n {/* Volume */}\n <button\n onClick={(e) => {\n e.stopPropagation();\n controls.setMuted(!state.muted);\n resetHideTimer();\n }}\n className=\"p-2 hover:bg-white/20 rounded-full transition-colors\"\n aria-label={state.muted ? 'Unmute' : 'Mute'}\n >\n {state.muted ? (\n <svg className=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z\" />\n </svg>\n ) : (\n <svg className=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z\" />\n </svg>\n )}\n </button>\n\n {/* Time */}\n <span className=\"text-sm font-mono\">\n {formatTime(state.currentTime)} / {formatTime(state.duration)}\n </span>\n </div>\n\n {/* Right side - minimize button */}\n <button\n onClick={(e) => {\n e.stopPropagation();\n tile.navigateToTile();\n }}\n className=\"p-2 hover:bg-white/20 rounded-full transition-colors\"\n aria-label=\"Minimize to tile\"\n >\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M20 12H4\" />\n </svg>\n </button>\n </div>\n </div>\n </div>\n </div>\n );\n}\n";
38
+ export declare const videoPageOverlayTemplate = "'use client';\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { useVideoState, useTile } from '@thewhateverapp/tile-sdk';\n\nfunction formatTime(seconds: number): string {\n if (!seconds || !isFinite(seconds)) return '0:00';\n const mins = Math.floor(seconds / 60);\n const secs = Math.floor(seconds % 60);\n return `${mins}:${secs.toString().padStart(2, '0')}`;\n}\n\nexport function PageOverlay() {\n const tile = useTile();\n const { state, controls } = useVideoState();\n const [showControls, setShowControls] = useState(true);\n const [hideTimeout, setHideTimeout] = useState<NodeJS.Timeout | null>(null);\n\n // Auto-hide controls after 3 seconds of inactivity when playing\n const resetHideTimer = useCallback(() => {\n if (hideTimeout) {\n clearTimeout(hideTimeout);\n }\n setShowControls(true);\n\n if (state.isPlaying) {\n const timeout = setTimeout(() => {\n setShowControls(false);\n }, 3000);\n setHideTimeout(timeout);\n }\n }, [state.isPlaying, hideTimeout]);\n\n // Show controls when video is paused\n useEffect(() => {\n if (!state.isPlaying) {\n setShowControls(true);\n if (hideTimeout) {\n clearTimeout(hideTimeout);\n setHideTimeout(null);\n }\n } else {\n resetHideTimer();\n }\n }, [state.isPlaying]);\n\n // Cleanup timeout on unmount\n useEffect(() => {\n return () => {\n if (hideTimeout) {\n clearTimeout(hideTimeout);\n }\n };\n }, [hideTimeout]);\n\n // Toggle controls on tap/click\n const handleToggleControls = useCallback((e: React.MouseEvent | React.TouchEvent) => {\n // Don't toggle if clicking on a button or control\n const target = e.target as HTMLElement;\n if (target.closest('button, .controls-area')) {\n return;\n }\n\n // Prevent default to avoid double-firing on touch devices\n e.preventDefault();\n\n if (showControls) {\n setShowControls(false);\n } else {\n resetHideTimer();\n }\n }, [showControls, resetHideTimer]);\n\n // Handle mouse movement to show controls\n const handleMouseMove = useCallback(() => {\n if (state.isPlaying) {\n resetHideTimer();\n }\n }, [state.isPlaying, resetHideTimer]);\n\n return (\n <div\n className=\"absolute inset-0 z-20 pointer-events-auto\"\n onClick={handleToggleControls}\n onTouchEnd={handleToggleControls}\n onMouseMove={handleMouseMove}\n >\n {/* Center play button when paused */}\n {!state.isPlaying && !state.isLoading && (\n <div className=\"absolute inset-0 flex items-center justify-center pointer-events-auto\">\n <button\n onClick={controls.play}\n className=\"bg-black/50 backdrop-blur-sm p-6 rounded-full text-white hover:bg-black/70 transition-colors\"\n aria-label=\"Play video\"\n >\n <svg className=\"w-16 h-16\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n </button>\n </div>\n )}\n\n {/* Controls - toggleable */}\n <div\n className={`controls-area absolute bottom-0 left-0 right-0 transition-opacity duration-300 ${\n showControls ? 'opacity-100' : 'opacity-0 pointer-events-none'\n }`}\n >\n {/* Gradient background */}\n <div className=\"absolute bottom-0 left-0 right-0 h-32 bg-gradient-to-t from-black/80 to-transparent pointer-events-none\" />\n\n <div className=\"absolute bottom-4 left-4 right-4 z-20 pointer-events-auto\">\n {/* Progress bar / Timeline */}\n <div\n className=\"h-1.5 bg-white/30 rounded-full overflow-hidden mb-3 cursor-pointer group hover:h-2 transition-all\"\n onClick={(e) => {\n e.stopPropagation();\n const rect = e.currentTarget.getBoundingClientRect();\n const percent = (e.clientX - rect.left) / rect.width;\n controls.seek(percent * state.duration);\n resetHideTimer();\n }}\n >\n <div\n className=\"h-full bg-white rounded-full transition-all duration-100 group-hover:bg-blue-400\"\n style={{ width: `${(state.currentTime / state.duration) * 100 || 0}%` }}\n />\n </div>\n\n {/* Controls row */}\n <div className=\"flex items-center justify-between text-white\">\n <div className=\"flex items-center gap-3\">\n {/* Play/Pause */}\n <button\n onClick={(e) => {\n e.stopPropagation();\n controls.toggle();\n }}\n className=\"p-2 hover:bg-white/20 rounded-full transition-colors\"\n aria-label={state.isPlaying ? 'Pause' : 'Play'}\n >\n {state.isPlaying ? (\n <svg className=\"w-6 h-6\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M6 4h4v16H6V4zm8 0h4v16h-4V4z\" />\n </svg>\n ) : (\n <svg className=\"w-6 h-6\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n )}\n </button>\n\n {/* Volume */}\n <button\n onClick={(e) => {\n e.stopPropagation();\n controls.setMuted(!state.muted);\n resetHideTimer();\n }}\n className=\"p-2 hover:bg-white/20 rounded-full transition-colors\"\n aria-label={state.muted ? 'Unmute' : 'Mute'}\n >\n {state.muted ? (\n <svg className=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z\" />\n </svg>\n ) : (\n <svg className=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z\" />\n </svg>\n )}\n </button>\n\n {/* Time */}\n <span className=\"text-sm font-mono\">\n {formatTime(state.currentTime)} / {formatTime(state.duration)}\n </span>\n </div>\n\n {/* Right side - minimize button */}\n <button\n onClick={(e) => {\n e.stopPropagation();\n tile.navigateToTile();\n }}\n className=\"p-2 hover:bg-white/20 rounded-full transition-colors\"\n aria-label=\"Minimize to tile\"\n >\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M20 12H4\" />\n </svg>\n </button>\n </div>\n </div>\n </div>\n </div>\n );\n}\n\n\nexport default PageOverlay;\n";
39
39
  /**
40
40
  * Video Preview Entry - Tile
41
41
  *
@@ -1 +1 @@
1
- {"version":3,"file":"layout.tsx.template.d.ts","sourceRoot":"","sources":["../../../src/templates/video/layout.tsx.template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,uUAY/B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,0kOA+LpC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,84OAsMpC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,suOAsMpC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,q4NA6LpC,CAAC"}
1
+ {"version":3,"file":"layout.tsx.template.d.ts","sourceRoot":"","sources":["../../../src/templates/video/layout.tsx.template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,uUAY/B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,2mOAkMpC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,+6OAyMpC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,suOAsMpC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,q4NA6LpC,CAAC"}
@@ -229,6 +229,9 @@ export function TileOverlay() {
229
229
  </>
230
230
  );
231
231
  }
232
+
233
+
234
+ export default TileOverlay;
232
235
  `;
233
236
  /**
234
237
  * Video Page Overlay Template
@@ -436,6 +439,9 @@ export function PageOverlay() {
436
439
  </div>
437
440
  );
438
441
  }
442
+
443
+
444
+ export default PageOverlay;
439
445
  `;
440
446
  /**
441
447
  * Video Preview Entry - Tile
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thewhateverapp/tile-sdk",
3
- "version": "0.17.10",
3
+ "version": "0.17.12",
4
4
  "description": "SDK for building interactive tiles on The Whatever App platform",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",