diff --git a/src/index.html b/src/index.html index b782601..26e2ed1 100644 --- a/src/index.html +++ b/src/index.html @@ -2050,24 +2050,38 @@ .bookStarterCardDesc { font-size: .76rem; color: var(--ink); margin-top: 8px; line-height: 1.35; } /* Phase J3: in / out ports for threaded text frames. The ports - are small square handles at the corners of every text-frame - layer; clicking out → in links the frames so chapter text - flows from one to the next. Colour signals state: + sit OUTSIDE the layer's bounding box on the top and bottom + edges (centered) so they don't collide with the 8 corner / + edge / rotation resize handles or the per-target controls + layered on the frame. Click out → click another in to thread. + Colour signals state: empty (slate) → unlinked linked (green) → part of a thread overset (red) → end of thread with overflow text */ .bookFramePort { position: absolute; - width: 12px; height: 12px; + width: 14px; height: 14px; background: #fff; border: 1.5px solid #475569; box-sizing: border-box; cursor: crosshair; - z-index: 50; + z-index: 60; pointer-events: auto; + left: 50%; + transform: translateX(-50%); } - .bookFramePort.in { top: -7px; left: -7px; border-radius: 0; } - .bookFramePort.out { bottom: -7px; right: -7px; border-radius: 0; } + .bookFramePort.in { top: -22px; border-radius: 0 0 7px 7px; } + .bookFramePort.out { bottom: -22px; border-radius: 7px 7px 0 0; } + .bookFramePort::before { + content: ''; + position: absolute; + left: 50%; + width: 0; height: 8px; + border-left: 1.5px dashed #475569; + transform: translateX(-50%); + } + .bookFramePort.in::before { top: 100%; } + .bookFramePort.out::before { bottom: 100%; } .bookFramePort.linked { background: #16a34a; border-color: #15803d; } .bookFramePort.overset { background: #dc2626; border-color: #991b1b; } .bookFramePort:hover { box-shadow: 0 0 0 2px rgba(96,165,250,.6); } @@ -2103,13 +2117,14 @@ outline-color: rgba(59,130,246,.85); } .bookFrameIdBadge { - position: absolute; bottom: 4px; left: 4px; - font-size: .58rem; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; - color: rgba(71,85,105,.65); - background: rgba(255,255,255,.7); - padding: 1px 4px; border-radius: 2px; + position: absolute; top: -16px; right: -2px; + font-size: .56rem; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + color: rgba(71,85,105,.55); + background: rgba(255,255,255,.85); + padding: 0 4px; border-radius: 2px; pointer-events: none; user-select: none; + z-index: 7; } /* J5: source-chapter chip on threaded text frames. Shows which @@ -23076,6 +23091,11 @@ const linkedIn = !!incoming; const isLoadingSource = _bookPortLoading?.layerId === layer.id; + // J3.b fix: stop pointerdown on the port from bubbling to the + // freeLayer's startLayerDrag handler — otherwise the drag + // starts and the click never fires. + const stopPointerDown = (ev) => { ev.stopPropagation(); }; + const inPort = document.createElement('div'); inPort.className = 'bookFramePort in' + (linkedIn ? ' linked' : ''); inPort.title = linkedIn @@ -23085,6 +23105,8 @@ : 'In port — empty. Click an "out" port elsewhere first to load a thread, then click this.'); inPort.dataset.framePort = 'in'; inPort.dataset.frameId = myFrameId; + inPort.addEventListener('pointerdown', stopPointerDown); + inPort.addEventListener('mousedown', stopPointerDown); inPort.addEventListener('click', (ev) => _bookPortOnClick(layer, 'in', ev)); if (linkedIn || linkedOut) { inPort.addEventListener('mouseenter', () => _bookHighlightThread(layer)); @@ -23103,6 +23125,8 @@ : 'Out port — text ends here. Click to load and then click another frame\'s in port to link.'); outPort.dataset.framePort = 'out'; outPort.dataset.frameId = myFrameId; + outPort.addEventListener('pointerdown', stopPointerDown); + outPort.addEventListener('mousedown', stopPointerDown); outPort.addEventListener('click', (ev) => _bookPortOnClick(layer, 'out', ev)); if (linkedIn || linkedOut) { outPort.addEventListener('mouseenter', () => _bookHighlightThread(layer)); @@ -23140,6 +23164,11 @@ lab.textContent = `📄 ${title}${positionTag}`; lab.title = `Chapter placed on this thread's head frame. ${chain.length > 1 ? `This is frame ${myIndex + 1} of ${chain.length} in the chain.` : 'Solo frame — link more frames with the out port to extend the thread.'}`; chip.appendChild(lab); + // J3.b fix: same pointerdown shield as ports — the chip + // sits on top of the freeLayer, so its events would + // otherwise start a drag. + chip.addEventListener('pointerdown', (ev) => ev.stopPropagation()); + chip.addEventListener('mousedown', (ev) => ev.stopPropagation()); // Only the head shows the clear (X) — clearing on a // mid-chain frame would be ambiguous. if (layer === head) { @@ -23147,6 +23176,8 @@ clearBtn.className = 'bookFrameSourceClear'; clearBtn.textContent = '×'; clearBtn.title = 'Unplace this chapter from the thread.'; + clearBtn.addEventListener('pointerdown', (ev) => ev.stopPropagation()); + clearBtn.addEventListener('mousedown', (ev) => ev.stopPropagation()); clearBtn.addEventListener('click', (ev) => { ev.preventDefault(); ev.stopPropagation();