Files
chatc2/.pi/skills/impeccable/scripts/live-browser-dom.js
T
AyronSantos 12d419de7c feat: comprehensive design system overhaul
Applied impeccable design critique, audit, harden, layout, distill, and polish across all pages.

DESIGN SYSTEM:
- Created PRODUCT.md (5 design principles, WCAG 2.1 AA target)
- Created DESIGN.md (34 color tokens, 6 radius scale, full component spec)
- Created .impeccable/design.json (sidecar with tonal ramps, shadows, motion)

ACCESSIBILITY:
- 33 aria-labels across 7 pages (was ~13)
- 63 <label> elements (was ~30)
- 6 <main> landmarks + role=navigation on all sidebars
- Esc to close modals on 4 pages
- Keyboard shortcuts (Ctrl+J/K, Ctrl+F) on chat

THEMING:
- 216 hardcoded colors replaced with var() references
- 449 !important removed from dark-mode.css
- Dark mode script unified (was duplicated 9x inline)
- All modals converted to .modal-overlay + .modal design system classes

CHAT:
- Error states for messages (.msg.erro, .msg.enviando)
- Connection status indicator
- Confirmation modal before finalizar
- Input bar reorganized (3 visible actions, + menu)
- Right panel accordions
- Send button disabled when empty
- Touch targets 44px on mobile

CLIENT DETAIL:
- Tabs/badges/sub-tabs use CSS classes instead of inline styles
- 'Iniciar Conversa' button now primary action
- alert() replaced with inline feedback
- Modal converted to design system classes

CLIENT LIST:
- Modal moved inside <body> (was HTML-invalid)
- alert() replaced with inline feedback
- Modal uses .show pattern

SETTINGS:
- 4 modals converted to design system classes
- 14 alert() calls replaced with inline feedback
- fecharModal() uses classList instead of style.display

ROUTES:
- border-radius normalized, hardcoded colors fixed

Score progression:
  Chat:        23 → 32/40
  Client List: 32 → 35/40
  Client Detail: 26 → 30/40
  Settings:     29 → 35/40
  Routes:       37 → 38/40
  Audit (project): 14/20 → 17/20
2026-06-23 16:58:12 +00:00

147 lines
4.2 KiB
JavaScript

/**
* Browser-side DOM helpers for Impeccable live mode.
*
* Kept separate from live-browser.js so future browser script parts can share
* chrome mounting, lookup, focus, and picker helpers without depending on the
* full overlay UI bundle.
*/
(function (root) {
'use strict';
if (!root) return;
function createLiveBrowserDomHelpers({
prefix,
skipTags,
document: doc = root.document,
css = root.CSS,
crypto = root.crypto,
} = {}) {
if (!prefix) throw new Error('prefix required');
if (!doc) throw new Error('document required');
const tagsToSkip = skipTags || new Set();
function own(el) {
return el && (el.id?.startsWith(prefix) || el.closest?.('[id^="' + prefix + '"]'));
}
function pickable(el) {
if (!el || el.nodeType !== 1) return false;
if (tagsToSkip.has(String(el.tagName || '').toLowerCase())) return false;
if (own(el)) return false;
const r = el.getBoundingClientRect();
return r.width >= 20 && r.height >= 20;
}
function desc(el) {
if (!el) return '';
let s = el.tagName.toLowerCase();
if (el.id) s += '#' + el.id;
else if (el.classList.length) s += '.' + [...el.classList].slice(0, 2).join('.');
return s;
}
function rectIsUsableAnchor(rect) {
return !!rect && rect.width > 0.5 && rect.height > 0.5;
}
function makeFrozenAnchor(el) {
if (!el || !el.getBoundingClientRect) return null;
const r = el.getBoundingClientRect();
if (!rectIsUsableAnchor(r)) return null;
const rect = {
x: r.x, y: r.y,
top: r.top, left: r.left,
right: r.right, bottom: r.bottom,
width: r.width, height: r.height,
};
return {
__impeccableFrozenAnchor: true,
tagName: el.tagName || 'DIV',
id: el.id || '',
classList: el.classList ? [...el.classList] : [],
hasAttribute: () => false,
getBoundingClientRect: () => rect,
};
}
function id8() {
if (crypto?.randomUUID) return crypto.randomUUID().replace(/-/g, '').slice(0, 8);
return (Math.random().toString(16).slice(2) + Date.now().toString(16)).slice(0, 8);
}
function cssId(id) {
if (css?.escape) return css.escape(id);
return String(id).replace(/([ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, '\\$1');
}
function liveUiRoot() {
const uiRoot = root.__IMPECCABLE_LIVE_UI_ROOT__;
if (uiRoot && typeof uiRoot.appendChild === 'function') return uiRoot;
return doc.body;
}
function uiAppend(el) {
liveUiRoot().appendChild(el);
return el;
}
function uiAppendStyle(styleEl) {
const uiRoot = liveUiRoot();
if (uiRoot && uiRoot !== doc.body) uiRoot.appendChild(styleEl);
else doc.head.appendChild(styleEl);
return styleEl;
}
function uiGetById(id) {
const uiRoot = liveUiRoot();
if (uiRoot?.getElementById) {
const found = uiRoot.getElementById(id);
if (found) return found;
}
if (uiRoot?.querySelector) {
const found = uiRoot.querySelector('#' + cssId(id));
if (found) return found;
}
return doc.getElementById(id);
}
function activeElementDeep() {
let active = doc.activeElement;
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
return active;
}
function defangOutsideHandlers(rootEl, { setPointerEvents = true } = {}) {
if (!rootEl) return;
if (setPointerEvents) {
rootEl.style.setProperty('pointer-events', 'auto', 'important');
}
const stop = (e) => e.stopPropagation();
rootEl.addEventListener('pointerdown', stop);
rootEl.addEventListener('mousedown', stop);
rootEl.addEventListener('focusin', stop);
}
return {
own,
pickable,
desc,
rectIsUsableAnchor,
makeFrozenAnchor,
id8,
cssId,
liveUiRoot,
uiAppend,
uiAppendStyle,
uiGetById,
activeElementDeep,
defangOutsideHandlers,
};
}
root.__IMPECCABLE_LIVE_DOM__ = {
version: 1,
createLiveBrowserDomHelpers,
};
})(typeof window !== 'undefined' ? window : globalThis);