[{"data":1,"prerenderedAt":815},["ShallowReactive",2],{"$fANwwio7MqknZgFiHZsdzOgZ1DiGSM1pv-8PkXC-hcxM":3},{"newThing":4,"project":5,"cycle":9,"sections":22,"threads":769,"otherDocs":770,"documents":771,"images":772,"archiveItems":812,"links":813},"Flat-overview → 3D-detail camera morph as the focus signal",{"id":6,"label":7,"liveBase":8},"qmap","Qmap",null,{"id":10,"num":11,"dir":12,"name":13,"title":14,"focus":8,"status":15,"statusNote":8,"created":16,"completed":8,"mode":8,"report":8,"recordless":17,"thumbnail":18,"imageCount":19,"artifactCount":20,"lastActivity":21,"hasPickup":17,"hasInitialPrompt":17},"c7",7,"c7.context-morphing","context-morphing","camera-focus","archived","2026-03-23",false,"\u002Fcycles\u002Fqmap\u002Fc7-apps-criticality.png",9,0,"2026-08-15",[23,62,91,127,178,388,426,475,573,649],{"key":24,"title":25,"empty":17,"ast":26},"objective","Goal",{"type":27,"children":28},"root",[29,37,58],{"type":30,"tag":31,"props":32,"children":33},"element","p",{},[34],{"type":35,"value":36},"text","Camera control experiments:",{"type":30,"tag":38,"props":39,"children":40},"ol",{},[41,47],{"type":30,"tag":42,"props":43,"children":44},"li",{},[45],{"type":35,"value":46},"Zoom and pan to frame selected actor and its connections",{"type":30,"tag":42,"props":48,"children":49},{},[50,56],{"type":30,"tag":51,"props":52,"children":53},"strong",{},[54],{"type":35,"value":55},"Camera angle slider",{"type":35,"value":57}," - transition from isometric to top-down view",{"type":30,"tag":59,"props":60,"children":61},"hr",{},[],{"key":63,"title":64,"empty":17,"ast":65},"outcome","Outcome",{"type":27,"children":66},[67,72,81],{"type":30,"tag":31,"props":68,"children":69},{},[70],{"type":35,"value":71},"Camera framing system complete. Click actor → camera animates to frame actor and connections. Unfocus → camera returns to initial view. Integrates cleanly with grouping transitions (re-centers if selection active). Core CameraControls now properly supports arbitrary lookAt targets for future pan\u002Fnavigation features.",{"type":30,"tag":31,"props":73,"children":74},{},[75,79],{"type":30,"tag":51,"props":76,"children":77},{},[78],{"type":35,"value":55},{"type":35,"value":80}," allows exploration of different viewing perspectives - useful for understanding spatial relationships from above vs the standard isometric view.",{"type":30,"tag":31,"props":82,"children":83},{},[84,89],{"type":30,"tag":51,"props":85,"children":86},{},[87],{"type":35,"value":88},"Rotation controls",{"type":35,"value":90}," provide quick access to cardinal viewing directions without drag interaction - useful for consistent screenshots or comparing layouts from fixed angles.",{"key":92,"title":93,"empty":17,"ast":94},"extra","Focus",{"type":27,"children":95},[96,124],{"type":30,"tag":97,"props":98,"children":99},"ul",{},[100,105,110,115],{"type":30,"tag":42,"props":101,"children":102},{},[103],{"type":35,"value":104},"Automatic camera framing when clicking an actor",{"type":30,"tag":42,"props":106,"children":107},{},[108],{"type":35,"value":109},"Smooth animated transitions for camera position and zoom",{"type":30,"tag":42,"props":111,"children":112},{},[113],{"type":35,"value":114},"Reset to initial view on background click",{"type":30,"tag":42,"props":116,"children":117},{},[118,122],{"type":30,"tag":51,"props":119,"children":120},{},[121],{"type":35,"value":55},{"type":35,"value":123}," (0% isometric → 100% top-down)",{"type":30,"tag":59,"props":125,"children":126},{},[],{"key":92,"title":128,"empty":17,"ast":129},"Approach",{"type":27,"children":130},[131,175],{"type":30,"tag":38,"props":132,"children":133},{},[134,162],{"type":30,"tag":42,"props":135,"children":136},{},[137,139],{"type":35,"value":138},"When actor is clicked:",{"type":30,"tag":97,"props":140,"children":141},{},[142,147,152,157],{"type":30,"tag":42,"props":143,"children":144},{},[145],{"type":35,"value":146},"Collect positions of clicked actor + all connected actors",{"type":30,"tag":42,"props":148,"children":149},{},[150],{"type":35,"value":151},"Calculate bounding box of those positions",{"type":30,"tag":42,"props":153,"children":154},{},[155],{"type":35,"value":156},"Compute frustum size needed to frame the bounding box",{"type":30,"tag":42,"props":158,"children":159},{},[160],{"type":35,"value":161},"Animate camera to center on and zoom to fit",{"type":30,"tag":42,"props":163,"children":164},{},[165,167],{"type":35,"value":166},"When background is clicked:",{"type":30,"tag":97,"props":168,"children":169},{},[170],{"type":30,"tag":42,"props":171,"children":172},{},[173],{"type":35,"value":174},"Animate camera back to initial state (captured on scene init)",{"type":30,"tag":59,"props":176,"children":177},{},[],{"key":92,"title":179,"empty":17,"ast":180},"Key Files",{"type":27,"children":181},[182,196,243,255,301,312,385],{"type":30,"tag":183,"props":184,"children":186},"h3",{"id":185},"new-sharedcameraanimationjs",[187,189],{"type":35,"value":188},"New: ",{"type":30,"tag":190,"props":191,"children":193},"code",{"className":192},[],[194],{"type":35,"value":195},"shared\u002FcameraAnimation.js",{"type":30,"tag":97,"props":197,"children":198},{},[199,210,221,232],{"type":30,"tag":42,"props":200,"children":201},{},[202,208],{"type":30,"tag":190,"props":203,"children":205},{"className":204},[],[206],{"type":35,"value":207},"calculateFocusBounds(positions, padding)",{"type":35,"value":209}," - Bounding box from actor positions",{"type":30,"tag":42,"props":211,"children":212},{},[213,219],{"type":30,"tag":190,"props":214,"children":216},{"className":215},[],[217],{"type":35,"value":218},"frustumSizeForBounds(bounds, aspect, padding)",{"type":35,"value":220}," - Required frustum to frame bounds",{"type":30,"tag":42,"props":222,"children":223},{},[224,230],{"type":30,"tag":190,"props":225,"children":227},{"className":226},[],[228],{"type":35,"value":229},"animateCameraFocus(camera, controls, config, options)",{"type":35,"value":231}," - Animated camera transition",{"type":30,"tag":42,"props":233,"children":234},{},[235,241],{"type":30,"tag":190,"props":236,"children":238},{"className":237},[],[239],{"type":35,"value":240},"captureCameraState(camera, controls)",{"type":35,"value":242}," - Snapshot camera state for reset",{"type":30,"tag":183,"props":244,"children":246},{"id":245},"modified-srccorecameracontrolsjs",[247,249],{"type":35,"value":248},"Modified: ",{"type":30,"tag":190,"props":250,"children":252},{"className":251},[],[253],{"type":35,"value":254},"src\u002Fcore\u002FCameraControls.js",{"type":30,"tag":97,"props":256,"children":257},{},[258,271,290],{"type":30,"tag":42,"props":259,"children":260},{},[261,263,269],{"type":35,"value":262},"Added ",{"type":30,"tag":190,"props":264,"children":266},{"className":265},[],[267],{"type":35,"value":268},"target",{"type":35,"value":270}," property (was hardcoded to origin)",{"type":30,"tag":42,"props":272,"children":273},{},[274,280,282,288],{"type":30,"tag":190,"props":275,"children":277},{"className":276},[],[278],{"type":35,"value":279},"setTarget(target)",{"type":35,"value":281}," \u002F ",{"type":30,"tag":190,"props":283,"children":285},{"className":284},[],[286],{"type":35,"value":287},"getTarget()",{"type":35,"value":289}," methods",{"type":30,"tag":42,"props":291,"children":292},{},[293,299],{"type":30,"tag":190,"props":294,"children":296},{"className":295},[],[297],{"type":35,"value":298},"rotateY()",{"type":35,"value":300}," now respects target for proper rotation around focus point",{"type":30,"tag":183,"props":302,"children":304},{"id":303},"modified-experimentsappvue",[305,306],{"type":35,"value":248},{"type":30,"tag":190,"props":307,"children":309},{"className":308},[],[310],{"type":35,"value":311},"ExperimentsApp.vue",{"type":30,"tag":97,"props":313,"children":314},{},[315,326,337,348,367],{"type":30,"tag":42,"props":316,"children":317},{},[318,324],{"type":30,"tag":190,"props":319,"children":321},{"className":320},[],[322],{"type":35,"value":323},"initialCameraState",{"type":35,"value":325}," ref - Stores initial camera state",{"type":30,"tag":42,"props":327,"children":328},{},[329,335],{"type":30,"tag":190,"props":330,"children":332},{"className":331},[],[333],{"type":35,"value":334},"focusCameraOnPositions(positions)",{"type":35,"value":336}," - Focus camera on actor positions",{"type":30,"tag":42,"props":338,"children":339},{},[340,346],{"type":30,"tag":190,"props":341,"children":343},{"className":342},[],[344],{"type":35,"value":345},"resetCameraToInitial()",{"type":35,"value":347}," - Return camera to initial view",{"type":30,"tag":42,"props":349,"children":350},{},[351,357,359,365],{"type":30,"tag":190,"props":352,"children":354},{"className":353},[],[355],{"type":35,"value":356},"selectActor()",{"type":35,"value":358}," - Now calls ",{"type":30,"tag":190,"props":360,"children":362},{"className":361},[],[363],{"type":35,"value":364},"focusCameraOnPositions",{"type":35,"value":366}," after highlighting",{"type":30,"tag":42,"props":368,"children":369},{},[370,376,377,383],{"type":30,"tag":190,"props":371,"children":373},{"className":372},[],[374],{"type":35,"value":375},"clearSelection()",{"type":35,"value":358},{"type":30,"tag":190,"props":378,"children":380},{"className":379},[],[381],{"type":35,"value":382},"resetCameraToInitial",{"type":35,"value":384}," after reset",{"type":30,"tag":59,"props":386,"children":387},{},[],{"key":92,"title":389,"empty":17,"ast":390},"Animation Config",{"type":27,"children":391},[392,405,418,421],{"type":30,"tag":393,"props":394,"children":400},"pre",{"className":395,"code":396,"language":397,"meta":398,"style":399},"language-javascript","{\n  focusDuration: 500,    \u002F\u002F ms for focus animation\n  resetDuration: 500,    \u002F\u002F ms for reset animation\n  minFrustumSize: 4,     \u002F\u002F Don't zoom in more than this\n  focusPadding: 2.0,     \u002F\u002F Extra padding around framed content\n  actorPadding: 1.0,     \u002F\u002F Padding around actor bounding box\n}\n","javascript","","undefined",[401],{"type":30,"tag":190,"props":402,"children":403},{"__ignoreMap":398},[404],{"type":35,"value":396},{"type":30,"tag":31,"props":406,"children":407},{},[408,410,416],{"type":35,"value":409},"Uses ",{"type":30,"tag":190,"props":411,"children":413},{"className":412},[],[414],{"type":35,"value":415},"easeOutCubic",{"type":35,"value":417}," for quick start, gentle landing.",{"type":30,"tag":59,"props":419,"children":420},{},[],{"type":30,"tag":422,"props":423,"children":424},"style",{},[425],{"type":35,"value":398},{"key":92,"title":427,"empty":17,"ast":428},"Notes",{"type":27,"children":429},[430,472],{"type":30,"tag":97,"props":431,"children":432},{},[433,462,467],{"type":30,"tag":42,"props":434,"children":435},{},[436,438,444,446,452,454,460],{"type":35,"value":437},"Reuses animation system from ",{"type":30,"tag":190,"props":439,"children":441},{"className":440},[],[442],{"type":35,"value":443},"morphAnimation.js",{"type":35,"value":445}," (",{"type":30,"tag":190,"props":447,"children":449},{"className":448},[],[450],{"type":35,"value":451},"animateValue",{"type":35,"value":453},", ",{"type":30,"tag":190,"props":455,"children":457},{"className":456},[],[458],{"type":35,"value":459},"lerp",{"type":35,"value":461},", easing functions)",{"type":30,"tag":42,"props":463,"children":464},{},[465],{"type":35,"value":466},"Camera maintains isometric angle during pan by offsetting position relative to target",{"type":30,"tag":42,"props":468,"children":469},{},[470],{"type":35,"value":471},"Frustum size clamped to CameraControls min\u002Fmax zoom constraints",{"type":30,"tag":59,"props":473,"children":474},{},[],{"key":92,"title":476,"empty":17,"ast":477},"Key Learnings",{"type":27,"children":478},[479,489,497,535,553,570],{"type":30,"tag":31,"props":480,"children":481},{},[482,487],{"type":30,"tag":51,"props":483,"children":484},{},[485],{"type":35,"value":486},"Isometric pan without rotation:",{"type":35,"value":488}," Store camera-to-target offset once, then apply to new targets. Moving both camera position and lookAt target by the same delta preserves the viewing angle.",{"type":30,"tag":31,"props":490,"children":491},{},[492],{"type":30,"tag":51,"props":493,"children":494},{},[495],{"type":35,"value":496},"Orthographic framing math:",{"type":30,"tag":97,"props":498,"children":499},{},[500,511,522],{"type":30,"tag":42,"props":501,"children":502},{},[503,509],{"type":30,"tag":190,"props":504,"children":506},{"className":505},[],[507],{"type":35,"value":508},"frustumSize",{"type":35,"value":510}," = vertical extent (top to bottom)",{"type":30,"tag":42,"props":512,"children":513},{},[514,516],{"type":35,"value":515},"Horizontal extent = ",{"type":30,"tag":190,"props":517,"children":519},{"className":518},[],[520],{"type":35,"value":521},"frustumSize × aspect",{"type":30,"tag":42,"props":523,"children":524},{},[525,527,533],{"type":35,"value":526},"Frame bounds by taking ",{"type":30,"tag":190,"props":528,"children":530},{"className":529},[],[531],{"type":35,"value":532},"max(width\u002Faspect, depth)",{"type":35,"value":534}," to ensure both fit",{"type":30,"tag":31,"props":536,"children":537},{},[538,543,545,551],{"type":30,"tag":51,"props":539,"children":540},{},[541],{"type":35,"value":542},"State capture\u002Frestore pattern:",{"type":35,"value":544}," The same ",{"type":30,"tag":190,"props":546,"children":548},{"className":547},[],[549],{"type":35,"value":550},"animateCameraFocus()",{"type":35,"value":552}," function handles both focus-in and reset—just pass different target configurations (captured initial state vs computed bounds).",{"type":30,"tag":31,"props":554,"children":555},{},[556,561,563,568],{"type":30,"tag":51,"props":557,"children":558},{},[559],{"type":35,"value":560},"CameraControls target property:",{"type":35,"value":562}," Extending controls with a mutable ",{"type":30,"tag":190,"props":564,"children":566},{"className":565},[],[567],{"type":35,"value":268},{"type":35,"value":569}," Vector3 enables proper rotation around arbitrary points (not just origin) and panning without losing isometric angle.",{"type":30,"tag":59,"props":571,"children":572},{},[],{"key":92,"title":574,"empty":17,"ast":575},"Camera Angle Slider",{"type":27,"children":576},[577,583,588,611,616,634,642,645],{"type":30,"tag":183,"props":578,"children":580},{"id":579},"implementation",[581],{"type":35,"value":582},"Implementation",{"type":30,"tag":31,"props":584,"children":585},{},[586],{"type":35,"value":587},"Uses spherical coordinates to interpolate camera position:",{"type":30,"tag":97,"props":589,"children":590},{},[591,601],{"type":30,"tag":42,"props":592,"children":593},{},[594,599],{"type":30,"tag":51,"props":595,"children":596},{},[597],{"type":35,"value":598},"Isometric view (0%):",{"type":35,"value":600}," phi = atan(√2) ≈ 54.7° from y-axis",{"type":30,"tag":42,"props":602,"children":603},{},[604,609],{"type":30,"tag":51,"props":605,"children":606},{},[607],{"type":35,"value":608},"Top-down view (100%):",{"type":35,"value":610}," phi ≈ 0° (nearly straight down)",{"type":30,"tag":31,"props":612,"children":613},{},[614],{"type":35,"value":615},"The slider maintains:",{"type":30,"tag":97,"props":617,"children":618},{},[619,624,629],{"type":30,"tag":42,"props":620,"children":621},{},[622],{"type":35,"value":623},"Current horizontal rotation (theta) - so you can rotate then adjust angle",{"type":30,"tag":42,"props":625,"children":626},{},[627],{"type":35,"value":628},"Current distance from target (radius)",{"type":30,"tag":42,"props":630,"children":631},{},[632],{"type":35,"value":633},"LookAt target position",{"type":30,"tag":393,"props":635,"children":637},{"className":395,"code":636,"language":397,"meta":398,"style":399},"\u002F\u002F Interpolate phi based on slider (0-100)\nconst t = cameraAngle \u002F 100;\nconst phi = isometricPhi * (1 - t) + topDownPhi * t;\n",[638],{"type":30,"tag":190,"props":639,"children":640},{"__ignoreMap":398},[641],{"type":35,"value":636},{"type":30,"tag":59,"props":643,"children":644},{},[],{"type":30,"tag":422,"props":646,"children":647},{},[648],{"type":35,"value":398},{"key":92,"title":650,"empty":17,"ast":651},"Rotation Controls",{"type":27,"children":652},[653,659,669,675,708,712,717,730,738,744,762,765],{"type":30,"tag":183,"props":654,"children":656},{"id":655},"ui-layout",[657],{"type":35,"value":658},"UI Layout",{"type":30,"tag":393,"props":660,"children":664},{"className":661,"code":663,"language":35},[662],"language-text","[-] [Reset Rotation] [+]\n",[665],{"type":30,"tag":190,"props":666,"children":667},{"__ignoreMap":398},[668],{"type":35,"value":663},{"type":30,"tag":183,"props":670,"children":672},{"id":671},"functions",[673],{"type":35,"value":674},"Functions",{"type":30,"tag":97,"props":676,"children":677},{},[678,688,698],{"type":30,"tag":42,"props":679,"children":680},{},[681,686],{"type":30,"tag":51,"props":682,"children":683},{},[684],{"type":35,"value":685},"Reset Rotation",{"type":35,"value":687}," - Sets theta to 0 (camera along +Z axis)",{"type":30,"tag":42,"props":689,"children":690},{},[691,696],{"type":30,"tag":51,"props":692,"children":693},{},[694],{"type":35,"value":695},"Rotate Left (-)",{"type":35,"value":697}," - Rotates 90° counter-clockwise",{"type":30,"tag":42,"props":699,"children":700},{},[701,706],{"type":30,"tag":51,"props":702,"children":703},{},[704],{"type":35,"value":705},"Rotate Right (+)",{"type":35,"value":707}," - Rotates 90° clockwise",{"type":30,"tag":183,"props":709,"children":710},{"id":579},[711],{"type":35,"value":582},{"type":30,"tag":31,"props":713,"children":714},{},[715],{"type":35,"value":716},"All rotation controls use spherical coordinates, preserving:",{"type":30,"tag":97,"props":718,"children":719},{},[720,725],{"type":30,"tag":42,"props":721,"children":722},{},[723],{"type":35,"value":724},"Current phi (vertical angle from slider)",{"type":30,"tag":42,"props":726,"children":727},{},[728],{"type":35,"value":729},"Current radius (zoom distance)",{"type":30,"tag":393,"props":731,"children":733},{"className":395,"code":732,"language":397,"meta":398,"style":399},"function rotateBy(angle) {\n  const spherical = new THREE.Spherical(\n    currentSpherical.radius,\n    currentSpherical.phi,\n    currentSpherical.theta + angle  \u002F\u002F +\u002F- π\u002F2 for 90°\n  );\n  \u002F\u002F ... apply to camera\n}\n",[734],{"type":30,"tag":190,"props":735,"children":736},{"__ignoreMap":398},[737],{"type":35,"value":732},{"type":30,"tag":183,"props":739,"children":741},{"id":740},"design-notes",[742],{"type":35,"value":743},"Design Notes",{"type":30,"tag":97,"props":745,"children":746},{},[747,752,757],{"type":30,"tag":42,"props":748,"children":749},{},[750],{"type":35,"value":751},"Reset goes to theta=0 (not 45°) for predictable \"front view\" alignment",{"type":30,"tag":42,"props":753,"children":754},{},[755],{"type":35,"value":756},"90° increments allow quick exploration of all four cardinal directions",{"type":30,"tag":42,"props":758,"children":759},{},[760],{"type":35,"value":761},"Controls work independently of the angle slider - can rotate at any tilt",{"type":30,"tag":59,"props":763,"children":764},{},[],{"type":30,"tag":422,"props":766,"children":767},{},[768],{"type":35,"value":398},[],[],[],[773,780,784,788,792,795,799,804,808],{"src":774,"kind":775,"order":776,"caption":777,"capturedAt":778,"route":779},"\u002Fcycles\u002Fqmap\u002Fc7-flat.png","hero",46,"Angle 100 — the flat overview end of the morph","2026-08-16T03:06:59.862Z","\u002Fexperiments\u002Fc7.context-morphing\u002F",{"src":781,"kind":775,"order":782,"caption":783,"capturedAt":778,"route":779},"\u002Fcycles\u002Fqmap\u002Fc7-detail.png",47,"Angle 0 — the 3D detail end",{"src":785,"kind":775,"order":786,"caption":787,"capturedAt":778,"route":779},"\u002Fcycles\u002Fqmap\u002Fc7-mid-morph.png",48,"Angle 50 — between contexts",{"src":789,"kind":775,"order":790,"caption":791,"capturedAt":778,"route":779},"\u002Fcycles\u002Fqmap\u002Fc7-offset.png",52,"Focus centering offsets in play",{"src":18,"kind":775,"order":793,"caption":794,"capturedAt":778,"route":779},53,"Apps regrouped by criticality at working angle",{"src":796,"kind":797,"order":798},"\u002Fcycles\u002Fqmap\u002Fc7.outcomes.png","legacy",100,{"src":800,"kind":801,"order":802,"caption":803,"capturedAt":778,"route":779},"\u002Fcycles\u002Fqmap\u002Fc7-rot-45.png","slice",45,"Rotation 45°",{"src":805,"kind":801,"order":806,"caption":807,"capturedAt":778,"route":779},"\u002Fcycles\u002Fqmap\u002Fc7-rot-180.png",180,"Rotation 180° — the far side",{"src":809,"kind":801,"order":810,"caption":811,"capturedAt":778,"route":779},"\u002Fcycles\u002Fqmap\u002Fc7-rot-315.png",315,"Rotation 315°",[],[814],{"route":779,"href":8},1794599443661]