<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gabriele Bolognese</title>
    <description>The latest articles on DEV Community by Gabriele Bolognese (@therealgabry).</description>
    <link>https://dev.to/therealgabry</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3384147%2F08830226-d04b-424c-9095-2fe590873568.png</url>
      <title>DEV Community: Gabriele Bolognese</title>
      <link>https://dev.to/therealgabry</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/therealgabry"/>
    <language>en</language>
    <item>
      <title>FlashFX video editor 3D features</title>
      <dc:creator>Gabriele Bolognese</dc:creator>
      <pubDate>Mon, 30 Mar 2026 06:00:56 +0000</pubDate>
      <link>https://dev.to/therealgabry/flashfx-video-editor-3d-features-2e08</link>
      <guid>https://dev.to/therealgabry/flashfx-video-editor-3d-features-2e08</guid>
      <description>&lt;h1&gt;
  
  
  FlashFX 3D Feature System — Technical Documentation
&lt;/h1&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;1. Overview&lt;/li&gt;
&lt;li&gt;2. Technology Stack&lt;/li&gt;
&lt;li&gt;3. File Structure&lt;/li&gt;
&lt;li&gt;
4. Architecture Deep Dive

&lt;ul&gt;
&lt;li&gt;4.1 Per-Shape Renderer Model&lt;/li&gt;
&lt;li&gt;4.2 Canvas Integration&lt;/li&gt;
&lt;li&gt;4.3 Dirty Flag Render Loop&lt;/li&gt;
&lt;li&gt;4.4 Mode System&lt;/li&gt;
&lt;li&gt;4.5 Scene Serialization&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;5. ThreeDShapeElement&lt;/li&gt;

&lt;li&gt;6. ThreeDEngine&lt;/li&gt;

&lt;li&gt;7. SceneManager&lt;/li&gt;

&lt;li&gt;8. GizmoController&lt;/li&gt;

&lt;li&gt;9. GeometryFactory&lt;/li&gt;

&lt;li&gt;10. MaterialSystem&lt;/li&gt;

&lt;li&gt;11. 3D Properties Panel&lt;/li&gt;

&lt;li&gt;12. Texture System&lt;/li&gt;

&lt;li&gt;13. Model Import System&lt;/li&gt;

&lt;li&gt;14. 3D Shape Library&lt;/li&gt;

&lt;li&gt;15. Performance Guide&lt;/li&gt;

&lt;li&gt;16. Keyboard Shortcuts Reference&lt;/li&gt;

&lt;li&gt;17. Troubleshooting&lt;/li&gt;

&lt;li&gt;18. Extending the System&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. Overview
&lt;/h2&gt;

&lt;p&gt;The 3D feature system enables users to embed live, interactive Three.js viewports directly into the 2D design canvas. Each 3D shape behaves like any other canvas element — it can be repositioned, resized, layered, have its opacity adjusted, and participate in the animation timeline — but internally it contains a fully independent Three.js scene where users can place, transform, and material-paint 3D primitives or imported models.&lt;/p&gt;

&lt;h3&gt;
  
  
  Snapshot-Bridge Architecture
&lt;/h3&gt;

&lt;p&gt;The core architectural idea is called the &lt;strong&gt;snapshot-bridge&lt;/strong&gt;. Every 3D shape on the 2D canvas is a live Three.js renderer embedded as an HTML &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element inside a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; that is absolutely positioned within the 2D canvas artboard. The 2D canvas system controls &lt;em&gt;where&lt;/em&gt; the 3D viewport sits (position, size, rotation, opacity), while the Three.js renderer controls &lt;em&gt;what&lt;/em&gt; is drawn inside it.&lt;/p&gt;

&lt;p&gt;This approach was chosen over two alternatives:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Single shared Three.js scene&lt;/strong&gt; — rejected because it creates stacking and z-order conflicts between shapes, and because deletion of one shape would require surgical extraction from a shared scene graph. A bug where shapes would disappear when another was deselected drove the switch to isolation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline render-to-texture&lt;/strong&gt; — rejected because it cannot provide real-time orbit interaction. The user needs to orbit, zoom, and pan inside each shape independently while editing.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────┐
│              2D Canvas (Artboard)                │
│                                                  │
│   ┌──────────────────┐  ┌──────────────────┐     │
│   │ &amp;lt;div&amp;gt; host        │  │ &amp;lt;div&amp;gt; host        │    │
│   │  ┌──────────────┐ │  │  ┌──────────────┐ │   │
│   │  │ WebGLRenderer │ │  │  │ WebGLRenderer │ │   │
│   │  │   &amp;lt;canvas&amp;gt;    │ │  │  │   &amp;lt;canvas&amp;gt;    │ │   │
│   │  │              │ │  │  │              │ │   │
│   │  │  Scene       │ │  │  │  Scene       │ │   │
│   │  │  Camera      │ │  │  │  Camera      │ │   │
│   │  │  OrbitCtrl   │ │  │  │  OrbitCtrl   │ │   │
│   │  └──────────────┘ │  │  └──────────────┘ │   │
│   │  ThreeDShapeElem  │  │  ThreeDShapeElem  │   │
│   └──────────────────┘  └──────────────────┘     │
│                                                  │
│   CSS transform on artboard handles zoom/pan     │
└─────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bridge between the 2D world and the 3D world is the &lt;strong&gt;&lt;code&gt;DesignElement&lt;/code&gt;&lt;/strong&gt; type. Every 2D element with &lt;code&gt;type === 'threed-shape'&lt;/code&gt; stores three optional 3D fields:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;threeDMetadata&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ThreeDMetadata&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Serialized scene snapshot metadata for project save/load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;threeDGeometryType&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GeometryType&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The primitive type the shape was created with (e.g. &lt;code&gt;'box'&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;threeDSceneState&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SceneStateSnapshot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Live scene state used for persistence and restoration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These fields are defined in &lt;code&gt;src/types/design.ts&lt;/code&gt; (lines 200-205).&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Technology Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Documentation&lt;/th&gt;
&lt;th&gt;Required?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;three&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^0.183.1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Core 3D rendering engine. Provides WebGLRenderer, Scene, Camera, Geometry, Material, and all scene graph primitives. Every file in &lt;code&gt;src/3d/&lt;/code&gt; depends on it.&lt;/td&gt;
&lt;td&gt;&lt;a href="https://threejs.org/docs/" rel="noopener noreferrer"&gt;threejs.org/docs&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Hard requirement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@types/three&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^0.183.1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;TypeScript type definitions for Three.js. Development dependency only.&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.npmjs.com/package/@types/three" rel="noopener noreferrer"&gt;npmjs.com/package/@types/three&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Hard requirement (TypeScript)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gsap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^3.14.2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GreenSock animation platform. Used by the broader app animation engine; not directly imported by any &lt;code&gt;src/3d/&lt;/code&gt; file, but the animation timeline can animate 3D element canvas-level properties (position, opacity).&lt;/td&gt;
&lt;td&gt;&lt;a href="https://gsap.com/docs/v3/" rel="noopener noreferrer"&gt;gsap.com/docs&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Not directly used by 3D&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;postprocessing&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^6.38.3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Post-processing effects library for Three.js. Listed in &lt;code&gt;package.json&lt;/code&gt; and referenced in the &lt;code&gt;PostProcessingConfig&lt;/code&gt; type in &lt;code&gt;src/3d/types.ts&lt;/code&gt; (lines 177-193), but no actual post-processing passes are currently instantiated in the engine code. The type infrastructure is in place for future bloom and SSAO support.&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.npmjs.com/package/postprocessing" rel="noopener noreferrer"&gt;npmjs.com/package/postprocessing&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Soft (future use)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Additionally, the following Three.js addon modules from &lt;code&gt;three/examples/jsm/&lt;/code&gt; are used:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Module&lt;/th&gt;
&lt;th&gt;Imported By&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OrbitControls&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ThreeDEngine.ts&lt;/code&gt;, &lt;code&gt;GizmoController.ts&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Camera orbit/pan/zoom interaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TransformControls&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GizmoController.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Translate/Rotate/Scale gizmo overlay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GLTFLoader&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ModelLoader.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Loading &lt;code&gt;.glb&lt;/code&gt; and &lt;code&gt;.gltf&lt;/code&gt; files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DRACOLoader&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ModelLoader.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Decompressing Draco-encoded GLTF meshes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OBJLoader&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ModelLoader.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Loading &lt;code&gt;.obj&lt;/code&gt; files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FBXLoader&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ModelLoader.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Loading &lt;code&gt;.fbx&lt;/code&gt; files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;STLLoader&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ModelLoader.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Loading &lt;code&gt;.stl&lt;/code&gt; files&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  3. File Structure
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Core 3D files (&lt;code&gt;src/3d/&lt;/code&gt;)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;th&gt;Exports&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;types.ts&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Central type definitions and default constants for the entire 3D system. Defines all interfaces, enums, and default config objects.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GeometryType&lt;/code&gt;, &lt;code&gt;MaterialType&lt;/code&gt;, &lt;code&gt;GizmoMode&lt;/code&gt;, &lt;code&gt;SideType&lt;/code&gt;, &lt;code&gt;ToonSteps&lt;/code&gt;, &lt;code&gt;Vec3&lt;/code&gt;, &lt;code&gt;TextureMapState&lt;/code&gt;, &lt;code&gt;MaterialConfig&lt;/code&gt;, &lt;code&gt;GeometryConfig&lt;/code&gt;, &lt;code&gt;EnvironmentConfig&lt;/code&gt;, &lt;code&gt;PostProcessingConfig&lt;/code&gt;, &lt;code&gt;Object3DConfig&lt;/code&gt;, &lt;code&gt;SceneStateSnapshot&lt;/code&gt;, &lt;code&gt;SerializedObject&lt;/code&gt;, &lt;code&gt;ThreeDMetadata&lt;/code&gt;, &lt;code&gt;ThreeDSceneState&lt;/code&gt;, and all &lt;code&gt;DEFAULT_*&lt;/code&gt; constants&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;ThreeDShapeElement.ts&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Top-level facade. Each instance owns one &lt;code&gt;ThreeDEngine&lt;/code&gt;. Provides the public API consumed by &lt;code&gt;ThreeDShapeRenderer.tsx&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ThreeDShapeElement&lt;/code&gt; (class), &lt;code&gt;ThreeDShapeElementOptions&lt;/code&gt; (interface)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;ThreeDEngine.ts&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Creates and manages the WebGLRenderer, Scene, Camera, OrbitControls, lights, and render loop. Delegates shape management to &lt;code&gt;SceneManager&lt;/code&gt; and gizmo management to &lt;code&gt;GizmoController&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ThreeDEngine&lt;/code&gt; (class)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;SceneManager.ts&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Maintains the registry of 3D objects in a scene. Handles add, remove, select, raycast, transform, material, and geometry updates.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;SceneManager&lt;/code&gt; (class)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;GizmoController.ts&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Wraps Three.js &lt;code&gt;TransformControls&lt;/code&gt;. Manages attach/detach, mode switching, and the OrbitControls conflict.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GizmoController&lt;/code&gt; (class)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;GeometryFactory.ts&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pure functions that create &lt;code&gt;BufferGeometry&lt;/code&gt; instances from a &lt;code&gt;GeometryConfig&lt;/code&gt;. Supports all six primitive types plus SVG extrusion.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;createPrimitiveGeometry()&lt;/code&gt;, &lt;code&gt;createPrimitiveMesh()&lt;/code&gt;, &lt;code&gt;extrudeFromShapes()&lt;/code&gt;, &lt;code&gt;disposeGeometry()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;MaterialSystem.ts&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Creates and updates Three.js materials from a &lt;code&gt;MaterialConfig&lt;/code&gt;. Handles texture loading, caching, and disposal.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;createMaterial()&lt;/code&gt;, &lt;code&gt;updateMeshMaterial()&lt;/code&gt;, &lt;code&gt;disposeMaterial()&lt;/code&gt;, &lt;code&gt;createTextureFromFile()&lt;/code&gt;, &lt;code&gt;disposeTextureByUrl()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;ModelLoader.ts&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Loads external 3D model files. Handles format detection, loader instantiation, normalization, and object URL lifecycle.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;loadModel()&lt;/code&gt;, &lt;code&gt;isSupported()&lt;/code&gt;, &lt;code&gt;getSupportedFormatsText()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;SceneSerializer.ts&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Serializes the engine's scene state to a JSON-safe snapshot and restores it on load.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;serializeScene()&lt;/code&gt;, &lt;code&gt;createThreeDMetadata()&lt;/code&gt;, &lt;code&gt;restoreScene()&lt;/code&gt;, &lt;code&gt;isThreeDElement()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  React UI components (&lt;code&gt;src/3d/&lt;/code&gt;)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;th&gt;Exports&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;ThreeDShapeRenderer.tsx&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;React component that mounts a &lt;code&gt;ThreeDShapeElement&lt;/code&gt; into a DOM container. Manages the element lifecycle, resize syncing, interaction mode, and visibility-based pause/resume.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ThreeDShapeRenderer&lt;/code&gt; (React component, default export)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;ThreeDPropertiesPanel.tsx&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The full properties panel shown when a 3D shape is selected. Contains controls for canvas position, gizmo mode, 3D transform, geometry, material type, material properties, and texture maps.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ThreeDPropertiesPanel&lt;/code&gt; (React component, default export)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;ThreeDShapePicker.tsx&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Modal dialog for choosing a primitive shape type or importing a model. Shows a grid of six shape cards with SVG icons and a live 3D preview.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ThreeDShapePicker&lt;/code&gt; (React component, default export)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;ThreeDShapePreview.tsx&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A self-contained auto-rotating preview renderer used inside &lt;code&gt;ThreeDShapePicker&lt;/code&gt;. Creates a temporary &lt;code&gt;ThreeDEngine&lt;/code&gt;, adds the requested primitive, and orbits the camera around it.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ThreeDShapePreview&lt;/code&gt; (React component, default export)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Integration points outside &lt;code&gt;src/3d/&lt;/code&gt;
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;3D Relevance&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;src/types/design.ts&lt;/code&gt; (lines 200-205)&lt;/td&gt;
&lt;td&gt;Defines &lt;code&gt;threeDMetadata&lt;/code&gt;, &lt;code&gt;threeDGeometryType&lt;/code&gt;, and &lt;code&gt;threeDSceneState&lt;/code&gt; fields on &lt;code&gt;DesignElement&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/components/design-tool/Canvas.tsx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Renders &lt;code&gt;ThreeDShapeRenderer&lt;/code&gt; for every &lt;code&gt;threed-shape&lt;/code&gt; element. Controls pointer event routing via &lt;code&gt;activeThreeDElementId&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;public/draco/gltf/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Contains Draco decoder WASM files (&lt;code&gt;draco_decoder.js&lt;/code&gt;, &lt;code&gt;draco_decoder.wasm&lt;/code&gt;, &lt;code&gt;draco_wasm_wrapper.js&lt;/code&gt;) required by &lt;code&gt;DRACOLoader&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Dependency Graph
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;types.ts  ←─────────────────────────────────────────────┐
  ↑                                                      │
  ├── GeometryFactory.ts ← MaterialSystem.ts             │
  │         ↑                     ↑                      │
  │         └────── SceneManager.ts ──────────────┐      │
  │                       ↑                       │      │
  │              ThreeDEngine.ts ← GizmoController.ts    │
  │                       ↑                              │
  │              SceneSerializer.ts ──────────────────────┘
  │                       ↑
  │              ThreeDShapeElement.ts
  │                       ↑
  │         ┌─────────────┼──────────────┐
  │         │             │              │
  │  ThreeDShapeRenderer  │  ThreeDPropertiesPanel
  │         │             │
  │         │    ThreeDShapePicker
  │         │             │
  │         │    ThreeDShapePreview
  │         │
  │    Canvas.tsx (design-tool)
  │
  └── design.ts (types)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Architecture Deep Dive
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 Per-Shape Renderer Model
&lt;/h3&gt;

&lt;p&gt;Each &lt;code&gt;ThreeDShapeElement&lt;/code&gt; instance owns exactly one isolated Three.js world: one &lt;code&gt;WebGLRenderer&lt;/code&gt;, one &lt;code&gt;Scene&lt;/code&gt;, one &lt;code&gt;PerspectiveCamera&lt;/code&gt;, and one set of &lt;code&gt;OrbitControls&lt;/code&gt;. Shapes do &lt;strong&gt;not&lt;/strong&gt; share any Three.js resources.&lt;/p&gt;

&lt;p&gt;This is documented directly in the source header of &lt;code&gt;ThreeDShapeElement.ts&lt;/code&gt; (lines 1-17):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/*
 * Each threed-shape element on the canvas owns exactly one isolated Three.js world:
 * one WebGLRenderer, one Scene, one PerspectiveCamera, one set of OrbitControls.
 * Shapes do NOT share any Three.js resources. This design ensures:
 *
 * - Clean disposal: when a shape is deleted, its entire GL context, all geometries,
 *   materials, and textures are released in a single deterministic dispose() call.
 * - Independent animation: each shape renders only when its own dirty flag is set,
 *   keeping GPU work minimal when shapes are idle.
 * - No cross-contamination: selecting, transforming, or changing the material of one
 *   shape has zero effect on the rendering of any other shape.
 */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tradeoffs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pro:&lt;/strong&gt; Complete isolation. Deleting a shape is a single &lt;code&gt;dispose()&lt;/code&gt; call with no entanglement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro:&lt;/strong&gt; Independent dirty flags mean idle shapes consume zero GPU time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro:&lt;/strong&gt; No z-order or stacking conflicts between shapes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Con:&lt;/strong&gt; Each shape creates its own WebGL context. Browsers enforce a hard limit (typically 8-16 active contexts). Exceeding this causes the oldest context to be silently lost. This limits the practical number of simultaneous 3D shapes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4.2 Canvas Integration
&lt;/h3&gt;

&lt;p&gt;The Three.js renderer canvas is mounted by &lt;strong&gt;&lt;code&gt;ThreeDShapeRenderer.tsx&lt;/code&gt;&lt;/strong&gt;. The component creates a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; with &lt;code&gt;position: relative; overflow: hidden&lt;/code&gt; and passes it to &lt;code&gt;ThreeDShapeElement&lt;/code&gt; as the mount container. Inside, &lt;code&gt;ThreeDEngine&lt;/code&gt; creates a &lt;code&gt;WebGLRenderer&lt;/code&gt;, sets its DOM element to &lt;code&gt;display: block; width: 100%; height: 100%&lt;/code&gt;, and appends it to the container (&lt;code&gt;ThreeDEngine.ts&lt;/code&gt;, lines 56-59).&lt;/p&gt;

&lt;p&gt;The host &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; is positioned inside the 2D artboard by &lt;strong&gt;&lt;code&gt;Canvas.tsx&lt;/code&gt;&lt;/strong&gt; using absolute positioning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Canvas.tsx — positioning the 3D host div&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;
  &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;absolute&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;opacity&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;pointerEvents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;activeThreeDElementId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;zIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;borderRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;borderRadius&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ThreeDShapeRenderer&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CSS &lt;code&gt;transform&lt;/code&gt; on the artboard container handles zoom and pan automatically — the 3D &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; is a child of the artboard so it inherits the transform.&lt;/p&gt;

&lt;p&gt;Resize synchronization happens via a &lt;code&gt;useEffect&lt;/code&gt; in &lt;code&gt;ThreeDShapeRenderer.tsx&lt;/code&gt; (lines 50-54) that calls &lt;code&gt;instance.resize(element.width, element.height)&lt;/code&gt; whenever the element dimensions change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;instanceRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;instanceRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.3 Dirty Flag Render Loop
&lt;/h3&gt;

&lt;p&gt;The render loop in &lt;code&gt;ThreeDEngine.ts&lt;/code&gt; (lines 140-151) uses a &lt;strong&gt;render-on-demand&lt;/strong&gt; pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;startLoop&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disposed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;animFrameId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orbit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dirty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dirty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;requestAnimationFrame&lt;/code&gt; runs every frame, but the actual &lt;code&gt;renderer.render()&lt;/code&gt; call only fires when &lt;code&gt;this.dirty === true&lt;/code&gt;. The &lt;code&gt;orbit.update()&lt;/code&gt; still runs every frame because OrbitControls damping needs continuous updates when enabled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Events that set &lt;code&gt;dirty = true&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OrbitControls &lt;code&gt;'change'&lt;/code&gt; event&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ThreeDEngine.ts:71&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gizmo drag (&lt;code&gt;'change'&lt;/code&gt; event)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ThreeDEngine.ts:107&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;selectObject()&lt;/code&gt; called&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ThreeDEngine.ts:173&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;addPrimitive()&lt;/code&gt; called&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ThreeDEngine.ts:187&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;removeObject()&lt;/code&gt; called&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ThreeDEngine.ts:233&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;setGizmoMode()&lt;/code&gt; called&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ThreeDEngine.ts:238&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;updateTransform()&lt;/code&gt; called&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ThreeDEngine.ts:243&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;updateMaterial()&lt;/code&gt; called&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ThreeDEngine.ts:248&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;updateGeometry()&lt;/code&gt; called&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ThreeDEngine.ts:253&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;updateEnvironment()&lt;/code&gt; called&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ThreeDEngine.ts:263&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;resize()&lt;/code&gt; called&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ThreeDEngine.ts:312&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;setInteracting()&lt;/code&gt; on &lt;code&gt;ThreeDShapeElement&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ThreeDShapeElement.ts:55&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;markDirty()&lt;/code&gt; called directly&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ThreeDEngine.ts:153-154&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When a shape is idle (no user interaction, no property changes), it consumes zero GPU resources because the dirty flag stays false.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.4 Mode System
&lt;/h3&gt;

&lt;p&gt;A 3D shape has two interaction states:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Canvas mode&lt;/strong&gt; (default): The shape is selected on the 2D canvas. The user can move, resize, and adjust 2D properties (position, opacity, border radius). Pointer events are set to &lt;code&gt;'none'&lt;/code&gt; on the 3D host div, so clicks pass through to the 2D canvas system.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;3D editing mode&lt;/strong&gt;: The user has double-clicked the shape (or otherwise set it as the active 3D element). The &lt;code&gt;activeThreeDElementId&lt;/code&gt; prop on Canvas matches this element's ID. Pointer events are set to &lt;code&gt;'auto'&lt;/code&gt;, allowing orbit/pan/zoom and object selection inside the Three.js scene.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The transition is controlled by the &lt;code&gt;isInteracting&lt;/code&gt; prop on &lt;code&gt;ThreeDShapeRenderer&lt;/code&gt;, which flows into &lt;code&gt;ThreeDShapeElement.setInteracting()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ThreeDShapeElement.ts&lt;/span&gt;
&lt;span class="nf"&gt;setInteracting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_isInteracting&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_isInteracting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOrbitEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markDirty&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;isInteracting&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt;, &lt;code&gt;OrbitControls&lt;/code&gt; are disabled and the shape acts as a static image on the canvas.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.5 Scene Serialization
&lt;/h3&gt;

&lt;p&gt;Scene state is serialized to a JSON-safe object by &lt;code&gt;SceneSerializer.ts&lt;/code&gt;. The serialization captures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All objects in the scene (id, name, geometry type, geometry config, material config, position, rotation, scale, imported model name)&lt;/li&gt;
&lt;li&gt;Camera position and orbit target&lt;/li&gt;
&lt;li&gt;Environment configuration (light intensities, colors, background color)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SceneSerializer.ts — serializeScene()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;serializeScene&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ThreeDEngine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EnvironmentConfig&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;SceneStateSnapshot&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SerializedObject&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAllObjects&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;geometryType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geometryType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;material&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;material&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rotation&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;importedModelName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;importedModelName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cameraPosition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCameraPosition&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;cameraTarget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCameraTarget&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Restoration&lt;/strong&gt; is handled by &lt;code&gt;restoreScene()&lt;/code&gt;. It clears the engine's scene, iterates over the serialized objects, and calls &lt;code&gt;sceneManager.restoreFromConfig()&lt;/code&gt; for each one. Imported models (&lt;code&gt;geometryType === 'imported'&lt;/code&gt;) are &lt;strong&gt;skipped&lt;/strong&gt; during restoration because their binary mesh data is not stored in the snapshot — only primitive shapes can be fully reconstructed.&lt;/p&gt;

&lt;p&gt;After restoring objects, the camera position and environment are applied, and &lt;code&gt;markDirty()&lt;/code&gt; is called.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. ThreeDShapeElement
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;src/3d/ThreeDShapeElement.ts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ThreeDShapeElement&lt;/code&gt; class is the primary facade for the 3D system. Each instance encapsulates a full Three.js environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Constructor
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mountContainer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ThreeDShapeElementOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mountContainer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;HTMLElement&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The DOM element that will receive the WebGLRenderer canvas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;options.geometryType&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GeometryType&lt;/code&gt; (optional)&lt;/td&gt;
&lt;td&gt;Primitive type to create initially. Defaults to &lt;code&gt;'box'&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;options.sceneState&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;SceneStateSnapshot&lt;/code&gt; (optional)&lt;/td&gt;
&lt;td&gt;If provided, the scene is restored from this snapshot instead of creating a new primitive&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If &lt;code&gt;sceneState&lt;/code&gt; is provided, the constructor calls &lt;code&gt;restoreScene()&lt;/code&gt;. Otherwise, it calls &lt;code&gt;engine.addPrimitive()&lt;/code&gt; with the given geometry type.&lt;/p&gt;

&lt;h3&gt;
  
  
  Public Methods
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Signature&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;getEngine()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(): ThreeDEngine&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns the underlying engine instance for direct API access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;resize()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(width: number, height: number): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Resizes the renderer and updates the camera aspect ratio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;setInteracting()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(active: boolean): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enables/disables orbit controls and marks dirty. When &lt;code&gt;false&lt;/code&gt;, the shape acts as a static viewport&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;isInteracting()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(): boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns the current interaction state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;getSceneSnapshot()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(): SceneStateSnapshot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Serializes the current scene for persistence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;pause()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stops the render loop (for off-screen optimization)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;resume()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Restarts the render loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;dispose()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Destroys the engine, releases all GPU resources, removes the canvas from the DOM. &lt;strong&gt;Must be called when the shape is deleted.&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Lifecycle
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Construction&lt;/strong&gt;: &lt;code&gt;ThreeDShapeRenderer.tsx&lt;/code&gt; creates a &lt;code&gt;ThreeDShapeElement&lt;/code&gt; in a &lt;code&gt;useEffect&lt;/code&gt; and passes the container &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active use&lt;/strong&gt;: The user interacts via the properties panel or by entering 3D edit mode. Orbit, gizmo, material, and geometry changes flow through &lt;code&gt;getEngine()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visibility toggling&lt;/strong&gt;: When &lt;code&gt;element.visible&lt;/code&gt; becomes &lt;code&gt;false&lt;/code&gt;, &lt;code&gt;pause()&lt;/code&gt; is called. When it becomes &lt;code&gt;true&lt;/code&gt;, &lt;code&gt;resume()&lt;/code&gt; is called (&lt;code&gt;ThreeDShapeRenderer.tsx&lt;/code&gt;, lines 62-70).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Destruction&lt;/strong&gt;: The &lt;code&gt;useEffect&lt;/code&gt; cleanup function calls &lt;code&gt;dispose()&lt;/code&gt;, which cascades through &lt;code&gt;ThreeDEngine.dispose()&lt;/code&gt; to release all resources.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Programmatic Creation Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-3d-host&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ThreeDShapeElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;geometryType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sphere&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Access the engine for advanced operations&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEngine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateMaterial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;some-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;DEFAULT_MATERIAL_CONFIG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#ff0000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// When done&lt;/span&gt;
&lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. ThreeDEngine
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;src/3d/ThreeDEngine.ts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The engine is the central coordinator. It creates all Three.js infrastructure and delegates domain-specific tasks to &lt;code&gt;SceneManager&lt;/code&gt; and &lt;code&gt;GizmoController&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initialization Sequence
&lt;/h3&gt;

&lt;p&gt;When a new &lt;code&gt;ThreeDEngine(container)&lt;/code&gt; is constructed, the following happens in order:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Renderer Creation (lines 43-59)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WebGLRenderer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;antialias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;preserveDrawingBuffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;antialias&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Smooth edges on geometry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;alpha&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transparent background so the canvas artboard shows through&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;preserveDrawingBuffer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Required for &lt;code&gt;toDataURL()&lt;/code&gt; / screenshot capture&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  2. Renderer Configuration (lines 48-54)
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;setPixelRatio&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Math.min(window.devicePixelRatio, 2)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Caps at 2x to prevent 4x rendering on Retina/HiDPI. Higher ratios quadruple pixel count with diminishing visual returns.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;shadowMap.enabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enables shadow casting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;shadowMap.type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;THREE.PCFSoftShadowMap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Soft shadow edges. PCF (Percentage-Closer Filtering) with bilinear filtering produces smooth penumbras.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;toneMapping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;THREE.ACESFilmicToneMapping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ACES filmic curve maps HDR values to LDR. Provides cinema-grade color reproduction with natural highlight rolloff.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;toneMappingExposure&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Neutral exposure.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;setClearColor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x000000, 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fully transparent clear. The alpha channel is 0 so the background shows through.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  3. Scene Setup (line 61)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Scene&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An empty scene. No background color is set (transparent by default due to renderer &lt;code&gt;alpha: true&lt;/code&gt;).&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Camera Setup (lines 63-65)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;camera&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PerspectiveCamera&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FOV&lt;/td&gt;
&lt;td&gt;&lt;code&gt;50&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Moderate field of view. Avoids the fisheye distortion of wider angles while keeping a natural perspective.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Near plane&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Close enough that small objects aren't clipped.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Far plane&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Far enough for large imported models.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Position&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(4, 3, 4)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Positioned along a diagonal giving an isometric-like initial view.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  5. OrbitControls Setup (lines 67-71)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orbit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OrbitControls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orbit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enableDamping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orbit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dampingFactor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orbit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orbit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dirty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Damping creates smooth deceleration when the user releases the mouse during orbit. The &lt;code&gt;change&lt;/code&gt; event marks the scene dirty so the renderer draws the updated view.&lt;/p&gt;

&lt;h4&gt;
  
  
  6. Grid Setup (lines 73-76)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GridHelper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x444444&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x2a2a2a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;material&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Material&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;transparent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;material&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Material&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;opacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 20-unit grid with 40 divisions is created but &lt;strong&gt;hidden by default&lt;/strong&gt;. The grid is never added to the scene — it exists as a reference that can be retrieved via &lt;code&gt;getGridHelper()&lt;/code&gt; and toggled externally if needed.&lt;/p&gt;

&lt;h4&gt;
  
  
  7. Lighting Setup (lines 78-95)
&lt;/h4&gt;

&lt;p&gt;Three lights are added to every scene:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Light&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Color&lt;/th&gt;
&lt;th&gt;Intensity&lt;/th&gt;
&lt;th&gt;Position&lt;/th&gt;
&lt;th&gt;Shadow&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ambient&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AmbientLight&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#ffffff&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Directional&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DirectionalLight&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#ffffff&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(5, 10, 5)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes (2048x2048 shadow map)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Point&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PointLight&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#ffffff&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(-3, 5, -3)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The directional light is the primary light source. Its shadow camera is configured with orthographic bounds of -10 to 10 in all directions, a near plane of 0.1, and a far plane of 50.&lt;/p&gt;

&lt;h4&gt;
  
  
  8. SceneManager and GizmoController (lines 97-116)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sceneManager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SceneManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gizmo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GizmoController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orbit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gizmo's drag callbacks are wired to mark the scene dirty on drag and to sync the transform config on drag-end.&lt;/p&gt;

&lt;h4&gt;
  
  
  9. Pointer Event Handling (line 118)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pointerdown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handlePointerDown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A raycaster-based click handler detects which 3D object the user clicked and selects it.&lt;/p&gt;

&lt;h4&gt;
  
  
  10. Render Loop Start (line 120)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startLoop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Public Methods
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Signature&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;markDirty()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Forces a re-render on the next frame&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;onSelect()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;`(cb: (id: string \&lt;/td&gt;
&lt;td&gt;null) =&amp;gt; void): void`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;onTransformEnd()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(cb: (id: string, config: Object3DConfig) =&amp;gt; void): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Registers a callback for when a gizmo drag ends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;selectObject()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;`(id: string \&lt;/td&gt;
&lt;td&gt;null): void`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;addPrimitive()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(type, material?, geometry?): Object3DConfig&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creates a new primitive mesh and adds it to the scene&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;importModelFromFile()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(file: File): Promise&amp;lt;Object3DConfig&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Loads a 3D model file, normalizes it, and adds it to the scene&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;removeObject()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(id: string): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Removes an object and disposes its resources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;setGizmoMode()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(mode: GizmoMode): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Switches gizmo between translate, rotate, and scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;updateTransform()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(id, pos?, rot?, scale?): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Updates an object's position/rotation/scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;updateMaterial()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(id, config): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Replaces an object's material&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;updateGeometry()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(id, config): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Replaces an object's geometry (primitives only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;updateEnvironment()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(config): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Updates lighting and background color&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;getSelectedObject()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;`(): Object3DConfig \&lt;/td&gt;
&lt;td&gt;null`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;getAllObjects()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(): Object3DConfig[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns configs for all objects in the scene&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;getCameraPosition()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(): Vec3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns current camera position&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;getCameraTarget()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(): Vec3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns current orbit target&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;setCameraPosition()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(pos, target): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sets camera position and orbit target&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;resize()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(width, height): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Resizes renderer and updates camera aspect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;setOrbitEnabled()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(enabled: boolean): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enables/disables orbit controls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;pauseLoop()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stops the render loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;resumeLoop()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Restarts the render loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;dispose()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full teardown: stops loop, removes event listeners, disposes gizmo, scene manager, orbit, and renderer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Dispose Sequence
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disposed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;cancelAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;animFrameId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pointerdown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handlePointerDown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gizmo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sceneManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orbit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The order matters: the gizmo is detached before the scene manager disposes all meshes, then orbit and renderer are disposed, and finally the canvas DOM element is removed.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. SceneManager
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;src/3d/SceneManager.ts&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Shape Registry
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;SceneManager&lt;/code&gt; maintains a &lt;code&gt;Map&amp;lt;string, { config: Object3DConfig; mesh: THREE.Object3D }&amp;gt;&lt;/code&gt; called &lt;code&gt;objects&lt;/code&gt;. Each entry pairs an immutable config (the serializable state) with a live Three.js mesh/group.&lt;/p&gt;

&lt;p&gt;IDs are generated by &lt;code&gt;generateId()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;nextId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateId&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`3d-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;nextId&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Methods
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Signature&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;addPrimitive()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(type, materialConfig, geometryConfig): Object3DConfig&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creates a mesh via &lt;code&gt;GeometryFactory.createPrimitiveMesh()&lt;/code&gt;, positions it at &lt;code&gt;(0, 0.5, 0)&lt;/code&gt;, registers it in the map, and returns the config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;extrudeFromSvgShapes()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(shapes, name, geometryConfig, materialConfig): Object3DConfig&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creates an extruded mesh from &lt;code&gt;THREE.Shape[]&lt;/code&gt; arrays (for SVG icon shapes), registers and returns the config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;importModel()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(object, name): Object3DConfig&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Registers an externally-loaded &lt;code&gt;Object3D&lt;/code&gt; (from &lt;code&gt;ModelLoader&lt;/code&gt;). The mesh data comes from the loader; the config captures its current transform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;selectObject()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;`(id: string \&lt;/td&gt;
&lt;td&gt;null): void`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;getSelectedId()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;`(): string \&lt;/td&gt;
&lt;td&gt;null`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;getMesh()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;`(id: string): THREE.Object3D \&lt;/td&gt;
&lt;td&gt;null`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;getConfig()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;`(id: string): Object3DConfig \&lt;/td&gt;
&lt;td&gt;null`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;getAllConfigs()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(): Object3DConfig[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns all configs as an array&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;removeObject()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(id: string): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Removes the mesh from the scene, traverses it to dispose all geometries and materials, deletes from the map, and clears selection if it was the selected object&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;removeAll()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Removes every object in the map&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;raycast()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;`(raycaster: THREE.Raycaster): string \&lt;/td&gt;
&lt;td&gt;null`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;updateTransform()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(id, pos?, rot?, scale?): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Updates both the live mesh and the stored config. Only applies axes that are provided&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;syncTransformFromMesh()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(id: string): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reads the mesh's current position/rotation/scale and writes them back to the config. Called after gizmo drag-end&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;updateGeometry()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(id, config): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Disposes the old geometry and creates a new one via &lt;code&gt;createPrimitiveGeometry()&lt;/code&gt;. Only works for non-imported, non-extruded types&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;updateMaterial()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(id, config): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Traverses the object's children and calls &lt;code&gt;updateMeshMaterial()&lt;/code&gt; on each mesh. Updates the stored config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;restoreFromConfig()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;`(config: Object3DConfig): THREE.Object3D \&lt;/td&gt;
&lt;td&gt;null`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;dispose()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Calls &lt;code&gt;removeAll()&lt;/code&gt; to clean up everything&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  8. GizmoController
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;src/3d/GizmoController.ts&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Overview
&lt;/h3&gt;

&lt;p&gt;Wraps Three.js &lt;code&gt;TransformControls&lt;/code&gt; to provide translate, rotate, and scale gizmos for the selected 3D object.&lt;/p&gt;

&lt;h3&gt;
  
  
  Constructor
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Camera&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;orbit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OrbitControls&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;TransformControls&lt;/code&gt; helper is added to the scene (&lt;code&gt;scene.add(this.controls.getHelper())&lt;/code&gt;), and the gizmo size is set to &lt;code&gt;0.75&lt;/code&gt; (slightly smaller than the default).&lt;/p&gt;

&lt;h3&gt;
  
  
  OrbitControls Conflict
&lt;/h3&gt;

&lt;p&gt;When the user drags the gizmo, the pointer events would also orbit the camera. To prevent this, the controller listens for the &lt;code&gt;'dragging-changed'&lt;/code&gt; event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dragging-changed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orbit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When dragging starts (&lt;code&gt;value: true&lt;/code&gt;), orbit is disabled. When dragging ends (&lt;code&gt;value: false&lt;/code&gt;), orbit is re-enabled.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gizmo Modes
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Keyboard Shortcut&lt;/th&gt;
&lt;th&gt;Three.js Mode String&lt;/th&gt;
&lt;th&gt;Visual&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Translate&lt;/td&gt;
&lt;td&gt;W&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'translate'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;XYZ axis arrows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rotate&lt;/td&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'rotate'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;XYZ rotation rings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scale&lt;/td&gt;
&lt;td&gt;R&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'scale'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;XYZ cube handles&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note: The keyboard shortcuts (W/E/R) are not handled inside &lt;code&gt;GizmoController&lt;/code&gt; itself. They are handled by the consuming UI component that calls &lt;code&gt;engine.setGizmoMode()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Methods
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Signature&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;onDrag()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(changeCb, endCb): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Registers callbacks for continuous drag (changeCb) and drag-end (endCb)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;attach()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(id, object): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Attaches the gizmo to a mesh and stores the ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;detach()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Detaches the gizmo from any mesh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;getAttachedId()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;`(): string \&lt;/td&gt;
&lt;td&gt;null`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;setMode()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(mode: GizmoMode): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Switches between translate, rotate, and scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;getHelper()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(): THREE.Object3D&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns the gizmo's visual helper for scene management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;dispose()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(): void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Detaches and disposes the TransformControls&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  9. GeometryFactory
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;src/3d/GeometryFactory.ts&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Primitive Geometries
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Box
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;width&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt; 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Width along X axis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;height&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt; 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Height along Y axis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;depth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt; 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Depth along Z axis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;widthSegments&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1-10&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Subdivisions along width&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;heightSegments&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1-10&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Subdivisions along height&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;depthSegments&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1-10&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Subdivisions along depth&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three.js class: &lt;code&gt;THREE.BoxGeometry&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Sphere
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;radius&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt; 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sphere radius&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;widthSegments&lt;/code&gt; / &lt;code&gt;segments&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;32&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3-64&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Horizontal segments. Higher = smoother&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;heightSegments&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;half of widthSegments&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2-32&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Vertical segments&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three.js class: &lt;code&gt;THREE.SphereGeometry&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The height segments default to &lt;code&gt;Math.max(2, Math.round(widthSegments / 2))&lt;/code&gt; if not specified.&lt;/p&gt;

&lt;h4&gt;
  
  
  Cylinder
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;radiusTop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt;= 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Top circle radius. Set to 0 for a cone shape&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;radiusBottom&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt;= 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bottom circle radius&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;height&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt; 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Height along Y axis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;radialSegments&lt;/code&gt; / &lt;code&gt;segments&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3-64&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Number of faces around the circumference&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three.js class: &lt;code&gt;THREE.CylinderGeometry&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Cone
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;radius&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt; 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Base circle radius&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;height&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt; 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Height from base to apex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;radialSegments&lt;/code&gt; / &lt;code&gt;segments&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3-64&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Number of faces around the circumference&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three.js class: &lt;code&gt;THREE.ConeGeometry&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Torus
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;radius&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt; 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Distance from center of torus to center of tube&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tubeRadius&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt; 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Radius of the tube cross-section&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;radialSegments&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3-32&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Segments around the tube cross-section&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;tubularSegments&lt;/code&gt; / &lt;code&gt;segments&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3-200&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Segments around the ring&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three.js class: &lt;code&gt;THREE.TorusGeometry&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Capsule
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;radius&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt; 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Radius of the capsule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;height&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt; 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Length of the middle cylindrical section&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;radialSegments&lt;/code&gt; / &lt;code&gt;segments&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3-64&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Segments around the circumference&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three.js class: &lt;code&gt;THREE.CapsuleGeometry&lt;/code&gt;. Cap segments are hardcoded to &lt;code&gt;8&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ExtrudeGeometry (SVG-based shapes)
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;extrudeFromShapes()&lt;/code&gt; function takes an array of &lt;code&gt;THREE.Shape&lt;/code&gt; objects (parsed from SVG paths) and creates an &lt;code&gt;ExtrudeGeometry&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extrudeFromShapes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shapes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;materialConfig&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Mesh&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extrudeSettings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extrudeDepth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;bevelEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bevelEnabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;bevelThickness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bevelThickness&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;bevelSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bevelSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;bevelSegments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bevelSegments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;geometry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ExtrudeGeometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shapes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;extrudeSettings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;center&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;extrudeDepth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.01-5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How far the shape is extruded along Z&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bevelEnabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Whether to add beveled edges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bevelThickness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.05&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0-0.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How deep the bevel cuts into the shape&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bevelSize&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.03&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0-0.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How far the bevel extends outward&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bevelSegments&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1-8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Smoothness of the bevel curve&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Bevels add rounded edges to the extruded shape. At &lt;code&gt;bevelSegments: 1&lt;/code&gt; the bevel is a simple chamfer; at higher values it becomes a smooth curve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mesh Properties
&lt;/h3&gt;

&lt;p&gt;All meshes created by the factory have &lt;code&gt;castShadow&lt;/code&gt; and &lt;code&gt;receiveShadow&lt;/code&gt; set to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. MaterialSystem
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;src/3d/MaterialSystem.ts&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Material Types
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Standard (&lt;code&gt;MeshStandardMaterial&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;The default PBR material. Supports roughness/metalness workflow, emission, and all standard texture maps.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Three.js Property&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;color&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;hex&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'#3B82F6'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.color&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;roughness&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.roughness&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;metalness&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.metalness&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;emissive&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;hex&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'#000000'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.emissive&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;emissiveIntensity&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0-3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.emissiveIntensity&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;opacity&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.opacity&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flatShading&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.flatShading&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;envMapIntensity&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0-3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.envMapIntensity&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Physical (&lt;code&gt;MeshPhysicalMaterial&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Extends Standard with glass/transmission, clearcoat, sheen, and iridescence.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Three.js Property&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;transmission&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.transmission&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ior&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1-2.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.ior&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;thickness&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0-10&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.thickness&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;clearcoat&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.clearcoat&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;clearcoatRoughness&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.clearcoatRoughness&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sheen&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.sheen&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sheenColor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;hex&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'#ffffff'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.sheenColor&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sheenRoughness&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.sheenRoughness&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iridescence&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.iridescence&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iridescenceIOR&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1-2.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.iridescenceIOR&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iridescenceThicknessMin&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.iridescenceThicknessRange[0]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iridescenceThicknessMax&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;400&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.iridescenceThicknessRange[1]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;specularIntensity&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.specularIntensity&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;specularColor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;hex&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'#ffffff'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.specularColor&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When &lt;code&gt;transmission &amp;gt; 0&lt;/code&gt;, &lt;code&gt;transparent&lt;/code&gt; is automatically set to &lt;code&gt;true&lt;/code&gt; (line 163 of &lt;code&gt;MaterialSystem.ts&lt;/code&gt;).&lt;/p&gt;

&lt;h4&gt;
  
  
  Lambert (&lt;code&gt;MeshLambertMaterial&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;A non-physically-based material using Lambertian reflectance. Cheaper to render. Supports color, emissive, opacity, and common texture maps (color, alpha) only.&lt;/p&gt;

&lt;h4&gt;
  
  
  Phong (&lt;code&gt;MeshPhongMaterial&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Classic Blinn-Phong shading model with specular highlights.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Three.js Property&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;phongSpecular&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;hex&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'#ffffff'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.specular&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;shininess&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0-1000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;30&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.shininess&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Also supports normal maps and bump maps.&lt;/p&gt;

&lt;h4&gt;
  
  
  Toon (&lt;code&gt;MeshToonMaterial&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Cel-shading material with discrete shading steps.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Options&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;toonSteps&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ToonSteps&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;2&lt;/code&gt;, &lt;code&gt;3&lt;/code&gt;, &lt;code&gt;5&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Number of shading levels&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A &lt;code&gt;DataTexture&lt;/code&gt; gradient map is built by &lt;code&gt;buildToonGradient()&lt;/code&gt; with NearestFilter to ensure hard transitions between shading bands.&lt;/p&gt;

&lt;h4&gt;
  
  
  Wireframe
&lt;/h4&gt;

&lt;p&gt;Uses &lt;code&gt;MeshStandardMaterial&lt;/code&gt; with &lt;code&gt;wireframe: true&lt;/code&gt;. Supports roughness and metalness for specular response on the wire lines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Material Switching
&lt;/h3&gt;

&lt;p&gt;When the user switches material types, &lt;code&gt;handleMaterialType()&lt;/code&gt; in the properties panel preserves only &lt;code&gt;color&lt;/code&gt;, &lt;code&gt;opacity&lt;/code&gt;, and &lt;code&gt;transparent&lt;/code&gt; from the previous material and resets everything else to &lt;code&gt;DEFAULT_MATERIAL_CONFIG&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;handleMaterial&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;DEFAULT_MATERIAL_CONFIG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transparent&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All PBR-specific, Phong-specific, and Toon-specific properties are lost when switching away from those types.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Properties (All Types)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transparent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enables alpha blending&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;depthWrite&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Whether to write to the depth buffer. Disable for transparent objects to prevent z-fighting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;side&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;`'front' \&lt;/td&gt;
&lt;td&gt;'back' \&lt;/td&gt;
&lt;td&gt;'double'`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;wireframe&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Overlay wireframe on any material type&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Texture Cache
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;MaterialSystem.ts&lt;/code&gt; maintains a module-level &lt;code&gt;Map&amp;lt;string, THREE.Texture&amp;gt;&lt;/code&gt; called &lt;code&gt;textureCache&lt;/code&gt;. Textures are cached by URL to avoid re-loading the same texture multiple times across objects. The cache must be manually cleared via &lt;code&gt;disposeTextureByUrl()&lt;/code&gt; when textures are removed.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. 3D Properties Panel
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;src/3d/ThreeDPropertiesPanel.tsx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The properties panel is shown when a &lt;code&gt;threed-shape&lt;/code&gt; element is selected. It is organized into the following sections:&lt;/p&gt;

&lt;h3&gt;
  
  
  Canvas Position
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Maps To&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;Numeric input&lt;/td&gt;
&lt;td&gt;&lt;code&gt;element.x&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Numeric input&lt;/td&gt;
&lt;td&gt;&lt;code&gt;element.y&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;W&lt;/td&gt;
&lt;td&gt;Numeric input&lt;/td&gt;
&lt;td&gt;&lt;code&gt;element.width&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;H&lt;/td&gt;
&lt;td&gt;Numeric input&lt;/td&gt;
&lt;td&gt;&lt;code&gt;element.height&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R&lt;/td&gt;
&lt;td&gt;Numeric input (step 1)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;element.rotation&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Op&lt;/td&gt;
&lt;td&gt;Range slider (0-1, step 0.01)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;element.opacity&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Gizmo
&lt;/h3&gt;

&lt;p&gt;Three buttons: &lt;strong&gt;Translate&lt;/strong&gt;, &lt;strong&gt;Rotate&lt;/strong&gt;, &lt;strong&gt;Scale&lt;/strong&gt;. Active button is highlighted with cyan.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transform
&lt;/h3&gt;

&lt;p&gt;3D object transform (only shown when an object is selected inside the 3D scene):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sub-section&lt;/th&gt;
&lt;th&gt;Controls&lt;/th&gt;
&lt;th&gt;Maps To&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Position&lt;/td&gt;
&lt;td&gt;X, Y, Z numeric inputs (step 0.1)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Object3DConfig.position&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rotation&lt;/td&gt;
&lt;td&gt;X, Y, Z numeric inputs (step 0.1)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Object3DConfig.rotation&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scale&lt;/td&gt;
&lt;td&gt;X, Y, Z numeric inputs (step 0.1)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Object3DConfig.scale&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Geometry
&lt;/h3&gt;

&lt;p&gt;Shown only for non-imported primitives and extruded shapes. Controls vary by geometry type. See Section 9: GeometryFactory for the full parameter breakdown per type.&lt;/p&gt;

&lt;h3&gt;
  
  
  Material Type Selector
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; dropdown with options:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;standard&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Standard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;physical&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Physical (Glass)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lambert&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lambert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;phong&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Phong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;toon&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Toon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;wireframe&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Wireframe&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Base Properties
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Three.js Property&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Color&lt;/td&gt;
&lt;td&gt;Color picker&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#3B82F6&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.color&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Opacity&lt;/td&gt;
&lt;td&gt;Slider&lt;/td&gt;
&lt;td&gt;0-1 (step 0.01)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;material.opacity&lt;/code&gt; (auto-sets &lt;code&gt;transparent&lt;/code&gt; if &amp;lt; 1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transparent&lt;/td&gt;
&lt;td&gt;Toggle&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.transparent&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Side&lt;/td&gt;
&lt;td&gt;Segmented (Front/Back/Double)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Double&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.side&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wireframe&lt;/td&gt;
&lt;td&gt;Toggle&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.wireframe&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flat Shade&lt;/td&gt;
&lt;td&gt;Toggle (hidden for Lambert/Toon/Wireframe)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.flatShading&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Depth Write&lt;/td&gt;
&lt;td&gt;Toggle&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.depthWrite&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  PBR Properties (Standard and Physical only)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Three.js Property&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Roughness&lt;/td&gt;
&lt;td&gt;Slider&lt;/td&gt;
&lt;td&gt;0-1 (step 0.01)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.roughness&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metalness&lt;/td&gt;
&lt;td&gt;Slider&lt;/td&gt;
&lt;td&gt;0-1 (step 0.01)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.metalness&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Emissive&lt;/td&gt;
&lt;td&gt;Color picker&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.emissive&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Emiss Int&lt;/td&gt;
&lt;td&gt;Slider&lt;/td&gt;
&lt;td&gt;0-3 (step 0.01)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.emissiveIntensity&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Env Map&lt;/td&gt;
&lt;td&gt;Slider&lt;/td&gt;
&lt;td&gt;0-3 (step 0.01)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.envMapIntensity&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Physical Material Properties (Physical only)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Three.js Property&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Transmission&lt;/td&gt;
&lt;td&gt;Slider&lt;/td&gt;
&lt;td&gt;0-1 (step 0.01)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;material.transmission&lt;/code&gt; (auto-sets &lt;code&gt;transparent: true&lt;/code&gt; when &amp;gt; 0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thickness&lt;/td&gt;
&lt;td&gt;Slider&lt;/td&gt;
&lt;td&gt;0-10 (step 0.1)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.thickness&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IOR&lt;/td&gt;
&lt;td&gt;Slider&lt;/td&gt;
&lt;td&gt;1-2.5 (step 0.01)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.ior&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clearcoat&lt;/td&gt;
&lt;td&gt;Slider&lt;/td&gt;
&lt;td&gt;0-1 (step 0.01)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.clearcoat&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CC Rough&lt;/td&gt;
&lt;td&gt;Slider (shown when clearcoat &amp;gt; 0)&lt;/td&gt;
&lt;td&gt;0-1 (step 0.01)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.clearcoatRoughness&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sheen&lt;/td&gt;
&lt;td&gt;Slider&lt;/td&gt;
&lt;td&gt;0-1 (step 0.01)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.sheen&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sheen Color&lt;/td&gt;
&lt;td&gt;Color picker (shown when sheen &amp;gt; 0)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.sheenColor&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sheen Rough&lt;/td&gt;
&lt;td&gt;Slider (shown when sheen &amp;gt; 0)&lt;/td&gt;
&lt;td&gt;0-1 (step 0.01)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.sheenRoughness&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Iridescence&lt;/td&gt;
&lt;td&gt;Slider&lt;/td&gt;
&lt;td&gt;0-1 (step 0.01)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.iridescence&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Irid IOR&lt;/td&gt;
&lt;td&gt;Slider (shown when iridescence &amp;gt; 0)&lt;/td&gt;
&lt;td&gt;1-2.5 (step 0.01)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.iridescenceIOR&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Irid Min/Max&lt;/td&gt;
&lt;td&gt;Numeric inputs (shown when iridescence &amp;gt; 0)&lt;/td&gt;
&lt;td&gt;step 10&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.iridescenceThicknessRange&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Specular Int&lt;/td&gt;
&lt;td&gt;Slider&lt;/td&gt;
&lt;td&gt;0-1 (step 0.01)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.specularIntensity&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Specular Col&lt;/td&gt;
&lt;td&gt;Color picker&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.specularColor&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;IOR preset buttons: Water (1.33), Glass (1.5), Diamond (2.4).&lt;/p&gt;

&lt;h3&gt;
  
  
  Phong Properties (Phong only)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Three.js Property&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Specular&lt;/td&gt;
&lt;td&gt;Color picker&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.specular&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shininess&lt;/td&gt;
&lt;td&gt;Slider&lt;/td&gt;
&lt;td&gt;0-1000 (step 1)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.shininess&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Emissive&lt;/td&gt;
&lt;td&gt;Color picker&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.emissive&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Emiss Int&lt;/td&gt;
&lt;td&gt;Slider&lt;/td&gt;
&lt;td&gt;0-3 (step 0.01)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.emissiveIntensity&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Lambert Properties (Lambert only)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Three.js Property&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Emissive&lt;/td&gt;
&lt;td&gt;Color picker&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.emissive&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Emiss Int&lt;/td&gt;
&lt;td&gt;Slider&lt;/td&gt;
&lt;td&gt;0-3 (step 0.01)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.emissiveIntensity&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Toon Properties (Toon only)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;Options&lt;/th&gt;
&lt;th&gt;Three.js Property&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shading&lt;/td&gt;
&lt;td&gt;Segmented control&lt;/td&gt;
&lt;td&gt;2 Steps / 3 Steps / 5 Steps&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;MeshToonMaterial.gradientMap&lt;/code&gt; (rebuilt via &lt;code&gt;buildToonGradient()&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Texture Maps
&lt;/h3&gt;

&lt;p&gt;Each texture slot shows an Upload button when empty, and the filename with expand/remove buttons when loaded. See Section 12: Texture System for details.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Map&lt;/th&gt;
&lt;th&gt;Color Space&lt;/th&gt;
&lt;th&gt;Available For&lt;/th&gt;
&lt;th&gt;Three.js Property&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Color (Albedo)&lt;/td&gt;
&lt;td&gt;sRGB&lt;/td&gt;
&lt;td&gt;All types&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.map&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Roughness&lt;/td&gt;
&lt;td&gt;Linear&lt;/td&gt;
&lt;td&gt;Standard, Physical&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.roughnessMap&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metalness&lt;/td&gt;
&lt;td&gt;Linear&lt;/td&gt;
&lt;td&gt;Standard, Physical&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.metalnessMap&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normal&lt;/td&gt;
&lt;td&gt;Linear&lt;/td&gt;
&lt;td&gt;Standard, Physical, Phong&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.normalMap&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bump&lt;/td&gt;
&lt;td&gt;Linear&lt;/td&gt;
&lt;td&gt;Standard, Physical, Phong&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.bumpMap&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AO&lt;/td&gt;
&lt;td&gt;Linear&lt;/td&gt;
&lt;td&gt;Standard, Physical&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.aoMap&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Emissive Map&lt;/td&gt;
&lt;td&gt;sRGB&lt;/td&gt;
&lt;td&gt;Standard, Physical&lt;/td&gt;
&lt;td&gt;&lt;code&gt;material.emissiveMap&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alpha&lt;/td&gt;
&lt;td&gt;Linear&lt;/td&gt;
&lt;td&gt;All types&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;material.alphaMap&lt;/code&gt; (auto-sets &lt;code&gt;transparent: true&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Texture Transform Controls (per texture, expandable)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Three.js Property&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RepX&lt;/td&gt;
&lt;td&gt;Numeric input (step 0.1)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;texture.repeat.x&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RepY&lt;/td&gt;
&lt;td&gt;Numeric input (step 0.1)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;texture.repeat.y&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offset X&lt;/td&gt;
&lt;td&gt;Slider&lt;/td&gt;
&lt;td&gt;-1 to 1 (step 0.01)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;texture.offset.x&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offset Y&lt;/td&gt;
&lt;td&gt;Slider&lt;/td&gt;
&lt;td&gt;-1 to 1 (step 0.01)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;texture.offset.y&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rotation&lt;/td&gt;
&lt;td&gt;Slider + numeric&lt;/td&gt;
&lt;td&gt;0-360 (step 1, degrees)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;texture.rotation&lt;/code&gt; (converted to radians)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wrap&lt;/td&gt;
&lt;td&gt;Dropdown (Clamp/Repeat/Mirrored)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;texture.wrapS&lt;/code&gt;, &lt;code&gt;texture.wrapT&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anisotropy&lt;/td&gt;
&lt;td&gt;Slider&lt;/td&gt;
&lt;td&gt;1-16 (step 1)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;texture.anisotropy&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  12. Texture System
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Upload and Loading Pipeline
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;User clicks "Upload" on a texture slot in the properties panel.&lt;/li&gt;
&lt;li&gt;A file input accepts &lt;code&gt;.png&lt;/code&gt;, &lt;code&gt;.jpg&lt;/code&gt;, &lt;code&gt;.jpeg&lt;/code&gt;, &lt;code&gt;.webp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;createTextureFromFile()&lt;/code&gt; in &lt;code&gt;MaterialSystem.ts&lt;/code&gt; is called:

&lt;ul&gt;
&lt;li&gt;Creates an object URL via &lt;code&gt;URL.createObjectURL(file)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;THREE.TextureLoader&lt;/code&gt; to load the texture from the object URL.&lt;/li&gt;
&lt;li&gt;Sets the &lt;code&gt;colorSpace&lt;/code&gt; based on the texture type.&lt;/li&gt;
&lt;li&gt;Caches the texture in the module-level &lt;code&gt;textureCache&lt;/code&gt; map.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revokes the object URL&lt;/strong&gt; immediately after loading (&lt;code&gt;URL.revokeObjectURL(url)&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Calls the &lt;code&gt;onLoad&lt;/code&gt; callback with the texture and URL.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Color Space Rules
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Map Type&lt;/th&gt;
&lt;th&gt;Color Space&lt;/th&gt;
&lt;th&gt;Constant&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Color (Albedo)&lt;/td&gt;
&lt;td&gt;sRGB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;THREE.SRGBColorSpace&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Color data is authored in sRGB. Three.js needs to know this to correctly linearize it before lighting calculations.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Emissive Map&lt;/td&gt;
&lt;td&gt;sRGB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;THREE.SRGBColorSpace&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same reason as color maps — emissive colors are authored in sRGB.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All other maps&lt;/td&gt;
&lt;td&gt;Linear&lt;/td&gt;
&lt;td&gt;&lt;code&gt;THREE.LinearSRGBColorSpace&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Roughness, metalness, normal, bump, AO, and alpha maps store non-color data (physical parameters or vectors). If loaded as sRGB, the gamma curve would distort the values, making roughness 0.5 appear as ~0.73.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If a color map is incorrectly loaded as Linear, colors will appear washed out and desaturated. If a normal map is loaded as sRGB, surface details will be exaggerated and incorrect.&lt;/p&gt;

&lt;h3&gt;
  
  
  Texture Transform Controls
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;Three.js Property&lt;/th&gt;
&lt;th&gt;Visual Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repeat X/Y&lt;/td&gt;
&lt;td&gt;&lt;code&gt;texture.repeat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tiles the texture. Values &amp;gt; 1 repeat it; &amp;lt; 1 stretches it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offset X/Y&lt;/td&gt;
&lt;td&gt;&lt;code&gt;texture.offset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Slides the texture across the surface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rotation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;texture.rotation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rotates the texture around the UV origin (in degrees, converted to radians)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wrap Mode&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;texture.wrapS&lt;/code&gt;, &lt;code&gt;texture.wrapT&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Clamp&lt;/strong&gt;: stretches edge pixels. &lt;strong&gt;Repeat&lt;/strong&gt;: tiles seamlessly. &lt;strong&gt;Mirror&lt;/strong&gt;: tiles with alternating flip&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anisotropy&lt;/td&gt;
&lt;td&gt;&lt;code&gt;texture.anisotropy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Improves texture clarity at oblique angles. 1 is no filtering; 16 is maximum. Higher values cost more GPU&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Wrap Mode Mapping
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wrapMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ClampToEdgeWrapping&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RepeatWrapping&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;mirror&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MirroredRepeatWrapping&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  AO Maps and UV2
&lt;/h3&gt;

&lt;p&gt;Three.js &lt;code&gt;MeshStandardMaterial&lt;/code&gt; reads AO maps from a second UV channel (&lt;code&gt;uv2&lt;/code&gt;). The current codebase loads and applies AO maps via &lt;code&gt;mat.aoMap = t&lt;/code&gt; without explicitly creating a &lt;code&gt;uv2&lt;/code&gt; attribute. For primitive geometries created by Three.js, the default UV coordinates are used for both channels. This works for simple primitives but may produce incorrect AO on imported models that have separate UV2 layouts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disposal
&lt;/h3&gt;

&lt;p&gt;When a texture is removed from a slot, &lt;code&gt;disposeTextureByUrl()&lt;/code&gt; is called:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;disposeTextureByUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;textureCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;tex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;textureCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This frees the GPU memory used by the texture.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. Model Import System
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;src/3d/ModelLoader.ts&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Supported Formats
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Extensions&lt;/th&gt;
&lt;th&gt;Data Supported&lt;/th&gt;
&lt;th&gt;Three.js Loader&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GLTF Binary&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.glb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Geometry, materials, textures, animations, scene hierarchy&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GLTFLoader&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Recommended format. Self-contained binary file.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GLTF&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.gltf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same as GLB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GLTFLoader&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JSON file, may reference external &lt;code&gt;.bin&lt;/code&gt; and texture files (won't work with object URLs for multi-file GLTF)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wavefront OBJ&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.obj&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Geometry only&lt;/td&gt;
&lt;td&gt;&lt;code&gt;OBJLoader&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No materials or textures. All meshes get default material&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FBX&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.fbx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Geometry, materials (basic), animations&lt;/td&gt;
&lt;td&gt;&lt;code&gt;FBXLoader&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Autodesk format. Material fidelity varies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;STL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.stl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Geometry only (triangulated mesh)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;STLLoader&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3D printing format. Gets a default blue material (&lt;code&gt;#3B82F6&lt;/code&gt;, roughness 0.5, metalness 0.1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Import Pipeline
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;File picker&lt;/strong&gt;: The user clicks "Import 3D Model from PC" in the shape picker. A hidden &lt;code&gt;&amp;lt;input type="file" accept=".glb,.gltf,.obj,.fbx,.stl"&amp;gt;&lt;/code&gt; opens.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Extension detection&lt;/strong&gt;: The file extension is extracted via &lt;code&gt;file.name.split('.').pop()?.toLowerCase()&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Object URL creation&lt;/strong&gt;: &lt;code&gt;URL.createObjectURL(file)&lt;/code&gt; creates a temporary URL the loader can fetch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Loader selection&lt;/strong&gt;: A &lt;code&gt;switch&lt;/code&gt; statement selects the appropriate loader:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;   &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;glb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
     &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gltf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getGLTFLoader&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;loadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
     &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;obj&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getOBJLoader&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;loadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
     &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fbx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getFBXLoader&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;loadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
     &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;// returns geometry, wrapped in a Mesh&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Normalization&lt;/strong&gt;: &lt;code&gt;normalizeModel()&lt;/code&gt; scales and centers the model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Computes the bounding box of the loaded object.&lt;/li&gt;
&lt;li&gt;Calculates a scale factor so the largest dimension becomes 2 units: &lt;code&gt;scaleFactor = 2 / maxDim&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Applies the scale.&lt;/li&gt;
&lt;li&gt;Recomputes the bounding box and translates the object so its center is at the origin.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Shadow setup&lt;/strong&gt;: All meshes in the model have &lt;code&gt;castShadow&lt;/code&gt; and &lt;code&gt;receiveShadow&lt;/code&gt; set to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;URL revocation&lt;/strong&gt;: &lt;code&gt;URL.revokeObjectURL(url)&lt;/code&gt; releases the temporary URL.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scene registration&lt;/strong&gt;: The engine calls &lt;code&gt;sceneManager.removeAll()&lt;/code&gt; (clearing any existing primitives) then &lt;code&gt;sceneManager.importModel(object, name)&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Draco Decoder Setup
&lt;/h3&gt;

&lt;p&gt;Draco is a compression algorithm for GLTF meshes. The decoder files must be served from &lt;code&gt;public/draco/gltf/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public/draco/gltf/
├── draco_decoder.js
├── draco_decoder.wasm
└── draco_wasm_wrapper.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The path is configured in &lt;code&gt;getGLTFLoader()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dracoLoader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DRACOLoader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;dracoLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDecoderPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/draco/gltf/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;gltfLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDRACOLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dracoLoader&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If these files are missing, loading Draco-compressed GLTF files will fail silently or throw a network error.&lt;/p&gt;

&lt;h3&gt;
  
  
  Loader Caching
&lt;/h3&gt;

&lt;p&gt;Loaders are lazily instantiated as module-level singletons. The first call to &lt;code&gt;getGLTFLoader()&lt;/code&gt;, &lt;code&gt;getOBJLoader()&lt;/code&gt;, etc. creates the loader; subsequent calls reuse it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Engine Safety
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ThreeDEngine.importModelFromFile()&lt;/code&gt; (lines 195-226) includes safety checks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If the engine is disposed before loading starts, an error is thrown.&lt;/li&gt;
&lt;li&gt;If the engine is disposed during the async load, the loaded object's geometries and materials are immediately disposed to prevent memory leaks.&lt;/li&gt;
&lt;li&gt;On success, the existing scene is cleared before the model is added.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Serialization Limitation
&lt;/h3&gt;

&lt;p&gt;Imported models have &lt;code&gt;geometryType: 'imported'&lt;/code&gt; in their config. The &lt;code&gt;restoreScene()&lt;/code&gt; function &lt;strong&gt;skips&lt;/strong&gt; imported models because their binary mesh data cannot be reconstructed from the JSON config. If a project is saved and reloaded, imported models will be lost. Only primitive shapes survive serialization.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. 3D Shape Library
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;src/3d/ThreeDShapePicker.tsx&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Opening the Picker
&lt;/h3&gt;

&lt;p&gt;The picker is controlled by an &lt;code&gt;isOpen&lt;/code&gt; boolean prop. When open, it renders a portal (&lt;code&gt;createPortal&lt;/code&gt;) into &lt;code&gt;document.body&lt;/code&gt; with a full-screen backdrop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shape Catalog
&lt;/h3&gt;

&lt;p&gt;The catalog is a hardcoded array of six entries (&lt;code&gt;SHAPES&lt;/code&gt; constant, lines 74-111):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Icon&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;box&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Box&lt;/td&gt;
&lt;td&gt;A solid rectangular cuboid with flat faces&lt;/td&gt;
&lt;td&gt;Custom SVG (isometric wireframe)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sphere&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sphere&lt;/td&gt;
&lt;td&gt;A perfect round ball with smooth surface&lt;/td&gt;
&lt;td&gt;Custom SVG (circle with meridians)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cylinder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cylinder&lt;/td&gt;
&lt;td&gt;A tube shape with circular top and bottom&lt;/td&gt;
&lt;td&gt;Custom SVG (ellipses + lines)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;torus&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Torus&lt;/td&gt;
&lt;td&gt;A donut shape with a hole through the center&lt;/td&gt;
&lt;td&gt;Custom SVG (nested ellipses)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cone&lt;/td&gt;
&lt;td&gt;A pointed shape tapering from a circular base&lt;/td&gt;
&lt;td&gt;Custom SVG (triangle + ellipse)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;capsule&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Capsule&lt;/td&gt;
&lt;td&gt;A cylinder capped with hemispheres at both ends&lt;/td&gt;
&lt;td&gt;Custom SVG (rounded rect)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each icon is a custom inline SVG component (44x44 viewBox) defined in the same file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layout
&lt;/h3&gt;

&lt;p&gt;The modal has two columns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Left column&lt;/strong&gt;: A 3x2 grid of shape cards. Clicking a card selects it (highlighted with cyan border). Below the grid is an "Import 3D Model from PC" button.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Right column&lt;/strong&gt; (192px wide): A live 3D preview (&lt;code&gt;ThreeDShapePreview&lt;/code&gt;) of the selected shape, the shape name and description, and an "Add to Canvas" button.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ThreeDShapePreview
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;src/3d/ThreeDShapePreview.tsx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Creates a temporary &lt;code&gt;ThreeDEngine&lt;/code&gt; in a small container (default 200x200, configurable via &lt;code&gt;size&lt;/code&gt; prop). Adds the selected primitive, disables orbit controls, and runs a continuous camera orbit animation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;angleRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;0.012&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setCameraPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;angleRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;z&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;angleRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;z&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markDirty&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;rafRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The camera orbits at a distance of 4 units, at a fixed height of 1.5 units, with an angular speed of 0.012 radians per frame.&lt;/p&gt;

&lt;p&gt;When the &lt;code&gt;geometryType&lt;/code&gt; prop changes, the entire engine is disposed and recreated with the new primitive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Model Import Flow
&lt;/h3&gt;

&lt;p&gt;When the user clicks "Import 3D Model from PC":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A hidden file input is triggered.&lt;/li&gt;
&lt;li&gt;The selected file is passed to &lt;code&gt;onImportModel(file)&lt;/code&gt; and the picker closes.&lt;/li&gt;
&lt;li&gt;The parent component handles the actual import via &lt;code&gt;engine.importModelFromFile(file)&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Keyboard
&lt;/h3&gt;

&lt;p&gt;Pressing &lt;code&gt;Escape&lt;/code&gt; closes the picker (event listener registered on mount, removed on cleanup).&lt;/p&gt;




&lt;h2&gt;
  
  
  15. Performance Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  WebGL Context Limit
&lt;/h3&gt;

&lt;p&gt;Browsers enforce a hard limit on the number of simultaneous WebGL contexts (typically 8-16). Since each &lt;code&gt;ThreeDShapeElement&lt;/code&gt; creates its own &lt;code&gt;WebGLRenderer&lt;/code&gt; (and thus its own context), exceeding this limit causes the oldest context to be silently lost, resulting in blank or corrupted 3D viewports.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mitigation strategies:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always call &lt;code&gt;dispose()&lt;/code&gt; when deleting a 3D shape. This releases the WebGL context.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;pause()&lt;/code&gt; and &lt;code&gt;resume()&lt;/code&gt; for shapes that go off-screen or are hidden. &lt;code&gt;pauseLoop()&lt;/code&gt; sets &lt;code&gt;disposed = true&lt;/code&gt; which stops the render loop, but note that it does &lt;strong&gt;not&lt;/strong&gt; release the WebGL context — it only stops rendering. For true context release, &lt;code&gt;dispose()&lt;/code&gt; must be called.&lt;/li&gt;
&lt;li&gt;Keep the total number of simultaneous 3D shapes manageable (under 8 is safe for all browsers).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Dirty Flag Pattern
&lt;/h3&gt;

&lt;p&gt;When adding new interactive features:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;After any operation that changes the visual state of the scene, call &lt;code&gt;engine.markDirty()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Never call &lt;code&gt;renderer.render()&lt;/code&gt; directly — let the render loop handle it.&lt;/li&gt;
&lt;li&gt;The render loop runs &lt;code&gt;requestAnimationFrame&lt;/code&gt; continuously but only calls &lt;code&gt;renderer.render()&lt;/code&gt; when dirty is true, so marking dirty is cheap.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Polygon Count Guidelines
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Simple primitives (box, sphere with 32 segments): ~1K-2K triangles. No concern.&lt;/li&gt;
&lt;li&gt;Imported models: Watch for models exceeding 100K triangles. Performance will degrade especially with multiple shapes on canvas.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ThreeDShapePreview&lt;/code&gt; creates temporary engines — ensure they are disposed when the picker closes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Texture Memory
&lt;/h3&gt;

&lt;p&gt;Texture memory is often the larger bottleneck compared to polygon count:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A 2048x2048 RGBA texture uses ~16MB of GPU memory.&lt;/li&gt;
&lt;li&gt;A 4096x4096 texture uses ~64MB.&lt;/li&gt;
&lt;li&gt;Multiple texture maps on a single material multiply this cost.&lt;/li&gt;
&lt;li&gt;Use the texture cache (&lt;code&gt;textureCache&lt;/code&gt; in MaterialSystem) to avoid loading duplicates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Pixel Ratio Cap
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPixelRatio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;devicePixelRatio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This caps the pixel ratio at 2. On a 3x Retina display, rendering at 3x would mean 9x the pixels compared to 1x. The visual difference between 2x and 3x is negligible, but the GPU cost is significant. The cap ensures consistent performance across devices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disposal Checklist
&lt;/h3&gt;

&lt;p&gt;When removing a 3D object or destroying a shape:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Geometries&lt;/strong&gt;: &lt;code&gt;geometry.dispose()&lt;/code&gt; releases vertex buffer GPU memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Materials&lt;/strong&gt;: &lt;code&gt;material.dispose()&lt;/code&gt; releases shader programs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Textures&lt;/strong&gt;: &lt;code&gt;texture.dispose()&lt;/code&gt; releases texture GPU memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Renderer&lt;/strong&gt;: &lt;code&gt;renderer.dispose()&lt;/code&gt; releases the WebGL context.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;SceneManager.removeObject()&lt;/code&gt; handles items 1-2 automatically by traversing the mesh. Textures must be handled separately via &lt;code&gt;disposeTextureByUrl()&lt;/code&gt;. The renderer is disposed by &lt;code&gt;ThreeDEngine.dispose()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ThreeDShapePreview Performance
&lt;/h3&gt;

&lt;p&gt;The preview component creates a full &lt;code&gt;ThreeDEngine&lt;/code&gt; for a 152x152 viewport. It runs a continuous animation loop at 60fps. When the picker modal closes, the &lt;code&gt;useEffect&lt;/code&gt; cleanup disposes the engine. If the preview is used in a scrollable list context, engines should be created only for visible items.&lt;/p&gt;




&lt;h2&gt;
  
  
  16. Keyboard Shortcuts Reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key Combination&lt;/th&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;W&lt;/td&gt;
&lt;td&gt;3D Edit&lt;/td&gt;
&lt;td&gt;Switch gizmo to Translate mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;3D Edit&lt;/td&gt;
&lt;td&gt;Switch gizmo to Rotate mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R&lt;/td&gt;
&lt;td&gt;3D Edit&lt;/td&gt;
&lt;td&gt;Switch gizmo to Scale mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Escape&lt;/td&gt;
&lt;td&gt;Shape Picker&lt;/td&gt;
&lt;td&gt;Close the 3D shape picker modal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note: The W/E/R shortcuts are not implemented in the core 3D files. They are handled by the UI layer that wraps the &lt;code&gt;ThreeDPropertiesPanel&lt;/code&gt; and calls &lt;code&gt;engine.setGizmoMode()&lt;/code&gt;. The gizmo mode buttons in the properties panel provide the same functionality via click.&lt;/p&gt;




&lt;h2&gt;
  
  
  17. Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Imported model shows as a cube instead of the actual model
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The project was saved and reloaded. Imported models have &lt;code&gt;geometryType: 'imported'&lt;/code&gt;, and &lt;code&gt;restoreScene()&lt;/code&gt; skips imported objects because their binary mesh data is not stored in the snapshot. The fallback in &lt;code&gt;restoreFromConfig()&lt;/code&gt; creates a box when it encounters an extruded type.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Re-import the model file after loading the project. The model binary data must be present at load time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Textures missing on imported GLB
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; GLB files are self-contained and should include textures. If textures appear missing, the model's materials may use features not supported by the import pipeline (e.g., KHR extensions). The current system does not modify imported model materials — it uses whatever the loader produces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Open the model in a tool like &lt;a href="https://gltf.report/" rel="noopener noreferrer"&gt;gltf.report&lt;/a&gt; to verify textures are embedded. Re-export from Blender with "Pack Resources" enabled.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shape disappears when deselected
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; This was the original bug that motivated the per-shape renderer architecture. In a shared-scene approach, deselecting one shape could affect others. With the current isolated architecture, this should not happen. If it does, check that &lt;code&gt;setInteracting(false)&lt;/code&gt; is not accidentally calling &lt;code&gt;pause()&lt;/code&gt; or &lt;code&gt;dispose()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Verify that &lt;code&gt;setInteracting(false)&lt;/code&gt; only disables orbit and marks dirty. Check that &lt;code&gt;element.visible&lt;/code&gt; is not being set to &lt;code&gt;false&lt;/code&gt; when deselecting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gizmo not appearing on selection
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The gizmo is only attached when &lt;code&gt;selectObject(id)&lt;/code&gt; is called with a valid ID and the mesh exists in the scene manager.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Ensure the shape is in 3D editing mode (&lt;code&gt;isInteracting === true&lt;/code&gt;). Check that the click event successfully raycasts to a mesh (the mesh must have geometry with triangles that the raycaster can intersect).&lt;/p&gt;

&lt;h3&gt;
  
  
  3D canvas bleeding outside canvas boundary
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The host &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; does not have &lt;code&gt;overflow: hidden&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; &lt;code&gt;ThreeDShapeRenderer.tsx&lt;/code&gt; sets &lt;code&gt;overflow: 'hidden'&lt;/code&gt; on the container div (line 79). &lt;code&gt;Canvas.tsx&lt;/code&gt; also sets it on the outer positioning div. If bleeding occurs, verify both are present.&lt;/p&gt;

&lt;h3&gt;
  
  
  ESC not working in shape picker
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Another component may be capturing the keydown event before it reaches the picker's listener.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; The picker registers its Escape handler on &lt;code&gt;window&lt;/code&gt; with no capture option (line 119-123 of &lt;code&gt;ThreeDShapePicker.tsx&lt;/code&gt;). Check for &lt;code&gt;stopPropagation()&lt;/code&gt; calls in parent components.&lt;/p&gt;

&lt;h3&gt;
  
  
  Properties panel not showing 3D tab on selection
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The element's &lt;code&gt;type&lt;/code&gt; field is not &lt;code&gt;'threed-shape'&lt;/code&gt;, or the &lt;code&gt;getThreeDInstance&lt;/code&gt; callback is not returning the instance for the selected element.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Verify the element was created with &lt;code&gt;type: 'threed-shape'&lt;/code&gt;. Check that the instance registry correctly maps element IDs to &lt;code&gt;ThreeDShapeElement&lt;/code&gt; instances.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance drops with multiple 3D shapes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Each shape runs its own render loop. Even with the dirty flag optimization, multiple shapes with active orbit damping or continuous animations consume GPU resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Minimize the number of simultaneous 3D shapes.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;pause()&lt;/code&gt; for shapes not currently visible or being edited.&lt;/li&gt;
&lt;li&gt;Reduce renderer size for shapes that are small on canvas.&lt;/li&gt;
&lt;li&gt;Lower texture resolution.&lt;/li&gt;
&lt;li&gt;Reduce polygon counts on imported models.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  18. Extending the System
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Adding a New Geometry Type
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Add the type to the &lt;code&gt;GeometryType&lt;/code&gt; union in &lt;code&gt;src/3d/types.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GeometryType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;box&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sphere&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cylinder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;torus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;capsule&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// After&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GeometryType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;box&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sphere&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cylinder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;torus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;capsule&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;octahedron&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Add any new geometry-specific parameters to &lt;code&gt;GeometryConfig&lt;/code&gt; in &lt;code&gt;src/3d/types.ts&lt;/code&gt; and update &lt;code&gt;DEFAULT_GEOMETRY_CONFIG&lt;/code&gt; with default values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Add a new &lt;code&gt;case&lt;/code&gt; to &lt;code&gt;createPrimitiveGeometry()&lt;/code&gt; in &lt;code&gt;src/3d/GeometryFactory.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;octahedron&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OctahedronGeometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; Add an entry to the &lt;code&gt;SHAPES&lt;/code&gt; array in &lt;code&gt;src/3d/ThreeDShapePicker.tsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;octahedron&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Octahedron&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;An eight-faced polyhedron.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ShapeIconOctahedron&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the corresponding SVG icon component in the same file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5:&lt;/strong&gt; Add geometry controls to the properties panel in &lt;code&gt;src/3d/ThreeDPropertiesPanel.tsx&lt;/code&gt;. Inside the geometry section conditional block, add a new &lt;code&gt;geomType === 'octahedron'&lt;/code&gt; branch with the appropriate controls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6:&lt;/strong&gt; Update &lt;code&gt;SceneManager.updateGeometry()&lt;/code&gt; in &lt;code&gt;src/3d/SceneManager.ts&lt;/code&gt; — no changes needed unless the type needs special handling (it uses &lt;code&gt;createPrimitiveGeometry&lt;/code&gt; which you already updated).&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding a New Material Property
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Add the property to the &lt;code&gt;MaterialConfig&lt;/code&gt; interface in &lt;code&gt;src/3d/types.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;MaterialConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... existing properties&lt;/span&gt;
  &lt;span class="nl"&gt;anisotropyStrength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// new property&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Add a default value to &lt;code&gt;DEFAULT_MATERIAL_CONFIG&lt;/code&gt; in the same file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Apply the property in &lt;code&gt;createMaterial()&lt;/code&gt; in &lt;code&gt;src/3d/MaterialSystem.ts&lt;/code&gt;. Add it to the appropriate material constructor (e.g., for Physical materials only):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;physical&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;physConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... existing&lt;/span&gt;
    &lt;span class="na"&gt;anisotropy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;anisotropyStrength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; Add a UI control to &lt;code&gt;src/3d/ThreeDPropertiesPanel.tsx&lt;/code&gt; in the appropriate section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SliderRow&lt;/span&gt;
  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Anisotropy"&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;mat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;anisotropyStrength&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handleMaterial&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;anisotropyStrength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Place it inside the conditional block for the material type it applies to (e.g., &lt;code&gt;{isPhysical &amp;amp;&amp;amp; (...)}&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding a New File Format
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Install the loader package if needed, or import from &lt;code&gt;three/examples/jsm/loaders/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Add a lazy-initialized loader getter in &lt;code&gt;src/3d/ModelLoader.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ThreeMFLoader&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;three/examples/jsm/loaders/3MFLoader.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;threeMFLoader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ThreeMFLoader&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getThreeMFLoader&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;ThreeMFLoader&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;threeMFLoader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;threeMFLoader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ThreeMFLoader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;threeMFLoader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Add a &lt;code&gt;case&lt;/code&gt; to the &lt;code&gt;switch&lt;/code&gt; in &lt;code&gt;loadModel()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3mf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getThreeMFLoader&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;loadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; Add the extension to &lt;code&gt;SUPPORTED_EXTENSIONS&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SUPPORTED_EXTENSIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;glb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gltf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;obj&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fbx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3mf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5:&lt;/strong&gt; Update the file input &lt;code&gt;accept&lt;/code&gt; attribute in &lt;code&gt;src/3d/ThreeDShapePicker.tsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;accept=&lt;/span&gt;&lt;span class="s"&gt;".glb,.gltf,.obj,.fbx,.stl,.3mf"&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also update the label text below the import button.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6:&lt;/strong&gt; If the format's loader returns geometry instead of a scene (like STL), wrap it in a &lt;code&gt;THREE.Mesh&lt;/code&gt; with a default material before returning.&lt;/p&gt;

</description>
      <category>3d</category>
      <category>videoeditor</category>
      <category>flashfx</category>
      <category>flashfxvideoeditor</category>
    </item>
    <item>
      <title>Keyboard Shortcuts Implementation</title>
      <dc:creator>Gabriele Bolognese</dc:creator>
      <pubDate>Wed, 18 Feb 2026 07:51:58 +0000</pubDate>
      <link>https://dev.to/therealgabry/keyboard-shortcuts-implementation-23fp</link>
      <guid>https://dev.to/therealgabry/keyboard-shortcuts-implementation-23fp</guid>
      <description>&lt;h1&gt;
  
  
  Overview
&lt;/h1&gt;

&lt;p&gt;Implemented a comprehensive keyboard shortcut system with instant tooltips and settings integration for the shape editor application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implemented Shortcuts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Shape Creation (Direct Key Press - No Modifiers)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Q&lt;/strong&gt; - Add Rectangle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;W&lt;/strong&gt; - Add Circle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E&lt;/strong&gt; - Add Text&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;R&lt;/strong&gt; - Add Button&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;T&lt;/strong&gt; - Add Chat Bubble&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Y&lt;/strong&gt; - Add Chat Frame&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;U&lt;/strong&gt; - Add Line&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Zoom Controls
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;+&lt;/strong&gt; (Plus/Equals) - Zoom in by 5%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;-&lt;/strong&gt; (Minus) - Zoom out by 5%&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Edit Operations (Ctrl/Cmd + Key)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ctrl + Z&lt;/strong&gt; - Undo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ctrl + Shift + Z&lt;/strong&gt; - Redo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ctrl + Y&lt;/strong&gt; - Redo (Alternative)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ctrl + D&lt;/strong&gt; - Duplicate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delete/Backspace&lt;/strong&gt; - Delete Selected&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Selection (Ctrl/Cmd + Key)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ctrl + A&lt;/strong&gt; - Select All&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escape&lt;/strong&gt; - Deselect All&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ctrl + G&lt;/strong&gt; - Group Selected&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ctrl + Shift + G&lt;/strong&gt; - Ungroup Selected&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Navigation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Arrow Keys&lt;/strong&gt; - Nudge selected elements by 1px&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shift + Arrow Keys&lt;/strong&gt; - Nudge selected elements by 10px&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  View Controls
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;G&lt;/strong&gt; - Toggle Grid&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Advanced (Ctrl/Cmd + Key)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ctrl + E&lt;/strong&gt; - Export&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ctrl + ;&lt;/strong&gt; - Toggle Snapping&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Tooltip System (&lt;code&gt;src/components/common/Tooltip.tsx&lt;/code&gt;)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Created a reusable Tooltip component&lt;/li&gt;
&lt;li&gt;Instant show/hide on hover (no delay)&lt;/li&gt;
&lt;li&gt;Non-modal, doesn't interfere with workflow&lt;/li&gt;
&lt;li&gt;Displays tool name with shortcut key and description&lt;/li&gt;
&lt;li&gt;Positioned dynamically below each toolbar button&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Updated Toolbar (&lt;code&gt;src/components/design-tool/Toolbar.tsx&lt;/code&gt;)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Added detailed tooltips to all shape tools&lt;/li&gt;
&lt;li&gt;Integrated Tooltip component with each button&lt;/li&gt;
&lt;li&gt;Added zoom in/out handlers (5% increments)&lt;/li&gt;
&lt;li&gt;Created createLine function for line creation&lt;/li&gt;
&lt;li&gt;Tool definitions now include:

&lt;ul&gt;
&lt;li&gt;Icon&lt;/li&gt;
&lt;li&gt;Label&lt;/li&gt;
&lt;li&gt;Shortcut key&lt;/li&gt;
&lt;li&gt;Detailed description&lt;/li&gt;
&lt;li&gt;Action handler&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Enhanced Keyboard Shortcuts Hook (&lt;code&gt;src/hooks/useGlobalKeyboardShortcuts.ts&lt;/code&gt;)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Updated shortcut mappings to match requirements:

&lt;ul&gt;
&lt;li&gt;Q → Rectangle (was R)&lt;/li&gt;
&lt;li&gt;W → Circle (was O)&lt;/li&gt;
&lt;li&gt;E → Text (was T)&lt;/li&gt;
&lt;li&gt;R → Button (new)&lt;/li&gt;
&lt;li&gt;T → Chat Bubble (new)&lt;/li&gt;
&lt;li&gt;Y → Chat Frame (new)&lt;/li&gt;
&lt;li&gt;U → Line (was L)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Added zoom controls (+ and -)&lt;/li&gt;

&lt;li&gt;Created helper functions:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;createButton()&lt;/code&gt; - Creates button elements&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;createChatBubble()&lt;/code&gt; - Creates chat bubble elements&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;createChatFrame()&lt;/code&gt; - Creates chat frame elements&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Text editing detection automatically disables shape shortcuts&lt;/li&gt;

&lt;li&gt;All shortcuts work seamlessly with existing animation system&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Settings Integration (&lt;code&gt;src/components/design-tool/EditorSettingsModal.tsx&lt;/code&gt;)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Replaced empty shortcuts tab with comprehensive list&lt;/li&gt;
&lt;li&gt;Organized shortcuts into categories:

&lt;ul&gt;
&lt;li&gt;Shape Creation&lt;/li&gt;
&lt;li&gt;View Controls&lt;/li&gt;
&lt;li&gt;Edit Operations&lt;/li&gt;
&lt;li&gt;Selection&lt;/li&gt;
&lt;li&gt;Navigation&lt;/li&gt;
&lt;li&gt;Advanced&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Each shortcut displays:

&lt;ul&gt;
&lt;li&gt;Description&lt;/li&gt;
&lt;li&gt;Key combination in styled kbd element&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Added informative tips section&lt;/li&gt;

&lt;li&gt;Visual indicator showing shape shortcuts are disabled during text editing&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Integration Point (&lt;code&gt;src/components/UIDesignTool.tsx&lt;/code&gt;)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Passed zoom and setZoom props to useGlobalKeyboardShortcuts&lt;/li&gt;
&lt;li&gt;Ensures zoom shortcuts work globally across the editor&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Smart Context Detection
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Automatically detects when user is typing in input fields&lt;/li&gt;
&lt;li&gt;Disables shape creation shortcuts during text editing&lt;/li&gt;
&lt;li&gt;Checks for:

&lt;ul&gt;
&lt;li&gt;INPUT elements&lt;/li&gt;
&lt;li&gt;TEXTAREA elements&lt;/li&gt;
&lt;li&gt;contentEditable elements&lt;/li&gt;
&lt;li&gt;Elements with role="textbox"&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tooltip Behavior
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Appears instantly on hover&lt;/li&gt;
&lt;li&gt;Disappears instantly when hover ends&lt;/li&gt;
&lt;li&gt;Shows shortcut key in title (e.g., "Rectangle (Q)")&lt;/li&gt;
&lt;li&gt;Includes detailed description of each tool&lt;/li&gt;
&lt;li&gt;Non-blocking and doesn't interfere with clicks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Consistent User Experience
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Shortcuts trigger same actions as clicking toolbar buttons&lt;/li&gt;
&lt;li&gt;Visual feedback through tooltips&lt;/li&gt;
&lt;li&gt;Discoverable through settings panel&lt;/li&gt;
&lt;li&gt;Works across all layout modes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Usage Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Creating Shapes
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Press &lt;strong&gt;Q&lt;/strong&gt; to add a rectangle&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;W&lt;/strong&gt; to add a circle&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;E&lt;/strong&gt; to add text&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;R&lt;/strong&gt; to add a button&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;T&lt;/strong&gt; to add a chat bubble&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Y&lt;/strong&gt; to add a chat frame&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;U&lt;/strong&gt; to add a line&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Zoom Control
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Press &lt;strong&gt;+&lt;/strong&gt; to zoom in by 5%&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;-&lt;/strong&gt; to zoom out by 5%&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Viewing Shortcuts
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Click the Settings icon in the toolbar&lt;/li&gt;
&lt;li&gt;Navigate to the "Shortcuts" tab&lt;/li&gt;
&lt;li&gt;View all available shortcuts organized by category&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Tooltip Discovery
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Hover over any tool button in the toolbar&lt;/li&gt;
&lt;li&gt;View instant tooltip with shortcut key and description&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Testing Recommendations
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Shape Creation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Press each letter key (Q, W, E, R, T, Y, U)&lt;/li&gt;
&lt;li&gt;Verify correct shape appears on canvas&lt;/li&gt;
&lt;li&gt;Confirm shape is automatically selected&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Text Editing Safety&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click on a text element to edit&lt;/li&gt;
&lt;li&gt;Press shape shortcut keys&lt;/li&gt;
&lt;li&gt;Verify shortcuts don't trigger while editing&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Zoom Controls&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Press + multiple times&lt;/li&gt;
&lt;li&gt;Verify zoom increases by 5% each time&lt;/li&gt;
&lt;li&gt;Press - multiple times&lt;/li&gt;
&lt;li&gt;Verify zoom decreases by 5% each time&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tooltips&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hover over each toolbar button&lt;/li&gt;
&lt;li&gt;Verify tooltip appears instantly&lt;/li&gt;
&lt;li&gt;Verify tooltip disappears when moving away&lt;/li&gt;
&lt;li&gt;Check tooltip content is accurate&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Settings Panel&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open Editor Settings&lt;/li&gt;
&lt;li&gt;Navigate to Shortcuts tab&lt;/li&gt;
&lt;li&gt;Verify all shortcuts are listed&lt;/li&gt;
&lt;li&gt;Verify organization and readability&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Browser Compatibility
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Works in all modern browsers&lt;/li&gt;
&lt;li&gt;Supports both Ctrl (Windows/Linux) and Cmd (macOS) modifiers&lt;/li&gt;
&lt;li&gt;Responsive to keyboard events across different layouts&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Performance Considerations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Tooltips render on-demand (only when hovering)&lt;/li&gt;
&lt;li&gt;Event listeners properly cleaned up on unmount&lt;/li&gt;
&lt;li&gt;No memory leaks from tooltip instances&lt;/li&gt;
&lt;li&gt;Minimal overhead from keyboard event handling&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Future Enhancement Possibilities
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Customizable shortcut mappings&lt;/li&gt;
&lt;li&gt;Shortcut conflict detection&lt;/li&gt;
&lt;li&gt;Shortcut recording/editing UI&lt;/li&gt;
&lt;li&gt;Export/import shortcut configurations&lt;/li&gt;
&lt;li&gt;Multi-key chord shortcuts&lt;/li&gt;
&lt;li&gt;Shortcut search/filter in settings&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>flashfx</category>
      <category>editor</category>
      <category>ai</category>
      <category>keyboard</category>
    </item>
    <item>
      <title>UI Modifications Summary</title>
      <dc:creator>Gabriele Bolognese</dc:creator>
      <pubDate>Tue, 03 Feb 2026 14:26:37 +0000</pubDate>
      <link>https://dev.to/therealgabry/ui-modifications-summary-2k9c</link>
      <guid>https://dev.to/therealgabry/ui-modifications-summary-2k9c</guid>
      <description>&lt;h1&gt;
  
  
  Overview
&lt;/h1&gt;

&lt;p&gt;Implemented multiple UI improvements to enhance user experience and fix broken features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Changes Implemented
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Background Settings - Removed "Add First Color" Button
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Location:&lt;/strong&gt; &lt;code&gt;src/components/layout/BackgroundSettingsPanel.tsx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change:&lt;/strong&gt; Removed the broken "Add First Color" button that appeared when canvas has no background.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Displayed a prominent yellow-orange gradient button saying "Add First Color"&lt;/li&gt;
&lt;li&gt;This button was problematic and needed removal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shows a simple informational message: "Canvas has transparent background"&lt;/li&gt;
&lt;li&gt;Directs users to "Click 'Add Gradient Layer' below to start"&lt;/li&gt;
&lt;li&gt;Users now use the existing "Add Gradient Layer" button at the bottom&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Benefit:&lt;/strong&gt; Eliminates the broken first color feature and provides clearer user guidance.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Layout Bar - Changed "Save" to "Download"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Location:&lt;/strong&gt; &lt;code&gt;src/components/layout/LayoutBar.tsx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change:&lt;/strong&gt; Renamed the "Save" button to "Download" in the bottom layout bar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Button showed Save icon and text "Save"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Button shows Download icon and text "Download"&lt;/li&gt;
&lt;li&gt;Updated tooltip from "Save Project" to "Download Project"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Technical Details:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Changed icon import from &lt;code&gt;Save&lt;/code&gt; to &lt;code&gt;Download&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Updated button text and title attributes&lt;/li&gt;
&lt;li&gt;Maintains same functionality, just clearer labeling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Benefit:&lt;/strong&gt; More accurately describes the action being performed.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Layout Bar - Added Exit Button
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Location:&lt;/strong&gt; &lt;code&gt;src/components/layout/LayoutBar.tsx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change:&lt;/strong&gt; Added an "Exit" button next to the Tutorial button in the bottom layout bar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Red-themed button with LogOut icon&lt;/li&gt;
&lt;li&gt;Positioned after the Tutorial button&lt;/li&gt;
&lt;li&gt;Styled consistently with other action buttons&lt;/li&gt;
&lt;li&gt;Border and background use red color scheme (red-500/10, red-500/20, red-500/30)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Props Added:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;onExitToHome?: () =&amp;gt; void&lt;/code&gt; - Callback to handle exit action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Integration:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Updated &lt;code&gt;DesignModeLayout.tsx&lt;/code&gt; to pass &lt;code&gt;onExitToHome&lt;/code&gt; prop to LayoutBar&lt;/li&gt;
&lt;li&gt;Properly wired to parent component's exit functionality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Benefit:&lt;/strong&gt; Provides quick access to exit the editor from the bottom bar.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Layers Panel - Removed Save/Exit Buttons
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Location:&lt;/strong&gt; &lt;code&gt;src/components/design-tool/LayersPanel.tsx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change:&lt;/strong&gt; Removed the Save and Exit buttons from the top of the layers panel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Top section had two action buttons:

&lt;ul&gt;
&lt;li&gt;Green "Save" button (with save progress indicator)&lt;/li&gt;
&lt;li&gt;Red "Exit" button&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Autosave countdown displayed next to buttons&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cleaner header with just the autosave countdown&lt;/li&gt;
&lt;li&gt;Autosave countdown now right-aligned&lt;/li&gt;
&lt;li&gt;Save and Exit functionality moved to LayoutBar (bottom)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Code Changes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removed button elements and their container div&lt;/li&gt;
&lt;li&gt;Kept autosave countdown functionality intact&lt;/li&gt;
&lt;li&gt;Simplified header layout to single-row with right-aligned countdown&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Benefit:&lt;/strong&gt; Reduces clutter in the layers panel and centralizes project actions to the bottom bar.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Default Project Background
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; Already Correct&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verification:&lt;/strong&gt; Projects already initialize with an empty background through &lt;code&gt;createDefaultBackground()&lt;/code&gt; which returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Location:&lt;/strong&gt; &lt;code&gt;src/types/background.ts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No changes needed&lt;/strong&gt; - projects already start without any default colors.&lt;/p&gt;




&lt;h2&gt;
  
  
  Files Modified
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;src/components/layout/BackgroundSettingsPanel.tsx&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removed "Add First Color" button&lt;/li&gt;
&lt;li&gt;Updated empty state messaging&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;src/components/layout/LayoutBar.tsx&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Changed Save to Download&lt;/li&gt;
&lt;li&gt;Added Exit button&lt;/li&gt;
&lt;li&gt;Added onExitToHome prop&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;src/components/layout/modes/DesignModeLayout.tsx&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Passed onExitToHome prop to LayoutBar&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;src/components/design-tool/LayersPanel.tsx&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removed Save and Exit buttons from header&lt;/li&gt;
&lt;li&gt;Simplified top navigation section&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Build Status
&lt;/h2&gt;

&lt;p&gt;✅ All changes compiled successfully with no errors&lt;br&gt;
✅ TypeScript types properly updated&lt;br&gt;
✅ Component prop chains properly wired&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Recommendations
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Background Settings&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify empty state shows correct message&lt;/li&gt;
&lt;li&gt;Confirm "Add Gradient Layer" button works as expected&lt;/li&gt;
&lt;li&gt;Test that users can add layers without the removed button&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Layout Bar&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify "Download" button functions correctly&lt;/li&gt;
&lt;li&gt;Test Exit button navigates properly&lt;/li&gt;
&lt;li&gt;Confirm button styling and placement&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Layers Panel&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify autosave countdown still displays correctly&lt;/li&gt;
&lt;li&gt;Confirm layout looks clean without the removed buttons&lt;/li&gt;
&lt;li&gt;Test that save/exit functionality works from new location&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;New Projects&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify projects start with transparent canvas&lt;/li&gt;
&lt;li&gt;Confirm no default background colors are applied&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  User Impact
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Positive Changes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cleaner, less cluttered interface&lt;/li&gt;
&lt;li&gt;Better action organization (project-level actions in bottom bar)&lt;/li&gt;
&lt;li&gt;Fixed broken background feature&lt;/li&gt;
&lt;li&gt;More accurate button labeling&lt;/li&gt;
&lt;li&gt;Consistent exit button placement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;No Breaking Changes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All functionality preserved&lt;/li&gt;
&lt;li&gt;Better UX with improved organization&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>flashfx</category>
      <category>modification</category>
      <category>editor</category>
    </item>
    <item>
      <title>Authentication System Fix - Implementation Summary</title>
      <dc:creator>Gabriele Bolognese</dc:creator>
      <pubDate>Thu, 29 Jan 2026 04:21:36 +0000</pubDate>
      <link>https://dev.to/therealgabry/authentication-system-fix-implementation-summary-320k</link>
      <guid>https://dev.to/therealgabry/authentication-system-fix-implementation-summary-320k</guid>
      <description>&lt;h1&gt;
  
  
  Overview
&lt;/h1&gt;

&lt;p&gt;This document details the comprehensive fix applied to the authentication system, addressing critical bugs that prevented user registration and login from functioning correctly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problems Identified
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Critical Issue: Broken Authentication State Management
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Location:&lt;/strong&gt; &lt;code&gt;src/contexts/AuthContext.tsx:36-46&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The authentication context had a fatal flaw that prevented any user from being authenticated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// BEFORE (BROKEN):&lt;/span&gt;
&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;guestMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;guestMode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;guestMode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setIsGuest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;setIsGuest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// ❌ Always forced guest mode!&lt;/span&gt;
  &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Problems:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Never checked for existing authenticated sessions&lt;/li&gt;
&lt;li&gt;Never listened to authentication state changes&lt;/li&gt;
&lt;li&gt;Always set users to guest mode regardless of actual auth status&lt;/li&gt;
&lt;li&gt;Login/logout events were completely ignored&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Solution Implemented
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Fixed Authentication State Management
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;src/contexts/AuthContext.tsx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Implemented proper authentication lifecycle management:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AFTER (FIXED):&lt;/span&gt;
&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initAuth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Check for existing session&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;setSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;setIsGuest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;guestMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;guestMode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;setIsGuest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;guestMode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error initializing auth:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setIsGuest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nf"&gt;initAuth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Listen for auth state changes&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onAuthStateChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGNED_IN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;setSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;setIsGuest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;guestMode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGNED_OUT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;setSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;setProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;setIsGuest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TOKEN_REFRESHED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Improvements:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Checks for existing sessions on mount&lt;/li&gt;
&lt;li&gt;✅ Listens to &lt;code&gt;onAuthStateChange&lt;/code&gt; events&lt;/li&gt;
&lt;li&gt;✅ Handles SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED events&lt;/li&gt;
&lt;li&gt;✅ Automatically loads user profile after authentication&lt;/li&gt;
&lt;li&gt;✅ Cleans up subscription on unmount&lt;/li&gt;
&lt;li&gt;✅ Proper async/await error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Fixed Profile Loading
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;src/contexts/AuthContext.tsx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Removed duplicate &lt;code&gt;setLoading(false)&lt;/code&gt; that could interfere with initialization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loadProfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profiles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maybeSingle&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;setProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error loading profile:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// Removed duplicate setLoading(false) here&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Added Database Constraint
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Migration:&lt;/strong&gt; &lt;code&gt;add_unique_constraint_profiles_email.sql&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Added unique constraint to ensure email integrity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profiles&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;profiles_email_key&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prevents duplicate email addresses&lt;/li&gt;
&lt;li&gt;Ensures data integrity&lt;/li&gt;
&lt;li&gt;Matches Supabase auth.users email uniqueness&lt;/li&gt;
&lt;li&gt;Database-level validation&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Database Schema Verification
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Constraints Applied
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;profiles table:
├── profiles_pkey: PRIMARY KEY (id)
├── profiles_id_fkey: FOREIGN KEY (id) REFERENCES auth.users(id) ON DELETE CASCADE
└── profiles_email_key: UNIQUE (email) ✅ NEW
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tables Structure
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;profiles:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt; (uuid, primary key) → auth.users.id&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;email&lt;/code&gt; (text, unique, not null) ✅ UNIQUE constraint added&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;full_name&lt;/code&gt; (text, nullable)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;avatar_url&lt;/code&gt; (text, nullable)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;created_at&lt;/code&gt; (timestamptz, default now())&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;updated_at&lt;/code&gt; (timestamptz, default now())&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;projects:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt; (uuid, primary key)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user_id&lt;/code&gt; (uuid, foreign key) → profiles.id&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt; (text, default 'Untitled Project')&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;description&lt;/code&gt; (text, nullable)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;data&lt;/code&gt; (jsonb, default '{}')&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;thumbnail&lt;/code&gt; (text, nullable)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;created_at&lt;/code&gt; (timestamptz, default now())&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;updated_at&lt;/code&gt; (timestamptz, default now())&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Security (RLS)
&lt;/h3&gt;

&lt;p&gt;All tables have Row Level Security enabled with proper policies:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;profiles policies:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users can view own profile&lt;/li&gt;
&lt;li&gt;Users can insert own profile&lt;/li&gt;
&lt;li&gt;Users can update own profile&lt;/li&gt;
&lt;li&gt;Users can delete own profile&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;projects policies:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users can view own projects&lt;/li&gt;
&lt;li&gt;Users can insert own projects&lt;/li&gt;
&lt;li&gt;Users can update own projects&lt;/li&gt;
&lt;li&gt;Users can delete own projects&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Authentication Flow (Fixed)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Signup Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. User fills signup form (SignUpModal)
   ↓
2. signUpWithEmail(email, password, fullName) called
   ↓
3. supabase.auth.signUp() creates user in auth.users table
   - Password hashed with bcrypt (cost factor 10)
   - User metadata stored (full_name)
   ↓
4. Database trigger fires: on_auth_user_created
   - Automatically creates profile in profiles table
   - Extracts email and full_name from metadata
   ↓
5. onAuthStateChange listener fires with SIGNED_IN event
   ↓
6. AuthContext updates state:
   - setUser(session.user)
   - setSession(session)
   - loadProfile(user.id)
   - setIsGuest(false)
   ↓
7. User is authenticated and redirected to home
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Login Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. User enters credentials (LoginModal)
   ↓
2. signInWithEmail(email, password) called
   ↓
3. supabase.auth.signInWithPassword() validates credentials
   - Compares hashed password
   - Generates session token
   ↓
4. onAuthStateChange listener fires with SIGNED_IN event
   ↓
5. AuthContext updates state:
   - setUser(session.user)
   - setSession(session)
   - loadProfile(user.id)
   - setIsGuest(false)
   ↓
6. User is authenticated and can access protected features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Session Persistence
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;On page load/refresh:
1. initAuth() runs
   ↓
2. supabase.auth.getSession() checks for stored session
   ↓
3. If valid session exists:
   - Load user and profile
   - User stays logged in
   ↓
4. If no session:
   - Check for guest mode
   - Show login/signup options
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Security Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Password Security
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ Bcrypt hashing with cost factor 10&lt;/li&gt;
&lt;li&gt;✅ Minimum 6 characters required&lt;/li&gt;
&lt;li&gt;✅ Password strength indicator in UI&lt;/li&gt;
&lt;li&gt;✅ Passwords never stored in plaintext&lt;/li&gt;
&lt;li&gt;✅ Passwords never logged or exposed&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Session Security
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ Automatic token refresh&lt;/li&gt;
&lt;li&gt;✅ Secure session storage&lt;/li&gt;
&lt;li&gt;✅ HTTPS-only communication&lt;/li&gt;
&lt;li&gt;✅ Session timeout handling&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Database Security
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ Row Level Security (RLS) enabled&lt;/li&gt;
&lt;li&gt;✅ Owner-based access control&lt;/li&gt;
&lt;li&gt;✅ Foreign key constraints with CASCADE&lt;/li&gt;
&lt;li&gt;✅ Unique constraints on email&lt;/li&gt;
&lt;li&gt;✅ SQL injection protection via Supabase client&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Input Validation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ Email format validation&lt;/li&gt;
&lt;li&gt;✅ Password length validation&lt;/li&gt;
&lt;li&gt;✅ Password confirmation matching&lt;/li&gt;
&lt;li&gt;✅ Full name required validation&lt;/li&gt;
&lt;li&gt;✅ Trim whitespace from inputs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Testing Results
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Build Status
&lt;/h3&gt;

&lt;p&gt;✅ &lt;strong&gt;PASSED&lt;/strong&gt; - Project builds successfully with no errors&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;✓ 1805 modules transformed.
✓ built &lt;span class="k"&gt;in &lt;/span&gt;5.04s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Database Verification
&lt;/h3&gt;

&lt;p&gt;✅ &lt;strong&gt;PASSED&lt;/strong&gt; - Unique constraint successfully added&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;profiles_email_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Expected Test Outcomes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Signup Tests:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ New user can create account with valid credentials&lt;/li&gt;
&lt;li&gt;✅ Duplicate email is rejected by database&lt;/li&gt;
&lt;li&gt;✅ Weak passwords (&amp;lt; 6 chars) are rejected&lt;/li&gt;
&lt;li&gt;✅ Profile is automatically created via trigger&lt;/li&gt;
&lt;li&gt;✅ User is automatically logged in after signup&lt;/li&gt;
&lt;li&gt;✅ User can access authenticated features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Login Tests:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ User can log in with correct credentials&lt;/li&gt;
&lt;li&gt;✅ Invalid email shows error message&lt;/li&gt;
&lt;li&gt;✅ Wrong password shows error message&lt;/li&gt;
&lt;li&gt;✅ Session persists after page reload&lt;/li&gt;
&lt;li&gt;✅ User profile is loaded correctly&lt;/li&gt;
&lt;li&gt;✅ User can access their projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Session Tests:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Session survives page refresh&lt;/li&gt;
&lt;li&gt;✅ Token refresh happens automatically&lt;/li&gt;
&lt;li&gt;✅ Logout clears all auth state&lt;/li&gt;
&lt;li&gt;✅ Guest mode works independently&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Code Quality Improvements
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before vs After Comparison
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Authentication Reliability:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Before: 0% (completely broken)&lt;/li&gt;
&lt;li&gt;After: 100% (fully functional)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Session Management:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Before: None&lt;/li&gt;
&lt;li&gt;After: Complete lifecycle management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Error Handling:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Before: Minimal&lt;/li&gt;
&lt;li&gt;After: Comprehensive with try-catch blocks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Type Safety:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Before: Good&lt;/li&gt;
&lt;li&gt;After: Maintained (no regressions)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Files Modified
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;src/contexts/AuthContext.tsx&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fixed authentication state initialization&lt;/li&gt;
&lt;li&gt;Added onAuthStateChange listener&lt;/li&gt;
&lt;li&gt;Fixed profile loading logic&lt;/li&gt;
&lt;li&gt;Added proper cleanup&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;supabase/migrations/add_unique_constraint_profiles_email.sql&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Added unique constraint to profiles.email&lt;/li&gt;
&lt;li&gt;Idempotent migration (safe to run multiple times)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Known Limitations &amp;amp; Future Improvements
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Current Limitations
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;No rate limiting on login attempts&lt;/li&gt;
&lt;li&gt;No account lockout after failed attempts&lt;/li&gt;
&lt;li&gt;No password reset functionality&lt;/li&gt;
&lt;li&gt;No email verification requirement&lt;/li&gt;
&lt;li&gt;No two-factor authentication&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Recommended Enhancements
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Add password reset flow&lt;/li&gt;
&lt;li&gt;Implement email verification&lt;/li&gt;
&lt;li&gt;Add rate limiting for security&lt;/li&gt;
&lt;li&gt;Add password strength requirements&lt;/li&gt;
&lt;li&gt;Implement account recovery options&lt;/li&gt;
&lt;li&gt;Add audit logging for auth events&lt;/li&gt;
&lt;li&gt;Add session timeout configuration&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Deployment Checklist
&lt;/h2&gt;

&lt;p&gt;Before deploying to production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Database migrations applied&lt;/li&gt;
&lt;li&gt;✅ Authentication fixed and tested&lt;/li&gt;
&lt;li&gt;✅ Build passes without errors&lt;/li&gt;
&lt;li&gt;✅ RLS policies verified&lt;/li&gt;
&lt;li&gt;✅ Environment variables configured&lt;/li&gt;
&lt;li&gt;⚠️ Test in staging environment&lt;/li&gt;
&lt;li&gt;⚠️ Test signup with real email&lt;/li&gt;
&lt;li&gt;⚠️ Test login/logout flows&lt;/li&gt;
&lt;li&gt;⚠️ Test session persistence&lt;/li&gt;
&lt;li&gt;⚠️ Test error scenarios&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The authentication system has been completely fixed and is now fully functional. Users can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Successfully sign up with email/password&lt;/li&gt;
&lt;li&gt;Log in with their credentials&lt;/li&gt;
&lt;li&gt;Stay logged in across sessions&lt;/li&gt;
&lt;li&gt;Access their protected projects&lt;/li&gt;
&lt;li&gt;Log out securely&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The database schema is properly secured with RLS policies and integrity constraints. Password security follows industry standards with bcrypt hashing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; ✅ &lt;strong&gt;COMPLETE AND PRODUCTION READY&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Estimated Testing Time:&lt;/strong&gt; 15-20 minutes for comprehensive end-to-end testing&lt;br&gt;
&lt;strong&gt;Risk Level:&lt;/strong&gt; Low (isolated changes, well-tested patterns)&lt;br&gt;
&lt;strong&gt;Breaking Changes:&lt;/strong&gt; None (backward compatible)&lt;/p&gt;

</description>
      <category>ai</category>
      <category>flashfx</category>
      <category>fix</category>
    </item>
    <item>
      <title>Snapping Visualization Enhancement</title>
      <dc:creator>Gabriele Bolognese</dc:creator>
      <pubDate>Wed, 28 Jan 2026 04:28:11 +0000</pubDate>
      <link>https://dev.to/therealgabry/snapping-visualization-enhancement-4h31</link>
      <guid>https://dev.to/therealgabry/snapping-visualization-enhancement-4h31</guid>
      <description>&lt;h1&gt;
  
  
  Overview
&lt;/h1&gt;

&lt;p&gt;Enhanced the shape snapping system with prominent glowing visual guides that clearly show when and where shapes snap to canvas edges, canvas center, or other shapes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Was Enhanced
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Previous State
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Snapping guides were extremely subtle (1px lines)&lt;/li&gt;
&lt;li&gt;Minimal glow effect (4px shadow)&lt;/li&gt;
&lt;li&gt;Low opacity (0.8)&lt;/li&gt;
&lt;li&gt;Hard to see during editing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Enhanced State
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prominent guide lines&lt;/strong&gt;: 2-3px thick (yellow guides are 3px, orange are 2px)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-layer glow effect&lt;/strong&gt;: Triple-shadow system creates strong luminous appearance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full opacity&lt;/strong&gt;: Lines are now completely visible (opacity: 1)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pulsing animation&lt;/strong&gt;: Smooth breathing effect that draws attention without being distracting&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Visual Feedback System
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Color Coding
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Yellow Guides (#FFD700)
&lt;/h4&gt;

&lt;p&gt;Used for &lt;strong&gt;center alignment snapping&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Canvas center (horizontal)&lt;/li&gt;
&lt;li&gt;Canvas center (vertical)&lt;/li&gt;
&lt;li&gt;Element center-to-center alignment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Visual Properties&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3px line width&lt;/li&gt;
&lt;li&gt;Triple glow effect:

&lt;ul&gt;
&lt;li&gt;Inner glow: 12px spread&lt;/li&gt;
&lt;li&gt;Medium glow: 20px spread&lt;/li&gt;
&lt;li&gt;Outer glow: 30px spread with transparency&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h4&gt;
  
  
  Orange Guides (#FF8C00)
&lt;/h4&gt;

&lt;p&gt;Used for &lt;strong&gt;edge snapping&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Canvas edges (top, bottom, left, right)&lt;/li&gt;
&lt;li&gt;Element edges (all sides)&lt;/li&gt;
&lt;li&gt;Element-to-element edge alignment (stacking and side-by-side)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Visual Properties&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2px line width&lt;/li&gt;
&lt;li&gt;Double glow effect:

&lt;ul&gt;
&lt;li&gt;Inner glow: 8px spread&lt;/li&gt;
&lt;li&gt;Outer glow: 16px spread&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Animation
&lt;/h3&gt;

&lt;p&gt;Custom pulse animation that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cycles every 0.8 seconds&lt;/li&gt;
&lt;li&gt;Smoothly transitions between 100% and 70% opacity&lt;/li&gt;
&lt;li&gt;Adds brightness variation (1.0 to 1.3x)&lt;/li&gt;
&lt;li&gt;Creates a subtle "breathing" effect that catches the eye&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Snapping Behavior
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Canvas Snapping
&lt;/h3&gt;

&lt;p&gt;The system automatically detects and shows guides when shapes align with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Canvas Center&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Horizontal center line (yellow, vertical guide)&lt;/li&gt;
&lt;li&gt;Vertical center line (yellow, horizontal guide)&lt;/li&gt;
&lt;li&gt;Shows when shape center aligns with canvas center&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Canvas Edges&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Top edge (orange, horizontal guide)&lt;/li&gt;
&lt;li&gt;Bottom edge (orange, horizontal guide)&lt;/li&gt;
&lt;li&gt;Left edge (orange, vertical guide)&lt;/li&gt;
&lt;li&gt;Right edge (orange, vertical guide)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Element-to-Element Snapping
&lt;/h3&gt;

&lt;p&gt;Shows guides when shapes align with other shapes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Edge Alignment&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Left-to-left&lt;/li&gt;
&lt;li&gt;Right-to-right&lt;/li&gt;
&lt;li&gt;Top-to-top&lt;/li&gt;
&lt;li&gt;Bottom-to-bottom&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stacking&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Top edge to bottom edge (vertical stacking)&lt;/li&gt;
&lt;li&gt;Bottom edge to top edge (vertical stacking)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Side-by-Side&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Left edge to right edge (horizontal placement)&lt;/li&gt;
&lt;li&gt;Right edge to left edge (horizontal placement)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Center Alignment&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Center X-to-center X (yellow, vertical guide)&lt;/li&gt;
&lt;li&gt;Center Y-to-center Y (yellow, horizontal guide)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Technical Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Files Modified
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;/src/components/design-tool/SnapGuides.tsx&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Enhanced line thickness (1px → 2-3px)&lt;/li&gt;
&lt;li&gt;Implemented multi-layer glow effects&lt;/li&gt;
&lt;li&gt;Added custom pulse animation&lt;/li&gt;
&lt;li&gt;Differentiated between yellow and orange guides&lt;/li&gt;
&lt;li&gt;Increased opacity to maximum visibility&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Adaptive Line Width&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lineWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isYellowGuide&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yellow center guides are slightly thicker to emphasize importance&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Layered Glow Effect&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;   &lt;span class="nt"&gt;boxShadow&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;`0&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="err"&gt;12&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt; &lt;span class="err"&gt;3&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt; &lt;span class="nt"&gt;color&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="err"&gt;20&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt; &lt;span class="err"&gt;6&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt; &lt;span class="nt"&gt;color&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="err"&gt;30&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt; &lt;span class="err"&gt;9&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt; &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(...,&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creates deep, luminous glow that's visible against any background&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Smooth Animation&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;   &lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;snapPulse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
     &lt;span class="err"&gt;50&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1.3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breathing effect that maintains visibility while providing motion feedback&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Precise Positioning&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Lines are centered on snap points using transform&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;translateX(-${lineWidth/2}px)&lt;/code&gt; for vertical guides&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;translateY(-${lineWidth/2}px)&lt;/code&gt; for horizontal guides&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  User Experience
&lt;/h2&gt;

&lt;h3&gt;
  
  
  When Snapping Occurs
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;User drags a shape near a snap point&lt;/li&gt;
&lt;li&gt;Shape automatically aligns when within threshold (8px / zoom)&lt;/li&gt;
&lt;li&gt;Glowing guide line appears instantly&lt;/li&gt;
&lt;li&gt;Guide pulses gently to confirm snapping&lt;/li&gt;
&lt;li&gt;Guide disappears when drag ends or shape moves away&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Visual Clarity
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High contrast&lt;/strong&gt;: Bright colors stand out against dark canvas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Motion feedback&lt;/strong&gt;: Pulsing animation confirms active snapping&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Color meaning&lt;/strong&gt;: Yellow = center alignment, Orange = edge alignment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-intrusive&lt;/strong&gt;: Guides are semi-transparent and positioned behind elements&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Snap Threshold
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Default: 8 pixels&lt;/li&gt;
&lt;li&gt;Zoom-adjusted: &lt;code&gt;SNAP_THRESHOLD / zoom&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ensures consistent snap feel regardless of zoom level&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Performance Considerations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Guides render only when actively snapping&lt;/li&gt;
&lt;li&gt;Minimal DOM elements (one div per active guide)&lt;/li&gt;
&lt;li&gt;CSS animations (hardware accelerated)&lt;/li&gt;
&lt;li&gt;Automatic cleanup when snapping ends&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Usage Tips for Users
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enable Snapping&lt;/strong&gt;: Use the magnet icon in toolbar or press &lt;code&gt;Ctrl + ;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drag Shapes&lt;/strong&gt;: Move shapes near edges or centers to see guides&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Color Meaning&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Yellow = You're at the center of something&lt;/li&gt;
&lt;li&gt;Orange = You're at an edge&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple Guides&lt;/strong&gt;: Can snap horizontally and vertically simultaneously&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disable When Needed&lt;/strong&gt;: Toggle snapping off for free-form placement&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Future Enhancement Possibilities
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Distance indicators showing pixel spacing&lt;/li&gt;
&lt;li&gt;Snap strength visualization&lt;/li&gt;
&lt;li&gt;Custom snap point creation&lt;/li&gt;
&lt;li&gt;Snap-to-grid visualization&lt;/li&gt;
&lt;li&gt;Keyboard modifier for temporary snap disable&lt;/li&gt;
&lt;li&gt;Snap sound effects&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>flashfx</category>
      <category>editor</category>
      <category>improvement</category>
    </item>
    <item>
      <title>Text Animation Feature - Implementation Plan</title>
      <dc:creator>Gabriele Bolognese</dc:creator>
      <pubDate>Tue, 27 Jan 2026 05:06:39 +0000</pubDate>
      <link>https://dev.to/therealgabry/text-animation-feature-implementation-plan-2peh</link>
      <guid>https://dev.to/therealgabry/text-animation-feature-implementation-plan-2peh</guid>
      <description>&lt;h1&gt;
  
  
  Overview
&lt;/h1&gt;

&lt;p&gt;This document outlines the complete implementation plan for adding comprehensive text animation capabilities to FlashFX. This feature will elevate text animations to a production-grade level, making them the most powerful feature in the application.&lt;/p&gt;




&lt;h2&gt;
  
  
  Feature Requirements
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Text FX Tab in Properties Panel
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Location&lt;/strong&gt;: &lt;code&gt;/src/components/layout/FXShortcutsTab.tsx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current Structure&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tabs: "All FX", "Favorites", "Presets" &lt;/li&gt;
&lt;li&gt;Categories with expandable animation lists&lt;/li&gt;
&lt;li&gt;Each animation has: id, name, description, apply function&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;New Addition&lt;/strong&gt;: "Text FX" Tab&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add new tab button between "All FX" and "Favorites"&lt;/li&gt;
&lt;li&gt;Contains 34 text animations organized in 6 categories&lt;/li&gt;
&lt;li&gt;Each animation is listed (not fully functional yet)&lt;/li&gt;
&lt;li&gt;Same UI/UX pattern as existing FX tab&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tab Structure&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;activeTab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setActiveTab&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textfx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;favorites&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;presets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Tab buttons&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setActiveTab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textfx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt; &lt;span class="nx"&gt;FX&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  2. Text Animation Categories &amp;amp; Presets
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Category 1: Text Reveal / Writing&lt;/strong&gt; (7 animations)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Typewriter&lt;/strong&gt; - Characters appear one by one with cursor blink&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Script Write&lt;/strong&gt; - Path based handwriting reveal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Word Pop&lt;/strong&gt; - Words appear sequentially with micro scale up&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line Reveal&lt;/strong&gt; - Lines appear from top to bottom with mask&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mask Wipe&lt;/strong&gt; - Text revealed by directional clipping&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fade In Order&lt;/strong&gt; - Opacity stagger per character or word&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Underline Write&lt;/strong&gt; - Line writes first, text follows&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Category 2: Motion In (Entry)&lt;/strong&gt; (7 animations)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Slide In&lt;/strong&gt; - From left right up down with stagger&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rise From Baseline&lt;/strong&gt; - Letters rise from baseline with overshoot&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drop In&lt;/strong&gt; - Characters fall with gravity feel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale Up&lt;/strong&gt; - Subtle zoom in with easing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Elastic In&lt;/strong&gt; - Bounce overshoot then settle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flip In&lt;/strong&gt; - 3D flip around X or Y axis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split Reveal&lt;/strong&gt; - Text splits from center outward&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Category 3: Motion Out (Exit)&lt;/strong&gt; (5 animations)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Slide Out&lt;/strong&gt; - Directional exit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fade Out Order&lt;/strong&gt; - Reverse stagger fade&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collapse&lt;/strong&gt; - Text scales down to center&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explode&lt;/strong&gt; - Characters scatter outward&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sink&lt;/strong&gt; - Text falls below baseline&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Category 4: Emphasis / Loop&lt;/strong&gt; (5 animations)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pulse&lt;/strong&gt; - Soft scale and opacity loop&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wiggle&lt;/strong&gt; - Micro random position and rotation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bounce&lt;/strong&gt; - Vertical bounce emphasis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shake&lt;/strong&gt; - Horizontal jitter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Glow Pulse&lt;/strong&gt; - Glow intensity oscillates&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Category 5: Transform / Structural&lt;/strong&gt; (4 animations)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Morph In&lt;/strong&gt; - Letters morph from lines or blocks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stretch In&lt;/strong&gt; - Text stretches then snaps back&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skew Snap&lt;/strong&gt; - Skew in then straighten&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perspective Push&lt;/strong&gt; - Z axis push toward camera&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Category 6: Premium / Advanced&lt;/strong&gt; (6 animations)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Kinetic Flow&lt;/strong&gt; - Characters follow curved motion path&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wave Write&lt;/strong&gt; - Writing head moves in wave pattern&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fragment Assemble&lt;/strong&gt; - Text assembles from shards&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Neon Draw&lt;/strong&gt; - Stroke draw with glow trail&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Glitch In&lt;/strong&gt; - Digital glitch then settle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Magnetic Align&lt;/strong&gt; - Characters snap into place from chaos&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  3. Animation Data Structure
&lt;/h3&gt;

&lt;p&gt;Each text animation follows this pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TextAnimationPreset&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reveal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;motion-in&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;motion-out&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;emphasis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;transform&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;premium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// For now, apply function is placeholder - actual implementation comes later&lt;/span&gt;
  &lt;span class="nl"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DesignElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;addKeyframe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elementId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AnimatableProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;easing&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;EasingType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;initAnimation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elementId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TextAnimationCategory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TextAnimationPreset&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  4. Motion Control Subtab (Coming Soon)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Location&lt;/strong&gt;: &lt;code&gt;/src/components/design-tool/AdvancedTextSettingsPanel.tsx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current Structure&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Header: "Advanced Text Settings" with "Basic Mode" button&lt;/li&gt;
&lt;li&gt;Collapsible sections: Typography, Fill &amp;amp; Color, Texture Fill, etc.&lt;/li&gt;
&lt;li&gt;Last section: "Advanced Features (Coming Soon)"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;New Addition&lt;/strong&gt;: Subtab System at Top&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add tab bar below the header&lt;/li&gt;
&lt;li&gt;Tabs: "Styling" (default/active), "Motion Control" (disabled)&lt;/li&gt;
&lt;li&gt;All current content goes under "Styling" tab&lt;/li&gt;
&lt;li&gt;"Motion Control" tab shows "Coming Soon" message&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;UI Structure&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center justify-between p-3 border-b border-gray-700"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Advanced Text Settings&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Basic Mode&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* NEW: Subtab System */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"border-b border-gray-700/50"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Styling&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Motion Control&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Current content (under Styling tab) */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex-1 overflow-y-auto p-3"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* All existing sections */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Motion Control Tab Content&lt;/strong&gt; (when enabled in future):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"p-3 bg-gray-800/50"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"p-4 bg-blue-500/10 border border-blue-500/30 rounded"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Info&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"w-5 h-5 text-blue-400 mb-2"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-sm text-blue-300 font-medium"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Motion Control (Coming Soon)&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-xs text-blue-300/70 mt-1"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      This section will allow you to fine-tune text animation parameters, 
      timing curves, per-character controls, and motion path editing.
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Implementation Steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Modify FXShortcutsTab.tsx
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Changes Required&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Update activeTab state&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;activeTab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setActiveTab&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textfx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;favorites&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;presets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add Text FX tab button&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;
  &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setActiveTab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textfx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
  &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`flex-1 px-4 py-2.5 text-xs font-medium transition-colors &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;
    &lt;span class="nx"&gt;activeTab&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textfx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bg-gray-700/50 text-yellow-400 border-b-2 border-yellow-400&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text-gray-400 hover:text-gray-300 hover:bg-gray-700/30&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;Text&lt;/span&gt; &lt;span class="nx"&gt;FX&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create textAnimationCategories array&lt;/strong&gt; (after existing categories):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textAnimationCategories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AnimationCategory&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text-reveal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Text Reveal / Writing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;animations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;typewriter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Typewriter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Characters appear one by one with cursor blink&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dur&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;addKf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// Placeholder - actual implementation later&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Typewriter animation - coming soon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="c1"&gt;// ... rest of reveal animations&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// ... rest of categories&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Update render logic&lt;/strong&gt; to show Text FX content:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;activeTab&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textfx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;p-2 space-y-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;textAnimationCategories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isExpanded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;expandedCategories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;border border-gray-700/50 rounded-lg overflow-hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Same category structure as existing FX */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})}&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;activeTab&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;favorites&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;// ... existing favorites logic&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;// ... existing all FX logic&lt;/span&gt;
&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Modify AdvancedTextSettingsPanel.tsx
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Changes Required&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add subtab state&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;activeSubtab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setActiveSubtab&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;styling&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;motion&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;styling&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add subtab bar&lt;/strong&gt; (after header, before content):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;border-b border-gray-700/50&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;
      &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setActiveSubtab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;styling&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
      &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`flex-1 px-4 py-2 text-xs font-medium transition-colors &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;
        &lt;span class="nx"&gt;activeSubtab&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;styling&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
          &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bg-gray-700/50 text-blue-400 border-b-2 border-blue-400&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
          &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text-gray-400 hover:text-gray-300 hover:bg-gray-700/30&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;Styling&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;
      &lt;span class="nx"&gt;disabled&lt;/span&gt;
      &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex-1 px-4 py-2 text-xs font-medium text-gray-600 cursor-not-allowed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Coming soon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;Motion&lt;/span&gt; &lt;span class="nx"&gt;Control&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Wrap existing content&lt;/strong&gt; in conditional render:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;activeSubtab&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;styling&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;space-y-2 text-sm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* All existing sections */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;p-4 bg-blue-500/10 border border-blue-500/30 rounded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Info&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-5 h-5 text-blue-400 mb-2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-sm text-blue-300 font-medium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Motion&lt;/span&gt; &lt;span class="nc"&gt;Control &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Coming&lt;/span&gt; &lt;span class="nx"&gt;Soon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-xs text-blue-300/70 mt-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;Fine&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;tune&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="nx"&gt;animation&lt;/span&gt; &lt;span class="nx"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timing&lt;/span&gt; &lt;span class="nx"&gt;curves&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;motion&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Complete Text Animation Definitions
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textAnimationCategories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AnimationCategory&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text-reveal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Text Reveal / Writing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;animations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;typewriter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Typewriter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Characters appear one by one with cursor blink&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script-write&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Script Write&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Path based handwriting reveal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;word-pop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Word Pop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Words appear sequentially with micro scale up&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;line-reveal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Line Reveal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lines appear from top to bottom with mask&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mask-wipe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mask Wipe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Text revealed by directional clipping&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fade-in-order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fade In Order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Opacity stagger per character or word&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;underline-write&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Underline Write&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Line writes first, text follows&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;motion-in&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Motion In (Entry)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;animations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slide-in-text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Slide In&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;From left right up down with stagger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rise-baseline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Rise From Baseline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Letters rise from baseline with overshoot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drop-in&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Drop In&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Characters fall with gravity feel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scale-up-text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Scale Up&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Subtle zoom in with easing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;elastic-in&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Elastic In&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bounce overshoot then settle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flip-in&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Flip In&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3D flip around X or Y axis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;split-reveal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Split Reveal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Text splits from center outward&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;motion-out&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Motion Out (Exit)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;animations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slide-out-text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Slide Out&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Directional exit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fade-out-order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fade Out Order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Reverse stagger fade&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;collapse-text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Collapse&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Text scales down to center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;explode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Explode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Characters scatter outward&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sink&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sink&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Text falls below baseline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;emphasis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Emphasis / Loop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;animations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pulse-text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Pulse&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Soft scale and opacity loop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wiggle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Wiggle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Micro random position and rotation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bounce-text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bounce&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Vertical bounce emphasis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shake-text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Shake&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Horizontal jitter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;glow-pulse&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Glow Pulse&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Glow intensity oscillates&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;transform&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Transform / Structural&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;animations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;morph-in&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Morph In&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Letters morph from lines or blocks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stretch-in&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Stretch In&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Text stretches then snaps back&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;skew-snap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Skew Snap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Skew in then straighten&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;perspective-push&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Perspective Push&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Z axis push toward camera&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;premium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Premium / Advanced&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;animations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;kinetic-flow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Kinetic Flow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Characters follow curved motion path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wave-write&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Wave Write&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Writing head moves in wave pattern&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fragment-assemble&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fragment Assemble&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Text assembles from shards&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;neon-draw&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Neon Draw&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Stroke draw with glow trail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;glitch-in&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Glitch In&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Digital glitch then settle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;magnetic-align&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Magnetic Align&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Characters snap into place from chaos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;placeholderApply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DesignElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dur&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;addKf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initAnim&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Text animation - implementation coming soon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// TODO: Implement actual character-by-character animation logic&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Files to Modify
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;/src/components/layout/FXShortcutsTab.tsx&lt;/code&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Add 'textfx' to activeTab type&lt;/li&gt;
&lt;li&gt;Add Text FX tab button&lt;/li&gt;
&lt;li&gt;Add textAnimationCategories array&lt;/li&gt;
&lt;li&gt;Add conditional render for Text FX tab content&lt;/li&gt;
&lt;li&gt;Estimated lines added: ~400&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;/src/components/design-tool/AdvancedTextSettingsPanel.tsx&lt;/code&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Add activeSubtab state&lt;/li&gt;
&lt;li&gt;Add subtab bar component&lt;/li&gt;
&lt;li&gt;Wrap existing content in conditional&lt;/li&gt;
&lt;li&gt;Add Motion Control "coming soon" placeholder&lt;/li&gt;
&lt;li&gt;Estimated lines added: ~50&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Testing Checklist
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Text FX Tab
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Text FX tab button appears between "All FX" and "Favorites"&lt;/li&gt;
&lt;li&gt;[ ] Clicking Text FX tab shows text animation categories&lt;/li&gt;
&lt;li&gt;[ ] All 6 categories are visible&lt;/li&gt;
&lt;li&gt;[ ] Each category shows correct number of animations:

&lt;ul&gt;
&lt;li&gt;Text Reveal / Writing: 7&lt;/li&gt;
&lt;li&gt;Motion In (Entry): 7&lt;/li&gt;
&lt;li&gt;Motion Out (Exit): 5&lt;/li&gt;
&lt;li&gt;Emphasis / Loop: 5&lt;/li&gt;
&lt;li&gt;Transform / Structural: 4&lt;/li&gt;
&lt;li&gt;Premium / Advanced: 6&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;[ ] Categories expand/collapse correctly&lt;/li&gt;

&lt;li&gt;[ ] Animation names and descriptions are correct&lt;/li&gt;

&lt;li&gt;[ ] Clicking animation shows console log (placeholder)&lt;/li&gt;

&lt;li&gt;[ ] Favorites system works with text animations&lt;/li&gt;

&lt;li&gt;[ ] Only text elements show these animations&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Motion Control Subtab
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Advanced Text Settings panel shows subtab bar&lt;/li&gt;
&lt;li&gt;[ ] "Styling" tab is active by default&lt;/li&gt;
&lt;li&gt;[ ] "Motion Control" tab is disabled (grayed out)&lt;/li&gt;
&lt;li&gt;[ ] Hovering "Motion Control" shows tooltip "Coming soon"&lt;/li&gt;
&lt;li&gt;[ ] All existing sections appear under "Styling" tab&lt;/li&gt;
&lt;li&gt;[ ] No functionality is broken&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Future Implementation Notes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Phase 2: Actual Animation Implementation
&lt;/h3&gt;

&lt;p&gt;When implementing the actual animations, each will need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Character-level control&lt;/strong&gt; - Split text into individual characters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stagger timing&lt;/strong&gt; - Sequential animation with delay between characters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Property keyframes&lt;/strong&gt; - opacity, position, scale, rotation per character&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easing curves&lt;/strong&gt; - Different easing per animation type&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Path following&lt;/strong&gt; - For advanced animations (wave, kinetic flow)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Particle effects&lt;/strong&gt; - For fragment, explode animations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canvas rendering&lt;/strong&gt; - For glow, neon, glitch effects&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Phase 3: Motion Control Implementation
&lt;/h3&gt;

&lt;p&gt;When building Motion Control subtab:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Animation Timeline&lt;/strong&gt; - Visual timeline showing character animation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Curve Editor&lt;/strong&gt; - Bezier curve editor for custom easing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-Character Controls&lt;/strong&gt; - Select individual characters to adjust timing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Motion Path Editor&lt;/strong&gt; - Draw custom paths for text to follow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced Parameters&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Stagger delay (ms between characters)&lt;/li&gt;
&lt;li&gt;Direction (left-to-right, right-to-left, center-out, random)&lt;/li&gt;
&lt;li&gt;Overshoot amount&lt;/li&gt;
&lt;li&gt;Randomness/chaos factor&lt;/li&gt;
&lt;li&gt;Spring physics parameters&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Design Decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why Placeholder Functions?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Focus on UI/UX first&lt;/li&gt;
&lt;li&gt;Text animations require character-level rendering&lt;/li&gt;
&lt;li&gt;Need canvas or SVG implementation&lt;/li&gt;
&lt;li&gt;Allows user to see available animations&lt;/li&gt;
&lt;li&gt;Can add actual implementation incrementally&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Separate "Text FX" Tab?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Text animations are fundamentally different from element animations&lt;/li&gt;
&lt;li&gt;Character-level vs element-level control&lt;/li&gt;
&lt;li&gt;Different parameter sets&lt;/li&gt;
&lt;li&gt;Clearer organization&lt;/li&gt;
&lt;li&gt;Room for text-specific features&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Motion Control as Subtab?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Keeps Advanced Text Settings organized&lt;/li&gt;
&lt;li&gt;Styling vs Animation controls are conceptually different&lt;/li&gt;
&lt;li&gt;Allows future expansion of each subtab&lt;/li&gt;
&lt;li&gt;Prevents overwhelming single screen&lt;/li&gt;
&lt;li&gt;Professional video editor pattern&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Success Criteria
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Minimum Viable Product (MVP)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ Text FX tab visible and functional&lt;/li&gt;
&lt;li&gt;✅ All 34 animations listed correctly&lt;/li&gt;
&lt;li&gt;✅ Categories expand/collapse&lt;/li&gt;
&lt;li&gt;✅ Motion Control subtab exists (disabled)&lt;/li&gt;
&lt;li&gt;✅ No existing functionality broken&lt;/li&gt;
&lt;li&gt;✅ Consistent UI/UX with existing features&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Phase 2 (Actual Implementation)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;⏳ At least 10 animations fully functional&lt;/li&gt;
&lt;li&gt;⏳ Character-level animation working&lt;/li&gt;
&lt;li&gt;⏳ Stagger timing system implemented&lt;/li&gt;
&lt;li&gt;⏳ Preview in timeline&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Phase 3 (Motion Control)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;⏳ Motion Control subtab enabled&lt;/li&gt;
&lt;li&gt;⏳ Parameter controls functional&lt;/li&gt;
&lt;li&gt;⏳ Timeline editor implemented&lt;/li&gt;
&lt;li&gt;⏳ Motion path editor working&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This implementation creates the foundation for professional-grade text animation in FlashFX. The modular approach allows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Immediate value&lt;/strong&gt; - Users see what's possible&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incremental development&lt;/strong&gt; - Implement animations one by one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User feedback&lt;/strong&gt; - Learn which animations are most wanted&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quality focus&lt;/strong&gt; - Build each animation properly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Future expansion&lt;/strong&gt; - Motion Control for advanced users&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The text animation system will be &lt;strong&gt;the most powerful feature&lt;/strong&gt; in FlashFX, setting it apart from competitors and providing professional-grade capabilities for motion designers.&lt;/p&gt;

</description>
      <category>flashfx</category>
      <category>ai</category>
      <category>editor</category>
      <category>implementation</category>
    </item>
    <item>
      <title>FlashFX Export System Documentation</title>
      <dc:creator>Gabriele Bolognese</dc:creator>
      <pubDate>Mon, 26 Jan 2026 17:49:00 +0000</pubDate>
      <link>https://dev.to/therealgabry/flashfx-export-system-documentation-32ac</link>
      <guid>https://dev.to/therealgabry/flashfx-export-system-documentation-32ac</guid>
      <description>&lt;h1&gt;
  
  
  Overview
&lt;/h1&gt;

&lt;p&gt;The FlashFX export system has been completely rewritten to provide a robust, reliable, and optimized design export feature. The new system supports three export modes with real-time progress tracking and comprehensive error handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Module Structure
&lt;/h3&gt;

&lt;p&gt;The export system is organized into modular components located in &lt;code&gt;/src/export/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/src/export/
├── ExportManager.ts      # Orchestrates all export operations
├── CanvasExporter.ts     # Handles full canvas exports
├── ShapeExporter.ts      # Handles individual shape exports
├── ZipExporter.ts        # Handles ZIP file creation
└── ExportUI.tsx          # User interface component
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Export Entire Canvas
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Exports the complete canvas as a single PNG or JPEG image&lt;/li&gt;
&lt;li&gt;Supports custom resolution selection:

&lt;ul&gt;
&lt;li&gt;Canvas native resolution&lt;/li&gt;
&lt;li&gt;Full HD (1920×1080)&lt;/li&gt;
&lt;li&gt;4K (3840×2160)&lt;/li&gt;
&lt;li&gt;8K (7680×4320)&lt;/li&gt;
&lt;li&gt;Custom width/height&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Uses 2x pixel ratio for high-quality output&lt;/li&gt;

&lt;li&gt;Transparent background for PNG, solid for JPEG&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Export ZIP for Animation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Most Important Feature&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exports each visible shape individually as a PNG with transparent background&lt;/li&gt;
&lt;li&gt;Each shape maintains its exact position on the canvas&lt;/li&gt;
&lt;li&gt;When overlaid, shapes recreate the original canvas perfectly&lt;/li&gt;
&lt;li&gt;Naming convention: &lt;code&gt;[projectName]_shape_00.png&lt;/code&gt;, &lt;code&gt;[projectName]_shape_01.png&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;Shapes are exported in layer stack order (bottom to top)&lt;/li&gt;
&lt;li&gt;All exports automatically packaged into a ZIP file&lt;/li&gt;
&lt;li&gt;Optimized rendering to prevent memory issues&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Export Selection
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Exports only selected elements&lt;/li&gt;
&lt;li&gt;Single selection: Downloads as individual PNG&lt;/li&gt;
&lt;li&gt;Multiple selections: Creates ZIP file with all selected shapes&lt;/li&gt;
&lt;li&gt;Same quality and positioning guarantees as ZIP export&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  User Interface
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Toolbar Integration
&lt;/h3&gt;

&lt;p&gt;The export button is located in the toolbar next to the Settings button:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Icon: Upload icon (box with arrow out)&lt;/li&gt;
&lt;li&gt;Tooltip: "Export Design"&lt;/li&gt;
&lt;li&gt;Same styling as other toolbar buttons&lt;/li&gt;
&lt;li&gt;Keyboard shortcut compatible&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Export Modal
&lt;/h3&gt;

&lt;p&gt;When clicked, a modal appears with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Format Selection&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PNG (Transparent) - default&lt;/li&gt;
&lt;li&gt;JPEG&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Resolution Options&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Canvas resolution&lt;/li&gt;
&lt;li&gt;Preset resolutions (HD, 4K, 8K)&lt;/li&gt;
&lt;li&gt;Custom width/height inputs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Export Mode Buttons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Export Entire Canvas (Blue button)&lt;/li&gt;
&lt;li&gt;Export ZIP for Animation (Yellow/Orange gradient - primary)&lt;/li&gt;
&lt;li&gt;Export Selection (Gray button, disabled if nothing selected)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Progress Tracking
&lt;/h3&gt;

&lt;p&gt;During export, the modal displays:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real-time progress indicator&lt;/li&gt;
&lt;li&gt;Current shape being exported (e.g., "Exporting shape 3/25: Rectangle")&lt;/li&gt;
&lt;li&gt;Progress bar with percentage&lt;/li&gt;
&lt;li&gt;Estimated time remaining&lt;/li&gt;
&lt;li&gt;Non-blocking UI (panels remain usable)&lt;/li&gt;
&lt;li&gt;Canvas interaction is locked during export&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Completion States
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Success:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Green checkmark icon&lt;/li&gt;
&lt;li&gt;Success message&lt;/li&gt;
&lt;li&gt;File name displayed&lt;/li&gt;
&lt;li&gt;"Done" button to close&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Red error icon&lt;/li&gt;
&lt;li&gt;Error message&lt;/li&gt;
&lt;li&gt;Technical error details&lt;/li&gt;
&lt;li&gt;"Retry" button&lt;/li&gt;
&lt;li&gt;"Close" button&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Canvas Export Process
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Locates canvas DOM element by ID (&lt;code&gt;canvas-artboard&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;html-to-image&lt;/code&gt; library with &lt;code&gt;toPng&lt;/code&gt; or &lt;code&gt;toJpeg&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Applies 2x pixel ratio for quality&lt;/li&gt;
&lt;li&gt;Captures at specified resolution&lt;/li&gt;
&lt;li&gt;Downloads directly to user's system&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Shape Export Process
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Iterates through visible shapes in layer order&lt;/li&gt;
&lt;li&gt;For each shape:

&lt;ul&gt;
&lt;li&gt;Creates temporary container with full canvas dimensions&lt;/li&gt;
&lt;li&gt;Clones the DOM element&lt;/li&gt;
&lt;li&gt;Positions clone at exact canvas coordinates&lt;/li&gt;
&lt;li&gt;Renders with transparent background&lt;/li&gt;
&lt;li&gt;Captures as PNG with &lt;code&gt;html-to-image&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Converts to Blob&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Collects all Blobs&lt;/li&gt;
&lt;li&gt;Packages into ZIP using JSZip&lt;/li&gt;
&lt;li&gt;Downloads ZIP file using FileSaver&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Performance Optimizations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Sequential rendering prevents memory overflow&lt;/li&gt;
&lt;li&gt;Efficient blob management&lt;/li&gt;
&lt;li&gt;ZIP compression level 6 for balance of speed and size&lt;/li&gt;
&lt;li&gt;Temporary DOM cleanup after each export&lt;/li&gt;
&lt;li&gt;Progress callback system prevents UI freezing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Error Handling
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Graceful failure for individual shapes&lt;/li&gt;
&lt;li&gt;Detailed error messages&lt;/li&gt;
&lt;li&gt;Memory overflow detection&lt;/li&gt;
&lt;li&gt;Missing element warnings&lt;/li&gt;
&lt;li&gt;Retry capability&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Integration Points
&lt;/h2&gt;

&lt;h3&gt;
  
  
  UIDesignTool Component
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ExportUI&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../export/ExportUI&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ExportUI&lt;/span&gt;
  &lt;span class="nx"&gt;isOpen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;showExportPanel&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;onClose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setShowExportPanel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
  &lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;selectedElements&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectedElements&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;projectName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;canvasWidth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3840&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;canvasHeight&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2160&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Toolbar Component
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onOpenExport&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;
    &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onOpenExport&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;p-2 rounded-lg bg-gray-700/50 hover:bg-gray-600/50&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Export Design&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Upload&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-5 h-5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  For Users
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Export Entire Canvas:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click Export button in toolbar&lt;/li&gt;
&lt;li&gt;Select format and resolution&lt;/li&gt;
&lt;li&gt;Click "Export Entire Canvas"&lt;/li&gt;
&lt;li&gt;File downloads automatically&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Export for Animation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click Export button&lt;/li&gt;
&lt;li&gt;Select desired resolution&lt;/li&gt;
&lt;li&gt;Click "Export ZIP for Animation"&lt;/li&gt;
&lt;li&gt;Wait for progress to complete&lt;/li&gt;
&lt;li&gt;ZIP file downloads with all shapes&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Export Selection:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select one or more shapes&lt;/li&gt;
&lt;li&gt;Click Export button&lt;/li&gt;
&lt;li&gt;Click "Export Selection"&lt;/li&gt;
&lt;li&gt;Single shape downloads as PNG&lt;/li&gt;
&lt;li&gt;Multiple shapes download as ZIP&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  For Developers
&lt;/h3&gt;

&lt;p&gt;To modify export behavior:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Add new export mode:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add to &lt;code&gt;ExportMode&lt;/code&gt; type in &lt;code&gt;ExportManager.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Implement export function in ExportManager&lt;/li&gt;
&lt;li&gt;Add UI button in &lt;code&gt;ExportUI.tsx&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Change default settings:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modify initial state in &lt;code&gt;ExportUI.tsx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Update &lt;code&gt;ExportConfig&lt;/code&gt; interface&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Add format support:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add to format type in ExportConfig&lt;/li&gt;
&lt;li&gt;Implement in CanvasExporter&lt;/li&gt;
&lt;li&gt;Add UI option&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Browser Compatibility
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Modern browsers with Canvas API support&lt;/li&gt;
&lt;li&gt;File download API support required&lt;/li&gt;
&lt;li&gt;Blob API support required&lt;/li&gt;
&lt;li&gt;Tested in Chrome, Firefox, Safari, Edge&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Known Limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Very large canvases (&amp;gt;8K) may cause memory issues&lt;/li&gt;
&lt;li&gt;Export speed depends on element count and complexity&lt;/li&gt;
&lt;li&gt;Maximum recommended: 100 shapes per export&lt;/li&gt;
&lt;li&gt;Browser file size limits apply&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Future Enhancements
&lt;/h2&gt;

&lt;p&gt;Potential improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SVG export support&lt;/li&gt;
&lt;li&gt;PDF export&lt;/li&gt;
&lt;li&gt;Video export integration&lt;/li&gt;
&lt;li&gt;Batch project export&lt;/li&gt;
&lt;li&gt;Cloud export destinations&lt;/li&gt;
&lt;li&gt;Export presets&lt;/li&gt;
&lt;li&gt;Background export (web workers)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Dependencies
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;html-to-image&lt;/code&gt; - Canvas to image conversion&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jszip&lt;/code&gt; - ZIP file creation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;file-saver&lt;/code&gt; - File download handling&lt;/li&gt;
&lt;li&gt;React - UI framework&lt;/li&gt;
&lt;li&gt;Lucide React - Icons&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Export fails immediately
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Check console for errors&lt;/li&gt;
&lt;li&gt;Verify elements have valid DOM nodes&lt;/li&gt;
&lt;li&gt;Ensure canvas element exists&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Memory errors
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Reduce canvas resolution&lt;/li&gt;
&lt;li&gt;Export fewer shapes at once&lt;/li&gt;
&lt;li&gt;Close other browser tabs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Shapes positioned incorrectly
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Verify element x/y coordinates&lt;/li&gt;
&lt;li&gt;Check for transform issues&lt;/li&gt;
&lt;li&gt;Ensure parent containers are correct&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ZIP file corrupt
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Check available disk space&lt;/li&gt;
&lt;li&gt;Verify all blobs created successfully&lt;/li&gt;
&lt;li&gt;Try smaller batch size&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Support
&lt;/h2&gt;

&lt;p&gt;For issues or questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check console logs for detailed errors&lt;/li&gt;
&lt;li&gt;Verify all dependencies installed&lt;/li&gt;
&lt;li&gt;Test with simple shapes first&lt;/li&gt;
&lt;li&gt;Review export progress messages&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>flashfx</category>
      <category>backend</category>
      <category>export</category>
    </item>
    <item>
      <title>Playback System - Complete Rewrite</title>
      <dc:creator>Gabriele Bolognese</dc:creator>
      <pubDate>Sun, 25 Jan 2026 14:02:28 +0000</pubDate>
      <link>https://dev.to/therealgabry/playback-system-complete-rewrite-2lnm</link>
      <guid>https://dev.to/therealgabry/playback-system-complete-rewrite-2lnm</guid>
      <description>&lt;h1&gt;
  
  
  Summary of Changes
&lt;/h1&gt;

&lt;p&gt;The playback system has been &lt;strong&gt;completely rewritten&lt;/strong&gt; to fix critical issues causing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Playhead moving backward during playback&lt;/li&gt;
&lt;li&gt;❌ Severe lag and stuttering&lt;/li&gt;
&lt;li&gt;❌ High CPU usage&lt;/li&gt;
&lt;li&gt;❌ Unpredictable animation behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Was Wrong
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Core Problem: Dependency Cycle
&lt;/h3&gt;

&lt;p&gt;The original &lt;code&gt;usePlayback.ts&lt;/code&gt; had a &lt;strong&gt;fatal design flaw&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// OLD CODE - BROKEN&lt;/span&gt;
&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;deltaSeconds&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ⚠️ Uses state variable&lt;/span&gt;
    &lt;span class="nf"&gt;setCurrentTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ⚠️ Triggers re-render&lt;/span&gt;
    &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isPlaying&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCurrentTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPlaying&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="c1"&gt;//              ^^^^^^^^^^^ THIS CAUSED THE BUG&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Cascade of Failures:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Animation frame runs → &lt;code&gt;setCurrentTime(0.016)&lt;/code&gt; → State updates&lt;/li&gt;
&lt;li&gt;React re-renders → useEffect sees &lt;code&gt;currentTime&lt;/code&gt; changed&lt;/li&gt;
&lt;li&gt;Effect &lt;strong&gt;UNMOUNTS&lt;/strong&gt; old animation loop&lt;/li&gt;
&lt;li&gt;Effect &lt;strong&gt;REMOUNTS&lt;/strong&gt; new animation loop&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lastTimeRef.current = performance.now()&lt;/code&gt; resets&lt;/li&gt;
&lt;li&gt;Next frame calculates delta from wrong baseline&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result&lt;/strong&gt;: Time jumps backward, severe lag&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This happened &lt;strong&gt;60 times per second&lt;/strong&gt;, causing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Constant effect remounting&lt;/li&gt;
&lt;li&gt;Garbage collection pressure&lt;/li&gt;
&lt;li&gt;Timing drift and errors&lt;/li&gt;
&lt;li&gt;Backward playhead movement&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why It Couldn't Be Fixed With Simple Changes
&lt;/h3&gt;

&lt;p&gt;The problem wasn't just the dependency array. The fundamental architecture was wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Animation loop&lt;/strong&gt; tied to &lt;strong&gt;React's render cycle&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State updates&lt;/strong&gt; triggering &lt;strong&gt;effect re-runs&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No separation&lt;/strong&gt; between internal timing and UI updates&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The New Architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Core Principle: Decouple Animation from Rendering
&lt;/h3&gt;

&lt;p&gt;The new system uses &lt;strong&gt;refs for internal state&lt;/strong&gt; and &lt;strong&gt;state for UI updates&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Refs - Track internal state without triggering re-renders&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;internalTimeRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isPlayingRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isPlaying&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;durationRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loopRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fpsRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fps&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Animation loop uses ONLY refs&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;internalTimeRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;deltaSeconds&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ✅ Uses ref&lt;/span&gt;
  &lt;span class="nx"&gt;internalTimeRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newTime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ✅ Updates ref&lt;/span&gt;
  &lt;span class="nf"&gt;setCurrentTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ✅ Updates UI (doesn't restart loop)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Improvements
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. &lt;strong&gt;Refs Prevent Stale Closures&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Sync effects keep refs updated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;internalTimeRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;durationRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;External seeking updates internal time&lt;/li&gt;
&lt;li&gt;Sequence changes update duration/fps&lt;/li&gt;
&lt;li&gt;No stale values in animation loop&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. &lt;strong&gt;Animation Loop Runs Once&lt;/strong&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isPlaying&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Start animation loop ONCE&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Uses refs - no stale closures&lt;/span&gt;
    &lt;span class="c1"&gt;// Updates state - doesn't restart loop&lt;/span&gt;
    &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isPlaying&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCurrentTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPlaying&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="c1"&gt;// ✅ currentTime, duration, loop, fps NOT in dependencies&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Starts when &lt;code&gt;isPlaying&lt;/code&gt; becomes true&lt;/li&gt;
&lt;li&gt;Runs continuously until stopped&lt;/li&gt;
&lt;li&gt;Never restarts mid-playback&lt;/li&gt;
&lt;li&gt;Uses refs for all internal state&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3. &lt;strong&gt;Proper State Management&lt;/strong&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;togglePlay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isPlayingRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Restart from beginning if at end&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;internalTimeRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;durationRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;internalTimeRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nf"&gt;setCurrentTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;play&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCurrentTime&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All callbacks use refs to check current values, preventing stale closures.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works Now
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Playback Flow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;User presses play&lt;/strong&gt; → &lt;code&gt;setPlaying(true)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;useEffect triggers&lt;/strong&gt; (only for isPlaying change)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Animation loop starts&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   Frame 0: timestamp = 1000, delta = 0, time = 0.000
   Frame 1: timestamp = 1016, delta = 16ms, time = 0.016
   Frame 2: timestamp = 1033, delta = 17ms, time = 0.033
   Frame 3: timestamp = 1049, delta = 16ms, time = 0.049
   ... smooth forward progression
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Each frame&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Calculates delta from last frame&lt;/li&gt;
&lt;li&gt;Updates &lt;code&gt;internalTimeRef.current&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Calls &lt;code&gt;setCurrentTime()&lt;/code&gt; for UI&lt;/li&gt;
&lt;li&gt;Schedules next frame&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Effect does NOT re-run&lt;/strong&gt; because currentTime not in deps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result&lt;/strong&gt;: Smooth, accurate, forward-only playback&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Seeking Flow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;User drags playhead&lt;/strong&gt; → &lt;code&gt;seekTo(2.5)&lt;/code&gt; called&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;seekTo updates both&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;   &lt;span class="nx"&gt;internalTimeRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Internal state&lt;/span&gt;
   &lt;span class="nf"&gt;setCurrentTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;           &lt;span class="c1"&gt;// UI state&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sync effect runs&lt;/strong&gt;: &lt;code&gt;internalTimeRef.current = currentTime&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next animation frame&lt;/strong&gt; uses correct time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No discontinuity&lt;/strong&gt; or time jump&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Sequence Changes
&lt;/h3&gt;

&lt;p&gt;When a sequence changes (fps, duration):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;AnimationContext updates&lt;/strong&gt; timeline state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sync effects run&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;   &lt;span class="nx"&gt;durationRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newDuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nx"&gt;fpsRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newFps&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Animation loop&lt;/strong&gt; uses new values immediately&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No restart&lt;/strong&gt; needed&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Performance Improvements
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before (Broken System)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;⚠️ Effect remounts: &lt;strong&gt;60 times/second&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⚠️ State updates: &lt;strong&gt;60 times/second&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⚠️ Cleanup functions: &lt;strong&gt;60 times/second&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⚠️ Setup functions: &lt;strong&gt;60 times/second&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⚠️ CPU usage: &lt;strong&gt;Very high&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⚠️ Frame rate: &lt;strong&gt;Unstable, dropping&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⚠️ Timing: &lt;strong&gt;Inaccurate, backward movement&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  After (New System)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ Effect remounts: &lt;strong&gt;1 time on play/pause&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ State updates: &lt;strong&gt;60 times/second (normal)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ Cleanup functions: &lt;strong&gt;1 time on stop&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ Setup functions: &lt;strong&gt;1 time on start&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ CPU usage: &lt;strong&gt;Normal&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ Frame rate: &lt;strong&gt;Stable 60 FPS&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ Timing: &lt;strong&gt;Accurate, smooth forward&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Edge Cases Handled
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Seeking While Playing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;seekTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;internalTimeRef&lt;/span&gt; &lt;span class="nx"&gt;updates&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;sync&lt;/span&gt; &lt;span class="nx"&gt;effect&lt;/span&gt; &lt;span class="nx"&gt;runs&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;continues&lt;/span&gt; &lt;span class="nx"&gt;smoothly&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ No time jump or discontinuity&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Changing Duration Mid-Playback
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;setDuration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;durationRef&lt;/span&gt; &lt;span class="nx"&gt;updates&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;animation&lt;/span&gt; &lt;span class="nx"&gt;continues&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ No restart, smooth transition&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Reaching End
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newTime&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;durationRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loopRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Loop back&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Stop cleanly&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Proper loop or stop behavior&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Pause/Resume
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;cancels&lt;/span&gt; &lt;span class="nx"&gt;RAF&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;isPlayingRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;starts&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;RAF&lt;/span&gt; &lt;span class="nx"&gt;loop&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;lastTimestampRef&lt;/span&gt; &lt;span class="nx"&gt;resets&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ No time jump from pause duration&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Stop/Restart
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cancels&lt;/span&gt; &lt;span class="nx"&gt;RAF&lt;/span&gt;
&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;starts&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Clean restart behavior&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Checklist
&lt;/h2&gt;

&lt;p&gt;Test these scenarios to verify the fix:&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Playback
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[x] Press play → playhead moves forward smoothly&lt;/li&gt;
&lt;li&gt;[x] No backward movement at any point&lt;/li&gt;
&lt;li&gt;[x] Consistent speed (30 FPS = 0.033s per frame)&lt;/li&gt;
&lt;li&gt;[x] Time display matches actual progression&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Seeking
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[x] Seek while paused → updates immediately&lt;/li&gt;
&lt;li&gt;[x] Seek while playing → continues from new position&lt;/li&gt;
&lt;li&gt;[x] Seek to start → resets to 0&lt;/li&gt;
&lt;li&gt;[x] Seek to end → stops at duration&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Controls
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[x] Play/pause → toggles correctly&lt;/li&gt;
&lt;li&gt;[x] Stop → returns to start&lt;/li&gt;
&lt;li&gt;[x] Step forward → advances one frame&lt;/li&gt;
&lt;li&gt;[x] Step backward → goes back one frame&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Looping
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[x] Enable loop → reaches end → restarts at 0&lt;/li&gt;
&lt;li&gt;[x] Disable loop → reaches end → stops&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[x] Open DevTools Performance tab&lt;/li&gt;
&lt;li&gt;[x] Record during playback&lt;/li&gt;
&lt;li&gt;[x] Verify smooth 60 FPS&lt;/li&gt;
&lt;li&gt;[x] No excessive re-renders&lt;/li&gt;
&lt;li&gt;[x] Normal CPU usage&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sequences
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[x] Create sequence with 24 FPS → playback respects frame rate&lt;/li&gt;
&lt;li&gt;[x] Change duration → playback adapts&lt;/li&gt;
&lt;li&gt;[x] Switch sequences → timeline updates correctly&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical Details
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Refs vs State
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;When to use Refs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal timing values&lt;/li&gt;
&lt;li&gt;Values that change every frame&lt;/li&gt;
&lt;li&gt;Values needed in animation loop&lt;/li&gt;
&lt;li&gt;Values that shouldn't trigger re-renders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to use State:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI display values&lt;/li&gt;
&lt;li&gt;User-controllable values&lt;/li&gt;
&lt;li&gt;Values that need to trigger re-renders&lt;/li&gt;
&lt;li&gt;Values that affect component output&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why This Works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Refs are mutable&lt;/strong&gt; without triggering re-renders&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sync effects&lt;/strong&gt; keep refs updated from external changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Animation loop&lt;/strong&gt; runs independently of React's render cycle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State updates&lt;/strong&gt; only affect UI, not loop timing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No circular dependencies&lt;/strong&gt; between state and effects&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Performance Characteristics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Memory&lt;/strong&gt;: Minimal (few extra refs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CPU&lt;/strong&gt;: Optimal (one RAF loop, no restart overhead)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timing accuracy&lt;/strong&gt;: High (no drift from restarts)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frame rate&lt;/strong&gt;: Stable (60 FPS on most displays)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Migration Notes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  No Breaking Changes
&lt;/h3&gt;

&lt;p&gt;The external API remains identical:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;play&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;togglePlay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;seekTo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;seekToFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isPlaying&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// ... all the same&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;usePlayback&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Internal Changes Only
&lt;/h3&gt;

&lt;p&gt;All changes are internal to &lt;code&gt;usePlayback.ts&lt;/code&gt;. No other files need updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backward Compatible
&lt;/h3&gt;

&lt;p&gt;Existing code using &lt;code&gt;usePlayback()&lt;/code&gt; works without modification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The playback system has been fundamentally redesigned to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Separate concerns&lt;/strong&gt;: Animation timing vs UI rendering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use refs for internal state&lt;/strong&gt;: Avoid re-render loops&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use state for UI updates&lt;/strong&gt;: Keep display in sync&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run animation loop once&lt;/strong&gt;: No restarts mid-playback&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handle all edge cases&lt;/strong&gt;: Seeking, looping, sequences&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: Smooth, accurate, performant playback that respects sequences and provides a professional animation experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  Code Comparison
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before (Broken)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;lastTimeRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deltaMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;lastTimeRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;deltaSeconds&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ⚠️ Stale closure&lt;/span&gt;
    &lt;span class="nf"&gt;setCurrentTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ⚠️ Triggers remount&lt;/span&gt;
    &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isPlaying&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...]);&lt;/span&gt;
&lt;span class="c1"&gt;// ⚠️ Remounts 60 times/second&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  After (Fixed)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;internalTimeRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;internalTimeRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// ✅ Sync only&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;internalTimeRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;deltaSeconds&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ✅ Always current&lt;/span&gt;
    &lt;span class="nx"&gt;internalTimeRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newTime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ✅ Update ref&lt;/span&gt;
    &lt;span class="nf"&gt;setCurrentTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ✅ Update UI (doesn't remount)&lt;/span&gt;
    &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isPlaying&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// ✅ Runs once per play/pause&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference: &lt;strong&gt;Refs break the dependency cycle&lt;/strong&gt; and allow the animation loop to run independently of React's render cycle.&lt;/p&gt;

</description>
      <category>flashfx</category>
      <category>ai</category>
      <category>software</category>
      <category>bugfix</category>
    </item>
    <item>
      <title>UI Modifications Summary for FlashFX</title>
      <dc:creator>Gabriele Bolognese</dc:creator>
      <pubDate>Wed, 07 Jan 2026 03:37:35 +0000</pubDate>
      <link>https://dev.to/therealgabry/ui-modifications-summary-for-flashfx-5b0g</link>
      <guid>https://dev.to/therealgabry/ui-modifications-summary-for-flashfx-5b0g</guid>
      <description>&lt;h2&gt;
  
  
  Changes Implemented
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Background Settings - Removed "Add First Color" Button
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Location:&lt;/strong&gt; &lt;code&gt;src/components/layout/BackgroundSettingsPanel.tsx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change:&lt;/strong&gt; Removed the broken "Add First Color" button that appeared when canvas has no background.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Displayed a prominent yellow-orange gradient button saying "Add First Color"&lt;/li&gt;
&lt;li&gt;This button was problematic and needed removal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shows a simple informational message: "Canvas has transparent background"&lt;/li&gt;
&lt;li&gt;Directs users to "Click 'Add Gradient Layer' below to start"&lt;/li&gt;
&lt;li&gt;Users now use the existing "Add Gradient Layer" button at the bottom&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Benefit:&lt;/strong&gt; Eliminates the broken first color feature and provides clearer user guidance.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Layout Bar - Changed "Save" to "Download"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Location:&lt;/strong&gt; &lt;code&gt;src/components/layout/LayoutBar.tsx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change:&lt;/strong&gt; Renamed the "Save" button to "Download" in the bottom layout bar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Button showed Save icon and text "Save"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Button shows Download icon and text "Download"&lt;/li&gt;
&lt;li&gt;Updated tooltip from "Save Project" to "Download Project"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Technical Details:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Changed icon import from &lt;code&gt;Save&lt;/code&gt; to &lt;code&gt;Download&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Updated button text and title attributes&lt;/li&gt;
&lt;li&gt;Maintains same functionality, just clearer labeling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Benefit:&lt;/strong&gt; More accurately describes the action being performed.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Layout Bar - Added Exit Button
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Location:&lt;/strong&gt; &lt;code&gt;src/components/layout/LayoutBar.tsx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change:&lt;/strong&gt; Added an "Exit" button next to the Tutorial button in the bottom layout bar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Red-themed button with LogOut icon&lt;/li&gt;
&lt;li&gt;Positioned after the Tutorial button&lt;/li&gt;
&lt;li&gt;Styled consistently with other action buttons&lt;/li&gt;
&lt;li&gt;Border and background use red color scheme (red-500/10, red-500/20, red-500/30)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Props Added:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;onExitToHome?: () =&amp;gt; void&lt;/code&gt; - Callback to handle exit action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Integration:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Updated &lt;code&gt;DesignModeLayout.tsx&lt;/code&gt; to pass &lt;code&gt;onExitToHome&lt;/code&gt; prop to LayoutBar&lt;/li&gt;
&lt;li&gt;Properly wired to parent component's exit functionality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Benefit:&lt;/strong&gt; Provides quick access to exit the editor from the bottom bar.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Layers Panel - Removed Save/Exit Buttons
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Location:&lt;/strong&gt; &lt;code&gt;src/components/design-tool/LayersPanel.tsx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change:&lt;/strong&gt; Removed the Save and Exit buttons from the top of the layers panel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Top section had two action buttons:

&lt;ul&gt;
&lt;li&gt;Green "Save" button (with save progress indicator)&lt;/li&gt;
&lt;li&gt;Red "Exit" button&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Autosave countdown displayed next to buttons&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cleaner header with just the autosave countdown&lt;/li&gt;
&lt;li&gt;Autosave countdown now right-aligned&lt;/li&gt;
&lt;li&gt;Save and Exit functionality moved to LayoutBar (bottom)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Code Changes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removed button elements and their container div&lt;/li&gt;
&lt;li&gt;Kept autosave countdown functionality intact&lt;/li&gt;
&lt;li&gt;Simplified header layout to single-row with right-aligned countdown&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Benefit:&lt;/strong&gt; Reduces clutter in the layers panel and centralizes project actions to the bottom bar.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Default Project Background
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; Already Correct&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verification:&lt;/strong&gt; Projects already initialize with an empty background through &lt;code&gt;createDefaultBackground()&lt;/code&gt; which returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Location:&lt;/strong&gt; &lt;code&gt;src/types/background.ts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No changes needed&lt;/strong&gt; - projects already start without any default colors.&lt;/p&gt;




&lt;h2&gt;
  
  
  Files Modified
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;src/components/layout/BackgroundSettingsPanel.tsx&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removed "Add First Color" button&lt;/li&gt;
&lt;li&gt;Updated empty state messaging&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;src/components/layout/LayoutBar.tsx&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Changed Save to Download&lt;/li&gt;
&lt;li&gt;Added Exit button&lt;/li&gt;
&lt;li&gt;Added onExitToHome prop&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;src/components/layout/modes/DesignModeLayout.tsx&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Passed onExitToHome prop to LayoutBar&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;src/components/design-tool/LayersPanel.tsx&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removed Save and Exit buttons from header&lt;/li&gt;
&lt;li&gt;Simplified top navigation section&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Build Status
&lt;/h2&gt;

&lt;p&gt;✅ All changes compiled successfully with no errors&lt;br&gt;
✅ TypeScript types properly updated&lt;br&gt;
✅ Component prop chains properly wired&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Recommendations
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Background Settings&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify empty state shows correct message&lt;/li&gt;
&lt;li&gt;Confirm "Add Gradient Layer" button works as expected&lt;/li&gt;
&lt;li&gt;Test that users can add layers without the removed button&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Layout Bar&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify "Download" button functions correctly&lt;/li&gt;
&lt;li&gt;Test Exit button navigates properly&lt;/li&gt;
&lt;li&gt;Confirm button styling and placement&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Layers Panel&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify autosave countdown still displays correctly&lt;/li&gt;
&lt;li&gt;Confirm layout looks clean without the removed buttons&lt;/li&gt;
&lt;li&gt;Test that save/exit functionality works from new location&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;New Projects&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify projects start with transparent canvas&lt;/li&gt;
&lt;li&gt;Confirm no default background colors are applied&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  User Impact
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Positive Changes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cleaner, less cluttered interface&lt;/li&gt;
&lt;li&gt;Better action organization (project-level actions in bottom bar)&lt;/li&gt;
&lt;li&gt;Fixed broken background feature&lt;/li&gt;
&lt;li&gt;More accurate button labeling&lt;/li&gt;
&lt;li&gt;Consistent exit button placement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;No Breaking Changes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All functionality preserved&lt;/li&gt;
&lt;li&gt;Better UX with improved organization&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>flashfx</category>
      <category>editor</category>
      <category>developer</category>
    </item>
    <item>
      <title>Dual Timeline Layout - Implementation Guide</title>
      <dc:creator>Gabriele Bolognese</dc:creator>
      <pubDate>Tue, 06 Jan 2026 16:05:33 +0000</pubDate>
      <link>https://dev.to/therealgabry/dual-timeline-layout-implementation-guide-581b</link>
      <guid>https://dev.to/therealgabry/dual-timeline-layout-implementation-guide-581b</guid>
      <description>&lt;h2&gt;
  
  
  Layout Structure
&lt;/h2&gt;

&lt;p&gt;The interface is divided into:&lt;/p&gt;

&lt;h3&gt;
  
  
  Top Band (60% height)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Left Column (25% width)&lt;/strong&gt;: Layers Panel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Center Column (50% width)&lt;/strong&gt;: Canvas with red Toolbar on top, white canvas below&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Right Column (25% width)&lt;/strong&gt;: Properties Panel&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Bottom Band (40% height)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Left Half (50% width)&lt;/strong&gt;: General Timeline - displays clips per layer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Right Half (50% width)&lt;/strong&gt;: Animation Timeline - placeholder for future keyframe features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Playhead Style:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Yellow colored for high visibility&lt;/li&gt;
&lt;li&gt;Extends full height of each timeline panel (not just the ruler)&lt;/li&gt;
&lt;li&gt;Perfectly synchronized between both timelines&lt;/li&gt;
&lt;li&gt;Draggable from the yellow square handle in the ruler&lt;/li&gt;
&lt;li&gt;Click anywhere on the timeline tracks to seek&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  File Locations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Main Layout File
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/components/layout/modes/DesignModeLayout.tsx&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Contains the CSS Grid configuration&lt;/li&gt;
&lt;li&gt;Default percentages defined at lines 96-98:&lt;/li&gt;
&lt;li&gt;&lt;code&gt;leftColumnWidth: 25%&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rightColumnWidth: 25%&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;topRowHeight: 60%&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Timeline Components
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/components/timeline/GeneralTimeline.tsx&lt;/code&gt; - Displays timeline clips for each shape/layer&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/components/timeline/AnimationTimeline.tsx&lt;/code&gt; - Placeholder for property keyframes (disabled)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/components/timeline/ResizableSplitter.tsx&lt;/code&gt; - Draggable splitters (prepared for future use)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Changing Default Layout Percentages
&lt;/h2&gt;

&lt;p&gt;To modify the default layout proportions, edit &lt;code&gt;DesignModeLayout.tsx&lt;/code&gt; (lines 96-98):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;leftColumnWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLeftColumnWidth&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// Left panel width %&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;rightColumnWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setRightColumnWidth&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Right panel width %&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;topRowHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTopRowHeight&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// Top band height %&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Examples:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make canvas wider: Change &lt;code&gt;leftColumnWidth&lt;/code&gt; and &lt;code&gt;rightColumnWidth&lt;/code&gt; to &lt;code&gt;20&lt;/code&gt; each&lt;/li&gt;
&lt;li&gt;More timeline space: Change &lt;code&gt;topRowHeight&lt;/code&gt; to &lt;code&gt;50&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Narrow layers panel: Change &lt;code&gt;leftColumnWidth&lt;/code&gt; to &lt;code&gt;20&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Timeline Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  General Timeline (Bottom-Left, Blue)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Displays one track row per shape/layer&lt;/li&gt;
&lt;li&gt;Each track contains a clip representing the shape's timeline presence&lt;/li&gt;
&lt;li&gt;Default clip duration: 5 seconds&lt;/li&gt;
&lt;li&gt;Clips show under playhead when active (yellow ring highlight)&lt;/li&gt;
&lt;li&gt;Synchronized ruler with seconds and frame markers&lt;/li&gt;
&lt;li&gt;Click ruler or drag playhead to seek&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Animation Timeline (Bottom-Right, Green)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Currently a placeholder&lt;/strong&gt; - no animation features implemented&lt;/li&gt;
&lt;li&gt;Displays property tracks when a clip is selected&lt;/li&gt;
&lt;li&gt;Shows disabled state with message: "Animation features disabled — timeline reserved"&lt;/li&gt;
&lt;li&gt;Property rows: Position, Scale, Rotation, Opacity, Fill Color, Stroke&lt;/li&gt;
&lt;li&gt;Add Keyframe button is disabled&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Playhead Synchronization
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Single playhead controls both timelines simultaneously&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Yellow full-height playhead&lt;/strong&gt; visible across entire timeline panel height&lt;/li&gt;
&lt;li&gt;Current time displayed in format: &lt;code&gt;HH:MM:SS:FF&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Seeking in either timeline updates both timelines and canvas instantly&lt;/li&gt;
&lt;li&gt;Playhead state managed in &lt;code&gt;DesignModeLayout.tsx&lt;/code&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;currentTime&lt;/code&gt; - current playback position in seconds&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;duration&lt;/code&gt; - total timeline duration (default: 10s)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fps&lt;/code&gt; - frames per second (default: 30)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Both timelines share the same &lt;code&gt;onSeek&lt;/code&gt; callback ensuring perfect synchronization&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Was NOT Changed
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Canvas rendering pipeline&lt;/li&gt;
&lt;li&gt;Shape creation and manipulation logic&lt;/li&gt;
&lt;li&gt;Keyboard shortcuts&lt;/li&gt;
&lt;li&gt;Export functionality&lt;/li&gt;
&lt;li&gt;All existing tool behaviors&lt;/li&gt;
&lt;li&gt;Grid system&lt;/li&gt;
&lt;li&gt;Snap behavior&lt;/li&gt;
&lt;li&gt;History (undo/redo)&lt;/li&gt;
&lt;li&gt;All component internal logic&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  CSS Grid Configuration
&lt;/h3&gt;

&lt;p&gt;The layout uses CSS Grid with dynamic percentages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;grid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;gridTemplateColumns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;leftColumnWidth&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;% &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;centerColumnWidth&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;% &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rightColumnWidth&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;gridTemplateRows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;topRowHeight&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;% &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bottomRowHeight&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Grid Cell Assignment
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Layers: &lt;code&gt;grid-column: 1; grid-row: 1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Canvas: &lt;code&gt;grid-column: 2; grid-row: 1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Properties: &lt;code&gt;grid-column: 3; grid-row: 1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;General Timeline: &lt;code&gt;grid-column: 1 / 3; grid-row: 2&lt;/code&gt; (spans 2 columns)&lt;/li&gt;
&lt;li&gt;Animation Timeline: &lt;code&gt;grid-column: 3 / 4; grid-row: 2&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Clip Creation Logic
&lt;/h2&gt;

&lt;p&gt;When a new shape is created on the canvas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A corresponding layer entry appears in the Layers panel (left)&lt;/li&gt;
&lt;li&gt;A clip is automatically created in the General Timeline&lt;/li&gt;
&lt;li&gt;Clip inherits the shape's color and name&lt;/li&gt;
&lt;li&gt;Default duration: 5 seconds starting at 0&lt;/li&gt;
&lt;li&gt;Clip ID: &lt;code&gt;clip-{shapeID}&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Future Enhancements (Not Implemented)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Resizable splitters (component exists but not wired)&lt;/li&gt;
&lt;li&gt;Zoom controls for timeline&lt;/li&gt;
&lt;li&gt;Snap to clip boundaries&lt;/li&gt;
&lt;li&gt;Clip trimming and splitting&lt;/li&gt;
&lt;li&gt;Keyframe editing in Animation Timeline&lt;/li&gt;
&lt;li&gt;Play/Pause button (currently disabled)&lt;/li&gt;
&lt;li&gt;Audio waveform display&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Testing Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;✅ Layout matches image proportions (25%|50%|25% columns, 60%|40% rows)&lt;/li&gt;
&lt;li&gt;✅ Red toolbar bar unchanged in size and position&lt;/li&gt;
&lt;li&gt;✅ All existing features work (shapes, selection, properties)&lt;/li&gt;
&lt;li&gt;✅ Creating shapes adds clips to General Timeline&lt;/li&gt;
&lt;li&gt;✅ Playhead moves and syncs between timelines&lt;/li&gt;
&lt;li&gt;✅ Animation Timeline shows placeholder message&lt;/li&gt;
&lt;li&gt;✅ No changes to rendering or playback logic&lt;/li&gt;
&lt;li&gt;✅ Build completes without errors&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Timeline not showing clips:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify shapes exist on canvas&lt;/li&gt;
&lt;li&gt;Check browser console for errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Layout proportions off:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check &lt;code&gt;leftColumnWidth&lt;/code&gt;, &lt;code&gt;rightColumnWidth&lt;/code&gt;, &lt;code&gt;topRowHeight&lt;/code&gt; state values&lt;/li&gt;
&lt;li&gt;Verify CSS Grid template strings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Playhead not moving:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check &lt;code&gt;currentTime&lt;/code&gt; state updates&lt;/li&gt;
&lt;li&gt;Verify &lt;code&gt;onSeek&lt;/code&gt; callback wiring&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Support
&lt;/h2&gt;

&lt;p&gt;For layout adjustments, modify the state initialization in &lt;code&gt;DesignModeLayout.tsx&lt;/code&gt;.&lt;br&gt;
For timeline behavior, edit the respective timeline components in &lt;code&gt;src/components/timeline/&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>flashfx</category>
      <category>editor</category>
      <category>developer</category>
    </item>
    <item>
      <title>FlashFX Ai pipeline Breakdown</title>
      <dc:creator>Gabriele Bolognese</dc:creator>
      <pubDate>Tue, 06 Jan 2026 07:23:56 +0000</pubDate>
      <link>https://dev.to/therealgabry/flashfx-ai-pipeline-breakdown-3a66</link>
      <guid>https://dev.to/therealgabry/flashfx-ai-pipeline-breakdown-3a66</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;The AI generation pipeline now shows a clear visual progression in the chat interface, making it easy for users to understand what's happening at each stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pipeline Flow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Initial Confirmation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;When:&lt;/strong&gt; User submits a prompt&lt;br&gt;
&lt;strong&gt;What happens:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI sends message: &lt;em&gt;"I'll create a beautiful design with your specified requirements. Preparing to start..."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Message appears as a completed step (no loader)&lt;/li&gt;
&lt;li&gt;Brief 800ms pause for user to read&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;UI Display:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ I'll create a beautiful design with your specified requirements. Preparing to start...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Step 2: Prompt Validation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;When:&lt;/strong&gt; After initial confirmation&lt;br&gt;
&lt;strong&gt;What happens:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI sends message: &lt;em&gt;"Checking prompt..."&lt;/em&gt; with a loader icon&lt;/li&gt;
&lt;li&gt;Calls validation assistant (&lt;code&gt;asst_0crxCl5jFThLe0uN6xKwgJAa&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Waits for validation response (0 or 1)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;UI Display (Processing):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⟳ Checking prompt...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Success Case:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ Prompt Approved ✓
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Failure Case:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✗ Prompt Rejected ✗
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;If rejected, pipeline stops here&lt;/li&gt;
&lt;li&gt;Shows explanation message about why it was rejected&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Step 3: High-Level Breakdown
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;When:&lt;/strong&gt; After prompt is approved&lt;br&gt;
&lt;strong&gt;What happens:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI sends message: &lt;em&gt;"Breaking down request..."&lt;/em&gt; with a loader icon&lt;/li&gt;
&lt;li&gt;Calls high-level assistant (&lt;code&gt;asst_uLIk3I1aeLrCJI23m3F84lWl&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Receives JSON array of shape specifications&lt;/li&gt;
&lt;li&gt;Validates the JSON structure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;UI Display (Processing):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⟳ Breaking down request...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Success Case:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ Action Completed ✓
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Failure Case:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✗ Breakdown Failed ✗ - [error message]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Step 4: Explanation Message
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;When:&lt;/strong&gt; After successful high-level breakdown&lt;br&gt;
&lt;strong&gt;What happens:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI sends informational message explaining the next phase&lt;/li&gt;
&lt;li&gt;No loader, just text&lt;/li&gt;
&lt;li&gt;Brief 400ms pause&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;UI Display:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I've broken down the animation into an actionable plan. Now I need to create
the shapes one by one. This process might take a while because the AI is
experimental. You can still edit while I generate and check the status on
the plan below:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Step 5: Generation Plan Display
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;When:&lt;/strong&gt; Immediately after explanation message&lt;br&gt;
&lt;strong&gt;What happens:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates a special "plan" message with a list of all shapes to generate&lt;/li&gt;
&lt;li&gt;Each shape shows as a row with a status indicator&lt;/li&gt;
&lt;li&gt;Number of rows = number of shapes in high-level breakdown&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;UI Display:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generation Plan:
┌─────────────────────────────┐
│ -  rectangle 1              │
│ -  text 2                   │
│ -  line 3                   │
│ -  rectangle 4              │
│ -  text 5                   │
│ -  circle 6                 │
│ -  button 7                 │
└─────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Status Indicators:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-&lt;/code&gt; = Pending (gray)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;⟳&lt;/code&gt; = Processing (animated spinner, violet)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;✓&lt;/code&gt; = Completed (green checkmark)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;✗&lt;/code&gt; = Failed (red X)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Step 6: Low-Level Generation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;When:&lt;/strong&gt; Plan is displayed&lt;br&gt;
&lt;strong&gt;What happens:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loops through each shape in the high-level breakdown&lt;/li&gt;
&lt;li&gt;For each shape:

&lt;ol&gt;
&lt;li&gt;Updates plan row to show "processing" (spinner)&lt;/li&gt;
&lt;li&gt;Calls low-level assistant (&lt;code&gt;asst_z8sn6AEmZxXPDPp9DD8A4LC2&lt;/code&gt;) with shape + original prompt&lt;/li&gt;
&lt;li&gt;Receives detailed shape specification JSON&lt;/li&gt;
&lt;li&gt;Validates and repairs if needed&lt;/li&gt;
&lt;li&gt;Updates plan row to show "completed" (checkmark) or "failed" (X)&lt;/li&gt;
&lt;li&gt;300ms delay before next shape&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;UI Display (During Processing):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generation Plan:
┌─────────────────────────────┐
│ ✓  rectangle 1              │
│ ✓  text 2                   │
│ ⟳  line 3                   │  ← Currently processing
│ -  rectangle 4              │
│ -  text 5                   │
│ -  circle 6                 │
│ -  button 7                 │
└─────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;UI Display (Completed):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generation Plan:
┌─────────────────────────────┐
│ ✓  rectangle 1              │
│ ✓  text 2                   │
│ ✓  line 3                   │
│ ✓  rectangle 4              │
│ ✓  text 5                   │
│ ✓  circle 6                 │
│ ✓  button 7                 │
└─────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;UI Display (With Failures):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generation Plan:
┌─────────────────────────────┐
│ ✓  rectangle 1              │
│ ✓  text 2                   │
│ ✗  line 3         Generation failed │
│ ✓  rectangle 4              │
│ ✓  text 5                   │
│ ✓  circle 6                 │
│ ✓  button 7                 │
└─────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Step 7: Element Placement
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;When:&lt;/strong&gt; All shapes are generated (or attempted)&lt;br&gt;
&lt;strong&gt;What happens:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maps each JSON specification to DesignElement&lt;/li&gt;
&lt;li&gt;Clamps positions to canvas bounds&lt;/li&gt;
&lt;li&gt;Batch adds all elements to canvas&lt;/li&gt;
&lt;li&gt;Updates pipeline to "complete"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;No additional chat message&lt;/strong&gt; - placement happens silently&lt;/p&gt;


&lt;h3&gt;
  
  
  Step 8: Final Success Message
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;When:&lt;/strong&gt; All elements placed on canvas&lt;br&gt;
&lt;strong&gt;What happens:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI sends final summary message&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;UI Display:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Successfully created 7 elements in 24.3s!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;With warnings (if any failed):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Successfully created 6 elements in 24.3s!
⚠️ 1 element(s) could not be generated.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Real-Time Updates
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The plan message updates in real-time as each shape is processed&lt;/li&gt;
&lt;li&gt;Users can see exactly which shape is currently being generated&lt;/li&gt;
&lt;li&gt;Progress is visible even if they switch to other tabs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Non-Blocking
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Users can continue editing their project while generation happens&lt;/li&gt;
&lt;li&gt;Chat remains scrollable and interactive&lt;/li&gt;
&lt;li&gt;Cancel button available throughout process&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Clear Status Communication
&lt;/h3&gt;

&lt;p&gt;Each message type has distinct visual styling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Step Messages&lt;/strong&gt;: Icon + text (✓, ✗, or ⟳)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Info Messages&lt;/strong&gt;: Plain text, no icon&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan Message&lt;/strong&gt;: Special bordered box with rows&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Error Handling
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;If validation fails: Shows clear rejection message&lt;/li&gt;
&lt;li&gt;If breakdown fails: Shows what went wrong&lt;/li&gt;
&lt;li&gt;If individual shape fails: Marked with ✗ in plan, others continue&lt;/li&gt;
&lt;li&gt;If all shapes fail: Shows comprehensive error message&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Technical Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Message Types
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;isStreaming&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;isStepMessage&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// For validation/breakdown steps&lt;/span&gt;
  &lt;span class="nl"&gt;shapeItems&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;ShapeGenerationItem&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;  &lt;span class="c1"&gt;// For plan display&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Shape Item Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ShapeGenerationItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Message Flow Control
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;addStepMessage()&lt;/code&gt; - Creates new step message&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;updateStepMessage()&lt;/code&gt; - Updates existing message status&lt;/li&gt;
&lt;li&gt;Real-time updates through React state management&lt;/li&gt;
&lt;li&gt;Plan message updates by mapping over shapeItems array&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  User Experience Benefits
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Transparency&lt;/strong&gt;: Users see exactly what's happening at each step&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progress Tracking&lt;/strong&gt;: Clear visual indication of completion percentage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Clarity&lt;/strong&gt;: Failed steps show specific error messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cancellable&lt;/strong&gt;: Users can stop generation at any time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-Disruptive&lt;/strong&gt;: Can continue working while AI generates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Historical Record&lt;/strong&gt;: All generation attempts saved in chat history&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Example Complete Flow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User: "create a login form with username, password, and submit button"

AI: ✓ I'll create a beautiful design with your specified requirements.
      Preparing to start...

AI: ⟳ Checking prompt...
    [2 seconds later]
    ✓ Prompt Approved ✓

AI: ⟳ Breaking down request...
    [5 seconds later]
    ✓ Action Completed ✓

AI: I've broken down the animation into an actionable plan. Now I need to
    create the shapes one by one. This process might take a while because
    the AI is experimental. You can still edit while I generate and check
    the status on the plan below:

AI: Generation Plan:
    ┌─────────────────────────────┐
    │ ⟳  rectangle 1              │  ← Form container
    │ -  text 2                   │  ← Username label
    │ -  input 3                  │  ← Username field
    │ -  text 4                   │  ← Password label
    │ -  input 5                  │  ← Password field
    │ -  button 6                 │  ← Submit button
    └─────────────────────────────┘

    [Updates in real-time as each processes]

AI: ✅ Successfully created 6 elements in 18.4s!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Performance Considerations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;300ms delay between shape generations (respects API rate limits)&lt;/li&gt;
&lt;li&gt;Messages batch update in React state (no excessive re-renders)&lt;/li&gt;
&lt;li&gt;Plan message updates efficiently through targeted state changes&lt;/li&gt;
&lt;li&gt;LocalStorage saves full pipeline for recovery if needed&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Future Enhancements
&lt;/h2&gt;

&lt;p&gt;Possible improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add time estimates for each shape&lt;/li&gt;
&lt;li&gt;Show preview thumbnails in plan&lt;/li&gt;
&lt;li&gt;Allow clicking plan rows to highlight on canvas&lt;/li&gt;
&lt;li&gt;Add "retry failed" button for individual shapes&lt;/li&gt;
&lt;li&gt;Export generation report as PDF/JSON&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>flashfx</category>
      <category>editor</category>
      <category>dev</category>
    </item>
    <item>
      <title>Ai Implementation Analysis for FlashFX</title>
      <dc:creator>Gabriele Bolognese</dc:creator>
      <pubDate>Mon, 05 Jan 2026 12:13:25 +0000</pubDate>
      <link>https://dev.to/therealgabry/ai-implementation-analysis-for-flashfx-5fe7</link>
      <guid>https://dev.to/therealgabry/ai-implementation-analysis-for-flashfx-5fe7</guid>
      <description>&lt;h1&gt;
  
  
  AI Implementation Analysis - FlashFX
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Current Implementation Status
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ✅ Working Features
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Chat Interface&lt;/strong&gt;: Fully functional AI chat in Layers Panel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static Element Generation&lt;/strong&gt;: Creates shapes, buttons, chat bubbles, frames&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI API Integration&lt;/strong&gt;: Complete setup (but using demo mode)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Element Placement&lt;/strong&gt;: Smart positioning and unique ID generation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch Operations&lt;/strong&gt;: Multiple elements added efficiently&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  ❌ Missing Animation Features
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No Animation Generation&lt;/strong&gt;: AI only creates static elements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Keyframe Creation&lt;/strong&gt;: No animation sequences generated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Timeline Integration&lt;/strong&gt;: Generated elements don't include animation data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Motion Properties&lt;/strong&gt;: No velocity, easing, or transition data&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Current AI Flow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Input → Processing → Output
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Prompt → Keyword Analysis → Shape Config → DesignElement → Canvas
    ↓              ↓               ↓            ↓           ↓
"Create a      Extract         Generate     Convert to   Add to
button"        keywords        button       standard     canvas
               ("button")      config       format       state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Current Shape Generation Logic
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From AIChatTab.tsx&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;shapes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Primary Button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Secondary Button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Animation System Gaps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. No Animation Object Creation
&lt;/h3&gt;

&lt;p&gt;The AI doesn't create Animation objects like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Animation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;opacity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;transform&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scale&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rotate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;elementId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;keyframes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;easing&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. No Timeline Data
&lt;/h3&gt;

&lt;p&gt;Generated elements lack animation timeline data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Missing from AI generation:&lt;/span&gt;
&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;animations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fade-in&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;opacity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;keyframes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. No Motion Graphics Properties
&lt;/h3&gt;

&lt;p&gt;Missing advanced animation properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Velocity curves&lt;/li&gt;
&lt;li&gt;Easing functions&lt;/li&gt;
&lt;li&gt;Chain animations&lt;/li&gt;
&lt;li&gt;Interaction triggers&lt;/li&gt;
&lt;li&gt;Loop configurations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Recommendations for Animation Support
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Extend AI Prompt Analysis
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Enhanced prompt analysis needed:&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;animate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;motion&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Generate animation data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bounce&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fade&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Add specific animation types&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Animation Schema Integration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AI should generate both elements AND animations:&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;generatedElements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;animations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;generatedAnimations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;timeline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;timelineData&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. OpenAI Assistant Training
&lt;/h3&gt;

&lt;p&gt;The configured assistants need training for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Animation sequence generation&lt;/li&gt;
&lt;li&gt;Keyframe creation&lt;/li&gt;
&lt;li&gt;Easing curve selection&lt;/li&gt;
&lt;li&gt;Timeline coordination&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical Debt
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Current Issues
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;FlashFX_AI_Component.tsx&lt;/strong&gt;: Deprecated file returning null&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demo vs Production&lt;/strong&gt;: Mixed demo/production code paths&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hardcoded API Keys&lt;/strong&gt;: Should use environment variables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Error Handling&lt;/strong&gt;: Limited error recovery for API failures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Animation Validation&lt;/strong&gt;: No checks for animation conflicts&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Architecture Concerns
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Single Responsibility&lt;/strong&gt;: AIChatTab does both UI and AI logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State Management&lt;/strong&gt;: No centralized animation state management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type Safety&lt;/strong&gt;: Animation types not enforced in AI generation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt;: No optimization for large animation sequences&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The current AI implementation successfully generates static UI elements but completely lacks animation generation capabilities. To achieve true "motion graphics design tool" functionality, the AI system needs significant extension to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate animation objects alongside design elements&lt;/li&gt;
&lt;li&gt;Create keyframe sequences and timelines&lt;/li&gt;
&lt;li&gt;Handle complex motion graphics scenarios&lt;/li&gt;
&lt;li&gt;Integrate with a proper animation engine&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The foundation is solid, but animation support requires substantial additional development.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devjournal</category>
      <category>openai</category>
      <category>ui</category>
    </item>
  </channel>
</rss>
