Created
February 11, 2026 09:51
-
-
Save JonnyBurger/790d114daa41589ad71e16b4675add4c to your computer and use it in GitHub Desktop.
Globe Code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ╭─── Claude Code v2.1.39 ──────────────────────────────────────────────────────╮ | |
| │ │ Tips for getting started │ | |
| │ Welcome back Jonny! │ Run /init to create a CLAUDE.md file with instr… │ | |
| │ │ ──────────────────────────────────────────────── │ | |
| │ ▐▛███▜▌ │ Recent activity │ | |
| │ ▝▜█████▛▘ │ No recent activity │ | |
| │ ▘▘ ▝▝ │ │ | |
| │ │ │ | |
| │ Opus 4.6 · Claude Max │ │ | |
| │ ~/voiceover-test │ │ | |
| ╰──────────────────────────────────────────────────────────────────────────────╯ | |
| ❯ /clear | |
| ⎿ (no content) | |
| ❯ use remotion best practices. | |
| create a new composition and use this globe: | |
| [React Bits Pro Globe Code - get at https://pro.reactbits.dev/docs/components/globe] | |
| just rotate the globe for 10 seconds, no arcs. | |
| overlay it with "The world is yours" in bold big Inter font | |
| ⏺ Let me start by understanding the current project structure and then loading | |
| the Remotion best practices. | |
| ⏺ Skill(remotion-best-practices) | |
| ⎿ Successfully loaded skill | |
| ⏺ Explore(Explore project structure) | |
| ⎿ Done (9 tool uses · 18.5k tokens · 18s) | |
| ⏺ Read 3 files (ctrl+o to expand) | |
| ⏺ Now let me read the existing files and install the needed package. | |
| ⏺ Read 1 file (ctrl+o to expand) | |
| ⏺ Bash(bunx remotion add @remotion/google-fonts) | |
| ⎿ Installing @remotion/google-fonts | |
| $ bun i @remotion/google-fonts@4.0.421 | |
| bun add v1.3.3 (274e01c7) | |
| … +9 lines (ctrl+o to expand) | |
| ⏺ Now let me create the Globe composition component. | |
| ⏺ Write(src/GlobeComposition.tsx) | |
| ⎿ Wrote 203 lines to src/GlobeComposition.tsx | |
| 1 import { useEffect, useRef, useState } from "react"; | |
| 2 import { | |
| 3 AbsoluteFill, | |
| 4 useCurrentFrame, | |
| 5 useVideoConfig, | |
| 6 delayRender, | |
| 7 continueRender, | |
| 8 } from "remotion"; | |
| 9 import { loadFont } from "@remotion/google-fonts/Inter"; | |
| … +193 lines (ctrl+o to expand) | |
| ⏺ Now update Root.tsx to register the new composition. | |
| ⏺ Update(src/Root.tsx) | |
| ⎿ Added 1 line | |
| 1 import "./index.css"; | |
| 2 import { Composition, CalculateMetadataFunction, staticFile } fr | |
| om "remotion"; | |
| 3 import { WinterScenes, WinterScenesProps } from "./Composition"; | |
| 4 +import { GlobeComposition } from "./GlobeComposition"; | |
| 5 import { getAudioDuration } from "./get-audio-duration"; | |
| 6 | |
| 7 const FPS = 30; | |
| ⏺ Update(src/Root.tsx) | |
| ⎿ Added 8 lines | |
| 38 return ( | |
| 39 <> | |
| 40 <Composition | |
| 41 + id="Globe" | |
| 42 + component={GlobeComposition} | |
| 43 + durationInFrames={10 * FPS} | |
| 44 + fps={FPS} | |
| 45 + width={1920} | |
| 46 + height={1080} | |
| 47 + /> | |
| 48 + <Composition | |
| 49 id="WinterScenes" | |
| 50 component={WinterScenes} | |
| 51 durationInFrames={600} | |
| ⏺ Let me verify the files look correct and check for type errors. | |
| ⏺ Read 1 file (ctrl+o to expand) | |
| ⏺ ide - getDiagnostics (MCP)(uri: "file:///Users/jonathanburger/voiceover-test/s | |
| rc/GlobeComposition.tsx") | |
| ⎿ [ | |
| { | |
| "uri": "file:///Users/jonathanburger/voiceover-test/src/GlobeCompo | |
| … +4 lines (ctrl+o to expand) | |
| ⏺ No type errors. Here's what I created: | |
| src/GlobeComposition.tsx - A new composition that: | |
| - Loads globe.gl from CDN and the land map image, using | |
| delayRender/continueRender to wait until ready | |
| - Renders a 3D globe with land dots (no arcs, labels, or rings) | |
| - Rotates the globe at 36°/second (one full revolution in 10 seconds) driven | |
| by useCurrentFrame() via pointOfView() with 0ms transition — fully | |
| frame-accurate | |
| - Overlays "The world is yours" centered in bold Inter (120px, white, with | |
| subtle text shadow) | |
| src/Root.tsx - Updated to register the new Globe composition (1920x1080, | |
| 30fps, 10 seconds = 300 frames) | |
| You can select the "Globe" composition in the Remotion Studio to preview it. | |
| ✻ Churned for 3m 11s | |
| ❯ open the studio | |
| ⏺ Bash(bunx remotion studio 2>&1 &) | |
| ⎿ Running in the background (↓ to manage) | |
| ⏺ Task Output(non-blocking) bbd6b24 | |
| ⎿ (No output) | |
| ⏺ Read 1 file (ctrl+o to expand) | |
| ⏺ Bash(bunx remotion studio 2>&1 &) | |
| ⎿ Running in the background (↓ to manage) | |
| ⏺ Task Output b7f30fa | |
| ⎿ (No output) | |
| ⏺ Read 1 file (ctrl+o to expand) | |
| ⏺ Bash(npx remotion studio --port 3123 2>&1) | |
| ⎿ Running in the background (↓ to manage) | |
| ⏺ Task Output bb786d8 | |
| ⎿ Already running on port 3000. | |
| Opened browser. Pass --force-new to force a new instance. | |
| ⏺ The Remotion Studio is already running on port 3000 and opened in your | |
| browser. Select the Globe composition from the sidebar to preview it. | |
| ✻ Cooked for 30s | |
| ⏺ Background command "Start Remotion Studio" completed (exit code 0) | |
| ⏺ The studio is open. Select the Globe composition from the left sidebar to see | |
| the rotating globe with the text overlay. | |
| ⏺ Background command "Start Remotion Studio from project directory" completed | |
| (exit code 0) | |
| ⏺ Studio is already running on port 3000. Go ahead and select the Globe | |
| composition in the sidebar to preview it. | |
| ⏺ Background command "Start Remotion Studio on port 3123" completed (exit code | |
| 0) | |
| ⏺ Studio is running at http://localhost:3000. Select the Globe composition to | |
| preview it. Let me know how it looks! | |
| ❯ render the video | |
| ⏺ Bash(npx remotion render Globe 2>&1) | |
| ⎿ Running in the background (↓ to manage) | |
| ⏺ Task Output bc91d81 | |
| ⎿ Bundling 6% | |
| Bundling 11% | |
| Bundling 51% | |
| … +264 lines (ctrl+o to expand) | |
| ⏺ The render completed at out/Globe.mp4 (460.8 kB), but the globe didn't render | |
| — WebGL contexts couldn't be created in the headless renderer. The video | |
| likely shows just the text on a black background. | |
| Let me retry with GPU-accelerated rendering and lower concurrency to fix the | |
| WebGL issue. | |
| ⏺ Bash(npx remotion render Globe --gl=angle --concurrency=1 2>&1) | |
| ⎿ Running in the background (↓ to manage) | |
| ⏺ Task Output be547e9 | |
| ⎿ Bundling 6% | |
| Bundling 12% | |
| Bundling 64% | |
| … +163 lines (ctrl+o to expand) | |
| ⏺ Render successful! The video is at out/Globe.mp4 (13.1 MB). This time | |
| --gl=angle --concurrency=1 resolved the WebGL issue — the much larger file | |
| size (13.1 MB vs 460 KB before) confirms the globe is rendering properly. | |
| ⏺ Bash(open /Users/jonathanburger/voiceover-test/out/Globe.mp4) | |
| ⎿ (No output) | |
| ⏺ Opened the video for you to preview. | |
| ✻ Churned for 1m 23s | |
| ⏺ Background command "Render the Globe composition" completed (exit code 0) | |
| ⏺ How does the video look? | |
| ⏺ Background command "Render Globe with GPU-accelerated WebGL" completed (exit | |
| code 0) | |
| ⏺ How does the video look? Let me know if you'd like any adjustments. | |
| ❯ remove the entrance animation of the globe and immediately put it in it's | |
| position | |
| ⏺ Read 1 file (ctrl+o to expand) | |
| ⏺ The globe.gl library has built-in transition animations when data loads. I | |
| need to disable those. | |
| ⏺ Update(src/GlobeComposition.tsx) | |
| ⎿ Added 4 lines | |
| 140 .pointResolution(4) | |
| 141 .pointAltitude(0) | |
| 142 .pointsMerge(true) | |
| 143 + .pointsTransitionDuration(0) | |
| 144 .arcsData([]) | |
| 145 + .arcsTransitionDuration(0) | |
| 146 .labelsData([]) | |
| 147 + .labelsTransitionDuration(0) | |
| 148 .ringsData([]) | |
| 149 + .ringsTransitionDuration(0) | |
| 150 (containerRef.current); | |
| 151 | |
| 152 const globeMat = world.globeMaterial(); | |
| ⏺ Now re-render. | |
| ⏺ Bash(npx remotion render Globe --gl=angle --concurrency=1 2>&1) | |
| ⎿ Running in the background (↓ to manage) | |
| ⎿ Found 1 new diagnostic issue in 1 file (ctrl+o to expand) | |
| ⏺ Task Output b0dec76 | |
| ⎿ Bundling 6% | |
| Bundling 12% | |
| Bundling 19% | |
| … +169 lines (ctrl+o to expand) | |
| ⏺ ringsTransitionDuration doesn't exist in the API. Let me fix that. | |
| ⏺ Update(src/GlobeComposition.tsx) | |
| ⎿ Removed 1 line | |
| 146 .labelsData([]) | |
| 147 .labelsTransitionDuration(0) | |
| 148 .ringsData([]) | |
| 149 - .ringsTransitionDuration(0) | |
| 149 (containerRef.current); | |
| 150 | |
| 151 const globeMat = world.globeMaterial(); | |
| ⏺ Bash(npx remotion render Globe --gl=angle --concurrency=1 2>&1) | |
| ⎿ Running in the background (↓ to manage) | |
| ⎿ Found 1 new diagnostic issue in 1 file (ctrl+o to expand) | |
| ⏺ Task Output bd7c9af | |
| ⎿ Bundling 6% | |
| Bundling 12% | |
| Bundling 21% | |
| … +163 lines (ctrl+o to expand) | |
| ⏺ Rendered successfully — out/Globe.mp4 (13 MB), no errors this time. The globe | |
| should now appear immediately without any entrance animation. | |
| ⏺ Bash(open /Users/jonathanburger/voiceover-test/out/Globe.mp4) | |
| ⎿ (No output) | |
| ⏺ Opened the video. How does it look now? | |
| ✻ Sautéed for 1m 36s | |
| ⏺ Background command "Re-render Globe with no entrance animation" completed | |
| (exit code 0) | |
| ⏺ How does the video look now? | |
| ⏺ Background command "Re-render Globe with fixed transition settings" completed | |
| (exit code 0) | |
| ⏺ Let me know how the video looks! Any adjustments needed? | |
| ❯ there is still an entrance transition. research and see how to disable it | |
| ⏺ Task(Research globe.gl entrance animation) | |
| ⎿ Done (9 tool uses · 18.2k tokens · 58s) | |
| ⏺ The fix is Globe({ animateIn: false }) — it's a constructor config option, not | |
| a chained method. | |
| ⏺ Update(src/GlobeComposition.tsx) | |
| ⎿ Added 1 line, removed 1 line | |
| 126 | |
| 127 // eslint-disable-next-line @typescript-eslint/no-expl | |
| icit-any | |
| 128 const Globe = (window as any).Globe; | |
| 129 - const world = Globe() | |
| 129 + const world = Globe({ animateIn: false })() | |
| 130 .globeImageUrl(createColorTexture("rgb(30, 30, 30)") | |
| ) | |
| 131 .backgroundColor("rgba(0, 0, 0, 0)") | |
| 132 .showAtmosphere(true) | |
| ⏺ Wait, I added an extra (). Let me fix that. | |
| ⏺ Update(src/GlobeComposition.tsx) | |
| ⎿ Added 1 line, removed 1 line | |
| 126 | |
| 127 // eslint-disable-next-line @typescript-eslint/no-expl | |
| icit-any | |
| 128 const Globe = (window as any).Globe; | |
| 129 - const world = Globe({ animateIn: false })() | |
| 129 + const world = Globe({ animateIn: false }) | |
| 130 .globeImageUrl(createColorTexture("rgb(30, 30, 30)") | |
| ) | |
| 131 .backgroundColor("rgba(0, 0, 0, 0)") | |
| 132 .showAtmosphere(true) | |
| ⏺ Now render. | |
| ⏺ Bash(npx remotion render Globe --gl=angle --concurrency=1 2>&1) | |
| ⎿ Running in the background (↓ to manage) | |
| ⏺ Task Output bab96fa | |
| ⎿ Bundling 6% | |
| Bundling 12% | |
| Bundling 20% | |
| … +163 lines (ctrl+o to expand) | |
| ⏺ Rendered successfully — out/Globe.mp4 (13.1 MB), no errors. The animateIn: | |
| false constructor option should now skip the entrance animation entirely. | |
| ⏺ Bash(open /Users/jonathanburger/voiceover-test/out/Globe.mp4) | |
| ⎿ (No output) | |
| ⏺ The globe should now appear immediately at its final position from frame 1. | |
| How does it look? | |
| ✻ Churned for 2m 7s | |
| ⏺ Background command "Render Globe with animateIn disabled" completed (exit code | |
| 0) | |
| ⏺ How does the video look now? Is the entrance animation gone? | |
| ❯ the globe is not yet positioned in the right way on the first frame, only | |
| from the second frame it is correct | |
| ⏺ The issue is that continueRender fires before the globe has actually rendered | |
| its first Three.js frame with the correct camera position. I need to wait for | |
| a render cycle after setting pointOfView. | |
| ⏺ Read 1 file (ctrl+o to expand) | |
| ⏺ I need to wait for the Three.js scene to actually render before telling | |
| Remotion the frame is ready. | |
| ⏺ Update(src/GlobeComposition.tsx) | |
| ⎿ Added 7 lines, removed 1 line | |
| 159 world.controls().enableZoom = false; | |
| 160 | |
| 161 globeInstanceRef.current = world; | |
| 162 - continueRender(handle); | |
| 162 + | |
| 163 + // Wait for Three.js to render a frame with the correc | |
| +t camera position | |
| 164 + requestAnimationFrame(() => { | |
| 165 + requestAnimationFrame(() => { | |
| 166 + continueRender(handle); | |
| 167 + }); | |
| 168 + }); | |
| 169 } catch (e) { | |
| 170 console.error("Globe init error:", e); | |
| 171 continueRender(handle); | |
| ⏺ Bash(npx remotion render Globe --gl=angle --concurrency=1 2>&1) | |
| ⎿ Running in the background (↓ to manage) | |
| ⏺ Task Output b4e5cf4 | |
| ⎿ Bundling 6% | |
| Bundling 12% | |
| Bundling 20% | |
| … +163 lines (ctrl+o to expand) | |
| ⏺ Rendered clean — no errors, 13.1 MB. | |
| ⏺ Bash(open /Users/jonathanburger/voiceover-test/out/Globe.mp4) | |
| ⎿ (No output) | |
| ⏺ The double requestAnimationFrame ensures Three.js has fully rendered a frame | |
| with the correct camera position before Remotion captures it. The first frame | |
| should now match the rest. How does it look? | |
| ✻ Sautéed for 1m 1s | |
| ⏺ Background command "Render Globe with rAF delay for first frame" completed | |
| (exit code 0) | |
| ⏺ How does the first frame look now? | |
| ❯ different, but the frame is still not centered. I think right now it only | |
| takes effect after the first useEffect | |
| ⏺ Right — the rotation useEffect runs before the async init completes, so | |
| globeInstanceRef.current is still null when it tries to set the position on | |
| frame 0. I need to restructure so continueRender only fires after the position | |
| is actually set. | |
| ⏺ Read 1 file (ctrl+o to expand) | |
| ⏺ The problem: the init useEffect is async, so by the time the globe is created, | |
| the rotation useEffect has already run (and globeInstanceRef.current was | |
| null). I need to use a state flag to trigger the rotation effect after init | |
| completes. | |
| ⏺ Update(src/GlobeComposition.tsx) | |
| ⎿ Added 19 lines, removed 14 lines | |
| 111 // eslint-disable-next-line @typescript-eslint/no-explicit-a | |
| ny | |
| 112 const globeInstanceRef = useRef<any>(null); | |
| 113 const [handle] = useState(() => delayRender("Loading globe") | |
| ); | |
| 114 + const [globeReady, setGlobeReady] = useState(false); | |
| 115 + const hasContinuedRef = useRef(false); | |
| 116 | |
| 117 const degreesPerSecond = 36; | |
| 118 const currentLng = (frame / fps) * degreesPerSecond; | |
| ... | |
| 155 globeMat.opacity = 1; | |
| 156 globeMat.shininess = 0.5; | |
| 157 | |
| 156 - world.pointOfView({ lat: 20, lng: 0, altitude: 2 }, 0) | |
| -; | |
| 158 world.controls().autoRotate = false; | |
| 159 world.controls().enabled = false; | |
| 160 world.controls().enableZoom = false; | |
| 161 | |
| 162 globeInstanceRef.current = world; | |
| 162 - | |
| 163 - // Wait for Three.js to render a frame with the correc | |
| -t camera position | |
| 164 - requestAnimationFrame(() => { | |
| 165 - requestAnimationFrame(() => { | |
| 166 - continueRender(handle); | |
| 167 - }); | |
| 168 - }); | |
| 163 + setGlobeReady(true); | |
| 164 } catch (e) { | |
| 165 console.error("Globe init error:", e); | |
| 166 continueRender(handle); | |
| ... | |
| 170 init(); | |
| 171 }, [handle, width, height]); | |
| 172 | |
| 173 + // Set camera position and signal frame readiness after glob | |
| +e is initialized | |
| 174 useEffect(() => { | |
| 179 - if (globeInstanceRef.current) { | |
| 180 - globeInstanceRef.current.pointOfView( | |
| 181 - { lat: 20, lng: currentLng, altitude: 2 }, | |
| 182 - 0, | |
| 183 - ); | |
| 175 + if (!globeReady || !globeInstanceRef.current) return; | |
| 176 + | |
| 177 + globeInstanceRef.current.pointOfView( | |
| 178 + { lat: 20, lng: currentLng, altitude: 2 }, | |
| 179 + 0, | |
| 180 + ); | |
| 181 + | |
| 182 + if (!hasContinuedRef.current) { | |
| 183 + hasContinuedRef.current = true; | |
| 184 + requestAnimationFrame(() => { | |
| 185 + requestAnimationFrame(() => { | |
| 186 + continueRender(handle); | |
| 187 + }); | |
| 188 + }); | |
| 189 } | |
| 185 - }, [currentLng]); | |
| 190 + }, [globeReady, currentLng, handle]); | |
| 191 | |
| 192 return ( | |
| 193 <AbsoluteFill style={{ backgroundColor: "black" }}> | |
| ⏺ Now render. | |
| ⏺ Bash(npx remotion render Globe --gl=angle --concurrency=1 2>&1) | |
| ⎿ Running in the background (↓ to manage) | |
| ⎿ Found 1 new diagnostic issue in 1 file (ctrl+o to expand) | |
| ⏺ Task Output b800c6c | |
| ⎿ Bundling 6% | |
| Bundling 12% | |
| Bundling 25% | |
| … +163 lines (ctrl+o to expand) | |
| ⏺ Clean render, 13.1 MB, no errors. | |
| ⏺ Bash(open /Users/jonathanburger/voiceover-test/out/Globe.mp4) | |
| ⎿ (No output) | |
| ⏺ Now the flow is: init creates the globe → setGlobeReady(true) triggers | |
| re-render → rotation effect runs with the actual globe instance, sets | |
| pointOfView, then waits 2 animation frames before calling continueRender. The | |
| first frame should now be correctly positioned from the start. | |
| ✻ Sautéed for 1m 37s | |
| ⏺ Background command "Render Globe with fixed first frame positioning" completed | |
| (exit code 0) | |
| ⏺ How does it look now? Is the first frame properly positioned? |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment