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
This commit is contained in:
2026-06-23 16:58:12 +00:00
parent cf720c37a1
commit 12d419de7c
113 changed files with 52423 additions and 951 deletions
@@ -0,0 +1,180 @@
/**
* Framework-neutral Impeccable live chrome contract.
*
* The production browser bundle is intentionally plain DOM so Svelte, React,
* Vue, and static adapters can all mount the same chrome. This module is the
* testable contract/inventory for that bundle; live-browser.js mirrors these
* values at runtime because it is served as a standalone script.
*/
export const LIVE_CHROME_MOUNT_CONTRACT = Object.freeze([
'root',
'transport',
'state',
'actions',
]);
export const LIVE_UI_SURFACES = Object.freeze([
{
key: 'global-bottom-bar',
ids: [
'impeccable-live-global-bar',
'impeccable-live-global-bar-brand',
'impeccable-live-pick-toggle',
'impeccable-live-insert-toggle',
'impeccable-live-detect-toggle',
'impeccable-live-detect-badge',
'impeccable-live-design-toggle',
'impeccable-live-page-chat',
'impeccable-live-page-chat-input',
'impeccable-live-page-chat-voice',
],
states: ['rest', 'hover', 'focus-visible', 'pressed', 'active', 'tooltip'],
},
{
key: 'pending-copy-edit-dock',
ids: ['impeccable-live-pending-dock'],
states: ['closed', 'open', 'hover', 'pressed', 'loading', 'rollback', 'keep-fixing'],
},
{
key: 'element-selection-chrome',
ids: [
'impeccable-live-highlight',
'impeccable-live-tooltip',
'impeccable-live-bar',
'impeccable-live-selection-pill',
'impeccable-live-input',
'impeccable-live-configure-voice',
'impeccable-live-configure-bar-tooltip',
],
states: ['rest', 'hover', 'focus-visible', 'pressed', 'disabled'],
},
{
key: 'action-picker',
ids: ['impeccable-live-picker'],
states: ['closed', 'open', 'option-hover', 'option-focus'],
},
{
key: 'edit-chrome',
ids: ['impeccable-live-edit-badge'],
states: ['enabled', 'disabled', 'editing', 'cancel', 'save', 'edited-content'],
},
{
key: 'generating-row',
ids: ['impeccable-live-bar', 'impeccable-live-shader'],
states: ['action-label', 'animated-dots', 'generating', 'done'],
},
{
key: 'variant-cycling-row',
ids: ['impeccable-live-bar', 'impeccable-live-params-panel'],
states: ['variant-1', 'variant-2', 'variant-3', 'left-disabled', 'right-disabled', 'dot-click', 'accept', 'discard'],
},
{
key: 'variant-params-panel',
ids: ['impeccable-live-params-panel'],
states: ['closed', 'open-above', 'open-below', 'range', 'steps', 'toggle'],
},
{
key: 'saving-confirmed-rows',
ids: ['impeccable-live-bar'],
states: ['saving', 'applying-variant', 'confirmed'],
},
{
key: 'insert-mode-chrome',
ids: [
'impeccable-live-insert-line',
'impeccable-live-insert-placeholder',
'impeccable-live-placeholder-resize',
'impeccable-live-insert-input',
'impeccable-live-insert-voice',
'impeccable-live-insert-create',
'impeccable-live-insert-create-tooltip',
],
states: ['toggle-active', 'line', 'placeholder', 'resize', 'enabled', 'disabled', 'tooltip'],
},
{
key: 'annotation-chrome',
ids: [
'impeccable-live-annot',
'impeccable-live-annot-svg',
'impeccable-live-annot-pins',
'impeccable-live-annot-clear',
],
states: ['overlay', 'drawing', 'pin', 'pin-edit', 'clear'],
},
{
key: 'design-system-panel',
ids: ['impeccable-live-design-host'],
states: ['closed', 'open', 'tabs', 'token-tiles', 'copy'],
},
{
key: 'toasts-and-errors',
ids: ['impeccable-live-toast'],
states: ['normal', 'error', 'no-variants-mounted'],
},
{
key: 'css-isolation-boundary',
ids: ['impeccable-live-root'],
states: ['shadow-root', 'style-tags', 'hostile-css'],
},
]);
export const LIVE_UI_COMPONENT_IDS = Object.freeze([
...new Set(LIVE_UI_SURFACES.flatMap((surface) => surface.ids)),
]);
export function resolveLiveUiRoot(env = globalThis) {
const doc = env?.document;
const explicit = env?.__IMPECCABLE_LIVE_UI_ROOT__
|| env?.window?.__IMPECCABLE_LIVE_UI_ROOT__;
if (explicit && typeof explicit.appendChild === 'function') return explicit;
return doc?.body || null;
}
export function getLiveUiElementById(id, env = globalThis) {
const doc = env?.document;
const root = resolveLiveUiRoot(env);
if (!id) return null;
if (root?.getElementById) {
const found = root.getElementById(id);
if (found) return found;
}
if (root?.querySelector) {
const found = root.querySelector('#' + escapeCssIdent(id));
if (found) return found;
}
return doc?.getElementById?.(id) || null;
}
export function appendToLiveUiRoot(el, env = globalThis) {
const root = resolveLiveUiRoot(env);
if (!root) throw new Error('Impeccable live UI root is not available');
root.appendChild(el);
return el;
}
export function appendStyleToLiveUiRoot(styleEl, env = globalThis) {
const doc = env?.document;
const root = resolveLiveUiRoot(env);
if (root && root !== doc?.body) {
root.appendChild(styleEl);
} else {
(doc?.head || doc?.body || root).appendChild(styleEl);
}
return styleEl;
}
export function activeElementDeep(doc = globalThis.document) {
let active = doc?.activeElement || null;
while (active?.shadowRoot?.activeElement) {
active = active.shadowRoot.activeElement;
}
return active;
}
function escapeCssIdent(value) {
if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {
return CSS.escape(String(value));
}
return String(value).replace(/([ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, '\\$1');
}