").addClass("match-container");
matchContainer.append(matchDiv);
roundDiv.append(matchContainer);match.serial = serial;
serialMap[`${group}-${serial}`] = {
...match,
group,
column,
serial
};groupMatches[column].push(match);
});columnsContainer.append(roundDiv);
if (columnIndex < columns.length - 1) {
columnsContainer.append($("
").addClass("connector-column"));
}
});groupDiv.append(columnsContainer);
groupScrollWrapper.append(groupDiv);setTimeout(() => {
positionMatches(groupMatches, group);
waitForLayoutAndDraw(serialMap, 20);
}, 100);
}bracketDiv.append(groupScrollWrapper);const observer = new MutationObserver(() => {
const visible = $(".bracket_wrapper:visible").length > 0;
if (visible) {
setTimeout(() => {
waitForLayoutAndDraw(window._lastSerialMap || {}, 20);
}, 100);
}
});observer.observe(document.body, { childList: true, subtree: true });
window.globalSerialMap = serialMap;
}
function positionMatches(groupMatches, group) {
const levels = Object.keys(groupMatches).sort((a, b) => +a - +b);
const matchCenters = {};
const spacing = 200;
let bottomMost = 0;
const totalMatches = Object.values(groupMatches).flat().length;
const isFiveTeamLayout = totalMatches === 4;
const isSixTeamLayout = totalMatches === 5;
const isSevenTeamLayout = totalMatches === 6;
const isTenTeamLayout = totalMatches === 9;
const isNineTeamLayout = totalMatches === 8;
if (isNineTeamLayout) {
const serialPositions = {
1: 200, // Game 1 (Round 1)
2: 100, // Game 2 (Round 2)
3: 300, // Game 3
4: 500, // Game 4
5: 700, // Game 5
6: 200, // Game 6 (Round 3)
7: 600, // Game 7
8: 400 // Game 8 (Final)
};[1, 2, 3, 4, 5, 6, 7, 8].forEach((serial) => {
const $match = $(`#match-serial-${group}-${serial}`).closest(".match-container");
if ($match.length) {
const y = serialPositions[serial];
$match.css({ position: "absolute", top: `${y}px` });
matchCenters[serial] = y + $match.outerHeight() / 2;
bottomMost = Math.max(bottomMost, y + $match.outerHeight());
}
});
// Draw connector lines based on logical flow
drawConnectorLine(group, 1, 2); // Game 1 → Game 2
drawConnectorLine(group, 2, 6); // Game 2 → Game 6
drawConnectorLine(group, 3, 6); // Game 3 → Game 6
drawConnectorLine(group, 4, 7); // Game 4 → Game 7
drawConnectorLine(group, 5, 7); // Game 5 → Game 7
drawConnectorLine(group, 6, 8); // Game 6 → Game 8
drawConnectorLine(group, 7, 8); // Game 7 → Game 8bottomMost = 0;
[1, 2, 3, 4, 5, 6, 7, 8].forEach((serial) => {
const $match = $(`#match-serial-${group}-${serial}`).closest(".match-container");
if ($match.length) {
const y = serialPositions[serial];
$match.css({ position: "absolute", top: `${y}px` });
matchCenters[serial] = y + $match.outerHeight() / 2;
bottomMost = Math.max(bottomMost, y + $match.outerHeight());
}
});// Apply accurate height to container
$(`.group[data-group="${group}"]`).css("height", bottomMost + 80); // 40px padding
return;
}// Ten Team Layout
if (isTenTeamLayout) {
const serialPositions = {
1: 200,
2: 600,
3: 100,
4: 300,
5: 500,
6: 700,
7: 200,
8: 600,
9: 400
};// Loop through matches 1 to 9 to set positions
[1, 2, 3, 4, 5, 6, 7, 8, 9].forEach((serial) => {
const $match = $(`#match-serial-${group}-${serial}`).closest(".match-container");
if ($match.length) {
const y = serialPositions[serial];
$match.css({ position: "absolute", top: `${y}px` });
matchCenters[serial] = y + $match.outerHeight() / 2;
bottomMost = Math.max(bottomMost, y + $match.outerHeight());
}
});// Draw connector lines for Round 1, 2, and 3
drawConnectorLine(group, 1, 3, 'left', 'bottom');
drawConnectorLine(group, 2, 5, 'left', 'bottom');
drawConnectorLine(group, 3, 7, 'right', 'top');
drawConnectorLine(group, 4, 7, 'right', 'bottom');
drawConnectorLine(group, 5, 8, 'right', 'top');
drawConnectorLine(group, 6, 8, 'right', 'bottom');
drawConnectorLine(group, 7, 9, 'right', 'top');
drawConnectorLine(group, 8, 9, 'right', 'bottom');bottomMost = 0;
[1, 2, 3, 4, 5, 6, 7, 8, 9].forEach((serial) => {
const $match = $(`#match-serial-${group}-${serial}`).closest(".match-container");
if ($match.length) {
const y = serialPositions[serial];
$match.css({ position: "absolute", top: `${y}px` });
matchCenters[serial] = y + $match.outerHeight() / 2;
bottomMost = Math.max(bottomMost, y + $match.outerHeight());
}
});
$(`.group[data-group="${group}"]`).css("height", bottomMost + 40); // 40px padding
return;
}
if (isSevenTeamLayout) {
const matchCenters = {};
let bottomMost = 0;// More generous vertical spacing between 1 → 2 → 3 (adjusted for more matches)
const serialPositions = {
1: 0, // Match 1 at the top
2: 180, // Match 2 below Match 1
3: 400, // Match 3 below Match 2
4: 620, // Match 4 below Match 3 (between 1 & 2)
5: 840, // Match 5 below Match 4 (between 2 & 3)
6: 1060, // Match 6 below Match 5 (between 4 & 5)
7: 1280 // Match 7 at the bottom (below Match 6)
};// Position Matches 1–7 (Play-ins)
[1, 2, 3, 4, 5, 6, 7].forEach((serial) => {
const $match = $(`#match-serial-${group}-${serial}`).closest(".match-container");
if ($match.length) {
const y = serialPositions[serial];
$match.css({ position: "absolute", top: `${y}px` });
matchCenters[serial] = y + $match.outerHeight() / 2;
bottomMost = Math.max(bottomMost, y + $match.outerHeight());
}
});
console.log('Height1'+bottomMost);
// Adjust positions for the additional matches if necessary:
// Match 4: between 1 & 2 (Positioned earlier in `serialPositions`)
const $match4 = $(`#match-serial-${group}-4`).closest(".match-container");
if ($match4.length) {
const centerY = (matchCenters[1] + matchCenters[2]) / 2 - $match4.outerHeight() / 2;
$match4.css({ position: "absolute", top: `${centerY}px` });
matchCenters[4] = centerY + $match4.outerHeight() / 2;
bottomMost = Math.max(bottomMost, centerY + $match4.outerHeight());
}// Match 5: between 2 and 3 — center with slight right offset
const $match5 = $(`#match-serial-${group}-5`).closest(".match-container");
if ($match5.length && matchCenters[2] && matchCenters[3]) {
const centerY = (matchCenters[2] + matchCenters[3]) / 2 - $match5.outerHeight() / 2;
const rightOffset = 40; // slight offset to center horizontally
$match5.css({ position: "absolute", top: `${centerY}px` });
matchCenters[5] = centerY + $match5.outerHeight() / 2;
bottomMost = Math.max(bottomMost, centerY + $match5.outerHeight());
}// Match 6: between match 4 and 5, centered to the right
const $match6 = $(`#match-serial-${group}-6`).closest(".match-container");
if ($match6.length && matchCenters[4] && matchCenters[5]) {
const centerY = (matchCenters[4] + matchCenters[5]) / 2 - $match6.outerHeight() / 2;
const rightOffset = 60; // adjust as needed for proper placement
$match6.css({ position: "absolute", top: `${centerY}px` });
matchCenters[6] = centerY + $match6.outerHeight() / 2;
bottomMost = Math.max(bottomMost, centerY + $match6.outerHeight());
}// Match 7: below match 6, at the bottom-most position
const $match7 = $(`#match-serial-${group}-7`).closest(".match-container");
if ($match7.length && matchCenters[6]) {
const centerY = matchCenters[6] + 180; // 180px down from match 6
$match7.css({ position: "absolute", top: `${centerY}px` });
matchCenters[7] = centerY + $match7.outerHeight() / 2;
bottomMost = Math.max(bottomMost, centerY + $match7.outerHeight());
}// Draw connector lines between matches
drawConnectorLine(group, 1, 4);
drawConnectorLine(group, 2, 5);
drawConnectorLine(group, 3, 5);
drawConnectorLine(group, 4, 6);
drawConnectorLine(group, 5, 6);
drawConnectorLine(group, 6, 7); // Adding connector line for Match 7// Set container height based on the bottom-most position
// Recalculate actual bottom-most point after all matches are positioned
bottomMost = 0;
[1, 2, 3, 4, 5, 6, 7].forEach((serial) => {
const $match = $(`#match-serial-${group}-${serial}`).closest(".match-container");
if ($match.length) {
const top = parseFloat($match.css("top")) || 0;
const height = $match.outerHeight() || 0;
bottomMost = Math.max(bottomMost, top + height);
}
});
// Apply accurate height to container
$(`.group[data-group="${group}"]`).css("height", bottomMost + 80); // 40px padding
return;
}
if (isSixTeamLayout) {
const matchCenters = {};
let bottomMost = 0;// Step 1: Position initial matches
const serialPositions = {
1: 0,
2: 100,
3: 200
};// Position matches 1-3
Object.entries(groupMatches).forEach(([level, matches]) => {
matches.forEach((match) => {
const matchId = `match-serial-${group}-${match.serial}`;
const $el = $(`#${matchId}`).closest(".match-container");if (!$el.length) return;const y = serialPositions[match.serial] ?? 0;
$el.css({ position: "absolute", top: `${y}px` });matchCenters[match.serial] = y + $el.outerHeight() / 2;
bottomMost = Math.max(bottomMost, y + $el.outerHeight());
});
});// Match 4: Between 1 and 2
const match4Id = `match-serial-${group}-4`;
const $match4 = $(`#${match4Id}`).closest(".match-container");
if ($match4.length) {
const avgY = (matchCenters[1] + matchCenters[2]) / 2 - $match4.outerHeight() / 2;
$match4.css({ position: "absolute", top: `${avgY}px` });
matchCenters[4] = avgY + $match4.outerHeight() / 2;
bottomMost = Math.max(bottomMost, avgY + $match4.outerHeight());
}// Match 5: Between 3 and 4
const match5Id = `match-serial-${group}-5`;
const $match5 = $(`#${match5Id}`).closest(".match-container");
if ($match5.length) {
const avgY = (matchCenters[3] + matchCenters[4]) / 2 - $match5.outerHeight() / 2;
$match5.css({ position: "absolute", top: `${avgY}px` });
matchCenters[5] = avgY + $match5.outerHeight() / 2;
bottomMost = Math.max(bottomMost, avgY + $match5.outerHeight());
}// Step 4: Draw connector lines
drawConnectorLine(group, 1, 4);
drawConnectorLine(group, 2, 4);
drawConnectorLine(group, 3, 5);
drawConnectorLine(group, 4, 5);// Step 5: Set container height
$(`.group[data-group="${group}"]`).css("height", bottomMost + 80);
}// Helper function
function drawConnectorLine(group, fromSerial, toSerial) {
const fromEl = $(`#match-serial-${group}-${fromSerial}`).closest(".match-container");
const toEl = $(`#match-serial-${group}-${toSerial}`).closest(".match-container");if (!fromEl.length || !toEl.length) {
console.warn(`Cannot draw line: from ${fromSerial} or to ${toSerial} not found.`);
return;
}const $group = $(`.group[data-group="${group}"]`);
const $svg = $group.find("svg.connector-layer");if (!$svg.length) {
console.warn(`Missing SVG layer in group ${group}`);
return;
}const fromOffset = fromEl.position();
const toOffset = toEl.position();const fromX = fromEl.position().left + fromEl.outerWidth();
const fromY = fromOffset.top + fromEl.outerHeight() / 2;
const toX = toEl.position().left;
const toY = toOffset.top + toEl.outerHeight() / 2;const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
line.setAttribute("x1", fromX);
line.setAttribute("y1", fromY);
line.setAttribute("x2", toX);
line.setAttribute("y2", toY);
line.setAttribute("stroke", "#333");
line.setAttribute("stroke-width", "2");$svg.append(line);
}// Positioning for 5-team layout
if (isFiveTeamLayout) {
const serialPositions = {
1: 200, // First round (Play-in)
3: 80, // Semifinal 1 (top)
2: 320, // Semifinal 2 (bottom)
4: 200 // Final (centered between semi 1 and 2)
};Object.entries(groupMatches).forEach(([level, matches]) => {
matches.forEach((match) => {
const matchId = `match-serial-${group}-${match.serial}`;
const $el = $(`#${matchId}`).closest(".match-container");if (!$el.length) return;const y = serialPositions[match.serial] ?? 0;
$el.css({ position: "absolute", top: `${y}px` });matchCenters[match.serial] = y + $el.outerHeight() / 2;
bottomMost = Math.max(bottomMost, y + $el.outerHeight());
});
});$(`.group[data-group="${group}"]`).css("height", bottomMost + 160);
return;
}// Default layout for other brackets
levels.forEach((level, levelIndex) => {
const matches = groupMatches[level];matches.forEach((match, i) => {
const matchId = `match-serial-${group}-${match.serial}`;
const $el = $(`#${matchId}`).closest(".match-container");if (!$el.length) return;if (levelIndex === 0) {
const pairIndex = Math.floor(i / 2);
const y = i * spacing + pairIndex * 40;
$el.css({ position: "absolute", top: `${y}px` });
matchCenters[match.serial] = y + $el.outerHeight() / 2;
bottomMost = Math.max(bottomMost, y + $el.outerHeight());
} else {
const prevLevel = levels[levelIndex - 1];
const prevMatches = groupMatches[prevLevel];
const prev1 = prevMatches[i * 2];
const prev2 = prevMatches[i * 2 + 1];if (prev1 && prev2) {
const center1 = matchCenters[prev1.serial];
const center2 = matchCenters[prev2.serial];
const centerY = (center1 + center2) / 2;
const topOffset = centerY - $el.outerHeight() / 2;
$el.css({ position: "absolute", top: `${topOffset}px` });
matchCenters[match.serial] = centerY;
bottomMost = Math.max(bottomMost, topOffset + $el.outerHeight());
}
}
});
});$(`.group[data-group="${group}"]`).css("height", bottomMost + 180);
}
function debounce(func, delay) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}
function waitForLayoutAndDraw(serialMap, maxTries = 10) {
if (window._isDrawingScheduled) return;window._isDrawingScheduled = true;
window._lastSerialMap = serialMap;let tries = 0;function tryDraw() {
const wrapper = document.querySelector(".bracket_wrapper");if (!wrapper) {
if (tries < maxTries) {
tries++;
requestAnimationFrame(tryDraw);
} else {
window._isDrawingScheduled = false; // Reset if failed after all tries
}
return;
}const rect = wrapper.getBoundingClientRect();
if (rect.width < 100 || rect.height < 100) {
if (tries < maxTries) {
tries++;
requestAnimationFrame(tryDraw);
} else {
window._isDrawingScheduled = false;
}
return;
}// ✅ Only draw once per layout session
if (!wrapper.dataset.hasDrawn) {
drawBracketConnectors(serialMap, true);
wrapper.dataset.hasDrawn = "true";
}// 🟢 Reset the flag after a short time (if needed later)
setTimeout(() => {
window._isDrawingScheduled = false;
wrapper.dataset.hasDrawn = ""; // Allow future redraws if needed
}, 1000);// 🟢 Add scroll listener once
if (!wrapper.dataset.scrollListenerAdded) {
console.log("Ketan testing");
wrapper.addEventListener(
"scroll",
debounce(() => drawBracketConnectors(window._lastSerialMap, true), 100)
);
wrapper.dataset.scrollListenerAdded = "true";
}
}requestAnimationFrame(tryDraw);
}function drawBracketConnectors(serialMap, forceRedraw = false) {
console.log("Ketan test");
const svgNS = "http://www.w3.org/2000/svg";
let svg = document.getElementById("bracket-connector-svg");
const wrapper = document.querySelector(".bracket_wrapper");const wrapperRect = wrapper.getBoundingClientRect();
const scrollLeft = wrapper.scrollLeft;
const scrollTop = wrapper.scrollTop;if (!svg) {
svg = document.createElementNS(svgNS, "svg");
svg.setAttribute("id", "bracket-connector-svg");
svg.style.position = "absolute";
svg.style.top = "0";
svg.style.left = "0";
svg.style.pointerEvents = "none";
wrapper.appendChild(svg);
}svg.style.width = wrapper.scrollWidth + "px";
svg.style.height = wrapper.scrollHeight + "px";const layoutHash = JSON.stringify(
Object.values(serialMap).map(m => {
const el = document.getElementById(`match-serial-${m.group}-${m.serial}`);
const rect = el?.getBoundingClientRect();
return {
serial: m.serial,
group: m.group,
column: m.column,
rect: rect ? {
top: rect.top,
left: rect.left,
width: rect.width,
height: rect.height
} : null
};
})
);if (!forceRedraw && window._lastLayoutHash === layoutHash) return;
window._lastLayoutHash = layoutHash;while (svg.firstChild) svg.removeChild(svg.firstChild);const groupMap = {};
for (const serial in serialMap) {
const match = serialMap[serial];
if (!groupMap[match.group]) groupMap[match.group] = [];
groupMap[match.group].push(match);
}for (const group in groupMap) {
const matches = groupMap[group].sort((a, b) => +a.serial - +b.serial);
const matchMap = {};matches.forEach(match => {
matchMap[match.serial] = match;
match.el = document.getElementById(`match-serial-${group}-${match.serial}`);
match.rect = match.el?.getBoundingClientRect();
});const isFiveMatch = matches.length === 4;
const isThreeTeam = matches.length === 2 && matchMap["1"] && matchMap["2"];
if (
matches.length === 9 &&
matchMap["1"] && matchMap["2"] &&
matchMap["3"] && matchMap["4"] &&
matchMap["5"] && matchMap["6"] &&
matchMap["7"] && matchMap["8"] &&
matchMap["9"]
) {
const m1 = matchMap[1];
const m2 = matchMap[2];
const m3 = matchMap[3];
const m4 = matchMap[4];
const m5 = matchMap[5];
const m6 = matchMap[6];
const m7 = matchMap[7];
const m8 = matchMap[8];
const m9 = matchMap[9];// Round 1
if (m1?.rect && m5?.rect) drawLConnectorCustom(m1.rect, m3.rect, "bottom", 40);
if (m2?.rect && m6?.rect) drawLConnectorCustom(m2.rect, m5.rect, "bottom", 40);// Round 2
if (m3?.rect && m7?.rect) drawLConnectorCustom(m3.rect, m7.rect, "top", 40);
if (m4?.rect && m7?.rect) drawLConnectorCustom(m4.rect, m7.rect, "bottom", 40);
if (m5?.rect && m8?.rect) drawLConnectorCustom(m5.rect, m8.rect, "top", 40);
if (m6?.rect && m8?.rect) drawLConnectorCustom(m6.rect, m8.rect, "bottom", 40);// Round 3 (Final)
if (m7?.rect && m9?.rect) drawLConnectorCustom(m7.rect, m9.rect, "top", 40);
if (m8?.rect && m9?.rect) drawLConnectorCustom(m8.rect, m9.rect, "bottom", 40);continue;
}if (matches.length === 6 &&
matchMap["1"] && matchMap["2"] && matchMap["3"] &&
matchMap["4"] && matchMap["5"] && matchMap["6"]) {const m1 = matchMap[1];
const m2 = matchMap[2];
const m3 = matchMap[3];
const m4 = matchMap[4];
const m5 = matchMap[5];
const m6 = matchMap[6];// Round 1 → Round 2
// M1 → M4 (top)
if (m1?.rect && m4?.rect) drawLConnectorCustom(m1.rect, m4.rect, "top", 40);// M2 → M5 (top)
if (m2?.rect && m5?.rect) drawLConnectorCustom(m2.rect, m5.rect, "top", 40);// M3 → M5 (bottom)
if (m3?.rect && m5?.rect) drawLConnectorCustom(m3.rect, m5.rect, "bottom", 40);// Round 2 → Final
// M4 → M6 (top)
if (m4?.rect && m6?.rect) drawLConnectorCustom(m4.rect, m6.rect, "top", 40);// M5 → M6 (bottom)
if (m5?.rect && m6?.rect) drawLConnectorCustom(m5.rect, m6.rect, "bottom", 40);continue;
}if (isThreeTeam) {
console.log("This is a testing");
const fromMatch = matchMap[1];
const toMatch = matchMap[2];if (fromMatch?.rect && toMatch?.rect) {
drawLConnectorCustom(fromMatch.rect, toMatch.rect, "right", 40);
}continue; // only skip current group
}
if (matches.length === 5) {
const m1 = matchMap[1], m2 = matchMap[2];
const m3 = matchMap[3], m4 = matchMap[4], m5 = matchMap[5];if (m1?.rect && m4?.rect) drawLConnectorCustom(m1.rect, m4.rect, "top", 40);
if (m2?.rect && m4?.rect) drawLConnectorCustom(m2.rect, m4.rect, "bottom", 40);
if (m3?.rect && m5?.rect) drawLConnectorCustom(m3.rect, m5.rect, "top", 40);
if (m4?.rect && m5?.rect) drawLConnectorCustom(m4.rect, m5.rect, "bottom", 40);
}if (isFiveMatch) {
const s1 = matchMap[1], s2 = matchMap[2], s3 = matchMap[3], s4 = matchMap[4];if (s1?.rect && s3?.rect) drawLConnectorCustom(s1.rect, s3.rect, "bottom");
if (s2?.rect && s4?.rect) drawLConnectorCustom(s2.rect, s4.rect, "top", 40);
if (s3?.rect && s4?.rect) drawLConnectorCustom(s3.rect, s4.rect, "bottom", 80);continue; // use continue, not return — don't break whole function
}const columns = [...new Set(matches.map(m => +m.column))].sort((a, b) => a - b);for (let colIndex = 1; colIndex < columns.length; colIndex++) {
const currentCol = columns[colIndex];
const prevCol = columns[colIndex - 1];const currentMatches = matches.filter(m => +m.column === currentCol);
const prevMatches = matches.filter(m => +m.column === prevCol);for (let i = 0; i < currentMatches.length; i++) {
const targetMatch = currentMatches[i];
const input1 = prevMatches[i * 2];
const input2 = prevMatches[i * 2 + 1];if (input1?.rect && input2?.rect && targetMatch?.rect) {
drawLConnectorCustom(input1.rect, targetMatch.rect, "top", 40);
drawLConnectorCustom(input2.rect, targetMatch.rect, "bottom", 40);
} else if ((input1?.rect || input2?.rect) && targetMatch?.rect) {
const fromRect = input1?.rect || input2?.rect;
const direction = input1?.rect ? "top" : "bottom";
drawLConnectorCustom(fromRect, targetMatch.rect, direction, 40);
}
}
}function drawLConnectorCustom(fromRect, toRect, toPoint = "center", elbowOffset = 40) {
const startX = fromRect.right - wrapperRect.left + scrollLeft;
const startY = fromRect.top + fromRect.height / 2 - wrapperRect.top + scrollTop;let toY;
const headerOffset = 48;if (toPoint === "top") {
toY = toRect.top + headerOffset + 10 - wrapperRect.top + scrollTop;
} else if (toPoint === "bottom") {
toY = toRect.bottom - 10 - wrapperRect.top + scrollTop;
} else {
toY = toRect.top + toRect.height / 2 - wrapperRect.top + scrollTop;
}const endX = toRect.left - wrapperRect.left + scrollLeft + 5;const path = document.createElementNS(svgNS, "path");
path.setAttribute("stroke", "#999");
path.setAttribute("stroke-width", "1.5");
path.setAttribute("fill", "none");
path.setAttribute("stroke-linecap", "round");const d = `
M ${startX},${startY}
H ${endX}
V ${toY}
`;path.setAttribute("d", d.trim().replace(/\s+/g, " "));
svg.appendChild(path);
}function drawTConnector(fromRect, toRect) {
const startX = fromRect.right - wrapperRect.left + scrollLeft;
const startY = fromRect.top + fromRect.height / 2 - wrapperRect.top + scrollTop;const endX = toRect.left - wrapperRect.left + scrollLeft;
const endY = toRect.top + toRect.height / 2 - wrapperRect.top + scrollTop;
const midX = (startX + endX) / 2;const path1 = document.createElementNS(svgNS, "path");
path1.setAttribute("stroke", "#999");
path1.setAttribute("stroke-width", "1.5");
path1.setAttribute("fill", "none");
path1.setAttribute("stroke-linecap", "round");
path1.setAttribute("d", `M ${startX},${startY} H ${midX}`);
svg.appendChild(path1);const path2 = document.createElementNS(svgNS, "path");
path2.setAttribute("stroke", "#999");
path2.setAttribute("stroke-width", "1.5");
path2.setAttribute("fill", "none");
path2.setAttribute("stroke-linecap", "round");
path2.setAttribute("d", `M ${endX},${endY} H ${midX}`);
svg.appendChild(path2);const verticalLine = document.createElementNS(svgNS, "path");
verticalLine.setAttribute("stroke", "#999");
verticalLine.setAttribute("stroke-width", "1.5");
verticalLine.setAttribute("fill", "none");
verticalLine.setAttribute("stroke-linecap", "round");
verticalLine.setAttribute("d", `M ${midX},${startY} V ${endY}`);
svg.appendChild(verticalLine);
}function drawHorizontalConnector(fromRect, toRect) {
const startX = fromRect.right - wrapperRect.left + scrollLeft;
const startY = fromRect.top + fromRect.height / 2 - wrapperRect.top + scrollTop;const endX = toRect.left - wrapperRect.left + scrollLeft;const path = document.createElementNS(svgNS, "path");
path.setAttribute("stroke", "#999");
path.setAttribute("stroke-width", "1.5");
path.setAttribute("fill", "none");
path.setAttribute("stroke-linecap", "round");const d = `M ${startX},${startY} H ${endX}`;
path.setAttribute("d", d.trim().replace(/\s+/g, " "));
svg.appendChild(path);
}function drawRightThenVertical(fromRect, toRect, align = "top") {
const startX = fromRect.right - wrapperRect.left + scrollLeft;
const startY = fromRect.top + fromRect.height / 2 - wrapperRect.top + scrollTop;
const elbowX = startX + 40;const toY = align === "top"
? toRect.top - wrapperRect.top + scrollTop
: toRect.bottom - wrapperRect.top + scrollTop;const toX = toRect.left - wrapperRect.left + scrollLeft;const path = document.createElementNS(svgNS, "path");
path.setAttribute("stroke", "#888");
path.setAttribute("stroke-width", "4");
path.setAttribute("fill", "none");
path.setAttribute("stroke-linejoin", "round");
path.setAttribute("stroke-linecap", "round");const d = `
M ${startX},${startY}
H ${elbowX}
V ${toY}
H ${toX}
`;
path.setAttribute("d", d.trim().replace(/\s+/g, " "));
svg.appendChild(path);
}function drawLine(from, to) {
if (!from?.rect || !to?.rect) return;const x1 = from.rect.right + scrollLeft - wrapperRect.left;
const y1 = from.rect.top + from.rect.height / 2 + scrollTop - wrapperRect.top;const x2 = to.rect.left + scrollLeft - wrapperRect.left;
const y2 = to.rect.top + to.rect.height / 2 + scrollTop - wrapperRect.top;const midX = (x1 + x2) / 2;const path = document.createElementNS(svgNS, "path");
path.setAttribute("stroke", "#999");
path.setAttribute("stroke-width", "2");
path.setAttribute("fill", "none");
path.setAttribute("d", `M ${x1},${y1} H ${midX} V ${y2} H ${x2}`);
svg.appendChild(path);
}function drawDefaultConnect(input1, input2, targetMatch) {
if (!input1?.rect || !input2?.rect || !targetMatch?.rect) return;const x1 = input1.rect.right + scrollLeft - wrapperRect.left;
const y1 = input1.rect.top + input1.rect.height / 2 + scrollTop - wrapperRect.top;const x2 = input2.rect.right + scrollLeft - wrapperRect.left;
const y2 = input2.rect.top + input2.rect.height / 2 + scrollTop - wrapperRect.top;const toX = targetMatch.rect.left + scrollLeft - wrapperRect.left;
const toY = targetMatch.rect.top + targetMatch.rect.height / 2 + scrollTop - wrapperRect.top;const midX = (x1 + toX) / 2;
const midY = (y1 + y2) / 2;const path = document.createElementNS(svgNS, "path");
path.setAttribute("stroke", "#999");
path.setAttribute("stroke-width", "2");
path.setAttribute("fill", "none");const d = `
M ${x1},${y1}
H ${midX}
M ${x2},${y2}
H ${midX}
M ${midX},${y1}
V ${y2}
M ${midX},${midY}
H ${toX}
`;
path.setAttribute("d", d.trim().replace(/\s+/g, ' '));
svg.appendChild(path);
}
}
}