diff --git a/apps/editor/app/viewer/[id]/page.tsx b/apps/editor/app/viewer/[id]/page.tsx
index 420fc1e5..bd7a541f 100644
--- a/apps/editor/app/viewer/[id]/page.tsx
+++ b/apps/editor/app/viewer/[id]/page.tsx
@@ -6,6 +6,7 @@ import { useParams } from 'next/navigation'
import { useEffect, useState } from 'react'
import { ViewerCameraControls } from './viewer-camera-controls'
import { ViewerOverlay } from './viewer-overlay'
+import { ViewerZoneSystem } from './viewer-zone-system'
export default function ViewerPage() {
const params = useParams()
@@ -56,7 +57,10 @@ export default function ViewerPage() {
+ {/* Custom Camera Controls */}
+ {/* Custom Zone System */}
+
)
diff --git a/apps/editor/app/viewer/[id]/viewer-zone-system.tsx b/apps/editor/app/viewer/[id]/viewer-zone-system.tsx
new file mode 100644
index 00000000..7ab68dc0
--- /dev/null
+++ b/apps/editor/app/viewer/[id]/viewer-zone-system.tsx
@@ -0,0 +1,37 @@
+'use client'
+
+import { type ZoneNode, sceneRegistry, useScene } from '@pascal-app/core'
+import { useViewer } from '@pascal-app/viewer'
+import { useFrame } from '@react-three/fiber'
+
+export const ViewerZoneSystem = () => {
+ useFrame(() => {
+ const { levelId, zoneId } = useViewer.getState().selection
+ const nodes = useScene.getState().nodes
+
+ sceneRegistry.byType.zone.forEach((id) => {
+ const obj = sceneRegistry.nodes.get(id)
+ if (!obj) return
+
+ const zone = nodes[id as ZoneNode['id']] as ZoneNode | undefined
+ if (!zone) return
+
+ // Hide zones if:
+ // 1. No level is selected
+ // 2. Zone is not on the selected level
+ // 3. A zone is already selected (hide all zones to show zone contents)
+ const isOnSelectedLevel = zone.parentId === levelId
+ const shouldShow = levelId && isOnSelectedLevel && !zoneId
+
+ obj.visible = shouldShow
+
+ // Also hide the label
+ const label = obj.getObjectByName('label')
+ if (label) {
+ label.position.y = shouldShow ? 1 : -1000
+ }
+ })
+ })
+
+ return null
+}