@@ -299,6 +299,17 @@ export default class extends Controller {
299299 }
300300
301301 this . applyZoom ( ) ;
302+ if ( this . hasCanvasTarget ) this . centerScroll ( this . canvasTarget ) ;
303+ }
304+
305+ // Center the horizontal scroll so a wide treebank opens on its middle rather
306+ // than one edge. rAF lets layout settle after the SVG width is applied.
307+ centerScroll ( scroller ) {
308+ if ( ! scroller ) return ;
309+ requestAnimationFrame ( ( ) => {
310+ const overflow = scroller . scrollWidth - scroller . clientWidth ;
311+ if ( overflow > 0 ) scroller . scrollLeft = overflow / 2 ;
312+ } ) ;
302313 }
303314
304315 sentenceMaxLevel ( sentence ) {
@@ -435,6 +446,7 @@ export default class extends Controller {
435446 svgContainer . innerHTML = "" ;
436447 svgContainer . appendChild ( svg ) ;
437448 this . applyZoom ( ) ;
449+ this . centerScroll ( svgContainer ) ;
438450 }
439451
440452 async toggleCard ( event ) {
@@ -551,6 +563,7 @@ class TreebankRenderer {
551563 this . phraseLevelHeight = 0 ; // total height of the phrase-level rows (set in render)
552564 this . PHRASE_LEVEL_STEP = 70 ; // vertical distance between consecutive phrase levels
553565 this . PHRASE_EDGE_ARC_HEIGHT = 50 ; // how high a phrase (constituency) edge arcs above its band
566+ this . PHRASE_EDGE_FOOT_LIFT = 28 ; // float phrase-edge arcs above the phrase nodes so they clear the phrase surface text drawn above each dot
554567 this . phraseTopGap = 0 ; // extra headroom so phrase-edge arcs clear the banner (set in render)
555568 this . tokenRowHeight = 90 ; // bottom row: location + arabic + POS label
556569 this . arcBandHeight = 0 ;
@@ -576,7 +589,7 @@ class TreebankRenderer {
576589 // Only reserve top headroom when phrase edges exist — otherwise their arcs
577590 // (which rise PHRASE_EDGE_ARC_HEIGHT above the top phrase row) collide with
578591 // the banner and overlap the text.
579- this . phraseTopGap = phraseEdges . length > 0 ? ( this . PHRASE_EDGE_ARC_HEIGHT + 24 ) : 0 ;
592+ this . phraseTopGap = phraseEdges . length > 0 ? ( this . PHRASE_EDGE_ARC_HEIGHT + this . PHRASE_EDGE_FOOT_LIFT + 24 ) : 0 ;
580593
581594 const tempSvg = document . createElementNS ( this . SVG_NS , "svg" ) ;
582595 tempSvg . style . position = "absolute" ;
@@ -616,7 +629,6 @@ class TreebankRenderer {
616629 svg . setAttribute ( "xmlns" , this . SVG_NS ) ;
617630 svg . setAttribute ( "data-base-width" , svgW ) ;
618631 svg . setAttribute ( "data-base-height" , svgH ) ;
619- svg . style . maxWidth = "100%" ;
620632 svg . style . display = "block" ;
621633
622634 this . drawBanner ( svg , svgW , bannerTextW ) ;
@@ -1081,11 +1093,16 @@ class TreebankRenderer {
10811093
10821094 if ( fromX === null || toX === null || fromY === null || toY === null ) continue ;
10831095
1084- const bandY = Math . min ( fromY , toY ) ;
1096+ // Float the drawn arc a fixed amount above the two node feet so it clears
1097+ // the phrase surface text. The nodes/blocks themselves stay on their band
1098+ // (kept inline) — only the arc path lifts.
1099+ const fromFootY = fromY - this . PHRASE_EDGE_FOOT_LIFT ;
1100+ const toFootY = toY - this . PHRASE_EDGE_FOOT_LIFT ;
1101+ const bandY = Math . min ( fromFootY , toFootY ) ;
10851102 const apexAbsY = bandY - ARC_HEIGHT ;
10861103
10871104 const path = document . createElementNS ( this . SVG_NS , "path" ) ;
1088- const d = `M ${ fromX } ,${ fromY } C ${ fromX } ,${ apexAbsY } ${ toX } ,${ apexAbsY } ${ toX } ,${ toY } ` ;
1105+ const d = `M ${ fromX } ,${ fromFootY } C ${ fromX } ,${ apexAbsY } ${ toX } ,${ apexAbsY } ${ toX } ,${ toFootY } ` ;
10891106 path . setAttribute ( "d" , d ) ;
10901107 path . setAttribute ( "fill" , "none" ) ;
10911108 path . setAttribute ( "stroke-width" , "1.5" ) ;
0 commit comments