// --- browser-bundle/40-overlay.js --- // The overlay UI: outlines + labels per flagged element, the page-level // banner, the hover spotlight, visibility toggling. Pure presentation over a // findings list — no rules, no thresholds, no snippet strings; the rule // names and categories come from the registry it is handed. Ported from // cli/engine/browser/injected/index.mjs Section 7. // // createImpeccableOverlay({ extensionMode, antipatterns }) -> // { highlight(el, findings), showPageBanner(findings), clearOverlays(), // remove(), toggleOverlays() -> visible, spotlight(target), // unspotlight(), highlightSelector(selector), setFirstScanDone(), // overlays } // Used by the in-page bundle (50-scan.js) and, as `overlay.js`, by the // extension's content script. function createImpeccableOverlay({ extensionMode = false, antipatterns = [] } = {}) { // Kinpaku gold — pinned to the site's brand token (see // site/styles/kinpaku-tokens.css --ks-kinpaku). Keep this in sync with // the picker's C.brand in skill/scripts/live-browser.js, the extension's // extension/shared/kinpaku.css, and the kit's picker section in // site/styles/kinpaku-kit.css. // // One color across both light and dark host pages. The marker is a gold // hairline plus a soft outer glow: the hairline holds its shape on a // light page, the glow carries it on a dark one, and neither reads as // body text, so the pair doesn't need WCAG AA against the page. The // label is the detector's tag: dark ink (LABEL_INK, --ks-on-gold) on // leaf gold, about 11.8:1, in the mono face the tag uses everywhere // else. Hover deepens the gold (preserves chroma, never drops it: // dropping chroma washes the gold into a sand/olive tone). const BRAND_COLOR = 'oklch(84% 0.19 80.46)'; const BRAND_COLOR_HOVER = 'oklch(74% 0.18 80)'; const LABEL_INK = 'oklch(14% 0.018 95)'; const LABEL_BG = BRAND_COLOR; const OUTLINE_COLOR = BRAND_COLOR; const GLOW = '0 0 0 3px oklch(84% 0.19 80.46 / 0.20), 0 0 12px oklch(84% 0.19 80.46 / 0.45)'; const GLOW_HOVER = '0 0 0 3px oklch(74% 0.18 80 / 0.28), 0 0 16px oklch(74% 0.18 80 / 0.6)'; // The page-level banner is a paper bar, not a gold one: gold is the mark, // the tag and the lit dot, and a full-width gold fill across someone's page // is the one place it was still upholstery. Paper with ink text, a gold // hairline along the bottom edge, a lit gold dot at the head, and each // finding wearing the same tag the panel gives it (gold for an AI tell, // gray for a quality issue). The bar carries its own shadow so it still // separates from a dark host page. const PAPER = 'oklch(97.8% 0 0)'; const PAPER_INK = 'oklch(13% 0 0)'; const PAPER_MUTED = 'oklch(46% 0 0)'; const PAPER_GRAY = 'oklch(92% 0 0)'; const GOLD_LINE = 'oklch(77% 0.13 82)'; const BANNER_LIFT = '0 1px 0 oklch(13% 0 0 / 0.10), 0 6px 18px oklch(0% 0 0 / 0.35)'; const MONO = 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace'; const SANS = 'system-ui, -apple-system, "Segoe UI", sans-serif'; // Inject hover styles via CSS (more reliable than JS event listeners) const styleEl = document.createElement('style'); styleEl.textContent = ` @keyframes impeccable-reveal { from { opacity: 0; } to { opacity: 1; } } .impeccable-overlay:not(.impeccable-banner) { pointer-events: none; outline: 1px solid ${OUTLINE_COLOR}; box-shadow: ${GLOW}; border-radius: 4px; transition: outline-color 0.15s ease, box-shadow 0.15s ease; animation: impeccable-reveal 0.4s cubic-bezier(0.16, 1, 0.3, 1) both; animation-play-state: paused; border-top-left-radius: 0; } .impeccable-overlay.impeccable-visible { animation-play-state: running; } .impeccable-overlay.impeccable-hover { outline-color: ${BRAND_COLOR_HOVER}; box-shadow: ${GLOW_HOVER}; z-index: 100001 !important; } .impeccable-overlay.impeccable-hover .impeccable-label { background: ${BRAND_COLOR_HOVER}; } .impeccable-overlay.impeccable-spotlight { z-index: 100002 !important; } .impeccable-overlay.impeccable-spotlight-dimmed { opacity: 0.15 !important; animation: none !important; filter: blur(3px); } .impeccable-spotlight-backdrop { position: fixed; top: 0; left: 0; right: 0; bottom: 0; backdrop-filter: blur(3px) brightness(0.6); -webkit-backdrop-filter: blur(3px) brightness(0.6); pointer-events: none; z-index: 99998; opacity: 0; outline: none !important; animation: none !important; } .impeccable-spotlight-backdrop.impeccable-visible { opacity: 1; } .impeccable-hidden .impeccable-overlay${extensionMode ? '' : ':not(.impeccable-banner)'} { display: none !important; } `; (document.head || document.documentElement).appendChild(styleEl); let firstScanDone = false; // Spotlight backdrop element (created lazily on first use) let spotlightBackdrop = null; let spotlightTarget = null; function getSpotlightBackdrop() { if (!spotlightBackdrop) { spotlightBackdrop = document.createElement('div'); spotlightBackdrop.className = 'impeccable-spotlight-backdrop'; document.body.appendChild(spotlightBackdrop); } return spotlightBackdrop; } function updateSpotlightClipPath() { if (!spotlightBackdrop || !spotlightTarget) return; const r = spotlightTarget.getBoundingClientRect(); // Match the overlay's outer edge: element rect + 3px (2px overlay offset + 1px hairline) const inset = 3; const radius = 5; // outline border-radius (4) + hairline width (1) const x1 = r.left - inset; const y1 = r.top - inset; const x2 = r.right + inset; const y2 = r.bottom + inset; const vw = window.innerWidth; const vh = window.innerHeight; // Outer rect + rounded inner rect (evenodd creates a hole) const path = `M0 0H${vw}V${vh}H0Z M${x1 + radius} ${y1}H${x2 - radius}A${radius} ${radius} 0 0 1 ${x2} ${y1 + radius}V${y2 - radius}A${radius} ${radius} 0 0 1 ${x2 - radius} ${y2}H${x1 + radius}A${radius} ${radius} 0 0 1 ${x1} ${y2 - radius}V${y1 + radius}A${radius} ${radius} 0 0 1 ${x1 + radius} ${y1}Z`; spotlightBackdrop.style.clipPath = `path(evenodd, "${path}")`; } function showSpotlight(target) { if (!target || !target.getBoundingClientRect) return; // Respect the spotlightBlur setting: if disabled, don't show the backdrop if (window.__IMPECCABLE_CONFIG__?.spotlightBlur === false) { spotlightTarget = target; return; } spotlightTarget = target; const bd = getSpotlightBackdrop(); updateSpotlightClipPath(); bd.classList.add('impeccable-visible'); } function hideSpotlight() { spotlightTarget = null; if (spotlightBackdrop) spotlightBackdrop.classList.remove('impeccable-visible'); } function isInViewport(el) { const r = el.getBoundingClientRect(); return r.top >= 0 && r.left >= 0 && r.bottom <= window.innerHeight && r.right <= window.innerWidth; } // Reposition spotlight on scroll/resize window.addEventListener('scroll', () => { if (spotlightTarget) updateSpotlightClipPath(); }, { passive: true }); window.addEventListener('resize', () => { if (spotlightTarget) updateSpotlightClipPath(); }); const overlays = []; const ANTIPATTERNS = antipatterns || []; const TYPE_LABELS = {}; const RULE_CATEGORY = {}; for (const ap of ANTIPATTERNS) { TYPE_LABELS[ap.id] = ap.name.toLowerCase(); RULE_CATEGORY[ap.id] = ap.category || 'quality'; } function isInFixedContext(el) { let p = el; while (p && p !== document.body) { if (getComputedStyle(p).position === 'fixed') return true; p = p.parentElement; } return false; } function positionOverlay(overlay) { const el = overlay._targetEl; if (!el) return; const rect = el.getBoundingClientRect(); if (overlay._isFixed) { // Viewport-relative coords for fixed targets overlay.style.top = `${rect.top - 2}px`; overlay.style.left = `${rect.left - 2}px`; } else { // Document-relative coords for normal targets overlay.style.top = `${rect.top + scrollY - 2}px`; overlay.style.left = `${rect.left + scrollX - 2}px`; } overlay.style.width = `${rect.width + 4}px`; overlay.style.height = `${rect.height + 4}px`; } function repositionOverlays() { for (const o of overlays) { if (!o._targetEl || o.classList.contains('impeccable-banner')) continue; // Skip overlays whose target is currently hidden (display: none on the overlay) if (o.style.display === 'none') continue; positionOverlay(o); } } let resizeRAF; const onResize = () => { cancelAnimationFrame(resizeRAF); resizeRAF = requestAnimationFrame(repositionOverlays); }; window.addEventListener('resize', onResize); // Reposition on scroll too -- catches sticky/parallax shifts window.addEventListener('scroll', onResize, { passive: true }); // Reposition when body resizes (lazy-loaded images, dynamic content, fonts loading) if (typeof ResizeObserver !== 'undefined') { const bodyResizeObserver = new ResizeObserver(onResize); bodyResizeObserver.observe(document.body); } // Track target element visibility via IntersectionObserver. // Uses a huge rootMargin so all *rendered* elements count as intersecting, // while display:none / closed
/ hidden modals etc. do not. // This is event-driven -- no polling needed. let overlayIndex = 0; const visibilityObserver = new IntersectionObserver((entries) => { for (const entry of entries) { const overlay = entry.target._impeccableOverlay; if (!overlay) continue; if (entry.isIntersecting) { overlay.style.display = ''; positionOverlay(overlay); if (!overlay._revealed) { overlay._revealed = true; if (firstScanDone) { // Subsequent reveals (re-scans, scroll-into-view): instant, no animation overlay.style.animation = 'none'; } else { // Initial scan: staggered cascade reveal overlay.style.animationDelay = `${Math.min((overlay._staggerIndex || 0) * 60, 600)}ms`; } requestAnimationFrame(() => { overlay.classList.add('impeccable-visible'); if (overlay._checkLabel) overlay._checkLabel(); }); } } else { overlay.style.display = 'none'; } } }, { rootMargin: '99999px' }); function detachOverlay(overlay) { if (!overlay) return; if (typeof overlay._cleanup === 'function') { try { overlay._cleanup(); } catch { /* best effort overlay teardown */ } } if (overlay._targetEl && overlay._targetEl._impeccableOverlay === overlay) { visibilityObserver.unobserve(overlay._targetEl); delete overlay._targetEl._impeccableOverlay; } const idx = overlays.indexOf(overlay); if (idx >= 0) overlays.splice(idx, 1); overlay.remove(); } // Reposition overlays after CSS transitions end (e.g. reveal animations). // Listens at document level so it catches transitions on ancestor elements // (the transform may be on a parent, not the flagged element itself). document.addEventListener('transitionend', (e) => { if (e.propertyName !== 'transform') return; for (const o of overlays) { if (!o._targetEl || o.classList.contains('impeccable-banner') || o.style.display === 'none') continue; if (e.target === o._targetEl || e.target.contains(o._targetEl)) { positionOverlay(o); } } }); const highlight = function(el, findings) { if (el._impeccableOverlay) detachOverlay(el._impeccableOverlay); const hasSlop = findings.some(f => RULE_CATEGORY[f.type || f.id] === 'slop'); const fixed = isInFixedContext(el); const rect = el.getBoundingClientRect(); const outline = document.createElement('div'); outline.className = 'impeccable-overlay'; outline._targetEl = el; outline._isFixed = fixed; Object.assign(outline.style, { position: fixed ? 'fixed' : 'absolute', top: fixed ? `${rect.top - 2}px` : `${rect.top + scrollY - 2}px`, left: fixed ? `${rect.left - 2}px` : `${rect.left + scrollX - 2}px`, width: `${rect.width + 4}px`, height: `${rect.height + 4}px`, zIndex: '99999', boxSizing: 'border-box', }); // Build per-finding label entries: ✦ prefix for slop const entries = findings.map(f => { const name = TYPE_LABELS[f.type || f.id] || f.type || f.id; const prefix = RULE_CATEGORY[f.type || f.id] === 'slop' ? '\u2726 ' : ''; return { name: prefix + name, detail: f.detail || f.snippet }; }); const allText = entries.map(e => e.name).join(', '); const label = document.createElement('div'); label.className = 'impeccable-label'; Object.assign(label.style, { position: 'absolute', bottom: '100%', left: '-2px', display: 'flex', alignItems: 'center', whiteSpace: 'nowrap', fontSize: '10px', fontWeight: '600', letterSpacing: '0.06em', textTransform: 'uppercase', color: LABEL_INK, lineHeight: '14px', background: LABEL_BG, fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace', borderRadius: '3px 3px 0 0', }); const textSpan = document.createElement('span'); textSpan.style.padding = '3px 8px'; textSpan.textContent = allText; label.appendChild(textSpan); // State for cycling mode let cycleMode = false; let cycleIndex = 0; let isHovered = false; let prevBtn, nextBtn; function updateCycleText() { const e = entries[cycleIndex]; textSpan.textContent = isHovered ? e.detail : e.name; } function enableCycleMode() { if (cycleMode || entries.length < 2) return; cycleMode = true; const btnStyle = { background: 'none', border: 'none', color: 'oklch(14% 0.018 95 / 0.7)', fontSize: '11px', cursor: 'pointer', padding: '3px 4px', fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace', lineHeight: '14px', pointerEvents: 'auto', }; const navGroup = document.createElement('span'); Object.assign(navGroup.style, { display: 'inline-flex', alignItems: 'center', flexShrink: '0', }); prevBtn = document.createElement('button'); prevBtn.textContent = '\u2039'; Object.assign(prevBtn.style, btnStyle); prevBtn.style.paddingLeft = '6px'; prevBtn.addEventListener('click', (e) => { e.stopPropagation(); cycleIndex = (cycleIndex - 1 + entries.length) % entries.length; updateCycleText(); }); nextBtn = document.createElement('button'); nextBtn.textContent = '\u203A'; Object.assign(nextBtn.style, btnStyle); nextBtn.style.paddingRight = '2px'; nextBtn.addEventListener('click', (e) => { e.stopPropagation(); cycleIndex = (cycleIndex + 1) % entries.length; updateCycleText(); }); navGroup.appendChild(prevBtn); navGroup.appendChild(nextBtn); label.insertBefore(navGroup, textSpan); textSpan.style.padding = '3px 8px 3px 4px'; updateCycleText(); } outline.appendChild(label); // Start hidden; the IntersectionObserver will show it once the target is rendered outline.style.display = 'none'; outline._staggerIndex = overlayIndex++; el._impeccableOverlay = outline; visibilityObserver.observe(el); // After first paint, check label width vs outline outline._checkLabel = () => { if (entries.length > 1 && label.offsetWidth > outline.offsetWidth) { enableCycleMode(); } }; // Hover: show detail text, darken const onMouseEnter = () => { isHovered = true; outline.classList.add('impeccable-hover'); outline.style.outlineColor = BRAND_COLOR_HOVER; label.style.background = BRAND_COLOR_HOVER; if (cycleMode) { updateCycleText(); } else { textSpan.textContent = entries.map(e => e.detail).join(' | '); } }; const onMouseLeave = () => { isHovered = false; outline.classList.remove('impeccable-hover'); outline.style.outlineColor = ''; label.style.background = LABEL_BG; if (cycleMode) { updateCycleText(); } else { textSpan.textContent = allText; } }; el.addEventListener('mouseenter', onMouseEnter); el.addEventListener('mouseleave', onMouseLeave); outline._cleanup = () => { el.removeEventListener('mouseenter', onMouseEnter); el.removeEventListener('mouseleave', onMouseLeave); }; document.body.appendChild(outline); overlays.push(outline); }; const showPageBanner = function(findings) { if (!findings.length) return; const banner = document.createElement('div'); banner.className = 'impeccable-overlay impeccable-banner'; Object.assign(banner.style, { position: 'fixed', top: '0', left: '0', right: '0', zIndex: '100000', background: PAPER, color: PAPER_INK, borderBottom: `1px solid ${GOLD_LINE}`, boxShadow: BANNER_LIFT, fontFamily: SANS, fontSize: '12px', display: 'flex', alignItems: 'center', pointerEvents: 'auto', height: '36px', overflow: 'hidden', maxWidth: '100vw', transform: 'translateY(-100%)', transition: 'transform 0.4s cubic-bezier(0.16, 1, 0.3, 1)', }); requestAnimationFrame(() => requestAnimationFrame(() => { banner.style.transform = 'translateY(0)'; })); // The detector's indicator, the same lit gold dot the panel puts on a // section head. const dot = document.createElement('span'); Object.assign(dot.style, { flexShrink: '0', width: '7px', height: '7px', margin: '0 0 0 12px', borderRadius: '50%', background: BRAND_COLOR, boxShadow: '0 0 0 1px oklch(13% 0 0 / 0.12), 0 0 4px oklch(84% 0.19 80 / 0.6)', }); banner.appendChild(dot); // Scrollable findings area const scrollArea = document.createElement('div'); Object.assign(scrollArea.style, { flex: '1', minWidth: '0', overflowX: 'auto', overflowY: 'hidden', display: 'flex', gap: '12px', alignItems: 'center', padding: '0 12px', scrollSnapType: 'x mandatory', scrollbarWidth: 'none', }); for (const f of findings) { const isSlop = RULE_CATEGORY[f.type] === 'slop'; const item = document.createElement('span'); Object.assign(item.style, { display: 'inline-flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', flexShrink: '0', scrollSnapAlign: 'start', }); const tag = document.createElement('span'); tag.textContent = TYPE_LABELS[f.type] || f.type; Object.assign(tag.style, { display: 'inline-flex', alignItems: 'center', minHeight: '16px', padding: '0 6px', borderRadius: '3px', background: isSlop ? BRAND_COLOR : PAPER_GRAY, color: isSlop ? LABEL_INK : PAPER_MUTED, fontFamily: MONO, fontSize: '10px', fontWeight: '600', letterSpacing: '0.06em', lineHeight: '1', textTransform: 'uppercase', }); const detail = document.createElement('span'); detail.textContent = f.detail; Object.assign(detail.style, { color: PAPER_INK, fontFamily: SANS, fontSize: '12px', }); item.appendChild(tag); item.appendChild(detail); scrollArea.appendChild(item); } banner.appendChild(scrollArea); // Controls area (only in standalone mode, not extension) if (!extensionMode) { const controls = document.createElement('div'); Object.assign(controls.style, { display: 'flex', alignItems: 'center', gap: '2px', padding: '0 8px', flexShrink: '0', }); // Toggle visibility button const toggle = document.createElement('button'); toggle.textContent = '\u25C9'; // circle with dot (visible state) toggle.title = 'Toggle overlay visibility'; Object.assign(toggle.style, { background: 'none', border: 'none', color: PAPER_MUTED, fontSize: '16px', cursor: 'pointer', padding: '0 4px', opacity: '0.85', transition: 'opacity 0.15s', }); let overlaysVisible = true; toggle.addEventListener('click', () => { overlaysVisible = !overlaysVisible; document.body.classList.toggle('impeccable-hidden', !overlaysVisible); toggle.textContent = overlaysVisible ? '\u25C9' : '\u25CB'; // filled vs empty circle toggle.style.opacity = overlaysVisible ? '0.85' : '0.5'; }); controls.appendChild(toggle); // Close button const close = document.createElement('button'); close.textContent = '\u00d7'; close.title = 'Dismiss banner'; Object.assign(close.style, { background: 'none', border: 'none', color: PAPER_MUTED, fontSize: '18px', cursor: 'pointer', padding: '0 4px', }); close.addEventListener('click', () => banner.remove()); controls.appendChild(close); banner.appendChild(controls); } document.body.appendChild(banner); overlays.push(banner); }; function clearOverlays() { for (const o of [...overlays]) detachOverlay(o); overlays.length = 0; visibilityObserver.disconnect(); overlayIndex = 0; } // Tear the UI down entirely (the extension's `remove` command). function remove() { clearOverlays(); styleEl.remove(); if (spotlightBackdrop) { spotlightBackdrop.remove(); spotlightBackdrop = null; } document.body.classList.remove('impeccable-hidden'); } // Toggle every overlay; returns the new visibility. function toggleOverlays() { const visible = !document.body.classList.contains('impeccable-hidden'); document.body.classList.toggle('impeccable-hidden', visible); return !visible; } // Spotlight the overlay of the element `selector` names (scrolling it into // view first so positionOverlay reads the post-scroll rect). function highlightSelector(selector) { try { const target = selector ? document.querySelector(selector) : null; if (!target) return; if (!isInViewport(target) && target.scrollIntoView) { target.scrollIntoView({ behavior: 'instant', block: 'center' }); } for (const o of overlays) { if (o.classList.contains('impeccable-banner')) continue; const isMatch = o._targetEl === target; o.classList.toggle('impeccable-spotlight', isMatch); o.classList.toggle('impeccable-spotlight-dimmed', !isMatch); if (isMatch) { // Force the matching overlay visible immediately, don't wait for IntersectionObserver o.style.display = ''; o.style.animation = 'none'; o.classList.add('impeccable-visible'); o._revealed = true; positionOverlay(o); } } showSpotlight(target); } catch { /* invalid selector */ } } function unspotlight() { hideSpotlight(); for (const o of overlays) { o.classList.remove('impeccable-spotlight'); o.classList.remove('impeccable-spotlight-dimmed'); } } return { highlight, showPageBanner, clearOverlays, remove, toggleOverlays, spotlight: showSpotlight, unspotlight, highlightSelector, setFirstScanDone() { firstScanDone = true; }, overlays, TYPE_LABELS, RULE_CATEGORY, }; }