diff --git a/apps/editor/components/tools/ceiling/ceiling-tool.tsx b/apps/editor/components/tools/ceiling/ceiling-tool.tsx
index 9736185b..0af3f984 100644
--- a/apps/editor/components/tools/ceiling/ceiling-tool.tsx
+++ b/apps/editor/components/tools/ceiling/ceiling-tool.tsx
@@ -2,6 +2,7 @@ import { CeilingNode, emitter, type GridEvent, type LevelNode, useScene } from '
import { useViewer } from '@pascal-app/viewer'
import { useEffect, useMemo, useRef, useState } from 'react'
import { BufferGeometry, DoubleSide, type Line, type Mesh, Shape, Vector3 } from 'three'
+import { mix, positionLocal } from 'three/tsl'
import { sfxEmitter } from '@/lib/sfx-bus'
import { CursorSphere } from '../shared/cursor-sphere'
@@ -69,6 +70,7 @@ export const CeilingTool: React.FC = () => {
const gridCursorRef = useRef(null)
const mainLineRef = useRef(null!)
const closingLineRef = useRef(null!)
+ const verticalLineRef = useRef(null!)
const currentLevelId = useViewer((state) => state.selection.levelId)
const setSelection = useViewer((state) => state.setSelection)
@@ -79,6 +81,18 @@ export const CeilingTool: React.FC = () => {
const previousSnappedPointRef = useRef<[number, number] | null>(null)
const shiftPressed = useRef(false)
+ // Static geometry: local y goes 0 (grid) → H (ceiling), mesh is positioned at gridY
+ const verticalGeo = useMemo(
+ () => new BufferGeometry().setFromPoints([new Vector3(0, 0, 0), new Vector3(0, CEILING_HEIGHT - GRID_OFFSET, 0)]),
+ [],
+ )
+
+ // opacityNode: positionLocal.y is 0 at grid, H at ceiling → fade from 0.6 to 0
+ const gradientOpacityNode = useMemo(
+ () => mix(0.6, 0.0, positionLocal.y.div(CEILING_HEIGHT - GRID_OFFSET).clamp()),
+ [],
+ )
+
// Update cursor position and lines on grid move
useEffect(() => {
if (!currentLevelId) return
@@ -117,6 +131,10 @@ export const CeilingTool: React.FC = () => {
previousSnappedPointRef.current = displayPoint
cursorRef.current.position.set(displayPoint[0], ceilingY, displayPoint[1])
gridCursorRef.current.position.set(displayPoint[0], gridY, displayPoint[1])
+
+ if (verticalLineRef.current) {
+ verticalLineRef.current.position.set(displayPoint[0], gridY, displayPoint[1])
+ }
}
const onGridClick = (_event: GridEvent) => {
@@ -262,6 +280,12 @@ export const CeilingTool: React.FC = () => {
+ {/* Vertical connector: local y=0 at grid, y=H at ceiling; position.y set to gridY on move */}
+ {/* @ts-ignore */}
+
+
+
+
{/* Preview fill */}
{previewShape && (