mirror of
https://github.com/hanzoai/insights.git
synced 2026-11-03 00:52:32 +00:00
bin/debrand renames text. A brand is also geometry and a hex triple, and neither is a name, so the rename walked straight past both: the upstream wordmark sat on the OAuth consent screens, the toolbar error page and the 2FA template drawn as vector paths, and inside index.html as a data URI that printed the logo into every visitor's browser console. `--check` was green through all of it. So the marks are now refused directly. check_marks() reads the three upstream hues in every spelling they are written in here -- `#1d4aff`, `%231D4AFF` inside a data URI, the 8-digit alpha form, and the decimal `(245, 78, 0)` that a hex grep cannot see -- plus two path segments that identify the wordmark even after its fills are recoloured, because recolouring it does not debrand it. The hue match is bounded by non-hex on both sides so the 64-char checksums in the lockfiles are not false failures, with `%23` spelled out as the exception since it ends in a hex digit. Exemptions go in CONTENT_ALLOWLIST with a reason, as they already do for names; no second check script. The decimal spelling immediately found four files no hex grep had reported: two toolbar pills whose drop shadow was a fixed brand blue under a var(--primary-3000) background, a clickmap overlay, and a snapshot-history glow frozen at the old accent's value. The tree it now refuses is clean. Decoration collapses to ink, reusing what the fork already decided rather than inventing a hue: base.scss kept the three brand slots and zeroed their saturation (#333 / #8f8f8f / #ccc), and --data-color-1 is already #3d3d3d, so the quill token palette, the charts fallback and the MCP palette converge on it instead of drifting from it. Where the artwork itself was upstream's -- the 2FA template, the MCP app logo, the OAuth region picker, the Stripe app icon and the five billing plan badges -- it is replaced by the mark in frontend/public/icons/safari-pinned-tab.svg. Someone else's product mark keeps someone else's colours: the vendor logos under frontend/public/services are untouched, the Hedgebox fixtures use Hedgebox's own declared accent, and the livestream country palette stays chromatic because it is data encoding, not decoration. .stylelintignore gains the generated quill dist, which has never been stylelint-clean and cannot be made so without editing generated output.
141 lines
5.3 KiB
JavaScript
141 lines
5.3 KiB
JavaScript
// Visible cursor overlay for recorded QA demo passes. Injected via the browser
|
|
// tool's evaluate/eval into the page being recorded; the overlay is visual only
|
|
// (pointer-events: none) and never affects the page under test. Re-inject after
|
|
// every navigation. Exposes window.__qaCursor.glide(x, y, ms) for smooth
|
|
// movement before a click; real mousemove/mousedown events also drive it, so
|
|
// plain element clicks still show the cursor jumping to the target and rippling.
|
|
;(() => {
|
|
if (window.__qaCursor) {
|
|
return 'already injected'
|
|
}
|
|
// keep in sync with the colors in scripts/annotate-evidence.py
|
|
// The ripple lands on whatever the page under test looks like, so it is a
|
|
// light ring with a dark edge, same as the cursor: one of the two always
|
|
// has contrast, whatever is underneath.
|
|
const RIPPLE = '#fff'
|
|
const RIPPLE_EDGE = '#151515'
|
|
const cursor = document.createElement('div')
|
|
cursor.setAttribute('data-qa-cursor', '')
|
|
cursor.style.cssText = [
|
|
'position: fixed',
|
|
'z-index: 2147483647',
|
|
'width: 22px',
|
|
'height: 22px',
|
|
'margin: -4px 0 0 -3px',
|
|
'pointer-events: none',
|
|
'transition: none',
|
|
'left: -100px',
|
|
'top: -100px',
|
|
].join(';')
|
|
cursor.innerHTML =
|
|
'<svg width="22" height="22" viewBox="0 0 22 22">' +
|
|
'<path d="M4 2 L4 17 L8 13.5 L11 20 L13.5 19 L10.5 12.5 L16 12 Z" ' +
|
|
'fill="#fff" stroke="#151515" stroke-width="1.4" stroke-linejoin="round"/></svg>'
|
|
document.documentElement.appendChild(cursor)
|
|
|
|
const moveTo = (x, y) => {
|
|
cursor.style.left = x + 'px'
|
|
cursor.style.top = y + 'px'
|
|
}
|
|
|
|
const ripple = (x, y) => {
|
|
const ring = document.createElement('div')
|
|
ring.style.cssText = [
|
|
'position: fixed',
|
|
'z-index: 2147483646',
|
|
'left: ' + (x - 18) + 'px',
|
|
'top: ' + (y - 18) + 'px',
|
|
'width: 36px',
|
|
'height: 36px',
|
|
'border: 3px solid ' + RIPPLE,
|
|
'box-shadow: 0 0 0 1px ' + RIPPLE_EDGE + ', inset 0 0 0 1px ' + RIPPLE_EDGE,
|
|
'border-radius: 50%',
|
|
'pointer-events: none',
|
|
'opacity: 0.9',
|
|
'transform: scale(0.35)',
|
|
'transition: transform 450ms ease-out, opacity 450ms ease-out',
|
|
].join(';')
|
|
document.documentElement.appendChild(ring)
|
|
requestAnimationFrame(() => {
|
|
ring.style.transform = 'scale(1)'
|
|
ring.style.opacity = '0'
|
|
})
|
|
setTimeout(() => ring.remove(), 500)
|
|
}
|
|
|
|
document.addEventListener('mousemove', (e) => moveTo(e.clientX, e.clientY), true)
|
|
document.addEventListener('mousedown', (e) => ripple(e.clientX, e.clientY), true)
|
|
|
|
const CHIP_COLORS = { pass: '#388600', fail: '#db3707', info: '#555' }
|
|
let captionBar = null
|
|
|
|
const caption = (text, status = 'info') => {
|
|
if (!text) {
|
|
if (captionBar) {
|
|
captionBar.remove()
|
|
captionBar = null
|
|
}
|
|
return 'caption cleared'
|
|
}
|
|
if (!captionBar) {
|
|
captionBar = document.createElement('div')
|
|
captionBar.setAttribute('data-qa-caption', '')
|
|
captionBar.style.cssText = [
|
|
'position: fixed',
|
|
'left: 0',
|
|
'right: 0',
|
|
'bottom: 0',
|
|
'z-index: 2147483645',
|
|
'display: flex',
|
|
'align-items: center',
|
|
'gap: 14px',
|
|
'padding: 12px 18px',
|
|
'background: #151515',
|
|
'color: #eeefe9',
|
|
'font: 600 17px/1.35 -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif',
|
|
'pointer-events: none',
|
|
].join(';')
|
|
captionBar.innerHTML = '<span data-qa-chip></span><span data-qa-text></span>'
|
|
document.documentElement.appendChild(captionBar)
|
|
}
|
|
const chip = captionBar.querySelector('[data-qa-chip]')
|
|
chip.textContent = status.toUpperCase()
|
|
chip.style.cssText = [
|
|
'background: ' + (CHIP_COLORS[status] || CHIP_COLORS.info),
|
|
'color: #fff',
|
|
'padding: 3px 14px',
|
|
'border-radius: 999px',
|
|
'font-size: 15px',
|
|
'flex: none',
|
|
].join(';')
|
|
captionBar.querySelector('[data-qa-text]').textContent = text
|
|
return 'caption set'
|
|
}
|
|
|
|
window.__qaCursor = {
|
|
caption,
|
|
glide(x, y, ms = 500) {
|
|
const startX = parseFloat(cursor.style.left) || 0
|
|
const startY = parseFloat(cursor.style.top) || 0
|
|
const start = performance.now()
|
|
const ease = (t) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2)
|
|
return new Promise((resolve) => {
|
|
const step = (now) => {
|
|
const t = Math.min(1, (now - start) / ms)
|
|
const k = ease(t)
|
|
moveTo(startX + (x - startX) * k, startY + (y - startY) * k)
|
|
if (t < 1) {
|
|
requestAnimationFrame(step)
|
|
} else {
|
|
resolve('done')
|
|
}
|
|
}
|
|
requestAnimationFrame(step)
|
|
})
|
|
},
|
|
moveTo,
|
|
ripple,
|
|
}
|
|
return 'injected'
|
|
})()
|