/** * Pixel Ruler Tool * * Injects measurement overlays onto a webpage for visual verification. * Used with Playwright to measure and verify element positioning. * * @module ruler */ export interface MeasurementOptions { /** CSS selectors for elements to measure */ selectors: string[]; /** Show center lines (horizontal and vertical) */ showCenterLines?: boolean; /** Show dimension labels (width x height) */ showDimensions?: boolean; /** Show position labels (top, left) */ showPosition?: boolean; /** Colors for different elements (cycles through) */ colors?: string[]; /** Limit number of elements per selector (default: 5) */ limit?: number; /** Show alignment comparison between first two selectors */ showAlignment?: boolean; } export interface ElementMeasurement { index: number; rect: { top: number; left: number; width: number; height: number; }; centerX: number; centerY: number; } export interface MeasurementResult { selector: string; elements: ElementMeasurement[]; } export interface RulerOutput { results: MeasurementResult[]; summary: string; alignment?: { verticalOffset: number; horizontalOffset: number; aligned: boolean; }; } /** * JavaScript function to inject into the page. * Creates visual measurement overlays on elements. */ export declare const measureElementsScript = "\n(function(options) {\n const {\n selectors = [],\n showCenterLines = true,\n showDimensions = true,\n showPosition = false,\n colors = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff'],\n limit = 5,\n showAlignment = true\n } = options || {};\n\n // Remove any existing measurement overlay\n const existingOverlay = document.getElementById('pixel-ruler-overlay');\n if (existingOverlay) existingOverlay.remove();\n\n // Create SVG overlay\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n svg.id = 'pixel-ruler-overlay';\n svg.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100vw;\n height: 100vh;\n pointer-events: none;\n z-index: 999999;\n `;\n document.body.appendChild(svg);\n\n const results = [];\n let allRects = [];\n\n // Process each selector\n selectors.forEach((selector, selectorIndex) => {\n const color = colors[selectorIndex % colors.length];\n const elements = document.querySelectorAll(selector);\n const selectorResult = { selector, elements: [] };\n\n Array.from(elements).slice(0, limit).forEach((element, elementIndex) => {\n const rect = element.getBoundingClientRect();\n const centerX = rect.left + rect.width / 2;\n const centerY = rect.top + rect.height / 2;\n\n selectorResult.elements.push({\n index: elementIndex,\n rect: { top: rect.top, left: rect.left, width: rect.width, height: rect.height },\n centerX,\n centerY\n });\n\n allRects.push({ rect, centerX, centerY, color, selector, elementIndex });\n\n // Draw bounding box\n const box = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n box.setAttribute('x', rect.left);\n box.setAttribute('y', rect.top);\n box.setAttribute('width', rect.width);\n box.setAttribute('height', rect.height);\n box.setAttribute('fill', 'none');\n box.setAttribute('stroke', color);\n box.setAttribute('stroke-width', '2');\n box.setAttribute('stroke-dasharray', '4,2');\n svg.appendChild(box);\n\n // Draw center lines\n if (showCenterLines) {\n // Horizontal center line\n const hLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n hLine.setAttribute('x1', rect.left);\n hLine.setAttribute('y1', centerY);\n hLine.setAttribute('x2', rect.left + rect.width);\n hLine.setAttribute('y2', centerY);\n hLine.setAttribute('stroke', color);\n hLine.setAttribute('stroke-width', '1');\n hLine.setAttribute('stroke-dasharray', '2,2');\n svg.appendChild(hLine);\n\n // Vertical center line\n const vLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n vLine.setAttribute('x1', centerX);\n vLine.setAttribute('y1', rect.top);\n vLine.setAttribute('x2', centerX);\n vLine.setAttribute('y2', rect.top + rect.height);\n vLine.setAttribute('stroke', color);\n vLine.setAttribute('stroke-width', '1');\n vLine.setAttribute('stroke-dasharray', '2,2');\n svg.appendChild(vLine);\n\n // Center point\n const centerDot = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n centerDot.setAttribute('cx', centerX);\n centerDot.setAttribute('cy', centerY);\n centerDot.setAttribute('r', '4');\n centerDot.setAttribute('fill', color);\n svg.appendChild(centerDot);\n }\n\n // Draw dimension labels\n if (showDimensions) {\n // Position the label OUTSIDE the bbox so it doesn't overlap\n // page content. Default: above the bbox (top); fall back below\n // when the element is at the very top of the page.\n const dimLabel = `${Math.round(rect.width)}\u00D7${Math.round(rect.height)}`;\n const labelHeight = 22;\n const labelW = Math.max(70, dimLabel.length * 9);\n const goAbove = rect.top >= labelHeight + 4;\n const ly = goAbove ? rect.top - labelHeight - 2 : rect.top + rect.height + 2;\n const lx = rect.left;\n\n const labelBg = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n labelBg.setAttribute('x', lx);\n labelBg.setAttribute('y', ly);\n labelBg.setAttribute('width', String(labelW));\n labelBg.setAttribute('height', String(labelHeight));\n labelBg.setAttribute('fill', color);\n labelBg.setAttribute('rx', '3');\n svg.appendChild(labelBg);\n\n const dimText = document.createElementNS('http://www.w3.org/2000/svg', 'text');\n dimText.setAttribute('x', lx + labelW / 2);\n dimText.setAttribute('y', ly + 15);\n dimText.setAttribute('fill', '#ffffff');\n dimText.setAttribute('font-family', 'system-ui, sans-serif');\n dimText.setAttribute('font-size', '13');\n dimText.setAttribute('font-weight', '700');\n dimText.setAttribute('text-anchor', 'middle');\n dimText.textContent = dimLabel;\n svg.appendChild(dimText);\n }\n\n // Draw position labels (separate, opposite side of dim label)\n if (showPosition) {\n const posText = document.createElementNS('http://www.w3.org/2000/svg', 'text');\n // Position label below-right outside the bbox.\n posText.setAttribute('x', rect.left + rect.width + 4);\n posText.setAttribute('y', rect.top + 14);\n posText.setAttribute('fill', color);\n posText.setAttribute('font-family', 'system-ui, sans-serif');\n posText.setAttribute('font-size', '12');\n posText.textContent = `(${Math.round(rect.left)}, ${Math.round(rect.top)})`;\n svg.appendChild(posText);\n }\n });\n\n results.push(selectorResult);\n });\n\n // Show alignment comparison between elements\n let alignment = null;\n if (showAlignment && allRects.length >= 2) {\n const first = allRects[0];\n const second = allRects[1];\n\n // Draw alignment line between centers\n const alignLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n alignLine.setAttribute('x1', first.centerX);\n alignLine.setAttribute('y1', first.centerY);\n alignLine.setAttribute('x2', second.centerX);\n alignLine.setAttribute('y2', second.centerY);\n alignLine.setAttribute('stroke', '#ffffff');\n alignLine.setAttribute('stroke-width', '2');\n svg.appendChild(alignLine);\n\n // Calculate vertical offset\n const verticalOffset = Math.round(first.centerY - second.centerY);\n const horizontalOffset = Math.round(first.centerX - second.centerX);\n\n // Offset label\n const midX = (first.centerX + second.centerX) / 2;\n const midY = (first.centerY + second.centerY) / 2;\n\n const offsetLabel = `\u0394y=${verticalOffset}px \u0394x=${horizontalOffset}px`;\n const offsetW = Math.max(140, offsetLabel.length * 9);\n const offsetH = 28;\n const offsetBg = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n offsetBg.setAttribute('x', midX - offsetW / 2);\n offsetBg.setAttribute('y', midY - offsetH / 2);\n offsetBg.setAttribute('width', String(offsetW));\n offsetBg.setAttribute('height', String(offsetH));\n offsetBg.setAttribute('fill', 'rgba(20,20,20,0.95)');\n offsetBg.setAttribute('rx', '4');\n svg.appendChild(offsetBg);\n\n const offsetText = document.createElementNS('http://www.w3.org/2000/svg', 'text');\n offsetText.setAttribute('x', midX);\n offsetText.setAttribute('y', midY + 6);\n offsetText.setAttribute('fill', '#ffffff');\n offsetText.setAttribute('font-family', 'system-ui, sans-serif');\n offsetText.setAttribute('font-size', '14');\n offsetText.setAttribute('font-weight', '700');\n offsetText.setAttribute('text-anchor', 'middle');\n offsetText.textContent = offsetLabel;\n svg.appendChild(offsetText);\n\n alignment = {\n verticalOffset,\n horizontalOffset,\n aligned: Math.abs(verticalOffset) <= 2 && Math.abs(horizontalOffset) <= 2\n };\n }\n\n return {\n results,\n summary: results.map(r => `${r.selector}: ${r.elements.length} elements`).join(', '),\n alignment\n };\n})\n"; /** * Measure elements and inject visual overlay using Playwright */ export declare function measureViaPlaywright(options: { selectors: string[]; url?: string; output?: string; showCenterLines?: boolean; showDimensions?: boolean; showPosition?: boolean; showAlignment?: boolean; limit?: number; colors?: string[]; verbose?: boolean; }): Promise; /** * Preset measurement for card headers (title + wing alignment) */ export declare function getCardHeaderPreset(): MeasurementOptions; /** * Preset measurement for navigation items */ export declare function getNavigationPreset(): MeasurementOptions; //# sourceMappingURL=ruler.d.ts.map